| name | queue |
| description | Guide for job queue patterns in multi-agent coordination. Use when deciding between background jobs vs inline execution, submitting long-running tasks, monitoring job progress, and handling failures. Covers when to queue, job priority, retry strategies, and monitoring patterns. |
| tags | ["queue","background-jobs","distributed-work","job-processing","reliability"] |
Queue Skill
Reliable job processing for multi-agent workflows using BullMQ and Redis.
When to Use Background Jobs (Queue)
Queue jobs when:
- Long-running operations (>500ms) - Embedding generation, PDF processing, data analysis
- Resource-intensive work - ML inference, image transcoding, complex computations
- Fault tolerance matters - Can fail and retry without blocking the caller
- Scaling needed - Process multiple jobs in parallel with workers
- Async is acceptable - Caller doesn't need immediate result
- Rate limiting required - Control throughput with concurrency settings
- Workflow coordination - Chain tasks or wait for results asynchronously
When NOT to Use Background Jobs
Use inline execution when:
- Sub-100ms operations - Simple data transforms, validation, cache lookups
- Need immediate result - Caller blocks waiting for response
- No failure handling needed - Single request, no retry logic
- Stateless one-shots - No persistence or monitoring required
Job Queue API
Creating a Queue
import { createSwarmQueue } from 'swarm-queue';
const queue = createSwarmQueue({
name: 'embeddings',
connection: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
},
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000,
},
removeOnComplete: true,
},
});
Submitting Jobs
const jobId = await queue.addJob('generate-embedding', {
text: 'Hello, world!',
model: 'text-embedding-3-small',
});
const jobId = await queue.addJob(
'agent-task',
{ agentId: 'worker-1', task: 'analyze' },
{ priority: 1 }
);
const jobId = await queue.addJob(
'retry-agent',
{ attempt: 2 },
{ delay: 30000 }
);
const jobId = await queue.addJob(
'webhook-call',
{ url: 'https://example.com/webhook' },
{
attempts: 5,
backoff: {
type: 'exponential',
delay: 1000,
},
}
);
Checking Job Status
const job = await queue.getJob(jobId);
if (job) {
console.log({
state: await job.getState(),
progress: job.progress(),
attempts: job.attemptsMade,
failedReason: job.failedReason,
});
}
const metrics = await queue.getMetrics();
console.log(metrics);
Canceling Jobs
await queue.removeJob(jobId);
Creating Workers
Workers process jobs from the queue. Start workers in separate processes or services.
import { createWorker } from 'swarm-queue';
const worker = await createWorker(
{
queueName: 'embeddings',
concurrency: 4,
connection: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
},
},
async (job) => {
try {
const { text, model } = job.data.payload;
job.updateProgress(25);
const embedding = await generateEmbedding(text, model);
job.updateProgress(100);
return {
success: true,
data: { embedding, dimensions: embedding.length },
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error. : ,
};
}
}
);
worker.();
process.(, () => {
worker.();
worker.();
});
Job Priority Patterns
Urgent vs Background
await queue.addJob('notify-coordinator', { error }, { priority: 0 });
await queue.addJob('merge-results', { results }, { priority: 1 });
await queue.addJob('update-metrics', { metrics }, { priority: 2 });
await queue.addJob('cleanup-cache', { cacheKey }, { priority: 3 });
Processing Priority in Workers
BullMQ automatically processes higher priority jobs first:
const metrics = await queue.getMetrics();
console.log(`Urgent jobs waiting: ${metrics.waiting}`);
Failure Handling and Retry Strategies
Exponential Backoff
const jobId = await queue.addJob('api-call', { endpoint: '/data' }, {
attempts: 4,
backoff: {
type: 'exponential',
delay: 1000,
},
});
Retry timeline: 1s → 2s → 4s → 8s → Failed (moves to dead-letter)
Fixed Delay
const jobId = await queue.addJob('webhook', { url }, {
attempts: 3,
backoff: {
type: 'fixed',
delay: 5000,
},
});
Dead Letter Pattern
After max retries, jobs fail and are no longer retried:
const metrics = await queue.getMetrics();
if (metrics.failed > 0) {
console.warn(`${metrics.failed} jobs have permanently failed`);
}
Monitoring and Observability
Real-Time Metrics
const metrics = await queue.getMetrics();
const queueHealth = {
throughput: metrics.completed,
backlog: metrics.waiting,
inProgress: metrics.active,
failureRate: metrics.failed / (metrics.completed + metrics.failed),
avgTimeInQueue: null,
};
console.log(`Queue health: ${JSON.stringify(queueHealth, null, 2)}`);
Monitoring Best Practices
- Track completion time: Record when jobs enter and exit the queue
- Failure alerts: Alert when failure rate exceeds threshold
- Backlog warnings: Warn when waiting jobs exceed capacity
- Worker health: Monitor worker process availability
- Job timeouts: Set reasonable timeout expectations per job type
setInterval(async () => {
const metrics = await queue.getMetrics();
const total = Object.values(metrics).reduce((a, b) => a + b);
if (metrics.failed > 0.1 * total) {
console.error('High failure rate detected');
}
if (metrics.waiting > 1000) {
console.warn('Large backlog detected - consider scaling workers');
}
}, 30000);
Common Job Types
Embedding Generation
const jobId = await queue.addJob('generate-embedding', {
documentId: 'doc-123',
text: 'Full document text here...',
model: 'text-embedding-3-small',
}, {
priority: 2,
attempts: 3,
backoff: { type: 'exponential', delay: 2000 },
});
const result = await generateEmbedding(payload.text, payload.model);
PDF Processing
const jobId = await queue.addJob('process-pdf', {
fileUrl: 'https://example.com/document.pdf',
pages: [1, 2, 3],
}, {
priority: 1,
attempts: 2,
removeOnComplete: true,
});
const text = await extractPdfText(payload.fileUrl, payload.pages);
Bulk Operations
const jobId = await queue.addJob('bulk-update', {
items: largeDataset,
operation: 'transform',
}, {
priority: 3,
attempts: 1,
});
for (const item of payload.items) {
await processItem(item);
job.updateProgress(++processed / payload.items.length * 100);
}
Task Coordination with Queues
Waiting for Results
Since queue jobs are async, use polling or events for results:
const jobId = await queue.addJob('analyze-data', { data });
let result = null;
const maxAttempts = 60;
for (let i = 0; i < maxAttempts; i++) {
const job = await queue.getJob(jobId);
const state = await job?.getState();
if (state === 'completed') {
result = job?.returnvalue;
break;
}
await new Promise(r => setTimeout(r, 5000));
}
if (result) {
console.log('Analysis complete:', result);
} else {
console.error('Job timeout');
}
Chaining Jobs
const jobId1 = await queue.addJob('generate-embedding', { text });
if (jobResult.success) {
const jobId2 = await queue.addJob('compare-embeddings', {
embedding: jobResult.data.embedding,
compareWith: otherEmbedding,
});
}
CLI Usage (swarm queue)
The swarm CLI provides queue management commands:
swarm queue submit embeddings '{"text":"hello","model":"small"}' --priority 1
swarm queue status embeddings job-id-123
swarm queue list embeddings --state waiting --limit 10
swarm queue list embeddings --state failed
swarm worker embeddings --concurrency 4
swarm queue cleanup embeddings --before 7d
Error Handling Strategies
Graceful Degradation
const jobId = await queue.addJob('expensive-compute', { data });
try {
const job = await queue.getJob(jobId);
const state = await job?.getState();
if (state === 'failed') {
return getCachedResult(data) || getDefaultResult();
}
} catch (error) {
return getCachedResult(data);
}
Dead Letter Handling
const metrics = await queue.getMetrics();
if (metrics.failed > 0) {
const failedJobs = [];
for (const job of failedJobs) {
console.error(`Failed job ${job.id}: ${job.failedReason}`);
}
}
Performance Tuning
Concurrency Settings
const worker = await createWorker(
{
queueName: 'embeddings',
concurrency: 1,
},
processor
);
const worker = await createWorker(
{
queueName: 'simple-tasks',
concurrency: 16,
},
processor
);
Job Options for Performance
await queue.addJob('cleanup', { data }, {
removeOnComplete: true,
attempts: 1,
});
await queue.addJob('agent-task', { data }, {
removeOnComplete: false,
attempts: 3,
});
Testing
Unit Testing Jobs
import { describe, test, expect } from 'bun:test';
describe('Queue Jobs', () => {
test('embedding job returns valid embedding', async () => {
const processor = async (job) => {
return {
success: true,
data: { embedding: [0.1, 0.2, 0.3] },
};
};
const result = await processor({
data: { payload: { text: 'hello', model: 'small' } },
});
expect(result.success).toBe(true);
expect(result.data.embedding).toHaveLength(3);
});
});
Integration Testing with Real Queue
test('job completes successfully in queue', async () => {
const queue = createSwarmQueue({ name: 'test-queue' });
const jobId = await queue.addJob('test-job', { data: 'test' });
const job = await queue.getJob(jobId);
expect(job).toBeDefined();
await queue.removeJob(jobId);
await queue.close();
});
Summary
- Use queues for long-running, fault-tolerant work
- Set appropriate priority and retry strategies
- Monitor queue health and failure rates
- Chain jobs at the application level
- Gracefully handle job failures with fallbacks
- Scale workers independently from the API