Executor and flows¶
Every tool call runs through one Executor pipeline. Flows sit on top: the local
FlowRunner turns each node into executor work, so guards, approval, cache, retries,
ledger and events apply inside flows too.
flowchart TB
APP["your code / framework integration"] --> FR["FlowRunner<br/>supersteps · merges · routes · pause"]
APP --> EX
FR -- "Call → run_spec<br/>Map → run_many<br/>node retry/timeout → parallel" --> EX["Executor<br/>one pipeline per call"]
EX --> T["tool functions"]
FR -. "FlowGraph (validated IR)" .-> C["compile_flow<br/>LangGraph · ADK"]
One call through the executor¶
flowchart TB
G["run guards"] --> A["validate args"] --> B["before_tool hooks<br/>(may short-circuit)"]
B --> C{"confirm?"}
C -- yes --> AP["approval port"]
C -- no --> D
AP --> D{"cache hit?"}
D -- hit --> F
D -- miss --> E["timeout + retry<br/>backoff, jitter"]
E --> F["after_tool hooks"] --> L["ledger entry"] --> H["cache store"] --> R["result + events"]
Retries never catch CancelledError, LangGraph GraphBubbleUp or ApprovalPending.
Details: Executor, Executor policies.
Fan-out, fan-in and waves¶
| API | Shape |
|---|---|
run_many(calls, concurrency=8) |
independent calls in parallel; results in input order |
as_completed(calls) |
results as they finish |
run_plan(steps) |
depends_on waves (Kahn layers); cycles rejected before running |
parallel(*work) |
non-tool work with the same timeout, retry and cancellation |
No batch raises an ExceptionGroup: the first escaping error is re-raised as itself.
Flows on the executor¶
A flow runs in supersteps, as in LangGraph. The frontier starts at the start node.
Each superstep runs the ready nodes concurrently, then merges their updates by each
field's rule (replace, append, sum, keyed). See Flows.
sequenceDiagram
participant R as FlowRunner
participant P as approval port
participant E as Executor
R->>R: ready = non-join nodes in frontier (joins wait)
R->>P: pause gates (kind="node")
P-->>R: approved / denied / ApprovalPending
R->>E: Call → run_spec · Map → run_many
E-->>R: values (ApprovalPending pauses the flow)
R->>R: merge updates, then edges + route targets
| Concept | Local runner | LangGraph | ADK 2.x Workflow |
|---|---|---|---|
| Superstep | ready set run together | Pregel superstep | one driver node runs each ready set (ctx.run_node) and merges |
| Static parallel branches | same superstep | multiple edges | same ready set |
Map |
one node, executor.run_many |
one node calling executor.run_many |
one node calling executor.run_many |
Join |
deferred until no other node is pending | defer=True node |
deferred by the driver |
Route |
fn(input state + own updates) |
add_conditional_edges |
evaluated by the driver |
| Merge rules | node-name order | reducers; replace / keyed as a channel that sees all of a superstep's writes |
merged by the driver |
| Loop cap | per-route counter, on_exhausted |
aa_loop_counts / aa_visits in state |
aa_loop_counts / aa_visits |
pause gate |
port asked before the superstep; nothing in it runs | interrupt_before the gated nodes |
confirmation before the ready set |
| Tool approval in a node | checkpoint keeps finished siblings; paused node re-runs | interrupt(); pending writes kept |
node re-runs on resume |
| Calls a resumed node already finished | replayed from the node's journal (intended improvement) | run again | run again |
| Node retry, timeout | Executor.parallel rules |
RetryPolicy, timeout= |
RetryConfig, timeout |
| Concurrency cap | FlowRunner(concurrency=), node concurrency |
max_concurrency |
max_concurrency |
| Run limit | max_steps |
recursion_limit |
the driver's step counter |
The compilers consume flow.validate() (the FlowGraph), never the builder. Parity
fixtures in tests/flow/parity.py hold the canonical cases (fan-out, loops, pauses, merge
order, routes, joins) with their expected final states; every target must reproduce them.
Ids that tie it together¶
| Id | Form | Why |
|---|---|---|
| run | ctx.run_id (run_…) |
guards, ledger and approvals scope to it; a resumed flow keeps it |
| node visit | "<flow>:<node>:<visit>" |
node events and the node context |
| tool call in a node | "<flow>:<node>:<visit>:<i>" |
a resumed node re-enters the same calls |
| node approval | tool="node:<flow>.<node>", kind="node" |
approval stores keep node and tool requests apart |
The local runner has no persistence: a paused run returns a FlowCheckpoint you resume
in the same process. Durable pause and resume come from the compiled targets.