ADK Architecture
The runtime is a graph of nodes. BaseNode is the unit of execution.
Workflow is a node that schedules other nodes along declared edges.
NodeRunner executes exactly one node. Runner owns the invocation and the
session. Agents are nodes too — BaseAgent extends BaseNode.
A node communicates with its parent through a per-execution Context, and
with the session through Events it yields. Those are two separate channels:
ctx carries the result upward, events carry persistence and streaming.
Read the source before relying on any signature here. These notes drift; the
code does not. Paths below are relative to src/google/adk/.
Pick a reference
| Question | Reference |
|---|---|
| What must a node implement? What may it yield? Which config fields exist? | BaseNode |
| How does the graph schedule nodes, dedup dynamic children, propagate interrupts? | Workflow |
| How does a caller start an invocation? | Runner |
What is Agent, and which methods do I call on it? |
Agent |
| I am subclassing an agent — what do I override? | BaseAgent |
What is on an Event, and what may I assume about its lifetime? |
Event |
What does a node read and write on ctx? |
Context |
| Who creates the child Context, stamps events, retries, catches errors? | NodeRunner |
| Why are Runner, NodeRunner and Workflow three separate things? | Runner roles |
| How does a human-in-the-loop pause and resume work for one node? | Checkpoint and resume |
How does a whole workflow survive a pause, and what does is_resumable change? |
Workflow resumability |
| How are spans created, and what attributes do they carry? | Observability |
| Why does the model not see the raw event log? | LLM context orchestration |
| Is this change a breaking change? Where does a new export belong? | API principles |
Where the code lives
| Concept | Module |
|---|---|
BaseNode, START |
workflow/_base_node.py |
Workflow, _LoopState |
workflow/_workflow.py |
Graph, edge compilation |
workflow/_graph.py |
NodeRunner |
workflow/_node_runner.py |
DynamicNodeScheduler |
workflow/_dynamic_node_scheduler.py |
ReplayManager (resume scan) |
workflow/utils/_replay_manager.py |
NodeInterruptedError, NodeTimeoutError |
workflow/_errors.py |
Context, ctx.run_node() |
agents/context.py |
ReadonlyContext |
agents/readonly_context.py |
InvocationContext |
agents/invocation_context.py |
BaseAgent, LlmAgent (aliased Agent) |
agents/base_agent.py, agents/llm_agent.py |
Event, NodeInfo |
events/event.py |
EventActions |
events/event_actions.py |
Branch paths (parent.child@1) |
events/_branch_path.py |
Node paths (wf@1/child@2) |
events/_node_path_builder.py |
Runner, InMemoryRunner |
runners.py |
LiveRequestQueue, LiveRequest |
live/live_request_queue.py |
Node spans, TelemetryContext |
telemetry/node_tracing.py |
ResumabilityConfig |
apps/_configs.py |
Everything under workflow/ is a leading-underscore module. Treat those names
as internal — they can change without a major version bump, so a change there
is not automatically a breaking change.