The Harness
A model can suggest the next action. It cannot, by itself, turn that suggestion into bounded, observable work. The harness is the runtime around the model that does so. It assembles model input, records progress, dispatches tools, waits for approval, enforces limits, and gives a person reliable ways to pause or stop a run.
This distinction is the chapter's foundation: the model proposes; the harness sequences and records; policy and identity systems decide authority; tools and external systems own effects. The harness enforces a decision, but it does not grant authority. It coordinates attempts, but it does not make external effects exactly once. Those boundaries keep a convenient agent loop from being mistaken for a transaction manager or a security principal.
The runtime contract
At its smallest, a harness repeats three operations: ask a model, execute an accepted tool call, and append the result. A production runtime also has to answer questions the loop leaves open:
| Concern | Contract the harness must expose |
|---|---|
| Identity | Which user, tenant, agent definition, and run produced this step? |
| State | What was durably accepted, and which work is still outstanding? |
| Authority | Which policy and approval authorize this exact action now? |
| Effects | Was an external operation committed, rejected, duplicated, or left with an unknown outcome? |
| Control | Where do pause, steer, cancel, kill, and fork take effect? |
| Limits | What time, cost, calls, bytes, mutations, and concurrency remain? |
| Recovery | What may be replayed, and what must first be reconciled? |
A useful formalization is a versioned transition function:
Here, e_t is an accepted event: a user command, model result, tool result,
approval decision, timer, or recovery observation. C_t is the set of commands
the transition emits. The state S_t = (r, h, w, m, p, k, b, q) contains:
r: run identity, status, and agent-definition version;h: the durable history head;w: the workspace or sandbox reference;m: the application-memory view and its version;p: the principal, policy snapshot, and approval records;k: the admitted tool-catalog version;b: remaining and reserved budgets; andq: outstanding work, attempts, leases, and unresolved effects.
The reducer is versioned because recovery must interpret old history with the same semantics that produced it. It should be deterministic over accepted events even when model and tool outputs are not. Four invariants follow:
- A terminal run emits no new commands.
- Every effect command records its actor, scope, approval, stable step ID, attempt number, idempotency key, and input fingerprint.
- Budgets never increase except through an explicit, authorized grant.
- One completion is accepted for an attempt lineage; late or duplicate results are retained as evidence but cannot silently advance state twice.
This event history is the source for restart and audit. A checkpoint is an optimization, not a second truth: it records a verified history position plus the versions, pending operation IDs, deadlines, workspace references, and known effect outcomes needed to continue.
Runs are state machines, not recursive functions
The states below are deliberately explicit. A client that receives paused or
waiting for approval should not have to infer that condition from missing
tokens on a stream. Likewise, needs reconciliation is a visible state, not a
generic failure that invites a blind retry.
| State | Meaning |
|---|---|
created |
Identity, definition, and initial budgets have been recorded. |
ready |
Admission succeeded; no child operation is active. |
running |
The reducer is processing an accepted event. |
waiting_model |
Waiting for model; the UI can display “waiting for model.” |
waiting_tool |
Waiting for tool; the exact call and deadline are visible. |
waiting_approval |
Waiting for approval before dispatching an effect. |
paused |
No new work is scheduled; resumable state remains durable. |
cancelling |
Cancellation was accepted and active children are being stopped. |
completed / failed |
Terminal success or terminal known failure. |
needs_reconciliation |
An external outcome is unknown or inconsistent. |
The transition journal must be committed before a command is delivered. That ordering prevents a worker crash from erasing the reason a tool was called. It cannot remove the classic uncertainty window: an external system may commit a write just before the worker crashes and records its receipt. Recovery must therefore understand effects, not merely replay functions.
Pin definitions; adapt protocols explicitly
Instance lifetime and definition versioning are independent choices. A shared runner can resolve immutable configuration per run; a freshly allocated runner can still load an unversioned prompt. Pin each run to an agent-definition version containing its instructions, model route, tool schemas, policy references, and harness reducer version. Record that version in every checkpoint. A rollout policy then routes new runs to a canary or stable definition and states whether existing runs remain pinned, migrate at a safe boundary, or stop.
Provider protocols are not interchangeable. A compatibility adapter should publish a feature matrix for tool calls, structured output, streaming, cancellation, usage data, continuation identifiers, and error semantics. When a feature cannot be represented, fail explicitly or declare the downgrade. Do not silently interpret “request accepted” as feature parity. Pin adapter and provider versions alongside the agent definition so an evaluation or incident can reproduce the actual path.
Control verbs have different meanings
Pause, resume, steer, cancel, kill, and fork are separate capabilities, not strength levels of one interrupt:
- Pause stops scheduling at a documented safe point. Active calls either finish, are cancelled, or remain listed as unresolved.
- Resume continues a paused state under compatible definition and policy versions. If those changed, migration is an explicit transition.
- Steer appends a user command to durable history. The contract says whether it applies before the next model call or after current work finishes.
- Cancel records
cancel_requested, rejects new work, and fans the request out to active children. Cooperative code must observe it; an RPC cancellation alone does not interrupt arbitrary server code (gRPC Authors 2024). - Kill forcefully terminates harness-owned execution, such as a process group or sandbox. It is an escalation path, not a rollback mechanism.
- Fork creates a new run namespace from a checkpoint, with isolated mutable state and a new idempotency scope. Already committed external effects remain shared reality.
Cancellation has two useful milestones. It is acknowledged when the request is durable and no new commands will be scheduled. It is quiescent when all harness-owned children have stopped or been marked unresolved. Remote work can continue after the harness stops waiting, and cancellation cannot undo a committed external effect. The runtime must reconcile, compensate, or ask an operator what to do with an unknown outcome. Temporal makes the same practical distinction between graceful cancellation and forceful termination, and notes that a remote activity needs cooperative heartbeats to receive cancellation (Temporal Technologies 2026).
An approval is another interruption, but it carries an integrity requirement. Show the approver the exact tool, target, arguments or diff, acting identity, expected effect, and cost. Bind the decision to a unique call ID and immutable payload hash; make it scoped, expiring, and single-use. Re-run authorization and input guardrails immediately before dispatch. If the payload or relevant policy changed while paused, require a new decision. Serializable pause-and-resume approval flows demonstrate the mechanism, including approvals raised by nested agents (OpenAI 2026). They do not replace execution authorization.
Durable work, duplicate delivery, and external effects
A resumable harness persists durable history independently of prompt context. Workers consume commands from a queue, claim them with a bounded lease, and write results against a stable step ID and attempt number. A lease limits concurrent ownership; it does not prove the previous owner stopped. For a correctness-critical resource, attach a monotonically increasing fencing token and require every write path to reject stale tokens. Without receiver-side validation, the token is only metadata.
Scheduling uses the same model. Give each occurrence a stable identity such as
(schedule_id, scheduled_at) and claim it through a durable unique record.
Duplicate delivery and execution are still possible. Kubernetes documents that
even a single-completion Job may start the same program twice
(Kubernetes Authors 2026). A scheduler lease prevents overlap while it remains
valid; it does not provide exactly-once effects.
Effect safety depends on the receiver:
| Operation | Retry rule |
|---|---|
| Read-only or proven idempotent | Bounded exponential backoff with jitter. |
| Receiver-keyed write | Reuse the same idempotency key; reject a different input fingerprint for that key. |
| Write with unknown outcome | Query status and reconcile before retry; compensate or require review if status is unavailable. |
| Authentication, authorization, validation, or business-rule failure | Normally non-retryable; surface it. |
Persist the error class, next-attempt time, absolute deadline, and retry budget so a restart cannot reset the policy. Automatic retries are useful precisely because operations can fail transiently, but an activity may execute more than once and should be designed accordingly (Temporal Technologies 2026).
Locks also belong at the resource they protect. A session lock serializes one conversation; it cannot protect a branch, customer account, or deployment touched by another run. Use resource-level concurrency through conditional writes, database constraints, protected branches, merge queues, or a lock keyed to the actual resource. A Git worktree separates local files; it does not serialize remote pushes or turn Git into a transaction.
A tool is a typed, authorized effect boundary
A name and JSON input schema are not enough. The harness needs a contract that can drive admission, approval, execution, retry, and result handling:
ToolSpec {
name, input_schema, output_schema, side_effect_class,
auth_scopes, approval_rule, timeout, retry_class,
idempotency, max_output_bytes, sandbox_profile
}
ToolResult {
status, payload | artifact_ref, receipt,
error_class, observed_at
}
Four gates must remain separate:
- Catalog admission decides whether a tool definition and implementation are trusted enough to register.
- Per-turn exposure selects the smallest relevant candidate set for the model. Retrieval improves selection; it never grants permission.
- Execution authorization checks the current principal, tenant, action, resource, arguments, policy, and bound approval immediately before dispatch.
- Result handling validates structure, caps inline size, stores large outputs as artifacts, records receipts, and treats returned text as untrusted data.
Tool names, descriptions, schemas, and results are all untrusted model input. Signing and review reduce tool poisoning, while authorization still applies to every concrete call, including calls made by a child agent. MCP similarly defines tools with input and optional output schemas, asks clients to validate results, and recommends keeping a human able to deny sensitive invocations (Model Context Protocol 2025). Its authorization guidance also forbids passing arbitrary tokens through to downstream servers and requires tokens to be bound to their intended resource (Model Context Protocol 2025; Campbell et al. 2020).
No universal tool-count threshold determines when a flat catalog fails. Measure selection quality with the actual model, schemas, and task distribution. When the catalog grows, retrieval, namespacing, or model-driven discovery can reduce per-turn exposure, but each adds a failure mode: missing the required tool, surfacing a dangerous irrelevant tool, or spending additional calls on discovery.
Stateful tools: separate three kinds of state
“Stateful tool” hides three different responsibilities:
- Connection state includes transport sessions, negotiated capabilities, cursors, and backpressure.
- Durable resource state lives in the external system: a file, ticket, transaction, or remote job.
- Model-visible state is an explicit handle or summary passed in later turns.
Recovery should reconnect, rediscover capabilities, and resume from a cursor or explicit handle when the protocol supports it. Otherwise it starts a new connection without pretending that durable resource state vanished. Protocols can change these mechanics; MCP, for example, has revised transports and authorization across dated specification versions (Model Context Protocol 2025; Model Context Protocol 2025). Pin the version and design to its declared guarantees, not to a story that every connection is a durable session.
Context is a view, not the record
The canonical history contains accepted commands, model and tool results, approval decisions, receipts, source links, and unresolved obligations. The working context is a lossy projection of that history:
where P selects material from canonical history H, within context budget
B, for current task state T. Prompt context, durable execution history, and
the personalization memory of Chapter 40 are different stores.
Compaction can summarize old turns. Masking can replace bulky observations with typed placeholders. Large outputs can become an artifact reference. Retrieval can restore evidence for the next decision. Every option loses something, so the projection should report what it omitted and preserve links back to source events. Compacting working context does not delete canonical evidence. In particular, it must preserve active constraints, pending approvals, incomplete plans, effect receipts, failures, and the provenance of any summary.
Longer context does not remove this design problem. Controlled studies find that performance can degrade with input length even when retrieval is correct (Du et al. 2025), while simple observation masking can rival more elaborate summarization in software-agent settings (Lindenbauer et al. 2025). The choice is empirical and task-specific.
A sandbox has several independent walls
“Runs in a container” is not an isolation contract. Specify each dimension:
| Dimension | Minimum question |
|---|---|
| Process | Can code signal, inspect, or escape into host or sibling processes? |
| Filesystem | Which paths are mounted, writable, persistent, and shared? |
| Network | Is egress default-deny, and which destinations and protocols are allowed? |
| Credentials | Are long-lived secrets absent and capabilities issued only for a checked action? |
| Resource limits | What CPU, memory, PIDs, disk, output, and network ceilings apply? |
| Lifetime | What survives pause, restart, cancellation, kill, expiry, and deletion? |
A worktree is not a security boundary. It reduces file collisions between coding runs, but it does not isolate the kernel, processes, network, host credentials, or a container-runtime socket. Untrusted execution needs a sandbox appropriate to the threat model, such as a hardened container, user-space kernel, or microVM, plus non-root execution, restricted mounts, dropped privileges, system-call policy, and default-deny egress (Souppaya et al. 2017).
Do not mount long-lived secrets. A broker can issue short-lived, narrowly scoped, audience- and resource-bound credentials after validating the exact action. The broker must also redact logs and support rotation and revocation. These controls reduce credential theft and blast radius; they do not make an authorized action safe. Killing a local sandbox likewise does not revoke an external effect already committed under its credential.
Budgets and admission must compose
Limits are a ledger, not warning counters. Before starting a model or tool call, reserve its worst allowed consumption; after completion, reconcile the actual usage and release the remainder. Reject work that cannot be reserved. Enforce independent ceilings for wall-clock and idle time, model calls, input and output tokens, money, tool calls, external mutations, bytes, child runs, concurrent operations, and sandbox resources.
A child receives a sub-budget from its parent, not a fresh allowance. Parent and child reservations therefore remain under the same monotone ceiling. Store absolute deadlines so a restart cannot grant more wall-clock time, and propagate the remaining deadline downstream. A local timeout only bounds how long the harness waits; remote work or billing may continue and must be cancelled or reconciled separately.
Admission combines identity and policy checks with atomic quota or capacity
reservation. A preflight capacity query is advisory because capacity can change
before scheduling. Bounded queues, per-tenant concurrency, priority and
fairness, startup deadlines, backpressure, and explicit pending conditions
make scarcity visible. Kubernetes resource quotas constrain aggregate namespace
consumption, while a Pod remains Pending when its request cannot be scheduled
(Kubernetes Authors 2026; Kubernetes Authors 2026).
Circuit breakers solve a different problem. They protect callers from a failing dependency with closed, open, and probe states. They do not replace run budgets and should not classify “exit code zero” as task success. Use structured result status, receipts, postcondition checks, and explicit goal verification.
Evaluate the harness as a system
A harness change is a treatment, not background noise. Compare it under a fixed model, task set, tool implementations, policy, sampling configuration, and resource envelope. Record the exact agent definition, harness and adapter versions, effective request, tool schemas, sandbox profile, and environment. Report task quality alongside the mechanisms intended to protect it:
- task success and invalid-tool-call rate;
- tool-selection precision and recall;
- approval-bypass rate and unauthorized-effect rate;
- cancellation acknowledgement latency and quiescence latency;
- post-cancel effect count and duplicate-effect rate;
- recovery divergence and unresolved-effect rate;
- context-critical-loss rate after compaction;
- isolation escape rate; and
- added latency and added cost against a minimal baseline.
Then inject failures at the boundaries: crash between external commit and journal receipt; deliver a command twice; expire a lease while a worker is paused; revoke authorization after approval; hang a subprocess; fill the queue; poison a tool description; compact just before a critical constraint; and fork after an effect. A clean happy-path trace does not exercise the contract.
Scores attributed to an agent are joint properties of the model, harness, tools, and environment. Benchmark infrastructure can leak answers or expose a grader that an agent learns to exploit (Wang et al. 2026). Therefore Chapter 47 and Chapter 52 must report and hold the harness version fixed when comparing models. A provider configuration file is useful, but the effective serialized request and observed response are the stronger reproduction artifacts when adapters insert defaults or omit unsupported fields.
Two questions remain open. First, no context-projection strategy reliably knows which detail a future step will need; compaction, masking, retrieval, and larger windows trade different errors. Second, a portable harness can normalize common operations but cannot manufacture provider capabilities or identical error and cancellation semantics. Both should be measured as explicit product choices, not hidden behind a generic “agent” API.
Operational contract checklist
Before trusting a harness with a long run, verify that it can answer:
- Which immutable definition and reducer version own this run?
- What event was accepted last, and what work or effect is unresolved?
- What exact payload and policy authorize the next side effect?
- When is cancellation acknowledged, and when is owned work quiescent?
- Which retry classes are safe, and who enforces idempotency?
- What remains in each parent and child budget?
- Which process, filesystem, network, credential, resource, and lifetime walls contain execution?
- Can recovery and fault-injection tests demonstrate these claims?
The harness therefore makes an agent operable, not merely runnable. The next chapter, Chapter 42, puts this contract under more pressure: a graphical interface creates longer action chains, weaker state observations, more latency, and side effects that are harder to identify before execution.
Further reading
Cited sources appear on the book's References page. The artifact below provides a compact reproduction of tool-description poisoning.
- Invariant Labs, “mcp-injection-experiments,” 2025. github.comA GitHub repository providing code snippets to reproduce MCP tool poisoning and prompt injection attacks against AI agents.
Comments
Log in to comment