The Loop
This section is the wiring diagram for one ADK turn. It shows what the runner owns, what it refuses to own, and where your code becomes the agent. If you are looking for a hidden orchestrator, stop looking. The seams are the product. Each page picks one seam — turn entry, the model loop, primitives, tools, events, middleware, trust, failure, budgets — and explains what it owns, what it doesn't, and how your code attaches to it.
First time looking at an ADK?
Start with How agents work for a plain-English orientation to the vocabulary — turn, dispatch, iteration, tool, context, middleware — before diving into the contract surface here. The rest of this section assumes you know what those words mean.
A turn is one end-to-end agent request: input arrives, the ADK threads it through your code, the model is called (possibly many times, with tool calls in between), state gets persisted, the turn resolves. The ADK does the bookkeeping; you bring the behaviour.
The runner is the bookkeeper, not the agent
There is no hidden agent loop. There is no orchestrator that retries on your behalf. There is no policy quietly intercepting your messages. If something happens during a turn, it happens because your code, your middleware, or your executor made it happen.
That position is the whole point. An ADK is not a black box that "just works" until it sets something on fire. Its job is to give you a tight set of primitives, force you to declare the behavior you want, make every safety property you declared traceable to code you wrote, and run against the model you picked. Everything in this section is a closer read of one of those primitives.
What one turn actually does
TIP
Every node above is a link. Click any stage to jump to the page that owns its contract.
A turn is initiated by exactly one call to TurnRunner.run(). The runner threads a TurnContext through five stages:
- Validate. Raw input is checked against a schema. A missing
TurnContext.systemPrompt, a broken abort controller, a malformed standing instruction — rejected before any callback fires. There is no "we'll fix it up for you." - Run input middleware. Each
turnInputPipelineruns in order. Retrieval happens here. Memories load. History gets packed. Policy gets enforced. Anything that should happen before the model sees the turn. - Dispatch to the LLM.
DispatchRunnertakes over. OneDispatchContextper iteration. YourTurnRunnerConfig.executorCallbackis the only place ADK code calls a model — the ADK has no opinion about which provider. The loop continues until the executor signalsctx.ack()(DispatchContext.ack, done) orctx.nack(error)(DispatchContext.nack, failed). - Run output middleware. Each
turnOutputPipelineruns in order. Tool calls get dispatched. Results get persisted. Refusals get filtered. Telemetry gets recorded. Anything that should happen after the model produced output but before the turn returns. - Resolve.
run()resolvesPromise<void>. There is no return value. Everything you need to see was emitted through events.
No iteration limit. No retry policy. No termination heuristic.
The ADK owns the bookkeeping; the agent's behavior is yours. If you need bounds — and you do — you build them out of ctx.iteration, ctx.toolCallCount, and middleware. They are easy. They just are not done for you.
What plugs in, and where
The ADK has four classes of seam, sorted by when they run. Put code in the wrong seam and the bug is not subtle: retrieval runs ten times, telemetry becomes load-bearing, or state leaks across iterations. Turn-scope middleware runs once around the dispatch loop. Iteration-scope middleware runs every time the model is called. Storage callbacks run on demand whenever the loop reads or writes a primitive. Event listeners run whenever the loop emits. The table below is not decoration. It is the blast-radius map.
Turn-scope
Once per turn
Runs once around the dispatch loop. This is where retrieval, persistence, and policy live.
Iteration-scope
Once per LLM iteration
Runs inside the dispatch loop, once each time the model is called.
executorCallbackLLMExecutorFnThe call to your model client. Streams content via helpers; signals completion with ctx.ack() / ctx.nack().
llmInputMiddlewareLLMExecCtxMiddlewareFn[]Before the executor. Iteration-scoped pre-work and bounds enforcement.
llmOutputMiddlewareLLMExecCtxMiddlewareFn[]After the executor. Per-iteration post-work, ack-on-no-tool-calls, repetition detection.
On-demand
Storage callbacks
Called whenever the loop needs to read or write a primitive. Your store, your shape, your transactions.
fetch*Callbackper-collectionReturning persisted messages, memories, thoughts, tool calls, retrievables, tools.
store* / mutate* / delete*per-collectionWriting the harness's primitives to your store, in whatever shape your store wants.
refreshStandingInstructionsCallbackcallbackProducing the per-turn standing-instruction set when the runner loads them.
Events
When the loop emits
Two buses with the same surface, separated by intent. Functional handlers are product behaviour; observability handlers are instrumentation.
The shape of each seam is fixed. The implementation behind it is yours. The bundled batteries (LLM adapters, storage, tool catalogs) are reference implementations of those seams — they are not load-bearing. The ADK runs identically whether every callback is a database hit, an in-memory map, or a no-op.
What this section covers
- Turn Runner — the entry point, eager config validation, the
TurnContext, the two event buses. - LLM Dispatch —
DispatchRunner, the iteration loop, the executor seam, theack/nacklifecycle. - Primitives —
Message,Memory,Thought,ToolCall,Retrievable,Tokenizable,Identity. - Tools —
Tool,ToolRegistry, schema-owned argument validation, collision policy. - Artifacts —
SpooledArtifact, the handle pattern, the ephemeralSpooledArtifact.forgeToolslifecycle. - Events — functional vs observability events and the rule that separates them.
- Pipelines — input and output pipelines,
ctx.stashfor cross-middleware state. - Gates —
TurnContext.waitForand the position the ADK takes on safety, RBAC, and human-in-the-loop. - Trust Tiers — envelopes, multi-identity rendering, RAG tiering, reasoning fences.
- Failure — exception codes, validation errors, gate failures, what
DispatchContext.ack/DispatchContext.nackactually mean. - Budgets — context-window estimation, runtime-resource bounds, spool-backed artifact access.
Three other places to look
- Extending — the recipe-and-pattern half of the docs.
- API reference — generated from source; the field-by-field contract surface that never drifts.
- Trust Tiers — the deepest rationale, with per-tier research sub-pages carrying the threat models and external citations.