Engineering · Guides · Agent runtime

The Agent Execution Stack

How AgencyCore runs AI agents: durable Inngest workflows composing a webhook-driven agent runtime over a pluggable execution backend. The system at the level of architecture, lifecycle, and the core components.

Engine Inngest · durable stepsBackend Claude Managed AgentsDurability webhook park / wakeTenancy per-app dispatch slugs
clientdispatch event·durable workflowhosted agent run
00

At a glance

Two subsystems hold this up. inngest_functions is the durable composition layer: event-triggered step functions that orchestrate AI work. agent_runtime is the atom underneath: a provider-neutral library that runs exactly one agent. The composition is durable; the backend is pluggable.

4
Named tiers
edge · core · backend · data
1
Swap point
provider-neutral interface
0
Workers held
while a run is parked
N
Subagents
fan out, failure-isolated
KEYComposition over an atom, pluggable backend

Agent Runtime knows how to run one agent and nothing about workflows. Everything above is composition: Agent Runner makes a run durable, Components batch runs into a capability, Workflows sequence capabilities into a product. And because Agent Runtime is an abstraction, not Anthropic-specific, Claude Managed Agents is just the current implementation behind the interface.

01

System design — the four tiers

Read top to bottom: from what a client calls down to where the work happens. Each tier hides the one below it. Click a box to trace what it connects to. The dashed line is the swap point — below it, the backend is replaceable.

API & Edgewhat clients call
Core ServiceInngest functions → agent runtime
Agent Execution Backendpluggable · one impl behind the interface
Data & Infrastructurethe state the stack stands on
Idea 1 · composition over an atom

The Core Service layer is itself a stack. Agent Runtime runs one agent; Agent Runner makes it durable; Components batch it; Workflows sequence it. Each layer adds exactly one thing.

Idea 2 · pluggable backend

Agent Runtime is an interface, not a vendor. Managed Agents satisfies the provider contract today; a background-worker backend that satisfies the same contract drops in with nothing above it changing.

02

Request lifecycle

One company-discovery run as a sequence across the four tiers, time flowing down. Solid arrows are calls; dashed arrows are async events and the SSE stream the client waits on. It plays on a loop — click any step to pin it.

ClientbrowserAPI & EdgeFastAPICore ServiceInngestAgent BackendManaged AgentsPOST /workflows/{id}/runscreate workflow_runs rowborn before dispatchdispatch gateslug ∈ DISPATCH_SLUGS?workflow/sonar.requestedInngest event202 { workflow_run_id, queued }sonar() wakes · run strategistrun_agent_batch · open sessionswebhooks → finalizeenrich · score · storeSSE: running · 1/2 · 2/2 · completed
03

Webhook-driven execution

The mechanic the whole stack rests on: a multi-minute agent run holds no worker. The runner parks on a durable wait; the backend finishes whenever it finishes and a signed webhook wakes the exact parked step by run_id.

Agent Runnerdurable · InngestManaged Agentshostedopen sessionsend kickoffwait_for_event ⏸ PARKEDno worker heldworking…seconds to minutessession.idled → POST webhookfire agent/session.endedwoken · poll_status terminal?run_id match · 60s backstopfetch result + usagebill once · mark terminal · SSE done
Webhooks can be lost; the parked wait is bounded to 60-second slices and re-confirms with poll_status on every wake, so the run still converges if a webhook never arrives.
04

Agent Runner — the durable bridge

Agent Runtime is a plain async library: open_run() returns once the session is open, but the answer arrives minutes later over a webhook. Agent Runner converts that into a durable, replay-safe Inngest step graph that retries deterministically and never double-bills. It exposes three entry points.

entry points
FunctionShapeUsed byFailure policy
run_agent_output1 required plannerWorkflowspermanent → NonRetriableError aborts workflow
run_agent_batchN subagents, parallelComponentspermanent isolated per-branch; siblings continue
agent_runstandalone queued runRuns APIon_failure closes run after retries

Components run on run_agent_batch: one deterministic prefetch, then a failure-isolated batch of subagents that fans back into one aggregated result. One branch below fails permanently — watch it isolate while its siblings still land.

prefetchExa · Huntersubagent 1company_searchsubagent 2company_searchsubagent 3permanent error · isolatedsubagent 4company_searchaggregate+ dedup{ items, subrun_failures }
05

Agent run state machine

Every agent_runs row walks this graph; all terminal writes are status-gated, so a webhook and a reaper racing to close the same run resolve to one outcome. Click a state to trace its transitions.

claim (CAS)finalizeerrortimeoutuserreaperqueuedrunningcompletedfailedexpiredcancelled
06

Error taxonomy

The runner translates the runtime’s neutral provider errors into Inngest retry decisions. The same error is handled differently on the single path versus inside a parallel batch. Pick a class.

TriggerHTTP 400 · 405 · 422
DecisionNonRetriableError
fail fast, no retries

A malformed request or unsupported operation will never succeed on retry. On the single path it raises NonRetriableError, which aborts the workflow immediately rather than burning the retry budget.

07

Configuration & the dispatch gate

INNGEST_WORKFLOW_DISPATCH_SLUGS is the rollout lever: per app, it routes a workflow to the new Inngest engine or the legacy Celery path. Both produce the same workflow_runs row and the same SSE frames — only the engine differs. Toggle to compare.

When the workflow’s slug is enabled for this environment, dispatch sends an Inngest event and the durable function drives the run.

send workflow/{slug}.requestedinngest_client.send
durable Inngest functionstep.run · step.invoke · wait_for_event
webhook-driven agent runsno worker held while parked
same workflow_runs + SSEcontract unchanged
08

Operations

Three things must be wired for the stack to run end-to-end, plus one debugging path for the most common failure — a run stuck at running.

1

Register the Anthropic webhook

Point the Console session webhook at /api/v1/agent-runtime/anthropic/webhook and set ANTHROPIC_WEBHOOK_SIGNING_KEY. Boot fails in production without it — the receiver cannot verify signatures, so no run ever finalizes.

2

Wire the Inngest app

Set INNGEST_EVENT_KEY + INNGEST_SIGNING_KEY (required in prod) and register the app so Inngest Cloud can reach /api/inngest. INNGEST_WORKFLOW_DISPATCH_SLUGS gates which workflows take the new path.

3

Watch the crons

reap-stale-agent-runs (every 15m) expires runs past deadline; aggregate-daily-ai-usage (00:05 UTC) rolls up billing. Both are Inngest TriggerCron functions, status-gated and idempotent.

4

Debug a stuck run

A row stuck at running usually means a missed webhook. Check the receiver logs for the session id, confirm the signing key, and let the 60s poll backstop or the reaper converge it. The agent_runs row carries provider_session_id and console_url for the cross-reference.

09

Appendix — enums & schema

The frozen contracts. Status and stop-reason live on the agent_runs row; the webhook event kinds and SSE types are the wire vocabulary between the backend, the runner, and the browser.

EnumValues
AgentRunStatusqueued · running · completed · failed · expired · cancelled
RunStopReasonend_turn · retries_exhausted · requires_action · error · terminated · timeout · invalid_output · cancelled
AgentWebhookEventKindrun_started · progress · idle · terminated · deleted · updated · unknown
SSE event_typesession.lifecycle (progress) · run (terminal)
10

Sources

AgencyCore · Engineering guide · The Agent Execution StackInngest · Claude Managed Agents