Approved execution stream contract
Approved execution stream contract
Section titled “Approved execution stream contract”Released in 0.23.33. This contract is additive within approval-v2
(protocol_version: "v2"); it does not introduce another stream version.
Consumers must require mithai 0.23.33 or later; 0.23.32 and earlier do not emit
tool_execution_started.
Events and identities
Section titled “Events and identities”A human-approved call emits tool_execution_started once after
approval_resolved and before post-approval admission, mutation checkpointing,
or transport work. Other calls and heartbeats may interleave. The event means
local execution has started, including admission; it proves neither remote
dispatch nor remote commitment. Immediate cancellation or expiry can still
produce a terminal not_executed decision.
{"protocol_version":"v2","sequence":5,"run_id":"run-1","channel_id":"C1","type":"tool_execution_started","tool_name":"mcp__last9__create_dashboard","tool_call_id":"write-1","approval_id":"approval-1"}The envelope uses the existing run sequence and exact tool-call/approval IDs.
Deduplicate replay by (run_id, sequence) and correlate a call by
(run_id, tool_call_id, approval_id). A new approval attempt has its own identity.
The start event carries no arguments, credentials, or claimed resource outcome.
Automatic approval, denied/expired approval, and human__ask do not produce it.
Adapters may implement the optional on_tool_execution_started(tool_name) hook;
existing on_tool_start and on_tool_end signatures are unchanged.
tool_started remains the pre-approval announcement. tool_completed.approved
records approval, not success. tool_execution remains the terminal execution
decision; execution_status: "executed" alone is not a successful mutation.
Read the result/error facts and the durable mutation_outcome/v1 projection.
No start event authorizes an automatic retry or substitutes for a receipt.
Deadlines and durable outcomes
Section titled “Deadlines and durable outcomes”Post-approval ordered admission polls cancellation and the effective call
ceiling, including while a preceding tool waits for human input. Checkpoint,
settlement, outcome-read and final-persistence waits are bounded. These changes
do not extend any tool budget: hosts must still explicitly select tools using
ApprovedMutationBudget. In particular, create_kpi and
create_alert_from_chart receive no extra allowance merely because their calls
are approved. Default MCP transport caps remain unchanged.
| Evidence | Terminal execution | Durable mutation projection |
|---|---|---|
| Cancelled/expired before dispatch | not_executed |
not_executed once any admitted intent settles |
| Remote response confirmed and settlement acknowledged | executed, successful result facts |
completed |
| Dispatched timeout or ambiguous transport failure | executed, error facts |
outcome_unknown |
| Response received but settlement wait expires | executed, error facts |
Unresolved until the storage write finishes |
Python cannot interrupt a synchronous backend write. Storage workers are bounded by a process-wide pool of 16 slots and retain their slot through late cleanup. They never dispatch the mutation. An abandoned intent settles as unsent when its backend returns; an already dispatched call’s settlement retains its actual result. A backend that never returns cannot promise an updated durable receipt: existing intent remains conservative and blocks replay. Pool saturation fails closed within the caller’s guard instead of adding unbounded workers.
A terminal stream may therefore precede durable cleanup. No late worker emits
after run_completed or run_failed. Query durable mutation state/outcomes
again after storage recovers. If logical-turn admission itself was still blocked,
use the original delivery identity to find its later receipt. Unknown outcomes
require authoritative reconciliation; confirmed non-execution still requires a
fresh approval before another attempt.
HTTP disconnects and replay
Section titled “HTTP disconnects and replay”An idle HTTP response continues delivering sequenced heartbeats (15 seconds on
the initial stream) while execution work is blocked. Subscriber loss releases
the subscription without cancelling execution. Both the initial response and
GET /api/runs/{run_id}/events?after=<sequence> release their subscriber when
ASGI exits, even if their synchronous iterator is suspended. Reattachment
replays the same identities and never restarts execution. Cleanup is idempotent,
so an old response cannot detach a newer subscriber.
Run-event replay is retained in memory under the existing retention rules. Durable mutation replay is separate; see Durable mutation outcomes.
Producer fixtures and verification
Section titled “Producer fixtures and verification”tests/fixtures/approved_execution_v2.json exports success, pre-dispatch expiry,
and dispatched-timeout examples generated through the real ASGI trigger,
approval-decision and replay routes. tests/approval_execution_fixtures.py
normalizes run/approval/mutation identities, removes heartbeat sequence offsets,
and selects lifecycle fields. The examples are projections, not complete SSE
transcripts; the sequence gaps retain omitted events. They also include durable
outcome projections for sibling consumers in last9/ai and last9/last9.
tests/test_approved_execution_http.py validates these fixtures and covers
create/update dashboard plus synthetic KPI/alert writes, cancellation, expiry,
transport timeout, HTTP heartbeat delivery, two consecutive subscriber losses,
replay identity, no ambiguous retry, and terminal fencing.
tests/test_approved_execution_lifecycle.py covers both engine entry points,
ordered admission stalls and blocked storage (including a backend holding the
session lock). The hosted read-only preparation/approval probe also checks the
start event before dispatch; no mutating hosted test is added.