docs(runner): document Phase 2 verification

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-07 21:09:28 +00:00
1 parent f32331a567
commit 7e0c8632da
17 files changed
+581 -27

No files matched your search

+20 -12
View File
@@ -6,10 +6,12 @@ production runner direction is Rust; `runner/` is its Cargo workspace. The
TypeScript surface remains a control-plane/client reference implementation.
Phase 0 runs both implementations against one language-neutral fixture. Phase 1
adds executable PRP schemas, a cross-language conformance corpus, deterministic
static replay, and a standalone browser reference page. Neither phase imports or
starts Paperclip's server, UI, CLI, or production database.
static replay, and a standalone browser reference page. Phase 2 adds the Rust
runner process, process-group supervision, a scripted fake harness over stdio
JSONL, a TypeScript mock core, and validated live browser replay. These phases do
not import or start Paperclip's server, UI, CLI, or production database.
## Phase 0–1 quick start
## Phase 0–2 quick start
From the repository root:
@@ -33,10 +35,12 @@ Run only the tracer with:
pnpm --filter @paperclipai/paperclip-runner trace:phase0
```
Replay the Phase 1 happy path in the CLI or open the browser devtool:
Replay the Phase 1 happy path, run a Phase 2 local session, or open the browser
devtool:
```sh
pnpm --filter @paperclipai/paperclip-runner replay:phase1
pnpm --filter @paperclipai/paperclip-runner trace:phase2 -- --scenario happy-path
pnpm --filter @paperclipai/paperclip-runner browser:dev --host 127.0.0.1 --port 4179
```
@@ -44,9 +48,9 @@ pnpm --filter @paperclipai/paperclip-runner browser:dev --host 127.0.0.1 --port
| Command | Purpose |
|---|---|
| `build` | Compile the TypeScript public surface, Rust workspace, and browser replay. |
| `build` | Compile the TypeScript public surface, Rust workspace, and browser devtool. |
| `typecheck` | Check TypeScript, Rust, generated schema sources, and browser types. |
| `test` | Run Rust/TypeScript fixture, mock-contract, stable-output, and negative-boundary tests. |
| `test` | Run Rust/TypeScript fixture, supervisor, fake-driver, live/replay, and boundary tests. |
| `check:forbidden-imports` | Reject TypeScript imports and Cargo path dependencies that cross into Paperclip core. |
| `check:phase0-parity` | Require byte-for-byte equivalent Rust and TypeScript tracer output. |
| `check:phase1-goldens` | Require all reducer snapshots and cross-language summaries to match checked goldens. |
@@ -57,8 +61,11 @@ pnpm --filter @paperclipai/paperclip-runner browser:dev --host 127.0.0.1 --port
| `trace:phase0:typescript` | Run the TypeScript reference tracer directly. |
| `replay:phase1` | Validate and reduce a fixture to a final snapshot. |
| `browser:dev` | Open the editable fixture/static replay reference page. |
| `test:browser` | Build the browser page, exercise replay states, and capture the screenshot. |
| `verify` | Run the complete Phase 0 and Phase 1 acceptance sequence. |
| `trace:phase2` | Run one native local session through the Rust runner and fake harness. |
| `record:phase2` | Capture a validated happy-path live trace as a replay fixture. |
| `browser:dev` | Start the standalone live/replay browser devtool. |
| `test:browser` | Exercise static replay and live scenarios, then capture screenshots. |
| `verify` | Run the complete Phase 0, Phase 1, and Phase 2 acceptance sequence. |
## Navigate
@@ -66,6 +73,8 @@ pnpm --filter @paperclipai/paperclip-runner browser:dev --host 127.0.0.1 --port
- [Tutorial index](docs/index.md)
- [Phase 0 hand-run tutorial](docs/tutorials/phase-00-standalone-tracer.md)
- [Phase 1 hand-run tutorial](docs/tutorials/phase-01-static-replay.md)
- [Phase 2 hand-run tutorial](docs/tutorials/phase-02-local-runner.md)
- [Phase 2 local protocol reference](docs/phase-02-local-protocol.md)
- [PRP compatibility/versioning policy](docs/protocol-compatibility.md)
- [Cumulative end-to-end tutorial](docs/tutorials/end-to-end.md)
- [Journal guide](docs/journal.md)
@@ -73,7 +82,6 @@ pnpm --filter @paperclipai/paperclip-runner browser:dev --host 127.0.0.1 --port
- [Implementation plan](spec/paperclip-native-runner-implementation-plan.md)
- [Normative spike specification](spec/paperclip-native-runner-spike-spec.md)
Phase 1 remains a static replay checkpoint, not the `paperclip-runnerd` daemon.
Network transport, process supervision, live browser transport, production
Paperclip integration, and real model harnesses remain behind later phase
checkpoints in the implementation plan.
Phase 2 is local and deterministic. It does not add durable network transport,
reconnect, production Paperclip integration, or a real model harness. Those
capabilities remain behind later checkpoints in the implementation plan.
+40 -2
View File
@@ -49,8 +49,9 @@ TypeScript schema constants/types -> validator -> deterministic reducer
```
The Rust `runner-core` crate establishes the production language/package
boundary and checks the same fixture summaries; it is not yet the Phase 2 runner
daemon.
boundary and checks the same fixture summaries. Phase 2 adds the package-local
`paperclip-runnerd` and `fake-harness` binaries without changing that dependency
direction.
## Language ownership
@@ -131,6 +132,43 @@ Card, and Textarea are source-compatible adaptations of shadcn primitives; all
visual values live in its local `styles.css` token layer. It imports the same
replay module as the CLI and does not create a browser-only protocol model.
## Phase 2 local process boundary
```text
TypeScript mock core
| PRP commands over stdin JSONL
v
paperclip-runnerd (Rust supervisor)
| fake-harness commands over stdin JSONL
v
fake-harness (Rust scripted driver)
| typed messages over stdout JSONL
v
paperclip-runnerd -> canonical PRP events -> mock core
|
v
browser NDJSON stream
```
The mock core starts one runner process. The runner creates a new process group
for one fake harness and its workers. The runner clears the inherited
environment and restores only the path needed to launch local executables. It
captures stderr and scripted log messages in a bounded tail.
The controller and harness links use newline-delimited JSON over stdio. This is
the smallest local transport that keeps process ownership clear. The browser
does not connect to the runner. A package-local Vite middleware exposes an HTTP
start/action API and an NDJSON event stream from the TypeScript mock core.
The runner publishes the structured semantic result before it publishes the
harness process exit fact. It then emits one `run.terminal` event. A non-zero
harness exit can coexist with a valid yielded result. Duplicate commands and
duplicate terminal messages cannot repeat side effects or close the run twice.
Every browser event passes `validatePrpEvent` and `applyPrpEvent`. When the run
ends, the browser reduces the complete event list again and compares the replay
snapshot with the live snapshot.
## Future integration rule
The [implementation plan](../spec/paperclip-native-runner-implementation-plan.md)
+5 -3
View File
@@ -3,19 +3,21 @@
The tutorials are cumulative and are always run from the repository root.
Each tutorial starts with an explanation of what the phase is and what the phase proves.
## Accepted implementation phases
## Implemented phases
- [Phase 0: standalone tracer](tutorials/phase-00-standalone-tracer.md) — install the workspace, validate its Rust/TypeScript boundary and knowledge bundle, then run the deterministic Rust mock-core path and cross-language parity check.
- [Phase 1: static PRP replay](tutorials/phase-01-static-replay.md) — validate the shared schema/fixture corpus, reduce a fixture in the CLI, and inspect the same final snapshot in the standalone browser page.
- [Phase 2: local runner and fake harness](tutorials/phase-02-local-runner.md) — run a supervised Rust process, exercise scripted live scenarios, resolve requests, interrupt a turn, and confirm live/replay parity.
- [Cumulative end-to-end tutorial](tutorials/end-to-end.md) — the shortest complete workflow available at the current phase.
## Reference
- [Architecture and dependency boundary](architecture.md)
- [PRP compatibility and versioning policy](protocol-compatibility.md)
- [Phase 2 local protocol and supervision](phase-02-local-protocol.md)
- [Engineering journal guide](journal.md)
- [Dated shadcn/ui and AI Elements compatibility note](research/2026-08-07-ui-library-compatibility.md)
- [Package README](../README.md)
Phase 2 is authorized and in progress. Add its tutorial when its runnable tracer is ready.
Later tutorials are added only after their phase is authorized and implemented.
Phase 2 is implemented and ready for its human checkpoint. Later tutorials are
added only after their phase is authorized and implemented.
@@ -0,0 +1,103 @@
# Phase 2 Local Protocol and Supervision Reference
## Scope
Phase 2 proves one native local session. It uses no Paperclip service, database,
network provider, or model. The TypeScript mock core, Rust runner, and Rust fake
harness are all inside `packages/paperclip-runner/`.
## Processes and transports
| Boundary | Transport | Input | Output |
|---|---|---|---|
| Mock core → runner | stdin JSONL | `paperclip.prp.command.v1` | — |
| Runner → mock core | stdout JSONL | — | `paperclip.runner.stream.v1` |
| Runner → fake harness | stdin JSONL | `paperclip.fake_harness.command.v1` | — |
| Fake harness → runner | stdout JSONL | — | `paperclip.fake_harness.message.v1` |
| Mock core → browser | local HTTP + NDJSON | start/actions | validated events and final trace |
Stderr is diagnostic input. It never becomes a command or canonical event.
## Deterministic lifecycle
1. `run.prepare` creates the harness process group.
2. The harness sends `ready`.
3. `session.open` starts one fake driver session.
4. `turn.start` starts one scripted turn.
5. The harness emits typed events, requests, logs, a semantic result, and a
terminal proposal.
6. The runner waits for the harness process and records `harness.exited`.
7. The runner emits one `run.terminal` event and exits zero.
The runner gives each canonical event a deterministic source ID, source
sequence, and timestamp. Script delay can be overridden for fast tests.
## Command rules
- `controllerSeq` starts at 1 and is contiguous.
- The runner stores the canonical JSON for each accepted `commandId`.
- The same ID and same content returns `duplicate` without repeating an effect.
- The same ID with different content is rejected.
- A sequence gap is rejected.
- Phase 2 supports prepare, open, turn start, request resolution, interruption,
stop, close, and cancel command shapes. A script consumes only the commands it
expects.
## Fake-driver scripts
Scripts live under `protocol/fixtures/phase-02/scripts/`.
| Script | Proof |
|---|---|
| `happy-path` | Lifecycle, tool/command item, file item, logs, result, and success terminal |
| `permission-input` | Permission request and free-text input round-trip |
| `interrupted` | Operator interruption, yielded result, exit 130, and cancelled terminal |
| `error` | Non-zero process exit stays separate from the yielded semantic result |
| `duplicate-terminal` | A second terminal proposal is ignored and diagnosed |
| `linger` | Supervisor terminates the harness and its worker process group |
## Cleanup and credential boundary
The runner starts the harness in a new process group on Unix. Normal exit waits
for the process. Controller EOF or explicit cleanup sends `TERM`, waits for the
configured grace time, and then sends `KILL` to the full group. Drop cleanup is
a final guard.
The mock core passes only a small process environment. The runner also clears
the inherited environment before it starts the fake harness. Paperclip keys,
model keys, and AgentMail keys are not forwarded.
## Bounded diagnostics
The runner keeps only the configured number of lines and bytes. It reports the
retained lines, retained bytes, and dropped-line count with `harness.exited`.
This log tail is separate from canonical events and the semantic result.
## Result and terminal authority
These facts are independent:
- `run.result.proposed` is the fake driver's structured work claim;
- `harness.exited` is the operating-system process fact;
- `run.terminal` is the one canonical terminal event for the local trace.
A scripted error can report useful yielded work and exit 7. An interruption can
report yielded work and exit 130. Neither process fact silently replaces the
semantic result.
## Browser live mode
The Vite middleware starts the same TypeScript mock-core controller used by
tests. It streams history plus new events as NDJSON. The browser validates each
event with the Phase 1 schema, checks run/session binding, and applies the Phase
1 reducer. At completion it replays the full event list and compares both final
snapshots.
The browser reuses package-local shadcn-style Button, Badge, Card, and Textarea
components. All visual values remain in `styles.css`.
## Deferred work
Phase 2 has no durable outbox, ACK, reconnect, runner restart recovery, real
harness, production Paperclip bridge, or browser-to-runner connection. Those
items require later authorized phases.
@@ -3,15 +3,16 @@
## Authority
The JSON Schema files in [`protocol/schemas/`](../protocol/schemas/) are the
language-neutral source of truth for the Phase 1 executable contract. The
language-neutral source of truth for the Phase 1 and Phase 2 executable contract. The
generated TypeScript schema module is checked against those files before every
TypeScript typecheck. Rust consumes the same fixtures and must produce the same
golden parity summaries.
The broader normative protocol remains the
[native-runner spike specification](../spec/paperclip-native-runner-spike-spec.md).
Phase 1 intentionally implements only validation and static replay; it does not
add transport, persistence, or live control-plane behavior.
Phase 2 reuses this contract for local live events. It adds package-local stdio
and stream envelopes, but it does not add durable transport, persistence, or
production control-plane behavior.
## Version fields
@@ -59,6 +60,25 @@ The CLI and browser import the same `replayPhase1FixtureText` function, so
validation, compatibility errors, and final snapshots cannot drift between the
two surfaces.
## Phase 2 local envelope rules
- Mock-core commands use `paperclip.prp.command.v1` over stdin JSONL.
- Runner output uses `paperclip.runner.stream.v1` over stdout JSONL.
- Fake-harness commands use `paperclip.fake_harness.command.v1`.
- Fake-harness output uses `paperclip.fake_harness.message.v1`.
- An equivalent repeated `commandId` returns a duplicate receipt and has no
second driver effect. Reuse with different data is rejected.
- A new command must use the next contiguous `controllerSeq`.
- Harness logs are bounded diagnostic data. They are not canonical PRP events.
- `run.result.proposed`, `harness.exited`, and `run.terminal` are separate facts
and appear in that order when a semantic result exists.
- The live browser rejects an event with an invalid schema, run ID, or session
ID before it reaches the reducer.
These envelopes are local Phase 2 implementation contracts. Phase 3 may add a
durable outbound transport, but it must preserve the canonical PRP event and
command semantics.
## Change policy
1. Change JSON Schema first.
@@ -2,13 +2,16 @@
## What this tutorial is
This tutorial combines each accepted Native Runner phase into one procedure. It currently includes Phase 0 and Phase 1.
This tutorial combines each implemented Native Runner phase into one procedure. It currently includes Phase 0, Phase 1, and Phase 2.
## What this tutorial proves
This tutorial proves that the standalone package boundary and static replay path work together. It does not use a live harness or Paperclip.
This tutorial proves that the standalone package boundary, static replay path,
and local live-run path work together. It does not use Paperclip or a real model.
The current system includes the Rust mock-core tracer and the shared protocol fixtures. It also includes CLI replay and browser replay.
The current system includes the Rust mock-core tracer, shared protocol fixtures,
the Rust supervisor, a scripted fake harness, CLI live runs, and browser live and
replay modes.
## Current end-to-end path
@@ -24,6 +27,11 @@ The current system includes the Rust mock-core tracer and the shared protocol fi
8. Exercise the duplicate, gap, unknown-field, and unsupported-version fixtures.
9. Open the [Phase 1 journal entry](../../knowledge/journal/2026-08-07-phase-01.md)
and its linked verification evidence.
10. Follow [Phase 2: Run the Local Runner and Fake Harness](phase-02-local-runner.md).
11. Run the happy, permission/input, interruption, error, and duplicate-terminal scenarios.
12. Open the browser live mode and confirm the completed run says `Match` for live and replay output.
13. Open the [Phase 2 journal entry](../../knowledge/journal/2026-08-07-phase-02.md)
and its linked verification evidence and screenshots.
The one-command form after installation is:
@@ -44,6 +52,14 @@ pnpm --filter @paperclipai/paperclip-runner verify
- source gaps are visible and never synthesized away;
- CLI and browser paths use the same validator/reducer module;
- browser components keep visual values in the package-local token layer.
- the Rust supervisor owns the fake harness process group and cleans it up when the controller closes;
- command IDs are idempotent and controller sequence numbers stay contiguous;
- runtime permission and input requests round-trip over the local protocol;
- process exit is recorded separately from the structured semantic result;
- bounded logs retain only their configured tail;
- exactly one terminal event closes every completed local trace;
- every live browser event passes the Phase 1 validator and reducer before display;
- replaying the completed live event list produces the same final snapshot.
The next phase may extend this tutorial only after its own implementation and
human checkpoint are authorized.
Phase 3 may extend this tutorial only after the Phase 2 human checkpoint is
accepted and the next phase is authorized.
@@ -0,0 +1,129 @@
# Phase 2: Run the Local Runner and Fake Harness
## What this phase is
Phase 2 is a local live-run path. A TypeScript mock core starts a Rust runner.
The Rust runner starts a scripted fake harness. The processes use JSON lines over
stdio.
## What this phase proves
This phase proves that a native session can run from start to finish without
Paperclip, a network provider, or a real model. It proves cleanup, request
resolution, interruption, bounded logs, one terminal result, and live/replay
parity.
## Prerequisites
- Node.js 20 or newer
- pnpm 9 or newer
- a stable Rust toolchain with `cargo`
- a Chromium-compatible browser
Run every command from the repository root on the
`PAP-16679-paperclip-runner` branch. Install the package dependencies and browser:
```sh
pnpm install --filter @paperclipai/paperclip-runner --lockfile=false --ignore-scripts --dev
pnpm --filter @paperclipai/paperclip-runner exec playwright install chromium
```
On a minimal Linux host, install the browser libraries too:
```sh
pnpm --filter @paperclipai/paperclip-runner exec playwright install-deps chromium
```
## 1. Run the package verification path
```sh
pnpm --filter @paperclipai/paperclip-runner verify
```
Expected: Rust, TypeScript, protocol, boundary, documentation, and browser
checks pass. The final Phase 2 line reports one terminal event and a successful
semantic result.
## 2. Run the happy path in the CLI
```sh
pnpm --filter @paperclipai/paperclip-runner trace:phase2 -- --scenario happy-path --quiet
```
Expected summary facts:
- `eventCount` is `18`;
- `terminalCount` is `1`;
- `semanticResult` is `done`;
- harness and runner exit codes are `0`.
Capture the live trace as a validated replay fixture:
```sh
pnpm --filter @paperclipai/paperclip-runner record:phase2
```
The command writes
`packages/paperclip-runner/knowledge/evidence/phase-02-happy-path.json`.
## 3. Run the scripted control flows
```sh
pnpm --filter @paperclipai/paperclip-runner trace:phase2 -- --scenario permission-input --quiet
pnpm --filter @paperclipai/paperclip-runner trace:phase2 -- --scenario interrupted --quiet
pnpm --filter @paperclipai/paperclip-runner trace:phase2 -- --scenario error --quiet
pnpm --filter @paperclipai/paperclip-runner trace:phase2 -- --scenario duplicate-terminal --quiet
pnpm --filter @paperclipai/paperclip-runner trace:phase2 -- --scenario happy-path --duplicate-turn-command --quiet
```
Expected:
- permission and input finish with `done`;
- interruption exits the harness with `130` and reports `yielded`;
- scripted error exits the harness with `7` and reports `yielded`;
- duplicate terminal still reports `terminalCount: 1`;
- duplicate turn command does not start a second turn.
## 4. Use the live browser
Start the package-local server:
```sh
pnpm --filter @paperclipai/paperclip-runner browser:dev --host 127.0.0.1 --port 4179
```
Open `http://127.0.0.1:4179`, then follow these steps:
1. Keep `Happy path` selected and choose **Start local run**.
2. Confirm the terminal badge says `succeeded`.
3. Confirm `Harness process exit` is `0` and `Semantic result` is `done`.
4. Confirm `Live and replay reducer output` says `Match`.
5. Select `Permission and input`, start the run, and choose **Allow**.
6. Enter a trace name and choose **Send input**. Confirm the run completes.
7. Select `Interruption`, start the run, and choose **Interrupt turn** when the
button becomes active.
8. Confirm the terminal badge says `cancelled`, the semantic result says
`yielded`, and the timeline contains one `run.terminal` event.
9. Switch to **Static replay** and confirm the Phase 1 fixture path still works.
Stop Vite with `Ctrl+C`.
## 5. Inspect the evidence
- [Complete live run](../../knowledge/evidence/phase-02-live-complete.png)
- [Permission request](../../knowledge/evidence/phase-02-live-permission.png)
- [Interrupted run](../../knowledge/evidence/phase-02-live-interrupted.png)
- [Exact verification record](../../knowledge/evidence/2026-08-07-phase-02-verification.md)
- [Engineering journal](../../knowledge/journal/2026-08-07-phase-02.md)
## What this proves
- the Rust runner owns and cleans the fake-harness process group;
- the fake driver emits deterministic live events and requests;
- logs are bounded;
- process exit and semantic result stay separate;
- duplicate commands and terminal proposals do not repeat effects;
- every browser event passes the Phase 1 validator and reducer;
- replay after completion produces the same final snapshot.
Phase 3 must not begin until the Phase 2 human checkpoint is accepted.
@@ -115,8 +115,8 @@ parity, the Rust Phase 0 tracer, and the Phase 1 CLI replay all passed.
[Phase 1 static replay screenshot](phase-01-static-replay.png)
- PNG: 1440 × 1137, RGB
- SHA-256: `eecdc71e35c76764916317239c2fd3dd1e2f60ab609ee48d12c2a1b6b67897ce`
- PNG: 1440 × 1201, RGB (refreshed by the Phase 2 browser suite)
- SHA-256: `9e77087e6bc16d3f8b7e730bde70eb2da11e359b71e24cd8ad7402a28f6680d0`
- Shows editable happy-path fixture input, validated terminal snapshot, result
summary, ordered nine-event timeline, and snapshot JSON disclosure.
@@ -0,0 +1,154 @@
---
type: Verification Evidence
title: Phase 2 verification
description: Exact tools, commands, results, and screenshots for the local runner and fake harness.
tags: [native-runner, phase-2, verification, rust, supervisor, fake-driver, browser]
status: stable
generated: { by: openai/gpt-5, at: 2026-08-07T21:10:00Z }
phase: "2"
---
# Environment
- OS: `Linux 6.17.0-1019-aws aarch64`
- Node.js: `v22.22.2`
- pnpm: `9.15.4`
- Vitest: `4.1.10`
- Vite: `6.4.1`
- Playwright: `1.61.1`
- Rust/Cargo: `1.97.1` in the run scratch directory
# Commands and observed results
## 1. Rust formatting, tests, and process cleanup
```sh
cargo fmt --manifest-path packages/paperclip-runner/runner/Cargo.toml --all -- --check
cargo test --manifest-path packages/paperclip-runner/runner/Cargo.toml --locked --workspace
```
Result: exit 0. Rust formatting passed. Fifteen library tests and one supervisor
integration test passed. The cleanup test proved that the harness and spawned
worker process group both stop.
## 2. TypeScript and live-run conformance
```sh
pnpm --filter @paperclipai/paperclip-runner test:typescript
pnpm --filter @paperclipai/paperclip-runner typecheck:typescript
pnpm --filter @paperclipai/paperclip-runner typecheck:browser
```
Result: exit 0. Seven Vitest files and 38 tests passed. The Phase 2 cases cover
driver lifecycle, command and file items, bounded logs, duplicate commands,
permission/input, interruption, error exit versus semantic result, duplicate
terminal suppression, controller-disconnect cleanup, credentials, and
live/replay parity.
## 3. CLI scenario matrix
```sh
for scenario in happy-path permission-input interrupted error duplicate-terminal; do
node packages/paperclip-runner/dist/cli/phase2-mock-core.js --scenario "$scenario" --quiet
done
node packages/paperclip-runner/dist/cli/phase2-mock-core.js \
--scenario happy-path --quiet --duplicate-turn-command
```
Observed facts:
| Scenario | Events | Terminal | Semantic result | Harness exit | Runner exit |
|---|---:|---:|---|---:|---:|
| happy path | 18 | 1 | `done` | 0 | 0 |
| permission/input | 19 | 1 | `done` | 0 | 0 |
| interrupted | 15 | 1 | `yielded` | 130 | 0 |
| error | 14 | 1 | `yielded` | 7 | 0 |
| duplicate terminal | 14 | 1 | `done` | 0 | 0 |
The duplicate turn command returned one duplicate receipt and did not create a
second `turn.started` event.
## 4. Browser build, interactions, and screenshots
```sh
pnpm --filter @paperclipai/paperclip-runner build:browser
pnpm --filter @paperclipai/paperclip-runner check:browser-tokens
pnpm --filter @paperclipai/paperclip-runner test:browser
```
Result: exit 0. Vite transformed 219 modules. The token check passed. Five
Playwright tests passed in 3.5 seconds. They cover Phase 1 static replay,
duplicate/version states, live completion and replay parity, permission/input,
and interruption with one terminal event.
The minimal host lacked Chromium libraries. The test used Ubuntu packages
extracted under `$PAPERCLIP_RUN_SCRATCH_DIR/playwright-libs` through
`LD_LIBRARY_PATH`; no system library was installed.
## 5. Boundary and knowledge checks
```sh
pnpm --filter @paperclipai/paperclip-runner check:forbidden-imports
pnpm --filter @paperclipai/paperclip-runner docs:validate
```
Result: exit 0. The standalone boundary passed. Documentation links and the OKF
v0.2 bundle passed validation.
## 6. Complete package acceptance sequence
```sh
pnpm --filter @paperclipai/paperclip-runner verify
```
Result: exit 0 with the run-scratch Rust and browser-library paths active. The
command built Rust, TypeScript, and browser outputs; ran all typechecks, tests,
goldens, parity, token, boundary, browser, docs, and OKF checks; ran the Phase 0
tracer and Phase 1 replay; and finished with the Phase 2 happy-path summary.
# Browser artifacts
## Completed live run
[Phase 2 completed run screenshot](phase-02-live-complete.png)
- PNG: 1440 × 1786, RGB
- SHA-256: `6c49da63df4de4e2d7e5958bd66cdefa492819474526c345328dcd2b575b887a`
- Shows 18 validated events, process exit 0, semantic result `done`, one terminal
event, and live/replay `Match`.
## Permission request
[Phase 2 permission screenshot](phase-02-live-permission.png)
- PNG: 1440 × 1131, RGB
- SHA-256: `192c0b244694d22cfe880e814dade1714ae48ab18a2047aaa2c7714419a0328b`
- Shows the live pending permission request before resolution.
## Interrupted run
[Phase 2 interrupted run screenshot](phase-02-live-interrupted.png)
- PNG: 1440 × 1591, RGB
- SHA-256: `11003e92e776afe670a4a2c958770b4f844ff373115ff93c3aee1e54238b2bd4`
- Shows exit 130, semantic result `yielded`, cancelled terminal state, one
terminal event, and live/replay `Match`.
# Acceptance mapping
- Deterministic Rust runner and supervisor: passed.
- Small local harness transport: stdio JSONL passed.
- Timing, error, permission, input, interruption, and terminal scripts: passed.
- Mock-core command/event loop through package contracts: passed.
- Bounded logs and separate process/semantic facts: passed.
- Driver conformance and controller/harness cleanup: passed.
- Duplicate commands and exactly one terminal event: passed.
- Browser live mode with Phase 1 validator/reducer: passed.
- Live/replay parity: passed.
- Package reference, tutorial, cumulative steps, evidence, OKF journal, and
screenshot set: linked and validated.
# Remaining risk
The local process and in-memory browser path do not prove Phase 3 durable
delivery or reconnect behavior. Production Paperclip behavior remains unchanged.
@@ -5,3 +5,4 @@
* [Phase 0 verification](2026-08-07-phase-00-verification.md) - Clean install, build, tests, boundary, documentation, OKF, and tracer results.
* [Phase 0 Rust correction verification](2026-08-07-phase-00-rust-correction-verification.md) - Rust build/tests, cross-language parity, and corrected boundary evidence.
* [Phase 1 verification](2026-08-07-phase-01-verification.md) - Schema, golden, reducer, Rust parity, CLI, browser, token, boundary, and screenshot evidence.
* [Phase 2 verification](2026-08-07-phase-02-verification.md) - Rust supervisor, fake-driver scenarios, cleanup, live/replay parity, browser flows, screenshots, and complete package verification.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

@@ -0,0 +1,81 @@
---
type: Engineering Journal Entry
title: Phase 2 local runner and fake harness
description: Decisions and evidence for deterministic Rust supervision, scripted live sessions, and browser parity.
tags: [native-runner, phase-2, rust, supervisor, fake-driver, live-replay, browser]
status: stable
generated: { by: openai/gpt-5, at: 2026-08-07T21:10:00Z }
entry_kind: phase
phase: "2"
---
# Context
Phase 2 must prove one full native session without Paperclip core, a network
provider, or a real model. It extends the accepted Phase 1 validator and reducer
instead of creating a second live-state contract.
# Decisions
1. Use JSONL over stdio for mock-core-to-runner and runner-to-harness traffic.
It is the smallest local transport and makes process ownership explicit.
2. Keep the production-direction supervisor and fake driver in Rust. Keep the
mock core and browser client in TypeScript behind package contracts.
3. Start the fake harness in a new process group. On controller EOF, termination,
or drop, stop the whole group with a bounded TERM-to-KILL grace period.
4. Clear inherited process environments. Restore only the executable path.
Never forward Paperclip, model-provider, or AgentMail credentials.
5. Treat command IDs as idempotency keys and require contiguous controller
sequence numbers before a new effect.
6. Keep log tails bounded by line and byte limits. Logs stay diagnostic and do
not become canonical events.
7. Keep the semantic result, harness process exit, and canonical terminal event
as three facts. Emit exactly one terminal event.
8. Use scripts for happy, request, interruption, error, duplicate-terminal, and
cleanup scenarios. Override delay in tests to keep runs fast.
9. Validate every browser event with the Phase 1 validator and apply the Phase 1
reducer. Replay the final event list and compare snapshots at completion.
10. Reuse the package's shadcn-style primitives and local token layer. Keep the
live surface dense, left-flowing, and free of duplicate state labels.
# Evidence
Exact tool versions, commands, results, CLI summaries, screenshots, and
acceptance mapping live in
[Phase 2 verification evidence](../evidence/2026-08-07-phase-02-verification.md).
The hand-run path is the
[Phase 2 tutorial](../../docs/tutorials/phase-02-local-runner.md).
# Failures and corrections
- The current run started without Cargo on `PATH`. The official stable toolchain
was installed only under the run scratch directory.
- Chromium first failed because eight Linux libraries were absent. The system
install path required a sudo password, so the required Ubuntu packages were
downloaded and extracted into run scratch. Playwright then passed without a
system change.
- The first browser attempt could not bind its loopback test port inside the
sandbox. The same package-local test was rerun with the approved local bind.
- Review found that a TypeScript controller error killed only the runner process
and could leave the harness group alive. The controller now closes stdin; the
Rust runner treats EOF as cleanup, terminates the group, records exit, and
emits one terminal event. A regression test covers this path.
# Known gaps
- No durable outbox, ACK, reconnect, or process restart recovery.
- No real harness or model session.
- No production Paperclip bridge, database, budget, approval, or status change.
- One runner owns one harness session and one scripted turn.
- The Vite middleware and in-memory live-run registry are development tools, not
a production server.
# Follow-up questions
- Which event and command fields must enter the Phase 3 durable outbox before a
reconnect test is meaningful?
- Should Phase 3 persist command receipts separately from canonical events or in
one ordered delivery ledger?
- What bounded replay window should the browser request after reconnect?
Phase 3 remains deferred until the Phase 2 human checkpoint is accepted.
@@ -5,3 +5,4 @@
* [Phase 0 boundary, journal, and runnable skeleton](2026-08-07-phase-00.md) - Decisions, implementation evidence, failures, gaps, and next questions for the standalone skeleton.
* [Phase 0 Rust boundary correction](2026-08-07-phase-00-rust-boundary-correction.md) - Root cause and corrected Rust/TypeScript ownership after the human checkpoint.
* [Phase 1 protocol, fixtures, reducer, and static replay](2026-08-07-phase-01.md) - Executable PRP schemas, deterministic replay decisions, browser findings, and checkpoint gaps.
* [Phase 2 local runner and fake harness](2026-08-07-phase-02.md) - Rust supervision, stdio protocol, scripted driver, live browser, cleanup, and parity decisions.
@@ -5,3 +5,4 @@
* **Creation**: Established the OKF v0.2 bundle, typed Phase 0 journal, verification evidence, template, and progressive-disclosure indexes.
* **Rust boundary correction**: Added the production-direction Cargo workspace, shared-fixture parity evidence, negative Cargo boundary coverage, and a superseding journal entry after the Phase 0 checkpoint exposed the TypeScript-only omission.
* **Phase 1 static replay**: Added executable PRP v1 schemas, cross-language fixture/golden parity, deterministic reducer, CLI/browser replay, compatibility policy, tutorial, screenshot, and verification evidence.
* **Phase 2 local runner**: Added the deterministic Rust supervisor, stdio fake-harness protocol, scripted scenarios, bounded logs, cleanup and terminal guards, TypeScript mock core, validated live browser mode, screenshots, tutorial, and verification evidence.