Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Cooperative harness execution implementation plan

- **Status:** Approved design; PR 1 decision and promotion shutdown complete, runtime implementation not started
- **Status:** Approved design; PR 1 decision/promotion shutdown and PR 2 execution-assurance protocol complete; operator recovery and harness integration remain pending
- **Date:** 2026-08-31
- **Audience:** Autopilot implementers and reviewers
- **Related:** [Architecture](architecture.md), [continuity implementation plan](2026-08-30-continuity-evidence-implementation-plan.md), [durable event engine ADR](adr/0001-durable-event-engine.md)
Expand Down
14 changes: 13 additions & 1 deletion skills/autopilot/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,19 @@ interface HarnessPort {
}
```

The capability manifest describes unattended execution, useful concurrency, event streaming, cancellation, restart reattachment, tool restrictions, and assurance level.
The capability manifest describes unattended execution, useful concurrency, event streaming, cancellation, tool restrictions, and assurance level. Its versioned execution-assurance profiles select implementation and review behavior independently:

```ts
interface ExecutionAssurance {
schemaVersion: 1;
owner: "runtime" | "harness";
continuity: "session" | "same-harness-instance" | "durable-subject";
terminality: "cooperative" | "process-supervised";
admission: "single-shot" | "idempotent";
}
```

`restartReattachment` remains a protocol-v1 compatibility field for older manifests and journals. New attempts persist their selected assurance before launch in `ATTEMPT_STARTED`, then persist the exact adapter, backend, and subject identity in `ATTEMPT_EXECUTION_ADMITTED`. A lost single-shot admission or cooperative continuity boundary becomes `EXECUTION_STATE_UNKNOWN`; the engine never converts a missing reattachment into a new launch.

Adapters return observations. They never write the journal or choose lifecycle transitions. On POSIX hosts, built-in CLI implementation executions run beneath a detached, attempt-scoped supervisor that owns the harness pipes and bounded output/activity capture. Before harness launch, a separately detached watchdog durably confirms readiness. The harness then joins the supervisor's known process group. All terminal publication is a watchdog-owned handshake: the supervisor publishes a bounded completion candidate, the watchdog terminates and confirms the group is quiescent, and only then publishes the durable result and terminal status. This also covers supervisor exit before child-identity publication. The reviewed Windows x64 Job Object helper will not be packaged because a custom process-management executable creates antivirus, application-reputation, architecture, and provenance risk. The source and runtime path remain temporarily for ordered removal, but the artifact-producing workflow is gone and Windows continues to report restart reattachment as unsupported. Windows currently uses `taskkill` only for the existing session-scoped direct-execution fallback. The approved replacement is version-pinned cooperative harness execution: only an exact terminal response from the uninterrupted harness instance may proceed to repository verification. Harness loss becomes `EXECUTION_STATE_UNKNOWN`, launches no replacement, and requires operator recovery. This boundary does not prove process-tree quiescence. The supervisor writes only fenced operational artifacts under `runs/<run-id>/executions/<execution-id>/`; it cannot write `events.jsonl`, receipts, leases, snapshots, or Git state. On supported POSIX attempts, a fresh coordinator reconstructs the exact request from the journaled attempt and immutable context, reattaches to running or terminal supervisor artifacts, and waits for terminal process-tree evidence before allowing a replacement attempt. Review executions remain session-scoped.

Expand Down
2 changes: 1 addition & 1 deletion skills/autopilot/docs/implementation-plan.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Harness-agnostic Autopilot implementation plan

- **Status:** Developer-preview implementation available; POSIX attempt-scoped implementation reattachment and controlled-fixture sealed restack successors are packaged. The reviewed Windows x64 Job Object artifact will not be packaged; Windows restart reattachment remains disabled while cooperative harness execution is implemented.
- **Status:** Developer-preview implementation available; POSIX attempt-scoped implementation reattachment, versioned per-mode execution assurance, exact admitted-subject journaling, and controlled-fixture sealed restack successors are packaged. The reviewed Windows x64 Job Object artifact will not be packaged; Windows restart reattachment remains disabled while operator recovery and cooperative harness integration are implemented.
- **Date:** 2026-08-22
- **Audience:** Autopilot implementers and reviewers
- **Governing design:** [Autopilot architecture](architecture.md)
Expand Down
6 changes: 6 additions & 0 deletions skills/autopilot/references/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ Adapters start fresh noninteractive sessions and normalize observations. They ca

The adapter parser bounds output and rejects malformed JSON-mode output. The runtime ignores model completion claims and inspects the worktree directly.

### Execution assurance

Protocol-v1 manifests may provide separate versioned execution-assurance profiles for implementation and review requests. Each profile records execution ownership (`runtime` or `harness`), continuity (`session`, `same-harness-instance`, or `durable-subject`), terminality (`cooperative` or `process-supervised`), and admission (`single-shot` or `idempotent`). Older manifests remain readable: `restartReattachment: true` maps to the existing runtime-owned process-supervised implementation path, while reviews and non-reattachable implementations remain session-scoped and cooperative.

Before launch, `ATTEMPT_STARTED` persists the selected assurance and exact request identity. After launch returns, `ATTEMPT_EXECUTION_ADMITTED` binds the adapter and harness versions, adapter execution ID, backend ID, subject ID, and harness-instance ID when required. Losing a single-shot launch reply, changing any exact reattachment identity, or losing a cooperative harness instance becomes `EXECUTION_STATE_UNKNOWN`. A missing reattachment never falls back to `launch()`.

For a `review` gate, the runtime sends a separate role-scoped request with no writable roots or worker write/process grants. Claude Code receives only read/search tools, Codex uses its read-only sandbox, and direct Pi receives only its read tool. OpenCode and any ambient operating-system access remain cooperative. The adapter extracts exactly one structured review marker; missing, contradictory, malformed, truncated, timed-out, or inconclusive output is `UNVERIFIED`. The runtime compares the complete tree, HEAD, refs, and Git configuration before and after review and rejects any mutation. Version-pinned disposable exact-tree runs passed with Pi 0.84.4, Codex 0.151.0, and OpenCode 1.18.25. Claude Code 2.1.251 now reaches its API credential path, but the identity-linked key requires an `ANTHROPIC_WORKSPACE_ID` that is not present in the validation environment, so edit and review remain unverified.

For Pi, Autopilot checks the standard Pi package directory for `pi-subagents` 0.53.0 or newer. When present, it loads only that extension and Autopilot's bridge, delegates the item to the resolved `worker` role through the public structured delegation API, and keeps the worker in Autopilot's existing worktree. The read-only exact-tree review role runs directly and is not subjected to the worker-only subagent terminal envelope. Autopilot does not install or update the extension. An older or absent installation uses the direct Pi process and reports that fallback through `doctor` and adapter limitations.
Expand Down
34 changes: 33 additions & 1 deletion skills/autopilot/runtime/dist/src/adapter-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,31 @@ export class CliHarnessAdapter {
eventStreaming: this.#configuration.expectsJsonLines,
cancellation: this.#configuration.cancellation,
restartReattachment: process.platform !== "win32" || windowsHelperSha256 !== undefined,
executionAssurance: {
schemaVersion: 1,
implementation: process.platform !== "win32" || windowsHelperSha256 !== undefined
? {
schemaVersion: 1,
owner: "runtime",
continuity: "durable-subject",
terminality: "process-supervised",
admission: "idempotent",
}
: {
schemaVersion: 1,
owner: "runtime",
continuity: "session",
terminality: "cooperative",
admission: "single-shot",
},
review: {
schemaVersion: 1,
owner: "runtime",
continuity: "session",
terminality: "cooperative",
admission: "single-shot",
},
},
restrictions: this.#configuration.assurance,
limitations: [
...this.#configuration.limitations,
Expand Down Expand Up @@ -266,6 +291,7 @@ export class CliHarnessAdapter {
protocolVersion: 1,
adapterExecutionId: handle.executionId,
startedAt: handle.startedAt,
subject: { schemaVersion: 1, backendId: "process-supervisor", subjectId: handle.executionId },
supervisor: { schemaVersion: 1, directory: handle.directory, requestHash: handle.requestHash },
};
}
Expand Down Expand Up @@ -301,7 +327,12 @@ export class CliHarnessAdapter {
}));
this.#executions.set(adapterExecutionId, { controller, promise });
this.#requests.set(adapterExecutionId, request);
return { protocolVersion: 1, adapterExecutionId, startedAt };
return {
protocolVersion: 1,
adapterExecutionId,
startedAt,
subject: { schemaVersion: 1, backendId: "direct-process", subjectId: adapterExecutionId },
};
}
async reattach(request) {
const environment = adapterEnvironment(request);
Expand All @@ -318,6 +349,7 @@ export class CliHarnessAdapter {
protocolVersion: 1,
adapterExecutionId: handle.executionId,
startedAt: handle.startedAt,
subject: { schemaVersion: 1, backendId: "process-supervisor", subjectId: handle.executionId },
supervisor: { schemaVersion: 1, directory: handle.directory, requestHash: handle.requestHash },
};
}
Expand Down
23 changes: 23 additions & 0 deletions skills/autopilot/runtime/dist/src/adapter-protocol.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import { type AssuranceLevel, type CapabilityGrant, type GrantFamily, type Predicate, type VerificationGate } from "./charter.js";
export interface ExecutionAssurance {
readonly schemaVersion: 1;
readonly owner: "runtime" | "harness";
readonly continuity: "session" | "same-harness-instance" | "durable-subject";
readonly terminality: "cooperative" | "process-supervised";
readonly admission: "single-shot" | "idempotent";
}
export interface ExecutionAssuranceProfiles {
readonly schemaVersion: 1;
readonly implementation: ExecutionAssurance;
readonly review: ExecutionAssurance;
}
export interface CapabilityManifest {
readonly protocolVersion: 1;
readonly adapterName: string;
Expand All @@ -11,6 +23,7 @@ export interface CapabilityManifest {
readonly eventStreaming: boolean;
readonly cancellation: boolean;
readonly restartReattachment: boolean;
readonly executionAssurance?: ExecutionAssuranceProfiles;
readonly restrictions: "enforced" | "cooperative";
readonly limitations: readonly string[];
}
Expand Down Expand Up @@ -81,6 +94,7 @@ export interface ReviewResult {
export interface ExecutionRequest {
readonly protocolVersion: 1;
readonly role: "implementation" | "review";
readonly executionAssurance?: ExecutionAssurance;
readonly runId: string;
readonly itemId: string;
readonly attemptId: string;
Expand All @@ -98,10 +112,17 @@ export interface ExecutionRequest {
readonly maximumOutputBytes: number;
readonly supervisionDirectory?: string;
}
export interface ExecutionSubject {
readonly schemaVersion: 1;
readonly backendId: string;
readonly subjectId: string;
readonly harnessInstanceId?: string;
}
export interface ExecutionHandle {
readonly protocolVersion: 1;
readonly adapterExecutionId: string;
readonly startedAt: string;
readonly subject?: ExecutionSubject;
readonly supervisor?: {
readonly schemaVersion: 1;
readonly directory: string;
Expand Down Expand Up @@ -130,6 +151,8 @@ export interface HarnessPort {
observe(handle: ExecutionHandle): Promise<ExecutionObservation>;
cancel(handle: ExecutionHandle): Promise<CancelResult>;
}
export declare function executionAssuranceFor(manifest: CapabilityManifest, role: ExecutionRequest["role"]): ExecutionAssurance;
export declare function parseExecutionAssurance(value: unknown, label: string): ExecutionAssurance;
export type AdapterMessage = {
readonly protocolVersion: 1;
readonly type: "capabilities";
Expand Down
52 changes: 52 additions & 0 deletions skills/autopilot/runtime/dist/src/adapter-protocol.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,55 @@
import { GRANT_FAMILIES, } from "./charter.js";
import { AutopilotError } from "./errors.js";
import { expectBoolean, expectInteger, expectLiteral, expectRecord, expectString, expectStringArray } from "./json.js";
const SESSION_COOPERATIVE_ASSURANCE = {
schemaVersion: 1,
owner: "runtime",
continuity: "session",
terminality: "cooperative",
admission: "single-shot",
};
function legacyExecutionAssurance(manifest) {
return {
schemaVersion: 1,
implementation: manifest.restartReattachment
? {
schemaVersion: 1,
owner: "runtime",
continuity: "durable-subject",
terminality: "process-supervised",
admission: "idempotent",
}
: SESSION_COOPERATIVE_ASSURANCE,
review: SESSION_COOPERATIVE_ASSURANCE,
};
}
export function executionAssuranceFor(manifest, role) {
return manifest.executionAssurance?.[role] ?? legacyExecutionAssurance(manifest)[role];
}
export function parseExecutionAssurance(value, label) {
const object = expectRecord(value, label);
if (object.schemaVersion !== 1) {
throw new AutopilotError("ADAPTER_UNSUPPORTED", `${label} schema version is not supported`);
}
return {
schemaVersion: 1,
owner: expectLiteral(object.owner, ["runtime", "harness"], `${label}.owner`),
continuity: expectLiteral(object.continuity, ["session", "same-harness-instance", "durable-subject"], `${label}.continuity`),
terminality: expectLiteral(object.terminality, ["cooperative", "process-supervised"], `${label}.terminality`),
admission: expectLiteral(object.admission, ["single-shot", "idempotent"], `${label}.admission`),
};
}
function parseExecutionAssuranceProfiles(value) {
const object = expectRecord(value, "manifest.executionAssurance");
if (object.schemaVersion !== 1) {
throw new AutopilotError("ADAPTER_UNSUPPORTED", "execution assurance schema version is not supported");
}
return {
schemaVersion: 1,
implementation: parseExecutionAssurance(object.implementation, "manifest.executionAssurance.implementation"),
review: parseExecutionAssurance(object.review, "manifest.executionAssurance.review"),
};
}
export function parseAdapterMessage(line, maximumBytes) {
if (Buffer.byteLength(line) > maximumBytes) {
throw new AutopilotError("ADAPTER_MALFORMED", "adapter message exceeds the configured line limit");
Expand Down Expand Up @@ -37,6 +86,9 @@ export function parseAdapterMessage(line, maximumBytes) {
eventStreaming: expectBoolean(manifest.eventStreaming, "manifest.eventStreaming"),
cancellation: expectBoolean(manifest.cancellation, "manifest.cancellation"),
restartReattachment: expectBoolean(manifest.restartReattachment, "manifest.restartReattachment"),
...(manifest.executionAssurance === undefined
? {}
: { executionAssurance: parseExecutionAssuranceProfiles(manifest.executionAssurance) }),
restrictions: expectLiteral(manifest.restrictions, ["cooperative", "enforced"], "manifest.restrictions"),
limitations: expectStringArray(manifest.limitations, "manifest.limitations"),
},
Expand Down
Loading