STAGE 1 / MILESTONES 11–14
Harness M11–M14: bounded tools, steerable turns, and durable sessions
Five bounded tools, Docker-only model mutations, FIFO steering, context compaction and durable replay—four Harness milestones backed by 710 offline tests and explicit recovery limits.
01 / FOUR MILESTONES, ONE SESSION
A useful agent needs more than a place to run commands.
M9 and M10 gave Harness explicit local and Docker workspaces. M11–M14 make that foundation useful for longer development sessions: five bounded tools, steering between requests, compacted context without erased history, and durable checkpoints that can reconstruct what the model was about to receive.
The development request was direct: implement the four roadmap milestones as written. The important refinement came at the mutation boundary. Instead of promising race-safe model writes into a changing host tree, M11 requires the reviewed isolated Docker capability for writes and process execution. Local model access remains read-only.
This note follows four merged pull requests, ending at fc8b3d9. The shared build chat provides the brief and final decision; the pinned source, tests, and hosted checks provide the implementation evidence.
02 / M11 — EXACTLY FIVE TOOLS
A small capability surface is easier to reason about.
The native development registry exposes exactly fs.read, fs.list, fs.write, process.exec, and git.diff. These are the policy and audit identities, not friendly labels for a second hidden set of model powers. The old read-file and sandbox-execution seams are migrated behind the canonical contract.
fs.readreads bounded workspace content.fs.listlists bounded workspace paths.fs.writewrites bounded UTF-8 text within the workspace scope.process.execaccepts an argument array and bounded execution settings.git.diffreturns the bounded difference from the initial workspace snapshot.
The new registry enforces strict input objects. A write with an extra field, a shell-style command object instead of argv, and out-of-scope paths all fail rather than being silently interpreted. The tool layer caps write text and serialized outputs at 128 KiB; process requests accept at most 128 arguments and a timeout of at most 30 seconds, subject to tighter workspace limits.
{"name":"fs.read","arguments":{"path":"fixture.txt"}}
{"name":"fs.write","arguments":{"path":"fixture.txt","contents":"new"}}
{"name":"process.exec","arguments":{"argv":["node","test.js"],"timeoutMs":10000}}
{"name":"git.diff","arguments":{}}“Argv-only” means the runner does not interpolate a command string through a shell. It does not make arbitrary programs harmless; policy, workspace scope, execution limits, and isolation remain necessary. Similarly, the diff tool is not permission to branch, commit, or mutate Git metadata.
03 / M11 — REMOVE HOST MUTATION AUTHORITY
Resolve the race by refusing the unsafe capability.
A path can look valid during a check and refer somewhere different by the time a mutation happens. The build’s answer was not another optimistic path check. Native model writes and process calls require an internally attested DockerWorkspace. Local and forged capabilities fail with WORKSPACE_ISOLATION_REQUIRED before effects.
The attestation is an in-process identity check backed by a WeakMap of reviewed method references. Trusted binding and restriction can preserve it; replacing an attested method invalidates it. Setting a property or copying a prototype is not enough. This is not cryptographic or hardware attestation, and it assumes the host integration code is trusted.
The Docker adapter carries forward M10’s bounded copied text tree, without mounting the source repository. Changes belong to that isolated state and can be returned as a patch. The local developer API still exists; it has not been promoted into model mutation authority.
The M11 fixture drives all five tools with FakeModel. It verifies an allowed edit, one test command, a diff, four invalid attempts, and an unchanged host fixture. Separate ordinary, symlink, hard-link, and parent-substitution cases verify local mutation rejection, untouched victim files, and zero process execution.
04 / PROVIDER NAMES ARE NOT POLICY NAMES
Translate at the wire, not throughout the system.
Some function-calling providers do not accept dots in tool names. The OpenAI-compatible adapter builds aliases only in a detached provider request, including prior assistant tool calls, and translates returned names back. Policy and persisted events retain canonical dotted names.
policy / runtime: fs.read
provider request: harness_66732e72656164
provider response: harness_66732e72656164
policy / audit: fs.read
Reject alias collisions, excessive alias length,
and unrecognized harness_ aliases.This avoids making authorization depend on a provider-specific spelling. It also keeps replayed tool history compatible with later provider requests without changing the audit vocabulary.
05 / M12 — STEER AT A SAFE BOUNDARY
“I received your message” and “the model used it” are different events.
Steering can arrive while a model request or tool is running. M12 appends steering.queued through the injected EventStore before steer() resolves. A serialized queue preserves accepted invocation order. The content is incorporated only at the next safe model-request boundary, recorded by steering.applied.
The concurrency fixture queues “model A” and “model B” during the model phase, then “tool A” and “tool B” during tool execution. The first request does not contain the new steering. The second contains all four in order.
Cancellation has explicit ordering too. A steering append already in progress finishes and remains recorded even if cancellation wins. Later steering is rejected once cancellation becomes visible. Accepted steering during a final model response causes another round; otherwise completion wins at its serialized terminal boundary. The invariant is one terminal outcome per turn, not a race between duplicate success and cancellation events.
06 / M12 — A NEW TURN, NOT A REWRITTEN PAST
Follow-up work inherits the session without replacing it.
A follow-up gets a new run and turn identity on the same logical session. It inherits original messages, pending steering, accumulated usage, and applicable defaults. Prior tool-call intentions and observations remain in the conversation.
The runtime rejects a second active turn for the same session, a reused turn ID, a replacement context, or a different EventStore for an in-memory follow-up. The test serializes the existing event history before the follow-up and verifies that its prefix is byte-for-byte unchanged afterward.
In-process continuity arrives here. Cross-restart storage comes in M14. Keeping those delivery stages separate matters: a runtime registry is not a durable database just because its API can read a session.
07 / M13 — TWO DIFFERENT LIMITS
Context occupancy is not the cumulative token budget.
The hard model-token budget records how much usage the session has accumulated. Context occupancy asks whether the next request fits the configured window, including tool definitions, system text, and reserved output. Compaction can reduce occupancy; it cannot refund prior model usage.
The implemented occupancy algorithm is utf8-upper-bound/v1: it uses the UTF-8 byte length of serialized messages, tools, and system text as a conservative estimate. Although event fields call these units tokens, this is not an exact provider tokenizer or a measurement of a provider’s internal prompt. That distinction belongs in the explanation.
Per-request admission
estimated occupancy + reserved output <= configured window
Session budget
previous usage + task-model usage + summary-model usage
must stay within the configured hard budget
Compaction reduces the first quantity.
It does not reset the second.The context policy specifies the window, compaction threshold, retained tail length, and output reservation. Accounting emits evidence before an oversized request is admitted. The next output limit is bounded by the remaining model budget and the context reservation.
08 / M13 — PRESERVE ORIGINALS, CHANGE THE VIEW
A summary is a checkpoint, not an eraser.
At the configured threshold, the runtime summarizes an older prefix and retains a recent tail. It adjusts the boundary so assistant tool intentions are not separated from their observations. Original history remains intact; the next model request uses preserved system messages, a versioned summary, and the tail.
The version-one compaction state records summary text, the original tail index, and the revision summarized. context.checkpoint persists the summary and immutable tail; context.compacted records before/after counts. The new view must actually reduce message count and estimated occupancy.
Summary generation is real model work: normal deadlines, stream validation, and hard-budget accounting apply. An empty or failed summary produces RUNTIME_SUMMARY_FAILED. A prefix that cannot be summarized within bounds, a non-reducing result, or a retained tail that still cannot fit produces RUNTIME_CONTEXT_OVERFLOW. No failure path is permission to discard originals.
The four compaction tests cover reconstruction, failure, summary usage exhausting the budget, and overflow. One fixture records 110 tokens for the summary and 23 for the next answer: the terminal usage is 133, not 23. Another proves a failed summary does not commit a destructive replacement.
09 / M14 — GIVE THE PORT STORAGE SEMANTICS
Append and read become a durable session contract.
SessionEventStore connects the kernel’s EventStore port to the existing SessionStore implementations. It binds to one session and owner, snapshots each event before returning control, and preserves the event ID instead of regenerating it during delivery.
- Ordering: storage assigns per-session sequence cursors; reads advance through ordered pages.
- Identical redelivery: the same event identity and payload do not create a second event.
- Conflicting redelivery: reused identity with different content produces
SESS_EVENT_CONFLICT. - Checkpoint compare-and-swap: writes use the expected revision and cannot silently overwrite a newer checkpoint.
- Owner fencing: the storage transaction checks the current owner before accepting effects, including checkpoint saves.
The adapter serializes its own appends. An older identical checkpoint redelivery cannot rewind the current cursor. A stale owner is not allowed to write merely because it holds an old revision or is retrying an apparently identical operation.
This is event-delivery idempotency and checkpoint concurrency control. It is not a claim that every arbitrary external tool effect has become exactly-once.
10 / M14 — SAVE THE NEXT REQUEST
A checkpoint needs enough state to explain the next action.
Durable adapters opt in with checkpoint version one. The runtime emits checkpoints at model boundaries—including summary requests—and terminal outcomes. The payload retains more than the latest assistant message.
Identity: runId, sessionId, turnId, phase, sessionTurns
History: versioned messageState and original revision
Model: identity, system/options, output limit, timeout
Accounting: usage, modelRequests, toolCalls, transcript bytes
Control: seen call IDs, grants, warnings, pending steering
Context: compaction state, context policy, budget
Next step: detached nextRequest for model/summary phases
Terminal: terminalStatus instead of nextRequestThe parser validates identities, usage consistency, turns, message state, compaction cursors, phase-specific fields, and a 16 MiB serialized bound. A future version raises RUNTIME_CHECKPOINT_VERSION; malformed content raises RUNTIME_CHECKPOINT_INVALID. There is no fallback that guesses how an unknown checkpoint should behave.
reconstructModelRequest() is pure. The SQLite test compares each reconstructed request with the request captured by FakeModel, excluding its transient AbortSignal. “Exact request” here means the detached runtime request—not a guaranteed identical provider response, network exchange, or future model execution.
11 / M14 IS NOT M15
Finished sessions can continue. Uncertain effects must not be repeated.
The SQLite reopen test restores a completed session into a fresh runtime, then starts a new follow-up. Prior messages and tool history survive, as do saved system instructions and provider options when the caller does not replace them. Compaction checkpoints retain the original history as well as the summarized view.
restoreSession() requires a committed terminal checkpoint matched by the terminal event. Missing or interrupted history is rejected with RUNTIME_CHECKPOINT_INTERRUPTED. Reconstructing what the next request would have been does not establish whether an interrupted request or process already had an effect.
M16 likewise remains the planned integration of the native kernel into a task-manifest runner. These four milestones do not establish that the kernel authored itself, replace the entire builder workflow, or complete the later self-hosting gates.
12 / RESULTS WITH THEIR EVIDENCE LANES
710 passing tests—and what those tests actually exercised.
During publication, typecheck and the full offline suite were rerun against the M14 merge’s tracked source. The result was 710 passed, 10 skipped, across 46 passing test files and one skipped file. No live provider, Postgres server, or Docker daemon was invoked for this reproduction.
- M11: five development-tool fixtures cover the copied-tree edit/test/diff path and local mutation rejection cases.
- M12: runtime fixtures cover model/tool-phase steering, concurrent FIFO order, immutable follow-up history, and cancellation ordering.
- M13: four context fixtures cover summary/tail reconstruction, failure, budget charging, and overflow.
- M14: three durable-replay fixtures exercise real SQLite close/reopen, exact detached requests, compaction state, version rejection, and terminal follow-ups.
- Postgres: fourteen session-store tests include the new adapter and owner-fenced checkpoint contracts using a scripted injected database.
- Live Docker: ten tests are deliberately skipped by the default suite; their existence is not a passing live result for this note.
The final PR’s retained exit report also records 710 passing tests and no allowed-path violations. Hosted CI on the exact merge passed, as did its CodeQL workflow. PR #15’s own CI and CodeQL checks passed before merge.
These are deterministic correctness and contract checks, not a load benchmark, security certification, live-Postgres qualification, or proof of exactly-once recovery under arbitrary process failure.
13 / READ THE IMPLEMENTATION
Follow the capability into the session store.
- Development tools: canonical registry, strict parameters, mutation guard, and output bounds.
- Workspace isolation identity: internal attestation and trusted-view inheritance.
- Provider adapter: detached aliases and canonical-name restoration.
- Runtime: steering, follow-up admission, compaction, and checkpoint boundaries.
- Context and checkpoint schema: effective message views and pure request reconstruction.
- SessionEventStore: serialized delivery, durable cursors, and checkpoint revisions.
- Event contract and pinned roadmap: implemented milestones and explicit remaining gates.
Merge sequence: M11 / PR #12 / a51ffc2, then M12 / PR #13 / 6e827ed, M13 / PR #14 / 7e4d2fa, and M14 / PR #15 / fc8b3d9. All four were merged on September 11, 2026. Source links in this article are pinned to the final merge, not a moving main branch.
14 / THE NEXT HONEST STEP
The session is inspectable. Recovery must now earn its authority.
M11–M14 connect capability control, interactive direction, context management, and durable state. The model sees a small tool surface; operators can steer without rewriting in-flight requests; summaries do not erase originals; and persistence can reproduce the next request without guessing.
The next milestone has a narrower and harder job: prove which interrupted work may safely continue, without repeating uncertain effects. After that comes the native task-runner integration and its own evidence. Each new layer should preserve the same rule: a passing test or saved checkpoint grants only the capability it actually proves.
For the foundation, read Harness M9–M10: local and Docker workspaces. For the requested scope and the mutation-safety decision, see the M11–M14 build chat.