docs: preserve native terminal dispositions

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-07 08:43:10 +00:00
1 parent 6e623c7add
commit 566c5e8411
1 file changed
+42 -18
@@ -174,10 +174,11 @@ heartbeat service
-> durable runner commands/events
-> NativeRunResult
-> AdapterExecutionResult compatibility conversion
-> existing workspace finalization and run completion
-> native-aware run/issue finalization
-> existing workspace finalization
```
This isolates the spike from the full heartbeat finalization surface.
This keeps workspace finalization and all legacy adapter behavior intact. The additive native discriminator extends run/issue finalization only where the legacy exit-code heuristic cannot represent a native disposition.
---
@@ -614,7 +615,7 @@ That is the proof that the abstraction is real.
21. Human steering becomes a durable control-plane command and is delivered over the same runner connection.
22. The harness emits a structured result or invokes the completion tool.
23. The runner emits `run.result`.
24. The control plane finalizes the native session, converts the result to the existing terminal adapter result shape, and runs existing workspace finalization.
24. The control plane finalizes the native session, converts the result to the additive native-aware adapter result shape, and runs native-aware run/issue finalization plus existing workspace finalization.
25. Paperclip applies issue status, handoff, cost, and work-product behavior.
26. The run and environment lease are released or retained according to warm policy.
@@ -712,8 +713,9 @@ if (resolveRuntimeMode(agent, run) === "native") {
});
}
// Existing workspace finalization, cost recording,
// handoff, release, and run finalization continue here.
// Existing workspace finalization and cost recording continue here.
// Run/issue finalization reads adapterResult.nativeFinalization
// for native mode and keeps the legacy heuristic otherwise.
```
### 8.2 Do not extend the heartbeat monolith indefinitely
@@ -2176,7 +2178,7 @@ Invalid result:
### 18.3 Mapping to Paperclip
The native runtime returns the structured result to existing finalization code.
The native runtime returns the structured result to the finalization pipeline. Native mode requires the additive finalizer contract in section 18.4; it must not pass a native result through the legacy exit-code-only decision path.
The finalizer:
@@ -2196,20 +2198,39 @@ The three vocabularies describe different layers. They are not independent outco
- `NativeExecutionResult.terminalState` is the native runtime lifecycle result;
- `AdapterExecutionResult` is the legacy heartbeat compatibility record.
The native finalizer must use this table. It must not infer a disposition from a process exit code.
`AdapterExecutionResult` needs one additive, typed discriminator before native mode can ship:
```ts
interface NativeFinalizationResult {
schema: "paperclip.native-finalization.v1";
terminalState: NativeExecutionResult["terminalState"];
disposition: NativeExecutionResult["disposition"];
}
interface AdapterExecutionResult {
// Existing fields stay unchanged.
nativeFinalization?: NativeFinalizationResult;
}
```
Legacy adapters omit `nativeFinalization` and keep the current exit-code heuristic. Native adapters must set it. The heartbeat finalizer must validate it against `resultJson`, select the run outcome and issue action from it, and only then use the compatibility fields for process diagnostics. `errorCode`, a null exit code, or a zero exit code is not a native disposition signal.
The native finalizer must use this table:
| Native disposition | Native terminal state | `AdapterExecutionResult` compatibility fields | Finalizer action |
|---|---|---|---|
| `done` | `completed` | `exitCode: 0`, `timedOut: false`, no error, structured result in `resultJson` | Apply the legal `done` issue transition. |
| `blocked` | `completed` | `exitCode: 0`, `timedOut: false`, no error, structured result and blocker in `resultJson` | Apply `blocked` only when the blocker payload and unblock owner/action are valid. Otherwise convert to `needs_review`. |
| `needs_review` | `completed` | `exitCode: 0`, `timedOut: false`, no error, structured result in `resultJson` | Create or bind a real reviewer, approval, interaction, or monitor path, then apply `in_review`. If no review path can be created, keep the issue `in_progress` and record a finalization error. |
| `yielded` | `completed` | `exitCode: 0`, `timedOut: false`, no error, structured result in `resultJson` | Keep the issue `in_progress` and enqueue the declared continuation or delegated follow-up. If no live continuation can be scheduled, convert to `needs_review`. |
| `failed` | `failed` | Nonzero driver exit code when available, otherwise `exitCode: 1`; set `errorMessage`, `errorCode`, and error metadata; include the failure disposition in `resultJson` | Apply the existing retry and recovery policy. Do not directly change the issue to a terminal status. |
| `cancelled` | `cancelled` | `exitCode: null`, `timedOut: false`, `errorCode: "cancelled"`, and the cancellation disposition in `resultJson` | Commit the heartbeat run cancellation before returning the adapter result. Change the issue to `cancelled` only when the cancellation scope explicitly includes the issue; otherwise leave the issue unchanged. |
| `done` | `completed` | `nativeFinalization` carries both values; `exitCode: 0`, `timedOut: false`, no error; structured result in `resultJson` | Persist the run as `succeeded`, then apply the legal `done` issue transition. |
| `blocked` | `completed` | `nativeFinalization` carries both values; `exitCode: 0`, `timedOut: false`, no error; structured result and blocker in `resultJson` | Persist the run as `succeeded`. Apply `blocked` only when the blocker payload and unblock owner/action are valid. Otherwise convert to `needs_review`. |
| `needs_review` | `completed` | `nativeFinalization` carries both values; `exitCode: 0`, `timedOut: false`, no error; structured result in `resultJson` | Persist the run as `succeeded`. Create or bind a real reviewer, approval, interaction, or monitor path, then apply `in_review`. If no review path can be created, keep the issue `in_progress` and record a finalization error. |
| `yielded` | `completed` | `nativeFinalization` carries both values; `exitCode: 0`, `timedOut: false`, no error; structured result in `resultJson` | Persist the run as `succeeded`. Keep the issue `in_progress` and enqueue the declared continuation or delegated follow-up. If no live continuation can be scheduled, convert to `needs_review`. |
| `failed` | `failed` | `nativeFinalization` carries both values; nonzero driver exit code when available, otherwise `exitCode: 1`; set `errorMessage`, `errorCode`, and error metadata; include the failure disposition in `resultJson` | Persist the run as `failed` and apply the existing retry and recovery policy. Do not directly change the issue to a terminal status. |
| `cancelled` | `cancelled` | `nativeFinalization` carries both values; `exitCode: null`, `timedOut: false`, optional cancellation diagnostics in `errorCode` and `resultJson` | Persist the run as `cancelled` from `nativeFinalization`, without relying on `errorCode` or the null-exit fallback. Change the issue to `cancelled` only when the cancellation scope explicitly includes the issue; otherwise leave the issue unchanged. |
All compatibility results set `signal: null` unless the harness reported a real signal. They preserve usage, cost, session, provider, model, billing, and runtime-service fields without changing their meaning. `resultJson` always contains `schema`, `disposition`, `summary`, and the validated structured result when one exists.
`failed` and `cancelled` are runtime-owned dispositions, so the model-facing `StructuredRunResult` and semantic tools do not accept them. The runtime constructs them from an explicit driver, policy, timeout, interrupt, or cancellation event. `done`, `blocked`, `needs_review`, and `yielded` require a valid `StructuredRunResult`; they all produce a successful compatibility exit because the run finalizer, not the legacy exit-code heuristic, applies their distinct issue and continuation behavior.
`failed` and `cancelled` are runtime-owned dispositions, so the model-facing `StructuredRunResult` and semantic tools do not accept them. The runtime constructs them from an explicit driver, policy, timeout, interrupt, or cancellation event. `done`, `blocked`, `needs_review`, and `yielded` require a valid `StructuredRunResult`; they all produce a successful process exit, but `nativeFinalization.disposition` preserves their distinct issue and continuation behavior.
Native-mode enablement is gated on finalizer conformance tests for all six rows. Each test must prove the persisted heartbeat run status, issue status, continuation/review side effect, and cancellation scope. A native adapter result without a valid `nativeFinalization` object is rejected as `native_finalization_missing`; it never falls back to the legacy success heuristic.
It does not create an issue comment for every tool call or token delta.
@@ -3406,7 +3427,7 @@ packages/native-runtime-protocol/test-vectors/
### NR-006 — Native Session Runtime integration in heartbeat orchestration
**Goal:** Add the feature-flagged native branch while preserving existing finalization.
**Goal:** Add the feature-flagged native branch while preserving workspace finalization and extending run/issue finalization additively.
**Primary files:**
@@ -3422,13 +3443,16 @@ server/src/services/environment-run-orchestrator.ts
- runner expectation/bootstrap;
- `NativeSessionRuntime.execute`;
- terminal conversion to `AdapterExecutionResult`;
- typed `nativeFinalization` validation and six-row disposition handling;
- native finalizer conformance tests for run status, issue status, continuation/review effects, and cancellation scope;
- cancellation hook;
- no behavior change for legacy adapters.
**Acceptance:**
- fake driver run completes through existing workspace finalization;
- fake driver run completes through native-aware run/issue finalization and existing workspace finalization;
- native run can be cancelled through existing board controls;
- a missing or invalid native finalization discriminator fails closed instead of using the legacy success heuristic;
- legacy integration tests are unchanged;
- no Paperclip skill materialization occurs in native mode.
@@ -3780,7 +3804,7 @@ The spike is successful only if all of the following are demonstrated.
- [ ] Checkout occurs before expensive sandbox/model execution.
- [ ] Existing budgets and workspace policy still apply.
- [ ] Existing finalization and legal issue transition still run.
- [ ] Existing workspace finalization still runs, and the additive native finalizer applies the legal issue transition for all six dispositions.
- [ ] Legacy adapters are unchanged.
- [ ] Runtime permission and governance approval are separate.
- [ ] Database state remains authoritative when the active producer disconnects.
@@ -3882,7 +3906,7 @@ These are real design choices, but none blocks the initial vertical slice.
>
> Implement a feature-flagged native runtime path that lets a Paperclip task run through a deterministic daemon inside the realized sandbox rather than through the existing one-shot adapter contract. The daemon must initiate an outbound authenticated WebSocket to the Paperclip control plane, supervise Codex app-server locally over stdio, normalize its thread/turn/item events, persist unacknowledged events for replay, accept durable steer/interrupt/permission commands, and emit a structured terminal result.
>
> Preserve the existing environment-run orchestrator, execution workspace realization, issue checkout, budgets, governance, run records, workspace finalization, and legacy adapters. Branch after the execution target is realized. Internally use a session-oriented Native Session Runtime; at terminal completion convert the native result to the existing adapter-result boundary so current finalization continues to work.
> Preserve the existing environment-run orchestrator, execution workspace realization, issue checkout, budgets, governance, run records, workspace finalization, and legacy adapters. Branch after the execution target is realized. Internally use a session-oriented Native Session Runtime; at terminal completion convert the native result to the additive native-aware adapter-result boundary. Extend run/issue finalization to consume the typed native disposition, and retain the legacy exit-code heuristic only for adapters that omit that discriminator.
>
> Native mode must not materialize the Paperclip skill or expose a Paperclip/runner credential to the model process. The model receives a compact task envelope plus a structured completion schema or tiny run-scoped semantic completion tools.
>