Production Agent Runtimes and Queues
Pick a runtime shape, checkpoint and replay, lease and fence jobs, retry scoped reads. Python and Go.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Defaults and numbers
- Troubleshooting
- Questions and tips
Pick the runtime shape#
- Request-response
- synchronous HTTP. The user waits. Short tasks only (<30s).
- Streaming
- SSE or WebSocket. Watch first-token latency and tail latency.
- Durable execution
- state checkpointed after every step. Auto-resumes on failure.
- Queue-based
- producer, worker pool, DLQ. Watch queue depth and DLQ size.
- Event-driven
- the agent subscribes to triggers. Watch event-to-start latency.
- Scheduled
- cron-shaped. Combine with durable execution.
- Workflow and activity
- deterministic orchestration code. Each LLM or tool call is a logged activity.
- Exactly-once effective
- at-least-once delivery plus an idempotent consumer.
From Lesson 14.29, Lesson 15.12, Lesson 16.22
Durable job store#
From durable-agent-jobs/API.md, durable-agent-jobs/README.md
Job lifecycle in the stages#
From solution/stage1.go, solution/stage2.go, solution/stage3.go, solution/stage4.go
Durable jobs CLI#
| Command | What it does |
|---|---|
go build -o / | Builds the reference binary in solution |
enqueue --store <dir> --input <file> | Adds jobs. Identical redelivery is safe |
worker --store <dir> --max-jobs 100 | Claims and runs up to 100 jobs |
status --store <dir> | Shows jobs, available effects and scope |
--crash-after effect | Exits 86 after the receipt exists |
--crash-after claim | Exits 86 before an output exists |
--now-ms 111 --lease-ms 10 | Logical clock. The old lease expired |
go run . demo | Runs the effect-crash sequence |
From durable-agent-jobs/README.md, durable-agent-jobs/API.md
Run the demos#
| Task | Code |
|---|---|
| Run a lesson demo | python3 code/ |
| Grade a project reference | --all --solution --strict |
| Run the cloud reader | python3 cli.py samples/ |
| Read live AWS instead | python3 cli.py <input> --mode aws |
| Pin the optional SDK | strands-agents= |
From Lesson 14.29, durable-agent-jobs/README.md, cloud-agent-with-aws-strands/README.md, cloud-agent-with-aws-strands/requirements-framework.txt
Activity replay from an event log#
@activity("call_llm")each LLM call is an activitydef call_llm(doc_count: int) -> str: return f"summary({doc_count}_docs)"hit = log.lookup(name, args)done event with the same argsif hit: return hit["result"]replay, no new executionlog.append({"name": name, "args": list(args), "status": "started"})result = fn(*args)runs only when not in the loglog.append({"name": name, "args": list(args), "status": "done", "result": result})From 15.12 code/main.py
Checkpoint per super-step#
store = CheckpointStore(db_path)SQLite table checkpointsstore.write(thread_id, super_step, dict(state))one row per steprestored = store.latest(thread_id)ORDER BY super_step DESCsuper_step, state = restoredsuper_step += 1resume after the last steprun_agent_with_checkpoint(store, "t-1", crash_at=3, goal=5)final = run_agent_with_checkpoint(store, "t-1", goal=5)worker 2await asyncio.gather(*(sim_llm_call_async() for _ in range(n)))From 16.22 code/main.py
Scoped cloud reader contract#
ALLOWED = {"inventory.list", "metrics.read", "logs.read"}validate_plan(raw, scope)one to ten actions, read-only, in scopeexecute(plan, provider, max_steps=5, max_chars=4000)request_key(operation, resource) # sha256 of the JSON paircached_read(operation, resource, provider, cache, retries=2)run(payload, provider)adds receipts with cached, attemptsparse_model_plan(text, scope)the model plan goes to validate_planFrom cloud-agent-with-aws-strands/API.md, solution/plan.py, solution/executor.py, solution/retry.py, solution/cli.py
Defaults#
| Setting | Default | Note |
|---|---|---|
--lease-ms | 30000 | Claim lease in milliseconds |
--max-attempts | 3 | Then the job waits for manual reconciliation |
--max-jobs | 1 | Jobs for one worker invocation |
--delay-ms | 0 | Range 0..60000 |
--now-ms | -1 | -1 means current wall time |
max_steps | 5 | Step budget. max_chars is 4000 |
retries | 2 | In cached_read. The CLI run passes 1 |
From durable-agent-jobs/API.md, solution/main.go, solution/executor.py, solution/retry.py, solution/cli.py
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
| Users hang up, retries compound | Request-response for a 5-minute task | Use a queue or durable shape |
| Failed jobs vanish | Queue workers without dead-letter | Add a DLQ and surface DLQ size |
| Failures invisible until a user reports | Background run without trace export | Export OpenTelemetry GenAI spans |
| Crash reruns tools and re-bills LLM calls | Naive while True loop | Log each call as an activity and replay |
| Completion returns conflict | Stale worker: version or lease changed | The new claim finishes the job |
| Job stays at the attempt cap | --max-attempts reached | Reconcile the visible job manually |
| Replay diverges | Wall clock inside a workflow decision | Register it as a side effect |
| 10,000 calls use 10GB of stacks | One thread for each LLM call | Use async for the I/O layer |
From Lesson 14.29, Lesson 15.12, Lesson 16.22, durable-agent-jobs/README.md, durable-agent-jobs/API.md, solution/stage3.go
Questions#
- Which checkpoint backend?
- PostgreSQL survives deploys. SQLite is local-dev only. Redis is ephemeral unless AOF/snapshot configured.
- When is durable execution wrong?
- Runs shorter than a few minutes with no human input, or strictly read-only retrieval.
- Does durability fix the 35-minute degradation?
- No. Add fresh HITL on re-entry and budget kill switches.
- Is the receipt exactly once?
- No. Execution is at least once. The receipt makes only this local effect idempotent.
- Why two provider reads for three steps?
- Repeated metrics.read uses the request-local cache.
- What does recovery show?
incident-summaryis completed v4, attempts 2, withreused_effect: true.
From Lesson 15.12, durable-agent-jobs/README.md, cloud-agent-with-aws-strands/README.md, Lesson 16.22
Tips#
- Start with FastAPI + Postgres. Upgrade when you measure it failing.
- Put a dedup key in every side-effect call.
- Use the outbox pattern for payments and external API calls.
- Use rainbow deploys. Never kill in-flight agent runs during deploys.
- Use async for I/O. Use threads only for CPU-bound post-processing.
- Keep the job store on one local POSIX filesystem. Do not put it on NFS.
- Do not edit storage files while workers run.
From Lesson 16.22, durable-agent-jobs/README.md, durable-agent-jobs/API.md