Queues
Queues define execution policies — concurrency, rate limiting, retries, and backoff strategies.
Queue Configuration
Every queue has a QueueConfig that controls how tasks in that queue are executed.
| Field | Type | Default | Description |
|---|---|---|---|
concurrency | number | 10 | Maximum concurrent running tasks |
max_retries | number | 3 | Default max retries per task |
timeout_secs | number | 300 | Default task timeout in seconds |
backoff | object | Exponential 1000ms | Default backoff strategy for retries |
rate_limit | object | null | Optional token-bucket rate limit |
retention_secs | number | null | Auto-delete terminal flows older than this (seconds). Null = keep forever. |
secrets | object | {} | Named secrets for ${secrets.*} interpolation in executor configs (see Secrets) |
Example: creating a queue
curl -X POST http://localhost:8080/api/v1/queues \
-H "Content-Type: application/json" \
-d '{
"id": "email-sends",
"config": {
"concurrency": 5,
"max_retries": 5,
"timeout_secs": 60,
"backoff": { "exponential_jitter": { "initial_delay_ms": 2000 } },
"rate_limit": { "max_burst": 20, "per_second": 10.0 }
}
}'
Fields you omit fall back to defaults. A minimal queue only needs an id:
curl -X POST http://localhost:8080/api/v1/queues \
-H "Content-Type: application/json" \
-d '{ "id": "default" }'
Concurrency Control
Each queue enforces a concurrency limit via a per-queue semaphore. This controls the maximum number of tasks running in parallel across all flows in that queue.
{
"id": "heavy-compute",
"config": {
"concurrency": 5
}
}
With concurrency: 5, at most 5 tasks from this queue will be running at any given time. Other ready tasks wait until a slot opens. This applies across all flows submitted to the queue.
Rate Limiting
Queues support optional rate limiting using a token bucket algorithm. This is useful when your tasks call external APIs with rate limits.
| Field | Type | Description |
|---|---|---|
max_burst | number | Token bucket capacity. Max tasks that can start in a burst. |
per_second | number | Token refill rate. Sustained tasks dispatched per second. |
{
"id": "api-calls",
"config": {
"rate_limit": {
"max_burst": 20,
"per_second": 10.0
}
}
}
This allows bursts of up to 20 tasks, then sustains 10 tasks per second. The bucket refills continuously at the per_second rate. If no rate limit is configured, tasks are dispatched as fast as the concurrency limit allows.
Retry & Backoff
When a task fails with retryable: true, the engine schedules a retry after a delay determined by the backoff strategy. Tasked supports three strategies:
Fixed
Constant delay between retries.
{
"fixed": {
"delay_ms": 1000
}
}
Every retry waits exactly delay_ms milliseconds. Use this when you want predictable, uniform retry intervals.
Exponential
Delay doubles with each retry attempt: initial_delay_ms * 2^attempt.
{
"exponential": {
"initial_delay_ms": 1000
}
}
With initial_delay_ms: 1000, retries wait 1s, 2s, 4s, 8s, and so on. This is the default backoff strategy.
Exponential with Jitter
Exponential backoff plus random jitter (50-150% of the base delay). This prevents thundering herd problems when many tasks retry simultaneously.
{
"exponential_jitter": {
"initial_delay_ms": 1000
}
}
With initial_delay_ms: 1000, the first retry waits between 500ms and 1500ms, the second between 1s and 3s, and so on.
Retry count
The max_retries setting controls how many times a task can be retried before being marked as permanently failed. Each task tracks its own remaining retry count.
Retention
Queues can optionally set a retention_secs to automatically clean up terminal flows (succeeded, failed, or cancelled) that are older than the specified age. This prevents unbounded storage growth for high-throughput queues.
| Field | Type | Default | Description |
|---|---|---|---|
retention_secs | number | null | Seconds to keep terminal flows. null = keep forever. |
The engine runs a cleanup sweep every hour (configurable via cleanup_interval in engine config). Any flow in a terminal state whose updated_at is older than retention_secs is deleted along with its tasks.
Example: 7-day retention
{
"id": "ephemeral-jobs",
"config": {
"concurrency": 10,
"retention_secs": 604800
}
}
Flows in the ephemeral-jobs queue are automatically deleted 7 days after reaching a terminal state. Running flows are never affected by retention cleanup.
Secrets
Queues can define named secrets that are resolved at runtime and interpolated into executor configs using ${secrets.<name>} syntax. This keeps sensitive values like API keys out of flow definitions.
Each secret is a SecretRef that specifies where to load the value from:
| Source | Type | Description |
|---|---|---|
env | string | Read the secret from an environment variable |
file | string | Read the secret from a file path (trimmed of trailing whitespace) |
Example: queue with secrets
{
"id": "ai-queue",
"config": {
"concurrency": 5,
"secrets": {
"ANTHROPIC_API_KEY": { "env": "ANTHROPIC_API_KEY" },
"DEPLOY_TOKEN": { "file": "/run/secrets/deploy-token" }
}
}
}
Tasks in this queue can reference these secrets in their executor config:
{
"id": "call-ai",
"executor": "agent",
"config": {
"provider": "claude",
"prompt": "Summarize this document",
"env": { "ANTHROPIC_API_KEY": "${secrets.ANTHROPIC_API_KEY}" }
}
}
The engine resolves ${secrets.ANTHROPIC_API_KEY} by reading the ANTHROPIC_API_KEY environment variable (as configured in the queue's secrets map) and substituting the value before dispatching the task.
Per-Task Overrides
Tasks can override the queue's default timeout_secs, retries, and backoff in the flow definition. The queue config provides the defaults; task-level values take precedence.
{
"tasks": [
{
"id": "quick-check",
"executor": "http",
"config": { "url": "https://api.example.com/health", "method": "GET" },
"timeout_secs": 5,
"retries": 1,
"backoff": { "fixed": { "delay_ms": 500 } }
},
{
"id": "long-job",
"executor": "shell",
"config": { "command": "./process-data.sh" },
"timeout_secs": 3600,
"retries": 0
}
]
}
Here quick-check uses a 5-second timeout with 1 retry and fixed backoff. long-job has an hour-long timeout and no retries, regardless of queue defaults.