All Posts Orchestrating AI Agents with Temporal: Lessons from Building Pravi Agent
#AI #Engineering #Infrastructure

Orchestrating AI Agents with Temporal: Lessons from Building Pravi Agent

Cavan Page ·

Every agent framework demo looks the same: a prompt goes in, the agent loops through tool calls, working code comes out. What the demos never show is what happens when the worker dies at minute 18 of a 20-minute run, or when three agents hit your rate limit at once, or when an agent quietly burns $40 before anyone notices.

Those aren’t agent problems. They’re workflow orchestration problems, and they’re the reason I built Pravi Agent on Temporal.

Pravi Agent is an agentic feature builder: it takes a GitHub issue, has an architect agent draft a plan, waits for a human to approve it, then has a dev agent implement the change in an isolated git worktree and open a draft PR. Python, FastAPI, Postgres, the Claude Agent SDK for the dev agent, LiteLLM for architect flexibility and Temporal holding the whole thing together.

This post is about that last part, because it’s the part nobody writes about.

The Problem: Agent Runs Are Long, Expensive and Fragile

A single dev agent run in Pravi Agent can take 20+ minutes and cost real money in tokens. That combination changes the engineering calculus completely.

If a stateless web request fails, you retry it and nobody cares. If an agent run fails at minute 18, a naive retry re-bills you for 18 minutes of LLM calls and re-does work that already succeeded. Multiply that by every deploy, worker restart and transient network error, and “just retry” becomes the most expensive line in your codebase.

Temporal’s core promise is durable execution: workflow state survives process death. When a Pravi Agent worker restarts mid-run, the workflow picks up exactly where it left off. Completed activities don’t re-run. The 18 minutes of work is still there.

This one property is why I’d reach for Temporal on any agentic system that runs longer than a minute. Everything else in this post is a bonus.

Two Task Queues: The Concurrency Lever Nobody Tells You About

Pravi Agent runs two Temporal task queues:

  • pravi-features - orchestration and lightweight activities: git operations, GitHub API calls, database writes
  • pravi-llm - token-intensive agent invocations, with worker concurrency capped via --max-activities

The split exists because these two workloads have opposite scaling characteristics. Git operations are cheap and you want them fast and parallel. LLM activities are expensive and rate-limited, and running 15 concurrent Claude sessions doesn’t make anything faster - it makes everything fail.

Capping concurrency on the LLM queue turns your rate limit from a runtime error into a queueing delay. Work waits its turn instead of blowing up. When you want more throughput, you raise the cap or add a worker - the backpressure is handled by Temporal, not by retry spaghetti in your application code.

If you take one architectural pattern from this post, take this one. It’s trivial to set up and it eliminates a whole class of production incidents.

Human Approval Gates Without the Pain

Pravi Agent has a hard rule: no agent-written code ships without a human approving the plan first, and the final PR merge is always manual.

In a queue-based architecture, “pause this job for a human decision” is genuinely annoying to build. You end up persisting intermediate state, polling for approval flags and reconstructing context when work resumes.

In Temporal it’s a signal. The workflow reaches the approval gate and waits - for hours or days if needed - without holding a worker slot or burning compute. When the human approves in the UI, the workflow wakes up and continues with all its state intact. The waiting costs nothing.

This matters beyond convenience. Human-in-the-loop gates are the main safety mechanism for autonomous agents, and if they’re painful to build, teams skip them. Temporal makes the safe architecture the easy one.

Not All Failures Deserve a Retry

When a dev agent run fails, Pravi Agent classifies the failure before deciding what to do:

  • Quota exhaustion - retrying immediately is pointless. Back off and let the queue drain.
  • Timeout - possibly transient. Retry with standard backoff.
  • Budget limit hit - retrying would spend more money on a task that already exceeded its ceiling. Stop and surface to a human.
  • Turn limit hit - the agent is likely stuck in a loop. More turns won’t help. Stop and surface.

Temporal’s per-activity retry policies make this straightforward: the classification happens in the activity, and different failure types map to retryable versus non-retryable errors. The alternative - a blanket retry policy - either gives up on recoverable failures or burns money on unrecoverable ones.

This is the detail I’d flag for anyone building agents: your failure taxonomy is as important as your prompt. LLM workloads fail in ways traditional workloads don’t, and each way needs a different answer.

Cost Control That Actually Enforces

The number one anxiety about autonomous agents is spend. Pravi Agent addresses it with budget ceilings that cascade down the work hierarchy: an epic has a budget, its features inherit shares of it and tasks inherit from features.

Enforcement happens twice. Pre-flight: a task that would exceed its remaining budget doesn’t start. Mid-run: spend is tracked as the agent works, and blowing the ceiling is a terminal failure, not a warning log.

Because all agent invocations flow through Temporal activities, there’s a single choke point where enforcement lives. No agent can spend money outside the workflow, so no agent can spend money outside the budget. Spend rolls up by persona and stack so you can see exactly where the money goes.

If your agent system’s cost control is a dashboard you check after the fact, you don’t have cost control. You have cost reporting.

The Temporal UI Is Your Agent Observability for Free

Pravi Agent names workflows deterministically - feature-<repo-slug>-<ticket-id> - with a reuse policy that only allows re-running failed workflows. That means duplicate submissions are idempotent by construction: starting the same ticket twice can’t create two runs.

Search attributes (RepoName, Domain, TicketId, PraviStatus) are indexed on every workflow, so the Temporal UI doubles as an operations dashboard. Which tickets are running, which failed, what’s waiting on approval - it’s all queryable without building any of it.

Agent observability is an entire product category right now. A decent chunk of it falls out of Temporal for free if you name your workflows well.

Frequently Asked Questions

Why not just use Celery or a job queue?

Queues move tasks; they don’t preserve workflow state across failures. The multi-step shape of agent work - plan, wait for approval, build, test, open PR - means state has to live somewhere between steps. With a queue that’s your job. With Temporal it’s the platform’s job, and durable resume after a crash comes with it.

Does Temporal add latency to agent runs?

Nothing meaningful. Activity scheduling overhead is milliseconds against LLM calls measured in seconds or minutes. The dominant cost in any agentic system is inference time, and Temporal doesn’t touch that.

When is Temporal overkill?

Single-shot LLM calls, short stateless pipelines and anything a cron job handles fine. The break-even is roughly: your runs are long enough that losing one hurts, or your flow has waits and human gates in the middle. Below that line, a queue is simpler and simpler wins.

Does this pattern only work with Claude?

No. Pravi Agent’s architect agent runs on any LiteLLM-supported provider, and the dev agent uses the Claude Agent SDK because it needs full tool execution. The orchestration layer doesn’t care what’s inside the activity - that’s the point of the seam.

The Takeaway

Agent frameworks are converging fast and they’re all fine. The differentiation in production agentic systems isn’t the reasoning loop - it’s durability, concurrency control, human gates, failure taxonomy and cost enforcement. That’s orchestration, and it’s a solved problem if you use tools built for it.

If you’re building an agent system today: put every LLM call in an activity, split your queues by cost profile, classify your failures before writing retry policies and make budgets a hard stop. You’ll skip most of the incidents that teach these lessons the expensive way.

Pravi Agent is open source at github.com/cavanpage/pravi-agent if you want to see the implementation.