05 · Runtime

The one runtime: the durable segment loop on Inngest, the AgentExecutor checks, and the Agno agent loop that reasons, calls tools and answers.

1 min read Updated Oct 16, 2026

05 · Runtime

The runtime executes every run that Runs admits. This page explains the core agent loop: how one agent run becomes durable segments, what one segment checks, and how the Agno agent reasons, calls tools and answers. The workflow executor shares the same durable shell, and its own page will cover it.

Agno is the only agent brain. Inngest gives durability, and AgencyCore code gives policy, budgets and storage. Agno owns one job: the model turn and the tool-call loop inside one segment.

05 · Runtime · the core agent loop
05 · Runtime · the core agent loop
Agno is the only agent brain. Inngest makes the loop durable. Numbered badges mark the issues listed at the bottom. Dashed means async, not ours, or not on this path.
Agno is the only agent brain. Inngest makes the loop durable. Numbered badges mark the issues listed at the bottom. Dashed means async, not ours, or not on this path.
Durable shell · one Inngest function per run (agent/run.execute)
Durable shell · one Inngest function per run (agent/run.execute)
04 · Runs
admits, freezes the
snapshot, dispatches
04 · Runsadmits, freezes thesnapshot, dispatches
Admission check
and claim
queued → running
Admission checkand claimqueued → running
segment.N step
90 s timer inside the
120 s step budget
segment.N step90 s timer inside the120 s step budget
Outcome → finalize
succeeded · failed · cancelled
or running · waiting
Outcome → finalizesucceeded · failed · cancelledor running · waiting
mark waiting, advance.N, then
wait for agent/approval.resolved
expires at min(24 h, run deadline)
mark waiting, advance.N, thenwait for agent/approval.resolvedexpires at min(24 h, run deadline)
⚠ 1
⚠ 1
⚠ 5
⚠ 5
waiting
waiting
approved → next segment
approved → next segment
running (timer cut) → advance, next segment
running (timer cut) → advance, next segment
One segment · AgentExecutor, then AgnoAgentRuntime
One segment · AgentExecutor, then AgnoAgentRuntime
AgentExecutor checks
· re-read the run, stop if not running
· accrual: tree budget and org day
· remainders: turns, tools, wall clock
· context brief on segment 0
· tools filtered by the frozen grant
AgentExecutor checks· re-read the run, stop if not running· accrual: tree budget and org day· remainders: turns, tools, wall clock· context brief on segment 0· tools filtered by the frozen grant
⚠ 4
⚠ 4
AgentExecutionRequest
framework neutral: no Agno type
crosses this boundary
AgentExecutionRequestframework neutral: no Agno typecrosses this boundary
Stop reason → outcome
completed · ceiling (partial)
needs_approval · cancelled · failed
then supersede unanswered approvals
Stop reason → outcomecompleted · ceiling (partial)needs_approval · cancelled · failedthen supersede unanswered approvals
Agno Agent · the only agent brain
Agno Agent · the only agent brain
1 · Model turn
arun or acontinue_run, streamed
history rehydrated per run id
1 · Model turnarun or acontinue_run, streamedhistory rehydrated per run id
⚠ 3
⚠ 3
3 · Answer checks
output schema · citations · receipts
up to 2 tool corrections,
then 1 fix with no tools
3 · Answer checksoutput schema · citations · receiptsup to 2 tool corrections,then 1 fix with no tools
2a · Read tool
runs inside the loop through
AgnoToolAdapter
2a · Read toolruns inside the loop throughAgnoToolAdapter
⚠ 2
⚠ 2
2b · Write or send tool
Agno pauses (external execution),
the runtime decides it in process
2b · Write or send toolAgno pauses (external execution),the runtime decides it in process
⚠ 6
⚠ 6
ToolInvoker · the only code that calls a handler
validate · redact · policy · accrual · connection
send window · count budget · idempotency claim · handler
ToolInvoker · the only code that calls a handlervalidate · redact · policy · accrual · connectionsend window · count budget · idempotency claim · handler
06 · Model provider
Agno Claude · Agno OpenAIChat
registered pairs only
06 · Model providerAgno Claude · Agno OpenAIChatregistered pairs only
AgnoEventBridge
one llm span + one usage row per turn
text.delta · tool.updated
AgnoEventBridgeone llm span + one usage row per turntext.delta · tool.updated
execute(run)
execute(run)
prompt · tokens
prompt · tokens
answer
answer
correct
correct
call · result
call · result
pause · result
pause · result
stream events
stream events
stop reason
stop reason
13 · Storage (Postgres on Supabase, schema agent)
13 · Storage (Postgres on Supabase, schema agent)
agent.runs
status · segment_index
frozen snapshot
agent.runsstatus · segment_indexfrozen snapshot
agent.sessions
Agno session + receipts
one write per segment
agent.sessionsAgno session + receiptsone write per segment
agent.spans · ai_usage_log
turn and tool counts
cost meter
agent.spans · ai_usage_logturn and tool countscost meter
agent.approvals
idempotency journal
run_control
agent.approvalsidempotency journalrun_control
read · write
read · write
Issues found in the analysis (2026-10-16)
Issues found in the analysis (2026-10-16)
1 · An approval expires at the run deadline, and waits spend the run's wall clock. A 300 s agent gives a person under 5 minutes to answer.
1 · An approval expires at the run deadline, and waits spend the run's wall clock. A 300 s agent gives aperson under 5 minutes to answer.
2 · A read tool cannot pause. Policy that asks for a person on a read tool fails the run with read_tool_needs_approval.
2 · A read tool cannot pause. Policy that asks for a person on a read tool fails the run withread_tool_needs_approval.
3 · Suspected: num_history_runs = max_segments, but one segment can make up to 4 Agno runs. A long run can drop its first task and tool results.
3 · Suspected: num_history_runs = max_segments, but one segment can make up to 4 Agno runs. A longrun can drop its first task and tool results.
4 · No budget override approval. A require_approval budget decision stops the run exactly like a deny.
4 · No budget override approval. A require_approval budget decision stops the run exactly like a deny.
5 · Agent runs wait on approvals only. Any other wait kind fails the run with wait_kind_not_implemented.
5 · Agent runs wait on approvals only. Any other wait kind fails the run with wait_kind_not_implemented.
6 · Stale comments: runtime.py says advance_segment has no caller and that no tool executes yet; execute.py says no runtime answers needs_approval.
6 · Stale comments: runtime.py says advance_segment has no caller and that no tool executes yet;execute.py says no runtime answers needs_approval.
What the colours mean
What the colours mean
the run starts here
the run starts here
costs an LLM call
costs an LLM call
platform plumbing
platform plumbing
a component
a component
an issue
an issue
Text is not SVG - cannot display
05 · Runtime · the core agent loop
05 · Runtime · the core agent loop
Agno is the only agent brain. Inngest makes the loop durable. Numbered badges mark the issues listed at the bottom. Dashed means async, not ours, or not on this path.
Agno is the only agent brain. Inngest makes the loop durable. Numbered badges mark the issues listed at the bottom. Dashed means async, not ours, or not on this path.
Durable shell · one Inngest function per run (agent/run.execute)
Durable shell · one Inngest function per run (agent/run.execute)
04 · Runs
admits, freezes the
snapshot, dispatches
04 · Runsadmits, freezes thesnapshot, dispatches
Admission check
and claim
queued → running
Admission checkand claimqueued → running
segment.N step
90 s timer inside the
120 s step budget
segment.N step90 s timer inside the120 s step budget
Outcome → finalize
succeeded · failed · cancelled
or running · waiting
Outcome → finalizesucceeded · failed · cancelledor running · waiting
mark waiting, advance.N, then
wait for agent/approval.resolved
expires at min(24 h, run deadline)
mark waiting, advance.N, thenwait for agent/approval.resolvedexpires at min(24 h, run deadline)
⚠ 1
⚠ 1
⚠ 5
⚠ 5
waiting
waiting
approved → next segment
approved → next segment
running (timer cut) → advance, next segment
running (timer cut) → advance, next segment
One segment · AgentExecutor, then AgnoAgentRuntime
One segment · AgentExecutor, then AgnoAgentRuntime
AgentExecutor checks
· re-read the run, stop if not running
· accrual: tree budget and org day
· remainders: turns, tools, wall clock
· context brief on segment 0
· tools filtered by the frozen grant
AgentExecutor checks· re-read the run, stop if not running· accrual: tree budget and org day· remainders: turns, tools, wall clock· context brief on segment 0· tools filtered by the frozen grant
⚠ 4
⚠ 4
AgentExecutionRequest
framework neutral: no Agno type
crosses this boundary
AgentExecutionRequestframework neutral: no Agno typecrosses this boundary
Stop reason → outcome
completed · ceiling (partial)
needs_approval · cancelled · failed
then supersede unanswered approvals
Stop reason → outcomecompleted · ceiling (partial)needs_approval · cancelled · failedthen supersede unanswered approvals
Agno Agent · the only agent brain
Agno Agent · the only agent brain
1 · Model turn
arun or acontinue_run, streamed
history rehydrated per run id
1 · Model turnarun or acontinue_run, streamedhistory rehydrated per run id
⚠ 3
⚠ 3
3 · Answer checks
output schema · citations · receipts
up to 2 tool corrections,
then 1 fix with no tools
3 · Answer checksoutput schema · citations · receiptsup to 2 tool corrections,then 1 fix with no tools
2a · Read tool
runs inside the loop through
AgnoToolAdapter
2a · Read toolruns inside the loop throughAgnoToolAdapter
⚠ 2
⚠ 2
2b · Write or send tool
Agno pauses (external execution),
the runtime decides it in process
2b · Write or send toolAgno pauses (external execution),the runtime decides it in process
⚠ 6
⚠ 6
ToolInvoker · the only code that calls a handler
validate · redact · policy · accrual · connection
send window · count budget · idempotency claim · handler
ToolInvoker · the only code that calls a handlervalidate · redact · policy · accrual · connectionsend window · count budget · idempotency claim · handler
06 · Model provider
Agno Claude · Agno OpenAIChat
registered pairs only
06 · Model providerAgno Claude · Agno OpenAIChatregistered pairs only
AgnoEventBridge
one llm span + one usage row per turn
text.delta · tool.updated
AgnoEventBridgeone llm span + one usage row per turntext.delta · tool.updated
execute(run)
execute(run)
prompt · tokens
prompt · tokens
answer
answer
correct
correct
call · result
call · result
pause · result
pause · result
stream events
stream events
stop reason
stop reason
13 · Storage (Postgres on Supabase, schema agent)
13 · Storage (Postgres on Supabase, schema agent)
agent.runs
status · segment_index
frozen snapshot
agent.runsstatus · segment_indexfrozen snapshot
agent.sessions
Agno session + receipts
one write per segment
agent.sessionsAgno session + receiptsone write per segment
agent.spans · ai_usage_log
turn and tool counts
cost meter
agent.spans · ai_usage_logturn and tool countscost meter
agent.approvals
idempotency journal
run_control
agent.approvalsidempotency journalrun_control
read · write
read · write
Issues found in the analysis (2026-10-16)
Issues found in the analysis (2026-10-16)
1 · An approval expires at the run deadline, and waits spend the run's wall clock. A 300 s agent gives a person under 5 minutes to answer.
1 · An approval expires at the run deadline, and waits spend the run's wall clock. A 300 s agent gives aperson under 5 minutes to answer.
2 · A read tool cannot pause. Policy that asks for a person on a read tool fails the run with read_tool_needs_approval.
2 · A read tool cannot pause. Policy that asks for a person on a read tool fails the run withread_tool_needs_approval.
3 · Suspected: num_history_runs = max_segments, but one segment can make up to 4 Agno runs. A long run can drop its first task and tool results.
3 · Suspected: num_history_runs = max_segments, but one segment can make up to 4 Agno runs. A longrun can drop its first task and tool results.
4 · No budget override approval. A require_approval budget decision stops the run exactly like a deny.
4 · No budget override approval. A require_approval budget decision stops the run exactly like a deny.
5 · Agent runs wait on approvals only. Any other wait kind fails the run with wait_kind_not_implemented.
5 · Agent runs wait on approvals only. Any other wait kind fails the run with wait_kind_not_implemented.
6 · Stale comments: runtime.py says advance_segment has no caller and that no tool executes yet; execute.py says no runtime answers needs_approval.
6 · Stale comments: runtime.py says advance_segment has no caller and that no tool executes yet;execute.py says no runtime answers needs_approval.
What the colours mean
What the colours mean
the run starts here
the run starts here
costs an LLM call
costs an LLM call
platform plumbing
platform plumbing
a component
a component
an issue
an issue
Text is not SVG - cannot display
One Inngest function holds a run. It runs the run as segments, and each segment is one durable step. Inside a segment, AgentExecutor checks the run, and the Agno agent loops on model turns and tool calls until it answers, pauses or reaches a ceiling. Numbered badges mark the issues at the end of this page.

Design rules

RuleWhy
Agno is the only agent framework. One file holds every Agno object: runtime/agent/agno/runtime.py.One brain means one tool loop, one session format and one set of failure modes.
No Agno type crosses AgentRuntime.execute(). runtime/agent/protocol.py holds only AgencyCore types, and a test refuses an agno import there.The rest of the platform does not depend on framework details.
The run is the session. The Agno session_id is the run id.A second key can disagree with the first.
A segment is one Inngest step. The step either returns a value or raises.A raise writes no session, so the Inngest replay starts from clean history.
Every tool call goes through ToolInvoker.Policy, budgets, idempotency and metering apply to every call, and the model cannot bypass them.
A ceiling ends the run as a partial success, not a failure.Work done before the ceiling stays done. partial_reason says why the rest did not happen.

The durable shell

One Inngest function, agent/run.execute in runtime/inngest/execute.py, owns one run from claim to finalize.

  1. Admission check and claim. The function checks admission, then moves the run from queued to running. The claim reads max_segments from the frozen snapshot one time, so the loop bound cannot move during a replay.
  2. Segment loop. Each segment is the step segment.N. An asyncio timer stops the segment at 90 seconds (MAX_SEGMENT_DURATION_S), inside the 120 second step budget. The last 30 seconds let the segment save its session and close its spans.
  3. Outcome. A segment answers an ExecutionOutcome:
    • succeeded, failed or cancelled ends the loop.
    • running means the timer cut the segment. The function runs advance.N and starts the next segment, which continues from the saved conversation.
    • waiting means a tool call needs a person. The function runs wait.mark.N, then advance.N, then waits for agent/approval.resolved. The wait times out at the approval expiry.
  4. Finalize. One step applies the terminal status and sends the run end event. A terminal status has one writer.

Each status write is its own step. Three writes in one step are not atomic, and a retry of a half-written step leaves the run at waiting with no wait and no writer.

One segment

AgentExecutor checks

AgentExecutor in runtime/execution/agent.py runs before every model call of a segment:

  1. Re-read the run. Stop if the status is not running, so a person who pressed stop does not pay for another turn.
  2. Parse the frozen snapshot and the frozen principal. A defect here is terminal, because every retry reads the same snapshot.
  3. Check accrual: the cost of the whole run tree, and the organization day cap.
  4. Compute the remainders. Turns and tool calls already spent in this run come from the span tree, and wall clock comes from started_at. Each segment gets what is left, never the full ceiling.
  5. Build a context brief on segment 0, and again after a wait when the policy says refresh_on_resume.
  6. Filter the frozen tools by the frozen grant.
  7. Build the AgentExecutionRequest and call AgentRuntime.execute().

After the segment, the executor maps the stop reason to an outcome. It also cancels approvals that no outcome waits on. A model can propose several write calls in one turn, but only one approval can park the run.

The Agno loop

AgnoAgentRuntime.execute() builds a new Agno Agent for each segment. Agno uses an InMemoryDb, and the runtime fills it from agent.sessions and writes it back at the end. Agno telemetry is off, because the default sends the run id to os-api.agno.com.

StepWhat happens
1 · Model turnarun() starts the turn, or acontinue_run() continues a paused run. The output is streamed. AgnoEventBridge opens one llm span and writes one usage row per turn, and it publishes text.delta and tool.updated.
2a · Read toolThe tool runs inside the Agno loop. AgnoToolAdapter calls ToolInvoker, and the result goes back to the model in the same turn.
2b · Write or send toolThe tool is declared for external execution, so Agno pauses. The runtime decides the pause in process through ToolInvoker, then continues the run. Only a call that policy sends to a person ends the segment as needs_approval. A denial goes back to the model as the call result.
3 · Answer checksThe final answer must pass the output schema, the citation check (every cited id must come from a tool result) and the receipt binding. A failed answer gets up to two correction turns with tools, then one fix turn with no tools.

The ceilings apply before every call, not one time per turn. One turn can hold ten write proposals, and a check per turn would let all ten run on a remainder of one. A bound run that reaches a ceiling while it holds receipts gets one closing turn with no tools, so it can answer from the work it already did.

Stop reasons

AgentExecutionResult.stop has five values, and AgentExecutor branches on nothing else.

StopRun outcome
completedsucceeded with the result
ceilingsucceeded with partial_reason set to limit_reached or budget_exhausted
needs_approvalwaiting on the approval, with its expiry
cancelledcancelled, and nothing more is written
failedfailed with a named RunError code

A retryable fault, such as a vendor 5xx or a database outage, raises instead of failing. Inngest replays the whole step, and the idempotency journal stops a tool effect from happening two times.

Storage

TableHolds
agent.runsThe status, segment_index, the waiting columns and the frozen snapshot
agent.sessionsThe Agno session, the composed instructions and the receipt ledger. A write at an older segment index is refused.
agent.spans, ai_usage_logOne span per unit of work, and one usage row per model call. The remainders and the cost meter read them.
agent.approvals, the idempotency journal, agent.run_controlApproval rows, claims of tool effects, and the cooperative cancel flag that the tool adapter reads

Where the code lives

Paths are relative to ac-python-api/src/agentic/.

ConcernStart here
The framework-neutral contractruntime/agent/protocol.py
The Agno loopruntime/agent/agno/runtime.py, with adapter.py, bridge.py, segment.py and session.py
Segment checks and outcome mappingruntime/execution/agent.py
Run kind dispatchruntime/execution/executor.py
The durable shellruntime/inngest/execute.py
Ceilings and step budgetsshared/ceilings.py
Model pairs that may publishruntime/definitions/model_registry.py
The tool call pipelineservices/tools/default_invoker.py

The Front Door is a second Agno call site, in entry_control/front_door/agno.py. It makes single-turn calls with no tools, so it does not use the segment loop.

Issues

The analysis of ac-python-api on 2026-10-16 found these issues. The numbers match the badges on the diagram.

1 · The approval window is the run's remaining wall clock

max_run_duration counts from started_at, and waits count too (_elapsed in runtime/execution/agent.py). _expiry in governance/policy/approvals/service.py sets an approval expiry to min(now + 24 h, run deadline). An agent with max_run_duration_s: 300, such as the CRM question agent or the company finder agent, gives a person less than 5 minutes to answer. After the approval, the next segment gets only the time that is left.

Fix: stop the wall clock while a run waits, or give each wait its own deadline that the run deadline does not cap.

2 · A read tool cannot ask a person

A read tool runs inside the Agno loop, and that loop has no pause. When policy answers require_approval for a read tool, the run fails with read_tool_needs_approval (runtime/agent/agno/runtime.py:1607).

Fix: refuse a policy rule that gates a read tool at publish time, or declare a gated read tool for external execution so that it pauses as a write does.

3 · Suspected: the Agno history window can drop early turns

The runtime sets num_history_runs = max_segments (runtime/agent/agno/runtime.py:376), so the window counts Agno runs. One segment can make up to four Agno runs: the first turn, two correction turns, and a fix or closing turn. Agno includes only messages from the last num_history_runs runs. A long run can then lose its first task and its early tool results. The citation check (_read_ids) also reads that history, so it can refuse an id that a dropped turn read. No test or live run has shown this yet.

Fix: set the window from the number of Agno runs, not segments, or remove the bound. Add a test for a run with corrections across several segments.

4 · No budget override approval

When accrual answers require_approval, the run stops as it does on deny (runtime/execution/agent.py:330). A person cannot approve extra spend.

5 · Agent runs wait on approvals only

Any other wait kind fails the run with wait_kind_not_implemented (runtime/inngest/execute.py:1534).

6 · Stale comments in the runtime

  • runtime/agent/agno/runtime.py:915 says advance_segment has no caller and that every later segment write is refused. runtime/inngest/execute.py:1726 calls it.
  • runtime/agent/agno/runtime.py:428 says no tool executes before ENG-2080. The invoker runs tools today.
  • The _wait_out docstring in runtime/inngest/execute.py:1922 says that no runtime answers needs_approval and that the recovery sweep is not built. Both exist now.

These comments describe a system that no longer exists, so they mislead the next reader.