Executor¶
Running tool calls: retries, plans and the tool loop. Concepts: Executor, Executor policies.
agent_adapter.exec.integration is a seam for flow compilers and framework bridges; most code never needs it. Checkpoint stores for crash-safe runs: Durable runs.
agent_adapter.exec¶
Executor: runs tools with guards, hooks, approval, cache, retries, batches, plans and loops.
OnError = Literal['collect', 'fail_fast']
module-attribute
¶
AgentDepthExceeded
¶
Backoff
dataclass
¶
Exponential backoff with jitter; delays in seconds.
delay(attempt, rng)
¶
Seconds to wait after failed attempt attempt (1-based).
Raises:
| Type | Description |
|---|---|
ToolUsageError
|
|
HandoffModel
¶
Wraps a model: once a handoff tool has answered, the loop ends with that answer.
LoopAgent
dataclass
¶
A run_tool_loop agent: its model, the tools it may use and its instructions.
first_messages(task)
¶
The history a fresh run starts from: the instructions, then the task.
SyncTwins
¶
run_sync, run_call_sync, run_many_sync and resume_sync for the Executor.
Each blocks until done on one long-lived loop shared by the process (a
runtime.LoopRunner), so loop-bound state such as clients and semaphores stays valid
across calls. In async code, await the async method instead.
resume_sync(approval_id, decision=None, ctx=None, *, decided_by='', reason='', timeout=None)
¶
resume from sync code: decide a parked call and run it once.
run_call_sync(call, ctx=None, *, registry=None, timeout=None)
¶
run_call from sync code; failures are in the ToolResult.
run_many_sync(calls, ctx=None, *, concurrency=None, on_error='collect', registry=None, timeout=None)
¶
run_many from sync code; results keep input order.
run_sync(tool, args=None, ctx=None, *, call_id=None, timeout=None)
¶
run from sync code; returns the value and raises like run.
Raises:
| Type | Description |
|---|---|
TimeoutError
|
past |
ToolUsageError
|
called from a tool already running on the shared loop. |
ToolLoopResult
dataclass
¶
Where a tool loop ended: the full history, the last reply and every tool result.
agent_depth()
¶
How many agent tools are running around the current call (0 outside any).
agent_tool(agent, *, name, description, executor=None, max_depth=DEFAULT_MAX_DEPTH, timeout=None, confirm=False, tags=())
¶
A tool (kind "agent") that runs agent on the model's task inside the calling run.
default_executor()
¶
The default Executor, created on first use, on the default registry.
handoff(to, *, name, description, executor=None, max_depth=DEFAULT_MAX_DEPTH, timeout=None)
¶
An agent tool tagged "handoff": under a HandoffModel, its answer is the loop's answer.
is_control_flow(exc)
¶
True for CancelledError, a registered type, a class named GraphBubbleUp in the MRO, or an exception group holding any of them.
recover_tool_loop(model, registry, run_id, *, checkpoints, checkpoint_key=None, rerun_in_doubt=False, max_steps=8, executor=None, ctx=None, output_schema=None, tool_choice='auto', concurrency=None, stage='tool_loop', request_options=None, compression=None, on_text=None, on_event=None)
async
¶
Continue a durable tool loop from its last checkpoint; finished tool calls replay.
A call that started but logged no result runs again only if its tool is
idempotent=True, or with rerun_in_doubt=True after you checked it. max_steps
counts the model calls of the whole run, before the crash too; tool_results and
usage cover this call only.
Raises:
| Type | Description |
|---|---|
CheckpointNotFound
|
nothing is stored for |
CheckpointConflict
|
another worker took the run, it ended, or it is not this loop. |
CheckpointStoreCorrupt
|
the stored checkpoint or its signature does not check out. |
RecoveryNeedsDecision
|
a call may have run and its tool is not idempotent. |
(ToolUsageError, ConfigError, ModelError)
|
as |
register_control_flow(*types)
¶
Treat types (and subclasses) as control flow in the executor and the approval ports.
Raises:
| Type | Description |
|---|---|
ToolUsageError
|
no types given, or one is not a BaseException subclass. |
reset_default_executor()
¶
Drop the default Executor and its default sinks; the next use starts fresh.
runner_for(executor)
¶
executor when given, else the default Executor.
Also exported here, documented under the canonical path: Executor, RunLimits, Step, acall, call, configure, run_tool_loop, set_default_executor.
agent_adapter.exec.checkpoints¶
Checkpoint stores for crash-safe runs: the port, and memory, SQLite, Redis and SQL backends.
CallState = Literal['started', 'done', 'pending', 'unrecordable']
module-attribute
¶
CheckpointKind = Literal['flow', 'loop']
module-attribute
¶
CheckpointStatus = Literal['running', 'paused', 'done', 'denied', 'failed']
module-attribute
¶
CallRecord
¶
Bases: BaseModel
One tool call of a durable run: started, done (result kept), pending or unrecordable.
Checkpoint
¶
Bases: BaseModel
One saved position of a run: what it is, its status, and the sealed document.
CheckpointStore
¶
Bases: Protocol
Where durable runs keep their latest checkpoint and the tool calls of the current step.
save is a compare-and-set on the step: step 0 only for a new run, else exactly one past
the stored step; anything else raises CheckpointConflict, so two workers never both
continue a run. A save that moves to a new segment drops the older segments' call logs.
record_call refuses a segment that is not the run's current one (CheckpointConflict).
MemoryCheckpointStore
¶
A CheckpointStore in a dict; it survives a failed run, not a process exit.
calls(run_id, segment)
async
¶
The call records of one segment, in the order first written.
delete(run_id)
async
¶
Forget the run and its call log; False when nothing was stored.
latest(run_id)
async
¶
The run's latest checkpoint, or None.
list_unfinished(*, name=None, limit=100)
async
¶
Runs whose latest checkpoint is running, oldest first, optionally of one name.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a negative |
record_call(run_id, segment, call)
async
¶
Keep a call record in the run's current segment (latest record per key wins).
Raises:
| Type | Description |
|---|---|
CheckpointConflict
|
|
save(run_id, step, checkpoint)
async
¶
Store the run's next checkpoint (compare-and-set on step).
Raises:
| Type | Description |
|---|---|
CheckpointConflict
|
|
ToolUsageError
|
a bad run id, step or checkpoint. |
RedisCheckpointStore
¶
CheckpointStore on Redis (agent-adapter[redis]), shared by every worker and host.
client_factory() returns a redis.asyncio.Redis (one per event loop), e.g.
lambda: Redis.from_url(url). save, record_call and delete are Lua scripts, so a
step's compare-and-set is atomic. A run's keys expire ttl_s after its last write
(None: never), except while it is paused: a paused run waits for a person and never
expires. On Redis Cluster put a hash tag in prefix ("{checkpoints}:"), since the
scripts touch a run's keys and the shared index together.
Raises:
| Type | Description |
|---|---|
ConfigError
|
|
aclose()
async
¶
Close the running loop's client.
calls(run_id, segment)
async
¶
The call records of one segment, in the order first written.
Raises:
| Type | Description |
|---|---|
CheckpointStoreCorrupt
|
a stored record is unreadable or has another key. |
delete(run_id)
async
¶
Forget the run and its call log; False when nothing was stored.
latest(run_id)
async
¶
The run's latest checkpoint, or None.
Raises:
| Type | Description |
|---|---|
CheckpointStoreCorrupt
|
the stored hash is unreadable or disagrees with itself. |
list_unfinished(*, name=None, limit=100)
async
¶
Runs whose latest checkpoint is running, oldest first, optionally of one name.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a negative |
record_call(run_id, segment, call)
async
¶
Keep a call record in the run's current segment (latest record per key wins).
Raises:
| Type | Description |
|---|---|
CheckpointConflict
|
|
save(run_id, step, checkpoint)
async
¶
Store the run's next checkpoint (compare-and-set on step).
Raises:
| Type | Description |
|---|---|
CheckpointConflict
|
|
CheckpointStoreCorrupt
|
the stored step is not an integer. |
ToolUsageError
|
a bad run id, step or checkpoint. |
SQLAlchemyCheckpointStore
¶
CheckpointStore on a SQLAlchemy async engine (agent-adapter[sqlalchemy]), every host.
engine is a URL ("postgresql+asyncpg://...") or a callable returning an
AsyncEngine; one engine is made per event loop. A step's compare-and-set is one
INSERT ... ON CONFLICT DO NOTHING (step 0) or UPDATE ... WHERE step = n - 1.
metadata holds both tables, for Alembic. Backend errors propagate (fail closed).
Raises:
| Type | Description |
|---|---|
ConfigError
|
a bad |
MissingDependency
|
SQLAlchemy is not installed. |
aclose()
async
¶
Dispose of the running loop's engine.
calls(run_id, segment)
async
¶
The call records of one segment, in the order first written.
Raises:
| Type | Description |
|---|---|
CheckpointStoreCorrupt
|
a stored record is unreadable or has another key. |
delete(run_id)
async
¶
Forget the run and its call log; False when nothing was stored.
latest(run_id)
async
¶
The run's latest checkpoint, or None.
Raises:
| Type | Description |
|---|---|
CheckpointStoreCorrupt
|
the stored row is unreadable or disagrees with itself. |
list_unfinished(*, name=None, limit=100)
async
¶
Runs whose latest checkpoint is running, oldest first, optionally of one name.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a negative |
record_call(run_id, segment, call)
async
¶
Keep a call record in the run's current segment (latest record per key wins).
Raises:
| Type | Description |
|---|---|
CheckpointConflict
|
|
save(run_id, step, checkpoint)
async
¶
Store the run's next checkpoint (compare-and-set on step).
Raises:
| Type | Description |
|---|---|
CheckpointConflict
|
|
ToolUsageError
|
a bad run id, step or checkpoint. |
SQLiteCheckpointStore
¶
CheckpointStore in a SQLite file (WAL), safe across threads, loops and processes.
Each call runs in a worker thread on its own connection; writes hold BEGIN IMMEDIATE,
so a step's compare-and-set is atomic for every process; timeout is the lock wait (s).
Raises:
| Type | Description |
|---|---|
ConfigError
|
a bad |
calls(run_id, segment)
async
¶
The call records of one segment, in the order first written.
Raises:
| Type | Description |
|---|---|
CheckpointStoreCorrupt
|
a stored record is unreadable or has another key. |
delete(run_id)
async
¶
Forget the run and its call log; False when nothing was stored.
latest(run_id)
async
¶
The run's latest checkpoint, or None.
Raises:
| Type | Description |
|---|---|
CheckpointStoreCorrupt
|
the stored row is unreadable or disagrees with itself. |
list_unfinished(*, name=None, limit=100)
async
¶
Runs whose latest checkpoint is running, oldest first, optionally of one name.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a negative |
record_call(run_id, segment, call)
async
¶
Keep a call record in the run's current segment (latest record per key wins).
Raises:
| Type | Description |
|---|---|
CheckpointConflict
|
|
save(run_id, step, checkpoint)
async
¶
Store the run's next checkpoint (compare-and-set on step).
Raises:
| Type | Description |
|---|---|
CheckpointConflict
|
|
ToolUsageError
|
a bad run id, step or checkpoint. |
StoredCheckpoint
¶
Bases: BaseModel
A run's latest checkpoint and its step; the next save must use step + 1.
idempotency_key()
¶
A key unique to the running tool call of a durable run, the same after a recovery.
Pass it to an external API that accepts idempotency keys (payments, mail), so a call that runs again after a crash, or on a worker that was cut off, is not applied twice.
agent_adapter.exec.control¶
agent_adapter.exec.control re-exports, documented under the canonical path: is_control_flow, register_control_flow.
agent_adapter.exec.integration¶
Semi-public executor seam for framework integrations (LangGraph, ADK); not in exec.__all__.
Integrations that drive the executor's approval, journal and resume internals import them from here, never from the private modules, so a refactor there keeps this surface stable.
AnswerEntry = tuple[ApprovalRequest, ApprovalDecision, bool]
module-attribute
¶
JournalData = dict[str, Any]
module-attribute
¶
Pending = tuple[ApprovalRequest, ApprovalStore | None]
module-attribute
¶
RAN_UNKEYED = 'ran, arguments not sealable'
module-attribute
¶
gather_unwrapped = _gather_unwrapped
module-attribute
¶
Run factories concurrently in input order; the first escaping exception cancels the rest.
make_answer = _make_answer
module-attribute
¶
The ResumeAnswer for a visit from its AnswerEntrys.
make_journal = _make_journal
module-attribute
¶
A Journal for one node visit (replays finished calls when the node re-runs).
settle_own = _settle_own
module-attribute
¶
Decide and consume a resumed answer's record in the executor's own store, if it is there.
visit_scope = _visit_scope
module-attribute
¶
Context manager installing a resume answer and a journal for one node visit.
Journal
¶
The finished executor calls of one node visit, replayed when a resumed node re-runs.
A call is keyed by tool, the canonical digest of its validated arguments, and how many
calls with that key came before it in the visit. Child journals (a Map item, a nested
flow's node) are keyed by name and occurrence. Pending results are never recorded; a call
that ran but cannot be recorded leaves a marker, so a pause fails instead of re-running it.
durable
property
¶
Whether a durable run logs this journal's calls.
child(name, seed=None, *, calls=True)
¶
The next child journal named name (recorded one, else a new one from seed).
claim(tool, arguments, returns=None)
¶
The next slot for this call (None when it is not journaled).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Any
|
The tool's return annotation; the classes it reaches may be replayed. |
None
|
Raises:
| Type | Description |
|---|---|
ToolUsageError
|
the call already ran in this visit with a result that could not be recorded (it must not run again). |
close()
¶
Record and replay nothing from now on (children too).
data(drop=())
¶
Everything recorded, as JSON data; children named in drop are left out.
record(slot, result)
¶
Keep a finished result for replay, or a marker when it cannot be kept.
settle(slot, result)
async
¶
Record a call's result, then tell the observer what was kept for its replay.
started(slot)
async
¶
Tell the observer a call is about to run, or that it replayed.
NotSealable
¶
approval_store(port)
¶
The approval store behind a port (port.store), if it has one.
check_plain(value, path='$')
¶
Refuse anything but exact JSON types (encoded data read back from elsewhere).
Raises:
| Type | Description |
|---|---|
NotSealable
|
a value is not plain JSON. |
decode(data, decoder=None, known=None)
¶
The value encode made data from; only call it on data whose MAC was checked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
known
|
Mapping[str, type] | None
|
The classes a |
None
|
Raises:
| Type | Description |
|---|---|
NotSealable
|
unknown tag, a class not allowed, or a value the decoder refused. |
encode(value, encoder=None, path='$')
¶
value in the tagged canonical form (JSON data).
Raises:
| Type | Description |
|---|---|
NotSealable
|
a value of an unknown type and no encoder (or the encoder failed). |
same_decision(stored, decision)
¶
True when a stored decision approves (or denies) the same way with the same edits.
unsealable_calls(data, where='')
¶
Calls in journal data that ran but could not be recorded (children included).