10 KiB
Capability Clean-Start Tutorial: The Paperclip-Style Issue Thread Over a Mock Control Plane
Time to first success: about 5 minutes. Two commands take you from a clean
checkout to 106 passing conformance cases and a Paperclip-style issue thread you
can click through in a browser. The whole tutorial runs from the repository
root. It starts no Paperclip service, contacts no Paperclip control plane, clones
no external eval repository, and holds no provider credential. Everything the
default path needs is checked in under packages/paperclip-runner/.
This is the canonical Capability tutorial for the final build. It covers both agent modes: scripted (deterministic) fake mode, which is fully offline, and Codex (bounded) live mode, which is optional and needs a locally authenticated Codex. The two modes share one mock control plane, one semantic tool catalog, one authorization engine, and one view contract. See execution modes and identity for the rules that separate them, and the future binding boundary for why none of this touches real Paperclip — that is future upload integration and needs separate approval.
For a deeper tour of the read-only scenario explorer and the capability contract, see the companion scenario-explorer tutorial; this page focuses on the issue-thread surface and the live loop.
What you need
- Node.js 20 or newer and pnpm 9 or newer. This tutorial was verified with Node 22.22.2 and pnpm 9.15.4.
- No Rust toolchain for the fake-mode steps (steps 1–4). The optional live steps
(step 5) build the
paperclip-runnerdbinary and need a Rust toolchain and a locally authenticated Codex. - No network access after
pnpm install.
Install the package workspace from the repository root:
NODE_ENV=development pnpm install --filter @paperclipai/paperclip-runner --frozen-lockfile --offline --ignore-scripts
This keeps the checked-in lockfile authoritative while using only packages
already present in the pnpm store, so the offline install does not re-resolve
dependencies to registry-latest versions. Setting NODE_ENV=development
ensures the complete test toolchain is installed even when the checkout
inherits NODE_ENV=production.
1. Prove the 106-case conformance suite (about 1 minute)
pnpm --filter @paperclipai/paperclip-runner test:capability-evals
This drives all 106 eval-derived cases across the 16 groups entirely in-process against the mock control plane. Expected final line:
Test Files 1 passed (1)
Tests 1 passed (1)
For the per-group counts, assertion classes, the 18-operation fake-agent matrix, and the 16-case bounded Codex binding sample, generate the parity report:
pnpm --filter @paperclipai/paperclip-runner report:capability-evals
Expected final line:
Capability eval conformance passed: 106 cases across 16 groups.
The report is written to .paperclip-local/evidence/capability/eval-parity-report.{json,md}.
It is generated on demand and is not committed; delete it before running
docs:validate, or run validation first.
2. Run the issue-thread contract and UI tests (about 1 minute)
pnpm --filter @paperclipai/paperclip-runner test:scenarios
Expected final lines:
Test Files 11 passed (11)
Tests 137 passed (137)
This covers the mock ControlPlanePort, the semantic tools with their
authorization and redaction rules, and the CapabilityIssueThreadSnapshot
view-model contract — including that every one of the twelve deterministic
screenshot states builds a schema-valid snapshot with no credential anywhere in
the projected view.
3. Open the issue thread in fake mode (about 2 minutes)
pnpm --filter @paperclipai/paperclip-runner console:issue-thread
Open http://127.0.0.1:4184/#/issue/hb-baseline?shot=thread-baseline.
You are looking at a Paperclip issue thread, not a dashboard. The header carries
three identity chips that stay visible in every mode — Fake agent,
In-process runner, and Mock Paperclip — plus the mock issue identifier
(reserved MCK- prefix), status, priority, and the Scenario / Replay / Reset /
Stop controls. The main column is one readable work thread; the Evidence panel is
collapsed on the right.
Swap the shot value to seed any of the twelve contract states — each one is a
different item type or interaction state from the
issue-thread UX contract:
shot= |
What it shows |
|---|---|
thread-baseline |
User message, model prose, a durable Recorded to mock thread comment, and a collapsed tool strip. |
turn-streaming |
A streaming turn with the composer in its streaming state (input editable, Stop primary). |
interaction-question-pending |
An inline ask_user_questions card that refuses an incomplete submit. |
interaction-confirmation-pending |
A revision-bound confirmation that requires a reject reason. |
interaction-resolved-mixed |
Accepted, rejected, expired, and stale interactions as durable history. |
denial-optional-tool |
A verbatim typed denial with its Evidence badge and no protected data. |
document-revision |
A document revision card bound to the mock document. |
deliverable-registered |
A registered deliverable reporting one size in one unit. |
disposition-terminal |
A terminal disposition that disables the composer. |
debug-panel-open |
The eight-section Evidence panel opened on the authorization records. |
reconnect-banner |
The reconnect banner and its Reconnected confirmation. |
replay-mode |
The replay progress strip with Step back, Next turn, and Play all. |
Open the Evidence panel (or the Evidence segment on a phone). Its eight
sections stay in a fixed order: Tools exposed, Calls & results, Authorization,
Control plane, Runner & events, State diff, Traceability, Parity. The Tools
section separates Agent tool — always, Agent tool — granted (with its
grant), and a Control plane (not exposed to the agent) list — what the model
cannot call is first-class evidence. Every strip, denial, and card deep-links
into its record, and each record links back to its thread anchor.
Resize the browser below 768px: the page collapses to a Thread / Evidence
segmented control, keeps Stop outside the ⋯ menu while a turn is active, and
never scrolls horizontally.
4. Record the deterministic screenshot matrix (about 1 minute)
# Recorded evidence generation is deferred from this release.
pnpm --filter @paperclipai/paperclip-runner check:capability:ui
The first writes 24 images — the twelve slugs at 1440×900 and 390×844 — to
.paperclip-local/evidence/capability/ui/. Every mobile capture asserts no horizontal
scroll before the shot. The second re-records and confirms all 24 reproduce
byte-for-byte, which is the property that makes the fake-mode matrix a stable
acceptance artifact. Both modes render fixture time only, so a fresh checkout
reproduces the committed images exactly.
5. Optional: drive a real Codex turn (live mode)
Live mode needs a Rust toolchain and a locally authenticated Codex. It starts a
real paperclip-runnerd process and a real Codex app-server session; the
package server owns the process, the tool loop, and the provider credential, and
the browser receives none of them. Skip this section if you do not have Codex
installed — nothing above depends on it.
Headless smoke over the package server:
pnpm --filter @paperclipai/paperclip-runner smoke:capability:ui
This creates a session, runs live turns, and asserts the identity reads
Real Codex / Real runnerd / Mock Paperclip, that a real tool call was
recorded with its authorization record, that the control-plane-owned list is
withheld from the agent, and that no credential appears in the view.
Capture the live browser frames (a real browser driving a real Codex turn):
# Recorded evidence generation is deferred from this release.
The frames land in .paperclip-local/evidence/capability/ui-live/. They are intentionally
not byte-stable — a real provider turn varies — so they are excluded from the
determinism gate and are the mode that satisfies the final live acceptance
criteria. A scripted (mode=fake) frame cannot.
Prove the process and network boundary directly:
pnpm --filter @paperclipai/paperclip-runner trace:live-runner -- --json
This checks a real semantic-tool mutation, a typed result, a same-thread second
turn, process ownership and cleanup (runnerExited, authorityCleared), and —
critically — noRealPaperclipRequest and noPaperclipAuthorityInChild.
Reset, stop, replay, and cleanup
These controls work the same in either mode because they act on the mock core and the runner session, not on the agent:
- Reset asks first, then restores the original clean mock seed under new run/session authority and rotates or clears the old authority. It does not touch another browser session.
- Stop cancels only the active turn and reaps the runnerd process group; the
session and transcript survive with a
Stopped by usermarker. - Replay reproduces a recorded canonical timeline. It is always scripted and always labelled fake-derived.
- Refresh / reconnect restores the same session, pending interaction, transcript, and mock state; reconnect starts a fresh runnerd and Codex and resumes the persisted thread without restarting the conversation.
What this does not do
- No ACPX implementation and no real control-plane integration. Real Paperclip binding is future upload integration and requires separate approval.
- No provider, runner, or control-plane credential in the browser, ever.
- Nothing you type is persisted beyond the mock session. A reload drops an unsent draft — the accepted trade against storing conversation content.
References: issue-thread UI, live runnerd and Codex loop, execution modes and identity.