Table of Contents
🌏 中文版
Orders 1–3 establish the interface, disposable workspace, and prompt, instruction, and explicit-memory pipeline. This article enters the native lane: how Rivumi's own AgentRunner turns one model request into tool execution and reaches either verified success or an explicit failure.
“Provider-neutral” has one narrow meaning here. The loop depends on canonical ModelProvider.complete() and ModelTurn values instead of embedding one vendor's wire format in the state machine. Translation for OpenAI, Anthropic, Gemini, or Workers AI belongs to order 5.
Data flow through a native turn
TaskContract + restored/new session state
│
▼
AgentRunner.run()
│
model.requested
│
▼
ModelProvider.complete(messages, tools)
│
▼
canonical ModelTurn
├─ tool_calls ─► approve ─► execute ─► observations ─┐
│ │
└─ no calls ─► verification ─► pass / repair / fail ◄┘
A new run pins the base SHA, writes request and session state, prepares the disposable workspace, and initializes messages. Resume restores step, usage, messages, and active wall time from the manifest. Once those paths converge, each iteration performs instruction or context reload and required reinjection, increments the step, emits model.requested, and receives a canonical ModelTurn.
When the turn contains tool calls, the runner records fingerprints, requests approval, sends each call to ToolExecutor, appends observations to messages, and enters another iteration. When no tool call exists, the runner does not trust the prose answer as completion; it enters _verify_all().
Verification is the completion boundary
The model saying “done” means only that it stopped requesting tools. _verify_all() reruns the commands in TaskContract.verification. Only an all-pass result produces RunStatus.COMPLETED with terminal_reason="verified". On failure, bounded output returns to the conversation as untrusted test output, giving the model another repair turn.
The native loop's success boundary is therefore neither a provider finish reason nor the assistant's final sentence. It is the point after the runner obtains fresh verification outcomes. Focused cases in tests/test_loop_e2e.py exercise this path with fixture models: emit tools, execute them, feed back failed checks, and only then complete or stop with an explicit terminal reason.
Deterministic guards may terminate first
AgentRunner does more than wait for the model to stop. It owns code-enforced guards:
max_stepsbounds the outer loop.- A wall-time budget covers workspace preparation, provider calls, tools, and verification.
- Repetition fingerprints count normalized tool names and arguments, producing
repeated_actionon repeated calls. - Token budgets stop execution when recorded usage exceeds the contract.
- Cancellation while waiting for a model or during execution produces
user_cancelled.
These limits are runner state, not suggestions in a system prompt. A representative fail-closed case is a model repeatedly issuing the same modification. The third occurrence is not another side effect; the run stops with the repetition terminal reason. _record_fingerprint() hashes normalized JSON, so changing argument-key order does not evade the guard.
Contracts make state persistable and inspectable
TaskContract, ModelTurn, ToolObservation, and VerificationOutcome are explicit data structures. ContractModel uses extra="forbid" and frozen models, preventing unknown fields from disappearing silently and preventing another component from mutating values already committed to session state.
That contract boundary lets the journal, resume path, and TUI consume the same state. It also limits the claim: provider-neutral does not mean providers behave identically. It means each adapter must normalize a response before the loop can consume it. The next article covers protocol translation and the gateway; retry, fallback, cache hints, and estimated cost are covered in order 6.
What this loop does not provide
Deterministic guards do not make the loop an OS sandbox. Path and argv enforcement, permission decisions, and kernel containment belong to orders 8–10. Subagent transactions, MCP authorization, and external-CLI handoff have different owners as well. The loop cannot guarantee that a model solves a task; it guarantees an inspectable terminal reason and requires verification before success.
For broader comparisons of where other coding agents place loops, tools, and verification, see Pi's minimal terminal harness and the Codex CLI overview. The next Rivumi article remains on Rivumi's code path and focuses on the canonical ModelProvider contract and protocol translation.
References
- Rivumi official repository — ground truth for
AgentRunner.run(), contracts, and loop tests - Rivumi M1 local harness document — design background for the native harness
- Pydantic model configuration — semantics of frozen models and extra-field validation
Loading...