STAGE 1 / MILESTONES 15–17

Harness M15–M17: safe continuation and an attested native builder

Restart-safe continuation, a kernel-backed task runner, and signed authorship evidence: Harness M15–M17 connect safe checkpoints to verifiable patches, with 724 offline tests and explicit live gates.

01 / FROM SAVED STATE TO ACCOUNTABLE WORK

Can the agent continue—and prove which changes it made?

M14 could reconstruct a saved request, but deliberately refused to resume uncertain interrupted work. M15–M17 move the boundary forward: continue from an explicitly safe checkpoint, run the native kernel behind the trusted task gate, and bind its output to verifiable authorship evidence.

The three milestones were delivered together in PR #16, merged on September 11, 2026 as cfe7a80. This note follows that pinned revision and the shared build chat, not later changes on main.

02 / M15 — SAFE MEANS BEFORE NEW INTENT

A stored checkpoint is not automatically permission to retry.

M15 adds a safe runtime checkpoint phase before the next model round. It captures the original session state, agent identity, tool definitions, and workspace snapshot. A model or summary checkpoint is different: it marks a region where execution may have begun, so it cannot authorize an automatic retry.

Continuation admission—not a generic retry looptext · source
safe checkpoint is the last committed boundary
  + same run / session / turn / model
  + same tool definitions and workspace snapshot
  + expired previous owner, fresh replacement owner
  + matching checkpoint revision and durable cursor
  = eligible for a fenced continuation

model intent or uncertain effect after that boundary
  = interrupted / indeterminate; do not execute again automatically

The runtime validates executable capabilities before consuming the checkpoint. A changed tool definition or workspace snapshot rejects continuation. The continuation restores prior messages, usage, counters, call identities, grants, warnings, steering, and compaction state instead of accepting a replacement conversation from the caller.

This continues the original run and turn. It does not emit a second agent-start event, turn-start event, or initial user message. A later follow-up is still a separate concept: new work after a completed turn.

03 / M15 — ONE OWNER TAKES OVER

Make the ownership change part of the durable transaction.

SQLite and Postgres implement continuation under their storage transaction or row lock. Admission checks the expected owner metadata and checkpoint revision, requires an expired lease and a different owner, and requires the safe checkpoint cursor to be the current event tail.

01Inspect checkpointRequire matching identity, tools, workspace, and safe phase.
02Claim under lockCheck expired lease, revision, metadata, and current cursor.
03Record takeoverAppend runtime.continued and advance owner and checkpoint cursor.
04Continue the turnRestore saved state without repeating start events or prior work.
Fenced continuation of the same logical turnA takeover changes ownership and records continuation together; a competing or stale owner cannot simply resume the same boundary.

runtime.continued is appended in the same transaction that replaces the expired owner and advances the checkpoint cursor. A competing recovery attempt loses that claim; the former owner can no longer append under its old identity.

If work is indeterminate, the recovery path records an interrupted outcome rather than guessing. It also avoids closing a competing owner’s healthy session merely because another claimant lost. Cancellation during safe recovery completes the original turn as canceled without requesting the model.

04 / M15 — TEST THE CRASH BOUNDARIES

The interesting test is what does not run twice.

The continuation suite walks both sides of the scenario’s event appends: before commit, and after commit but before acknowledgement. It also injects failure before and after the tool effect. Only a latest safe checkpoint is resumable; other outcomes must make no additional model request and leave the effect count unchanged.

A separate fixture kills an actual child process with SIGKILL after a committed tool round. A file counter has already advanced from zero to one. The parent reopens SQLite, continues with a replacement owner, and verifies the counter stays at one. There is one tool call and two model requests across the session—not a duplicated tool effect.

The real-process fixture’s observable resulttext · source
child: tool increments counter → counter = 1
child: commit safe boundary → SIGKILL
parent: reopen SQLite → claim continuation
parent: finish next model round

counter remains 1
one tool.call across the session
two model.request events across the session

05 / M16 — THE KERNEL BECOMES A TASK AGENT

Keep branch control outside the model’s five tools.

The native TaskAgent path now sits behind the existing bootstrap and exit gate. The trusted CLI validates the manifest, selects the exact task branch, checks scope, invokes the builder, applies its returned patch, runs exit tests, and records a structured report.

01Trusted preflightManifest, clean input, exact tasks/<id> branch, write approval.
02Native runtimeMinimalAgentRuntime with five tools in DockerWorkspace.
03Return the patchVerify unchanged host/control inputs, then apply scoped output.
04Exit gateRun tests, verify generated tree, persist evidence and seal the report.
Native builder inside the trusted task workflowThe model works through the existing five tools; the surrounding CLI owns repository preparation, patch application, tests, and evidence.

createNativeTaskAgent() requires an immutable image digest and the Docker workspace selector. There is no local fallback. It copies tracked input and the manifest overlay into a temporary source tree, compiles manifest permissions, and uses a SQLite-backed event store.

The model still receives only the M11 development tools. It does not gain a sixth tool to create branches, stage files, commit, push, or merge. Those operations belong to trusted workflow code and human review.

Bootstrap defaults to the offline native path. Upstream Pi remains available through explicit legacy selection. The default native model is FakeModel; selecting a native image alone does not turn the command into a live-model developer. That limitation matters when interpreting “self-host runner.”

06 / M16 — ONE END-TO-END OFFLINE FIXTURE

Edit, test, restart, return a patch—without touching main.

The integration fixture begins with a temporary repository containing an old text file and a task manifest. FakeModel writes the new text, invokes a test command, asks for a diff, and finishes. The trusted exit test independently checks the resulting host file.

Assertions verify the task branch, unchanged main reference, one test execution inside the protocol fixture, four model requests, one continuation event, unique event IDs, and a passing run-report/v2. A manifest write rule set to ask is resolved by the trusted run’s explicit approval.

The deliberate native restart closes and reopens SQLite and creates a new runtime while retaining the workspace object in the integration process. Docker execution is an injected protocol fixture. The separate M15 SIGKILL test supplies real process-death evidence; these are distinct tests, not a claim that a real Docker-backed builder survived a complete process loss here.

The important delivered integration is that model work now feeds the existing gate and reviewable patch path. Live-provider behavior and a deployed image still need their own qualification.

07 / M17 — A BUILDER NAME IS NOT PROVENANCE

Do not let a run claim a patch that was already there.

A free-form builder name in a report cannot establish authorship. M17 registers native TaskAgent identities and associates returned results with their exact agent instance. A result carrying a convincing name but lacking that trusted association is not sufficient.

Before model invocation, the native gate rejects pre-existing source changes even if they are inside allowed paths. The one permitted exception is the attested manifest control overlay. Independent fixtures seed source as unstaged, staged, and committed changes; all three are blocked with NATIVE_PREAUTHORED_INPUT, before any model request.

During the run, the gate checks that the builder source identity and manifest digest have not changed, and that the host tree still matches its pre-builder snapshot. After patch application, generated paths must remain in scope and the manifest overlay must remain unchanged. Exit tests cannot silently alter the attested generated tree and still produce a passing report.

08 / M17 — BIND THE EVIDENCE CHAIN

Record the inputs, runtime, workspace, events, and output together.

native-builder/v1 — evidence field guidetext · source
Entrypoint       MinimalAgentRuntime.TaskAgent/v1
Builder revision content digest of selected platform TS sources + lockfile
Control input    manifest digest and input base SHA
Execution        model, image digest, workspace/session/run identities
Snapshots        initial/generated workspace and pre/post-builder Git state
Output           generated Git tree and canonical patch digest
Audit            runtime event log and workspace lifecycle log digests

The builder revision is a content digest of the selected TypeScript source trees and lockfile—not merely a branch name, and not a hash of every byte in the repository. Generated tree capture uses a separate Git index so the verifier does not stage the user’s working index. Canonical patches disable external diff and text-conversion behavior.

01Clean base + manifestRecord explicit control input and pre-builder state.
02Registered native runBind runtime, model, image, workspace, and event evidence.
03Generated outputCapture tree, canonical patch, snapshots, and log digests.
04Signed reportSeal the complete report after tests and tree verification.
The authorship evidence chainThe gate binds controlled input to recorded execution and output. Later acceptance verification checks the candidate against that signed record.

The report is sealed with Ed25519 after tests and final tree verification. The signing key belongs to the trusted gate and is not passed to the model or workspace. The implementation supports an explicit signing key; without one it generates a process-local authority.

A signature binds the report to a key. It does not independently establish that the key or builder is trustworthy. Portable verification must use a separately pinned public key, not accept the key embedded in the report as its own trust root.

09 / M17 — VERIFY THE CANDIDATE AND ACCEPTED PATCH

A valid report must still match the code being reviewed.

harness verify-native validates the report, trusted signature, retained artifact digests, candidate tree, and canonical patch. If an accepted commit and its base are supplied, their patch must match the attested patch exactly.

Verifier command shape—replace these example paths and refstext · source
harness verify-native tasks/runs/example-report.json \
  --trusted-key trusted-builder-public.pem \
  --candidate candidate-ref \
  --accepted accepted-ref \
  --accepted-base accepted-base-ref

The tests cover a candidate matching the generated tree, a changed report, a tampered patch artifact, and a rebased or squashed patch on top of unrelated upstream work. The accepted tree can differ from the candidate tree because its base changed; the exact patch must not.

A human conflict edit breaks patch equivalence and is rejected with a direction to return that work to the builder. The successful verifier returns native-acceptance/v1 evidence linking candidate, accepted commit, base, trees, and patch.

This verifier exists as a read-only command. Passing PR CI for the M17 implementation is not the same as proving that this repository merge itself was generated by the attested native path. The candidate/accepted binding demonstrated here belongs to the independent temporary-repository fixture.

10 / RESULTS AND EVIDENCE LANES

724 tests, with real and simulated boundaries identified.

The build chat reports 724 passing tests, typecheck, evaluations, and the exit gate. During this publication, typecheck, the full offline suite, and evaluations were reproduced on the pinned merge.

  • Offline suite: 724 passed; ten live-Docker tests remain skipped.
  • Continuation: deterministic append/effect failure injection, competing ownership, cancellation, and a real child-process SIGKILL with SQLite reopen.
  • Native runner: FakeModel, a Docker protocol executor fixture, a temporary Git repository, and an independent exit test.
  • Authorship: seeded-source rejection, signed report/artifact checks, candidate matching, patch-equivalent acceptance, and rejection of human conflict edits.
  • Postgres: injected transaction contracts, not a live database trial.

PR CI and PR CodeQL passed. The exact merge also passed CI and CodeQL.

No paid model call, live Docker image qualification, live Postgres deployment, load benchmark, or security certification is claimed by these results. “Offline gate implemented” is the correct milestone status.

11 / FOLLOW THE IMPLEMENTATION

Read recovery first, then the gate, then the verifier.

12 / M18 MUST EARN THE NEXT CLAIM

A mechanism for proving authorship is not self-authorship.

The next planned milestone uses a real model through the attested native path to add harness doctor to the project’s own CLI. It needs its own clean task, branch, safe restart, evidence, independent verification, human review, and merge.

Only after that gate is accepted does the resulting M18 candidate become the first qualified successor builder. The M17 revision that performed the work does not silently qualify itself. That separation keeps the self-hosting claim tied to a concrete authored change.

M15–M17 supply the prerequisites: a narrow safe continuation point, a native task path inside the trusted gate, and an independently verifiable link from clean inputs to accepted output.

Read the M11–M14 foundation or the M15–M17 build chat for the decisions leading here.

CONTINUE EXPLORING

Trace the change from a safe checkpoint to verifiable output.

Inspect safe continuation, the offline native TaskAgent, and signed candidate/accepted-patch evidence in the pinned public merge. Live self-hosted qualification remains M18.