Skip to main content

What is Agent Queuing?

Agent Queuing allows you to execute AI agents asynchronously using Laravel’s queue system. This is useful for long-running tasks, background processing, and scenarios where you don’t need immediate responses.

Background Processing

Run LLM agents and media generation in the background without blocking requests

Laravel Integration

Uses Laravel’s native queue system - works with any queue driver (Redis, SQS, database, etc.)

Retry & Timeout

Built-in retry logic and configurable timeouts for reliable execution

Event-Driven

Dispatches events on completion and failure for easy integration

Quick Start

LLM Agent Queuing

use App\Agents\MyAgent;

// Queue an agent for background processing
$result = MyAgent::ask('Analyze this large dataset...')
    ->onQueue('agents')
    ->go();

// Returns immediately with job info
// [
//     'job_dispatched' => true,
//     'job_id' => 'uuid-here',
//     'queue' => 'agents',
//     'agent' => 'my_agent'
// ]

Media Agent Queuing

use Vizra\VizraADK\Agents\ImageAgent;

// Queue image generation
$result = ImageAgent::run('A futuristic cityscape')
    ->onQueue('media')
    ->then(fn($image) => $image->storeAs('cityscape.png'))
    ->go();

// [
//     'job_dispatched' => true,
//     'job_id' => 'uuid-here',
//     'queue' => 'media',
//     'agent' => 'image_agent',
//     'prompt' => 'A futuristic cityscape'
// ]
When you specify a queue with onQueue(), async mode is automatically enabled - no need to call async() separately.

LLM Agent Queuing

LLM agents can be queued using the fluent executor API. This is useful for tasks that may take a while to complete, such as complex analysis or multi-step reasoning.

Enabling Async Execution

// Method 1: Enable async mode explicitly
MyAgent::ask('Process this...')
    ->async()
    ->go();

// Method 2: Specify a queue (auto-enables async)
MyAgent::ask('Process this...')
    ->onQueue('agents')
    ->go();

Queue Configuration Options

MethodDescriptionDefault
async(bool $enabled = true)Enable/disable async executionfalse
onQueue(string $queue)Specify queue name (auto-enables async)'default'
delay(int $seconds)Delay job executionnull
tries(int $tries)Number of retry attempts3
timeout(int $seconds)Job timeout in seconds300 (5 min)

Complete LLM Agent Example

use App\Agents\AnalysisAgent;

$result = AnalysisAgent::ask('Analyze the quarterly sales data and generate insights')
    ->forUser($user)                    // Associate with user
    ->withSession('analysis-session')   // Track with session ID
    ->withContext([                     // Add extra context
        'report_type' => 'quarterly',
        'department' => 'sales',
    ])
    ->onQueue('analysis')               // Use specific queue
    ->delay(30)                         // Wait 30 seconds before processing
    ->tries(5)                          // Retry up to 5 times
    ->timeout(600)                      // 10-minute timeout
    ->go();

// Store the job ID for tracking
$jobId = $result['job_id'];

Media Agent Queuing

Media agents (ImageAgent, AudioAgent) support the same queuing options plus additional features like post-generation callbacks and auto-storage.

Queue Configuration Options

MethodDescriptionDefault
async(bool $enabled = true)Enable/disable async executionfalse
onQueue(string $queue)Specify queue name (auto-enables async)'default'
delay(int $seconds)Delay job executionnull
tries(int $tries)Number of retry attempts3
timeout(int $seconds)Job timeout in seconds120 (2 min)
then(Closure $callback)Post-generation callbacknull
store(?string $disk)Auto-store with generated filename-
storeAs(string $filename, ?string $disk)Auto-store with specific filename-

Post-Generation Callbacks

Use the then() method to execute code after generation completes:
use Vizra\VizraADK\Agents\ImageAgent;

ImageAgent::run('A product photo of a leather bag')
    ->onQueue('media')
    ->then(function ($image) {
        // Store the image
        $image->storeAs('products/leather-bag.png');

        // Update database
        Product::find(123)->update([
            'image_path' => $image->path(),
        ]);

        // Send notification
        Notification::send($user, new ImageReady($image));
    })
    ->go();
The then() callback must be serializable for queue processing. Avoid using $this or non-serializable objects inside the closure.

Auto-Storage Options

Configure automatic storage directly in the fluent chain:
// Store with auto-generated filename (ULID)
ImageAgent::run('A landscape photo')
    ->onQueue('media')
    ->store()
    ->go();

// Store with specific filename
ImageAgent::run('A portrait photo')
    ->onQueue('media')
    ->storeAs('portraits/user-avatar.png')
    ->go();

// Store to a specific disk
ImageAgent::run('A banner image')
    ->onQueue('media')
    ->storeAs('banners/hero.png', 's3')
    ->go();

Complete Media Agent Example

use Vizra\VizraADK\Agents\ImageAgent;

$result = ImageAgent::run('Professional headshot of a business executive')
    ->forUser($user)
    ->withSession('avatar-generation')
    ->using('openai', 'dall-e-3')
    ->size('1024x1024')
    ->quality('hd')
    ->style('natural')
    ->onQueue('media')
    ->delay(5)
    ->tries(3)
    ->timeout(180)
    ->then(function ($image) use ($user) {
        $image->storeAs("avatars/{$user->id}.png");

        $user->update(['avatar_path' => $image->path()]);
    })
    ->go();

Job Response & Tracking

When you dispatch an agent to a queue, you receive an immediate response with job information:

LLM Agent Response

$result = MyAgent::ask('...')->onQueue('agents')->go();

// Returns:
[
    'job_dispatched' => true,
    'job_id' => '550e8400-e29b-41d4-a716-446655440000',
    'queue' => 'agents',
    'agent' => 'my_agent'
]

Media Agent Response

$result = ImageAgent::run('...')->onQueue('media')->go();

// Returns:
[
    'job_dispatched' => true,
    'job_id' => '550e8400-e29b-41d4-a716-446655440000',
    'queue' => 'media',
    'agent' => 'image_agent',
    'prompt' => 'The original prompt...'
]

Retrieving Results from Cache

Job results are automatically cached for retrieval:
// LLM Agent results (cached for 1 hour)
$result = cache()->get("agent_job_result:{$jobId}");
$meta = cache()->get("agent_job_meta:{$jobId}");

// Media Agent results (cached for 1 hour)
$mediaResult = cache()->get("media_job_result:{$jobId}");

// Check for failures (cached for 24 hours)
$failure = cache()->get("agent_job_failure:{$jobId}");
$mediaFailure = cache()->get("media_job_failure:{$jobId}");

Cache Key Reference

Cache KeyTTLDescription
agent_job_result:{jobId}1 hourLLM agent execution result
agent_job_meta:{jobId}1 hourLLM agent job metadata
media_job_result:{jobId}1 hourMedia generation result with URLs/paths
agent_job_failure:{jobId}24 hoursLLM agent failure info
media_job_failure:{jobId}24 hoursMedia generation failure info

Events

The queuing system dispatches events on job completion and failure, allowing you to react to agent execution status.

LLM Agent Events

EventDescription
agent.job.completedFired when any agent job completes successfully
agent.{name}.completedAgent-specific completion event (e.g., agent.my_agent.completed)
agent.job.failedFired when any agent job permanently fails

Media Agent Events

EventDescription
media.job.completedFired when any media job completes successfully
media.{name}.completedAgent-specific completion event (e.g., media.image_agent.completed)
media.job.failedFired when any media job permanently fails

Event Listener Examples

use Illuminate\Support\Facades\Event;

// Listen for all agent job completions
Event::listen('agent.job.completed', function (array $data) {
    Log::info('Agent job completed', [
        'job_id' => $data['job_id'],
        'agent_class' => $data['agent_class'],
        'result' => $data['result'],
    ]);
});

// Listen for a specific agent
Event::listen('agent.analysis_agent.completed', function (array $data) {
    $result = $data['result'];
    $sessionId = $data['session_id'];

    // Process the analysis result
    ProcessAnalysisResult::dispatch($result, $sessionId);
});

// Listen for media generation completion
Event::listen('media.image_agent.completed', function (array $data) {
    $response = $data['response'];
    $jobId = $data['job_id'];

    Log::info("Image generated: " . $response->url());
});

// Handle failures
Event::listen('agent.job.failed', function (array $data) {
    Log::error('Agent job failed', [
        'job_id' => $data['job_id'],
        'agent_class' => $data['agent_class'],
        'error' => $data['error'],
    ]);

    // Notify admin or trigger alerting
    Notification::route('slack', config('services.slack.webhook'))
        ->notify(new AgentJobFailed($data));
});
Event payloads include the job_id, agent_class, session_id, and the full result or response object. Use these to update your database, send notifications, or trigger follow-up actions.

Job Tagging

Jobs are automatically tagged for easy filtering and monitoring with tools like Laravel Horizon.

LLM Agent Tags

// Tags applied to AgentJob
[
    'vizra:{agent_name}',    // e.g., 'vizra:my_agent'
    'session:{session_id}'   // e.g., 'session:user_123_abc'
]

Media Agent Tags

// Tags applied to MediaGenerationJob
[
    'vizra:media',
    'vizra:{agent_name}',    // e.g., 'vizra:image_agent'
    'session:{session_id}'
]

Viewing Tags in Horizon

If you’re using Laravel Horizon, you can filter jobs by these tags:
// In your Horizon config, jobs will appear with tags like:
// vizra:my_agent, session:user_123_abc

// Search for all jobs from a specific agent
// Filter: vizra:analysis_agent

// Search for all media generation jobs
// Filter: vizra:media

Configuration & Prerequisites

Queue Driver Configuration

Agent queuing uses Laravel’s standard queue system. Configure your preferred driver in .env:
QUEUE_CONNECTION=redis
Supported drivers include redis, database, sqs, beanstalkd, and others.

Running Queue Workers

Process queued jobs with Laravel’s queue worker:
# Process all queues
php artisan queue:work

# Process specific queue
php artisan queue:work --queue=agents

# Process multiple queues with priority
php artisan queue:work --queue=agents,media,default

# With Horizon (recommended for production)
php artisan horizon

Supervisor Configuration

For production, use Supervisor to keep queue workers running:
[program:agent-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path/to/artisan queue:work --queue=agents,media --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
numprocs=4
redirect_stderr=true
stdout_logfile=/path/to/logs/agent-worker.log

Horizon Configuration

For advanced monitoring and management, use Laravel Horizon:
config/horizon.php
'environments' => [
    'production' => [
        'agent-workers' => [
            'connection' => 'redis',
            'queue' => ['agents', 'media'],
            'balance' => 'auto',
            'processes' => 4,
            'tries' => 3,
            'timeout' => 600,
        ],
    ],
],

Best Practices

Use Dedicated Queues

Separate agent and media jobs from other application jobs using dedicated queues

Set Appropriate Timeouts

LLM tasks may need longer timeouts (5-10 min), while media generation is typically faster (2 min)

Monitor with Horizon

Use Laravel Horizon for real-time monitoring, metrics, and job management

Handle Failures

Always listen for failure events and implement appropriate error handling
// Separate queues by workload type
->onQueue('agents')      // For LLM agents
->onQueue('media')       // For image/audio generation
->onQueue('analysis')    // For long-running analysis tasks

Timeout Guidelines

Task TypeRecommended Timeout
Simple LLM queries60-120 seconds
Complex reasoning/analysis300-600 seconds
Image generation120 seconds
Audio generation180 seconds
Multi-step workflows600+ seconds

Complete Examples

Background Report Generation

use App\Agents\ReportAgent;
use App\Models\Report;

class ReportController extends Controller
{
    public function generate(Request $request)
    {
        $report = Report::create([
            'user_id' => $request->user()->id,
            'status' => 'processing',
            'type' => $request->input('type'),
        ]);

        $result = ReportAgent::ask("Generate a {$request->input('type')} report")
            ->forUser($request->user())
            ->withSession("report-{$report->id}")
            ->withContext([
                'report_id' => $report->id,
                'date_range' => $request->input('date_range'),
            ])
            ->onQueue('reports')
            ->timeout(600)
            ->go();

        $report->update(['job_id' => $result['job_id']]);

        return response()->json([
            'message' => 'Report generation started',
            'report_id' => $report->id,
            'job_id' => $result['job_id'],
        ]);
    }
}

// Event listener for completion
Event::listen('agent.report_agent.completed', function ($data) {
    $sessionId = $data['session_id']; // 'report-123'
    $reportId = str_replace('report-', '', $sessionId);

    Report::find($reportId)->update([
        'status' => 'completed',
        'content' => $data['result'],
    ]);
});

Batch Image Generation

use Vizra\VizraADK\Agents\ImageAgent;

class ProductImageController extends Controller
{
    public function generateBatch(Request $request)
    {
        $products = Product::whereNull('image_path')->get();
        $jobs = [];

        foreach ($products as $product) {
            $result = ImageAgent::run("Product photo of: {$product->name}")
                ->forUser($request->user())
                ->withSession("product-{$product->id}")
                ->using('openai', 'dall-e-3')
                ->square()
                ->hd()
                ->style('natural')
                ->onQueue('media')
                ->then(function ($image) use ($product) {
                    $path = "products/{$product->id}.png";
                    $image->storeAs($path);
                    $product->update(['image_path' => $path]);
                })
                ->go();

            $jobs[] = [
                'product_id' => $product->id,
                'job_id' => $result['job_id'],
            ];
        }

        return response()->json([
            'message' => count($jobs) . ' image generation jobs queued',
            'jobs' => $jobs,
        ]);
    }
}