Files
PaperClipAI/packages/paperclip-runner/docs/protocol-compatibility.md
T
DottaandPaperclip 06d4fe3121 perf(runner): preload essential Claude task tools (#16885)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Runner exposes assigned tools to Claude through an authenticated
MCP bridge.
> - Claude can defer these tools and search for them before a simple
task can finish.
> - Title and terminal tools are common requirements for a task turn.
> - This pull request marks those three tools for immediate loading by
Claude.
> - The remaining tools keep the SDK deferral policy and the existing
access checks.

## Linked Issues or Issue Description

Refs #15993, #15994, #15997.

**What existing behavior does this improve?**
Claude Runner tool discovery during task execution.

**Current behavior**
Claude can use ToolSearch to discover the mandatory title and terminal
tools before it answers a simple task.

**Proposed behavior**
Expose `set_task_title`, `paperclip_finish`, and `paperclip_block` in
the first Claude request through per-tool `anthropic/alwaysLoad`
metadata. Keep other tools deferred under the SDK policy.

**Reason and benefit**
Remove an avoidable tool discovery step without changing the model,
effort, or authorization. Repeated product measurements will check the
latency benefit.

## What Changed

- Add closed metadata for the three essential tools only in Claude
runtime hosts.
- Keep incoming metadata discarded and private tools hidden. Keep other
providers unchanged.
- Test real bridge catalogs and provider routing. Check the pinned
Claude SDK request against a loopback fixture with no paid calls.
- Advance the pending Copilot source binding because it attests the
shared bridge file. Preserve historical profile readers and
qualification limits.

## Verification

- Focused bridge, admission, profile, and effort controls: 185 passed,
one skipped. Three provider-routing cases pass.
- Runner TypeScript compilation passes.
- The production SDK fixture loads exactly three essential tools with
the change and zero in the controlled baseline. The document tool stays
deferred.
- Six real API-key tasks used Opus 5.5 / High in alternating order A B B
A A B. Median first assistant receipt was 22.021 s baseline vs 17.060 s
candidate (22.5% lower). Median total was 24.119 s vs 19.150 s (20.6%
lower). ToolSearch was 3/3 baseline vs 0/3 candidate. All six tasks
saved a title and accepted finish. A separate candidate follow-up
resumed the same provider session and finished without ToolSearch.
- Median startup was 4.786 s baseline vs 5.381 s candidate. This change
does not improve startup. Three samples per version give directional
evidence only.
- Estimated cost for the six tasks was $0.850900800. Candidate total was
$0.450341800 vs baseline $0.400559000; the first candidate wrote a new
cache prefix. These rate-card estimates are not invoices.
- The benchmark used source `5aed3c6a083cf8e6246815455abde3b29ecbbbac`.
Rebuilding both bundles at master-reconciled head
`d2da07259048c0a2bebd210c35cc0ebb3ff42c67` produced identical SHA-256
values. Baseline bundle SHA-256:
`10a92c373bba4fb630bf4fefeab953d850a77cf8258bc2e94711410a73a78ecd`.
Candidate:
`40348fc67a3b1ae5cc1f27f933f369981d31bcdf5d1bdc7b1797968ddfe53938`.
Baseline differs only by compiling the Claude preload selector as false.
Fresh task sessions avoid checkpoint changes.
- Full local runtime-host runs exposed credential lease collisions in
existing cases. A later complete host run passed 67 cases and timed out
in an existing admission-abort case; that case passes in isolation. All
failures are retained. Focused controls pass. Complete Runner suites,
repository typecheck, tests, browser checks, and build pass in cloud CI
on this head.

## Risks

- These three tool schemas add input to Claude requests. Measurements
must include token usage and cost.
- Copilot source identity changes because it includes the bridge source
hash. It remains pending qualification. Its catalog and executable
closure do not change.
- The test fixture proves request shape. It does not prove real-provider
latency or platform package qualification.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, tool use, and code
execution. The exact serving model ID and context window are not exposed
in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass (focused controls and
isolated cancellation check; broad local failures are retained above)
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-19 14:29:22 -05:00

19 KiB
Raw Blame History

PRP Compatibility and Versioning Policy

Authority

The JSON Schema files in protocol/schemas/ are the language-neutral source of truth for the Replay and Local runner 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 reviewable architecture and trust boundaries are defined in architecture.md; durable transport and recovery behavior is defined in durable-recovery.md. Local runner reuses the protocol 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

Field Replay support Compatibility rule
protocolVersion 1 Required. Negotiate the highest overlapping version; no overlap fails closed.
fixtureVersion 1 Required by the conformance corpus. Unknown values fail closed.
event.schemaVersion 1 Required on every event. Unknown values fail closed before reduction.
capabilities.semanticTools.schemaVersion 1 Optional advertisement. When present, an unknown required version fails closed.
payload.semantic_tool.schemaVersion 1 Optional on paired semantic tool input/result events. When present, an unknown required version fails closed.
terminal.stopReason.schemaVersion 1 Optional budget/cost receipt. When present, an unknown required version fails closed.
Typed schema discriminators *.v1 Required. Unknown required schema identities fail JSON Schema validation.

Wire protocol versions and fixture-corpus versions are independent. A fixture format can evolve without changing PRP, and a future PRP version can be represented only after the consumer advertises support for it.

Forward compatibility

  • Unknown object properties are accepted and preserved by validation. Reducers ignore fields they do not understand until a later schema version gives those fields defined behavior.
  • Unknown required versions, schema discriminators, enum values, and required fields fail closed. A consumer must never guess at their semantics.
  • Scripted fixtures bind every event to the fixture run/session, require contiguous controller command order, exactly one unique proposed result, and exactly one unique terminal event.
  • The top-level fixture result must equal the run.result.proposed payload after canonical key ordering. Repeated sourceEventId deliveries must be byte-equivalent after the same normalization.

The forward-compatibility fixture proves that optional fields survive validation without changing the v1 snapshot. The unsupported-version fixture proves that a required v2 protocol cannot be replayed by this consumer.

Within-turn checklist snapshots

plan.updated / paperclip.plan.updated.v1 is a complete, ordered snapshot of the provider's checklist for one active turn. It is not a Paperclip Plan document and must never be inferred from assistant prose, Codex proposed-plan items, or generic TodoWrite output. Every replacement uses the provider turn ID as planId; PRP sourceSeq, not an optional provider revision, determines snapshot order. An empty step array clears the checklist, and complete is true only when a non-empty snapshot contains only completed steps. The legacy document-coupling fields are always syncStatus: "not_applicable" and documentRevision: null.

Qualified adapter profile Checklist support
Direct Codex App Server turn/plan/updated
ACPX Codex Structured ACP plan entries
ACPX Claude Structured ACP plan entries
ACPX Pi Unavailable; no production-qualified profile is exposed
OpenCode Unsupported until it exposes a structured plan event

Codex turn/diff/updated is normalized separately as the latest same-turn workspace.change.updated snapshot. Together these two independent event families can drive a turn-status UI without changing the public PRP family or adding a control-plane endpoint.

Provider-neutral semantic receipts

  • capabilities.semanticTools advertises stable operation IDs, availability, required claims, and redaction disposition without naming a provider API.
  • mcp_app.tool_input and mcp_app.tool_result may carry paired semantic_tool envelopes. Correlation IDs must match the containing event; operation ID and idempotency key must match across the pair.
  • Content is represented by a canonical SHA-256 digest plus allowlisted typed references. Raw credentials, provider payloads, and hidden identifiers do not belong on the wire.
  • Result receipts distinguish success, denial, conflict, exact duplicate, unavailable, and failure. They can name the authorization boundary, safe revision, artifact/work-product refs, immutable governed targets, and bounded wake/monitor causality.
  • terminal.stopReason records budget/cost kind, stable code, retryability, limit class, safe aggregate, and decision receipt.

These fields are trace evidence only. The v1 reducer ignores semantic_tool payloads, so adding or extending the optional envelope has no projection effect. Eval trace_completeness treats PRP wire receipts as authoritative when present and retains the pre-existing scalar fallback for live evidence that has not yet emitted them.

Provider-neutral structured input

Harness-initiated forms cross PRP as paperclip.runtime_request.v2 with requestKind: "runtime", type: "input", and an embedded paperclip.question_set.v1. A submission is always { "action": "submit", "response": paperclip.question_response.v1 }. Codex answer objects, OpenCode answer arrays, and ACP typed content exist only inside their adapters; origin may retain the provider method and adapter name for diagnostics but never provider response data.

Text-mode questions may include initialText (at most 100,000 Unicode code points, within the existing 196 KiB question-set bound). It supplies an editable starting draft, including empty text and whitespace. ACP plain-string default values map without trimming; numeric defaults become editable numeric text. Select questions do not accept this field. The UI preserves saved drafts and existing responses ahead of provider defaults. Initial text never submits or resolves a request: the operator must explicitly submit, and all existing response validation still applies. Canonical text answers retain submitted whitespace and literal escape sequences; legacy and select custom answers retain their existing normalization. JSON Schema, TypeScript and Rust count draft Unicode code points alike. Existing submitted-answer limits still count UTF-16 code units; a draft can require editing before it satisfies those limits or field-specific constraints. Required blank answers remain invalid. Redaction, company scope and durable replay rules remain in force.

Question and option order is significant, while answers are keyed by stable question IDs and selections reference stable option IDs. The canonical modes are text, single_select, and multi_select. Text validation is repeated at the untrusted server edge and again against the persisted question set before a provider receives the translated response.

Every harness adapter must add a paperclip.question_adapter_fixture.v1 fixture proving its native request normalizes to the canonical shape and its canonical response can be translated back. The shared fixture format deliberately contains both native and canonical objects so adding a provider does not change PRP or the UI contract.

V1 runtime requests and their legacy resolutions remain accepted during the migration. A request that contains no structured form stays on the legacy path; once a provider supplies a form, malformed or unsupported fields fail closed instead of silently degrading. ACPX sidecars advertise only form elicitation and use sidecar protocol v2 runtime.input_requested / input.resolve frames.

The live lifecycle pauses and resumes the same provider turn. If the provider process is lost first, Paperclip emits one non-replayable runtime_request.expired fact and materializes an idempotent durable ask_user_questions interaction using the identical question set. Explicit cancellation and already-resolved requests never create that fallback.

Native execution permission compatibility

paperclip.native-execution-input.v4 pins the effective harness permission policy in the closed provider configuration: approvalPolicy for Codex and permissionMode for OpenCode and ACPX. The pinned value participates in provider-session identity, so an incompatible idle or recovered session is replaced on the next execution. An active turn is never mutated in place.

paperclip.native-execution-input.v5 remains the ordinary input format. Claude inputs with explicitly configured effort use v9 with the optional provider.claudeEffort field. V9 retains the compact v3 model envelope; effort is host configuration, never model-visible task text. V6 remains the separate OpenAI Dot format. Readers retain persisted v1-v8 contracts, including v7 home modes and v8 Muse. Claude effort is acknowledged on each ACP connection before prompting, bound into the session identity and recovery checkpoint, and cross-checked against the public provider descriptor. Missing effort retains the provider default; values are never clamped.

The v5 input format retains the v4 permission pinning and adds optional completion-source references; paperclip.native-model-envelope.v3 is the corresponding explicit model projection. Readers continue to accept persisted v1-v4 inputs, and v5 readers must preserve the historical behavior of inputs that do not carry the new optional fields. Missing Codex and OpenCode policy fields retain their historical effective behavior. Legacy ACPX permissionPolicy: "interactive" is interpreted as approve-reads, while new v4 and v5 ACPX executions default to approve-all at the server boundary.

When a healthy provider session is resumed, its persisted v4 or v5 input format is retained even if the newly built input uses the other format. This avoids rotating an active session for a presentation-only schema change. A safe rollback from v5 to v4 removes only the optional completion-source references; it retains the task, contract, provider, workspace, and permission fields. Format changes still go through the normal provider-session identity checks, and an active turn is never mutated in place.

See Adding a harness for the permission catalog, isolation rules, and provider conformance requirements.

Seven conformance fixtures cover artifact success, redacted denial without fallback, stale conflict plus duplicate retry, governed target and continuation causality, budget/cost stop, unknown optional fields, and rejection of an unknown required version. The six accepted fixtures have shared TypeScript and Rust golden parity summaries.

Replay semantics

  • Events are applied in fixture order and ordered independently by (sourceKind, sourceInstanceId, sourceSeq).
  • A repeated source event ID has no second projection effect.
  • A forward source-sequence gap is recorded explicitly; the reducer never invents a missing event.
  • An event at or behind the committed source cursor is ignored and recorded as out of order.
  • Replaying an already-applied batch leaves the snapshot unchanged.

The CLI and browser import the same replayReplayFixtureText function, so validation, compatibility errors, and final snapshots cannot drift between the two surfaces.

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 Local runner implementation contracts.

Durable wire rules

  • The runner opens loopback ws:// or hostname-verified wss://, or accepts a preview-proxy connection on its fixed listener, and completes the PRP v1 authenticated handshake before any command result or event.
  • A one-use bootstrap bearer capability returns a short-lived connection lease in welcome. Later connections use that lease. Neither raw capability is durable state.
  • welcome.payload.connectionLeaseRenewalVersion: 1 opts into authenticated lease_renew / lease_renewed control frames. Renewal extends the persisted expiry on the same live authority without restarting provider work. Identity, protocol, and revocation epoch remain fixed; expired or revoked leases cannot renew. See durable recovery for retry and warm-handoff rules. Peers lacking this capability retain their original lease expiry.
  • hello.resume reports the last processed controller sequence, next source sequence, cumulative ACK cursor, and current unacknowledged range.
  • welcome selects the one overlapping protocol version, returns the core's cumulative ACK cursor, and carries at most one durable pending command.
  • An event is durable before send. Event IDs and source sequences stay stable across replay and process restart.
  • An ACK is cumulative. The runner rejects a cursor behind its durable ACK or beyond its produced source cursor.
  • An equal repeated command ID and canonical digest returns its stored result. Reuse with different bytes fails closed and cannot repeat an effect.
  • Frames are bounded at 1 MiB and upgrade headers at 16 KiB. Unknown or invalid required protocol data fails closed; malformed JSON is a bounded diagnostic.

Runnerd build-metadata contract v2 advertises the exact transport inventory: dial_ws_loopback, dial_wss, and listen_ws. Plaintext dial destinations must resolve entirely to loopback. Public dial targets require TLS trust and hostname validation; a private CA bundle augments the platform roots and must be a bounded, private, regular file. Listener mode binds to 0.0.0.0 and a single run-bound path. The optional --listen-port selects a port in 1..=65535 and defaults to 43127. Warm attachments retain the existing listening port. All modes retain the same message/frame bounds and PRP authentication.

These are package-local Durable recovery and transport rules. Control-plane admission and deployment policy remain separately reviewed work.

Change policy

  1. Change JSON Schema first.
  2. Regenerate the TypeScript schema module.
  3. Add or revise a shared fixture and its golden snapshot/summary.
  4. Prove TypeScript and Rust parity.
  5. Update this policy and the normative spike specification when behavior changes.

Breaking changes require a new required version. Additive optional fields may remain in v1 only when old consumers can safely ignore them.

Package-level compatibility

PRP is one independently versioned component of the runner bundle. Catalog, runner-client, control-plane-adapter, testkit, and eval-corpus compatibility is declared by PAPERCLIP_RUNNER_COMPATIBILITY and checked before execution by assertPaperclipRunnerCompatibility. A mismatch fails with paperclip_runner_incompatible and stable per-issue codes; a provider-specific tool error is not a compatibility negotiation mechanism.

See ADR 0001 for the component rules and clean-consumer packaging gate.

Evals integration negotiation

The packed ./evals entry point adds a stricter execution preflight for the App/Evals join. assertPaperclipRunnerEvalCompatibility requires simultaneous agreement on package semver, runnerd build metadata, a common PRP version, semantic catalog version and SHA-256 digest, harness-driver contract and required capabilities, and the native-execution version. It reports paperclip_runner_eval_incompatible with expected/received values for every mismatch and must run before launching a provider.

runnerd itself reports paperclip-runner/runnerd-build-metadata/v1 from --build-metadata. The consumer passes its path and expected content digest to resolvePaperclipRunnerdArtifact; implicit PATH or source-tree discovery is not part of the contract. Native attempt output is paperclip-runner/native-execution/v1, whose parser accepts unknown additive fields but rejects unknown required versions and inconsistent terminal, semantic-denial, usage, or transcript facts. Full fields and the deterministic gate are exercised by the package-local deterministic conformance suite.

Claude effort after the Codex default merge

Native input v9 adds configured Claude effort while retaining v1–v8 readers. It carries the explicit home mode from v7; v8 Muse remains separate. The shared executor retains master's provider and cleanup behavior. Copilot's shared source attestation advances from pending profile 21 to 22 without replacing historical fixtures. Pi advances from master's profile 24 to 27 to bind the combined source, including master's OpenRouter attribution. The prior branch profile 24 is preserved separately from master's published profile 24. Provider executable bytes remain unchanged. These new source identities require fresh compatibility checks; earlier provider runs retain their original identities.

Claude essential task tools

Claude's runner-owned MCP bridge marks set_task_title, paperclip_finish and paperclip_block with _meta["anthropic/alwaysLoad"]: true. The pinned SDK can include these definitions in its first request without a ToolSearch round trip. Other public tools keep their existing deferral policy. Private operations stay undiscoverable, and incoming tool definitions cannot set this metadata themselves. The host enables this only for Claude; Codex and Copilot catalogs are unchanged. No server-wide tool preloading, model change or permission change is introduced.

The shared bridge's source binding advances pending Copilot profile 22 to 23. Profile 22 remains decodable and its fixture is immutable. Copilot remains pending; the revision does not qualify a new provider or change its executable closure.