mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-08 21:03:51 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip Runner needs repeatable evaluation contracts. > - Evaluation code must stay separate from provider launch and production orchestration. > - Offline fixtures need stable compatibility, scoring, traceability, and report rules. > - Published Runner consumers need only the supported evaluation contract surface. > - This pull request adds offline evaluation tooling and a workspace-private matrix kernel. > - The benefit is deterministic evaluation without credentials or paid provider calls. ## Linked Issues or Issue Description Refs #11297 This pull request extracts the offline evaluation unit from the earlier aggregate Runner work. ## What Changed - Add a workspace-private, provider-neutral evaluation matrix kernel. - Add the public `@paperclipai/paperclip-runner/evals` compatibility and native execution contracts. - Add fail-closed runnerd artifact and protocol compatibility checks. - Add deterministic workflow catalogs, scoring, traceability, and report generation. - Add sanitized Codex, OpenCode, and ACPX fixtures. - Add package-boundary and clean-consumer checks. - Add the eval package manifest to the Docker dependency stage. - Add the generated protocol fixture digest without changing the lockfile. ## Verification GitHub Actions must run: - Runner TypeScript and Rust type checks. - Runner unit and protocol tests. - Evaluation kernel tests. - Workflow traceability checks. - Clean-consumer and package-boundary checks. - Repository test, type-check, build, policy, and security gates. No local test command was run. The repository owner requested GitHub-only verification. ## Risks This is a large greenfield review surface with 51 files. The code does not launch a live provider or load credentials. Package and protocol drift fail closed. The workspace lockfile remains under the existing CI-owned process. ## Model Used OpenAI Codex with the GPT-5 agent model. The work used high reasoning, repository inspection, tool use, and parallel code review. ## 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 - [ ] I have run tests locally and they pass - [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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
303 lines
14 KiB
Markdown
303 lines
14 KiB
Markdown
# Architecture and Standalone Boundary
|
|
|
|
## Dependency direction
|
|
|
|
```text
|
|
language-neutral protocol fixture
|
|
|
|
|
+---------------+---------------+
|
|
v v
|
|
Rust runner-core + mock path TypeScript contracts + mock path
|
|
| |
|
|
+---------------+---------------+
|
|
v
|
|
byte-identical Conformance result
|
|
|
|
Paperclip App production binding --> implements ControlPlanePort
|
|
Eval/conformance consumers --> use packed runtime + ./evals + ./testing exports
|
|
```
|
|
|
|
The dependency arrow always points from an implementation toward a contract.
|
|
The standalone package does not reach backward into a Paperclip implementation.
|
|
The production implementation lives in
|
|
`server/src/services/native-runtime/paperclip-control-plane-port.ts`; it depends
|
|
on the public port, never the reverse. The accepted package/export ownership is
|
|
recorded in [ADR 0001](adr/0001-runner-testing-eval-package-boundaries.md).
|
|
|
|
## Public package surfaces
|
|
|
|
- The package root is runtime-only: PRP, runner/client contracts, normalized
|
|
backends, catalog/dispatcher, and compatibility preflight.
|
|
- `./evals` exposes the stable native-attempt/build-metadata join and explicit
|
|
digest-verified runnerd artifact resolution.
|
|
- `./testing` contains deterministic mocks and conformance kits.
|
|
- The workspace-private `@paperclipai/paperclip-eval-kernel` contains generic
|
|
structural matrix orchestration and is a development-only dependency.
|
|
Runner-specific cases, scorers, and reports remain package-local; paid
|
|
campaigns remain external.
|
|
|
|
## Core contracts
|
|
|
|
- `ControlPlanePort` is the narrow surface through which a runner opens a run,
|
|
appends ordered events, and submits a terminal structured result.
|
|
- `HarnessDriver` owns a local harness session and its provider-specific
|
|
identity, event, turn, snapshot, and close behavior.
|
|
- `NativeSessionBackend` normalizes local runner and hosted-provider sessions for
|
|
a future control-plane consumer. Environment placement is not implied by the
|
|
backend type.
|
|
|
|
The TypeScript contracts name responsibility and dependency direction while
|
|
the executable replay path provides a deterministic oracle:
|
|
|
|
```text
|
|
protocol/schemas/*.json
|
|
| generate/check | shared fixtures
|
|
v v
|
|
TypeScript schema constants/types -> validator -> deterministic reducer
|
|
| |
|
|
v v
|
|
CLI browser devtool
|
|
|
|
|
golden parity summaries
|
|
|
|
|
v
|
|
Rust runner-core oracle
|
|
```
|
|
|
|
The Rust `runner-core` crate establishes the production language/package
|
|
boundary and checks the same fixture summaries. Local runner adds the package-local
|
|
`paperclip-runnerd` and `fake-harness` binaries without changing that dependency
|
|
direction.
|
|
|
|
The clean-consumer gate packs the declared root, `./evals`, and `./testing`
|
|
exports and stages the release runnerd executable as a separately checksummed
|
|
artifact.
|
|
|
|
## Language ownership
|
|
|
|
- Rust is the production direction for deterministic runner behavior,
|
|
supervision, durable delivery, and the eventual `paperclip-runnerd` binary.
|
|
- TypeScript owns the control-plane/browser side and remains a useful reference
|
|
client/test oracle.
|
|
- JSON Schema and shared fixtures are the language-neutral authority. Conformance
|
|
keeps its narrow tracer fixture; Replay adds the executable PRP v1 schema and
|
|
conformance corpus without silently changing the accepted Conformance path.
|
|
- `check:conformance-parity` prevents either implementation from introducing a
|
|
language-specific observable result.
|
|
- `check:replay-parity` prevents TypeScript replay and the Rust production
|
|
direction from disagreeing on identity, terminal state, duplicates, or gaps.
|
|
|
|
## Allowed dependencies
|
|
|
|
- Rust crates declared by the package-local Cargo workspace.
|
|
- Node.js standard-library modules.
|
|
- Third-party packages declared by this workspace.
|
|
- Files within `packages/paperclip-runner/`.
|
|
- A future explicit generated-schema package only after architecture review and
|
|
an allowlist change in the boundary checker.
|
|
|
|
## Forbidden dependencies
|
|
|
|
The following imports and package dependencies are rejected:
|
|
|
|
- `server/`, `ui/`, and `cli/` implementation paths;
|
|
- `@paperclipai/db` and production database schema or client modules;
|
|
- `@paperclipai/shared`, adapter utilities, and other Paperclip workspace
|
|
internals unless a boundary review explicitly allows a public contract;
|
|
- relative or absolute imports that escape `packages/paperclip-runner/`.
|
|
|
|
This rule applies to type-only imports, exports, dynamic imports, CommonJS
|
|
`require` calls, Rust include/path attributes, and Cargo path dependencies. The
|
|
negative fixtures under `test-fixtures/` intentionally reference `server/` and
|
|
must fail the checker.
|
|
|
|
## Enforcement
|
|
|
|
```sh
|
|
pnpm --filter @paperclipai/paperclip-runner check:forbidden-imports
|
|
pnpm --filter @paperclipai/paperclip-runner test
|
|
pnpm --filter @paperclipai/paperclip-runner check:replay-parity
|
|
```
|
|
|
|
The first command scans the package source, scripts, and manifest. The test
|
|
command additionally asserts that the negative fixture is rejected. The normal
|
|
scan excludes that fixture so a deliberate proof does not make the package fail.
|
|
|
|
## Conformance process boundary
|
|
|
|
The mock core is an in-memory adapter, not a Paperclip server. Starting it only
|
|
changes local object state. The tracer performs this sequence:
|
|
|
|
1. load and validate `protocol/fixtures/conformance-minimal-run.json`;
|
|
2. start the mock adapter;
|
|
3. open the fixture run through `ControlPlanePort`;
|
|
4. append contiguous typed events;
|
|
5. submit the matching terminal result;
|
|
6. print a stable JSON identity/result and stop the adapter.
|
|
|
|
No socket, database, browser, Paperclip process, or model process is started.
|
|
The default command executes this sequence in Rust. The TypeScript reference
|
|
executes the same sequence, and the parity check compares their complete stdout.
|
|
|
|
## Static replay boundary
|
|
|
|
`replayReplayFixtureText` is the single entry point used by the CLI and browser.
|
|
It parses JSON, validates JSON Schema plus cross-record bindings, and only then
|
|
calls the reducer. The reducer is pure: it clones input state, applies an event
|
|
at most once by source event ID, records source gaps/out-of-order deliveries,
|
|
and never performs I/O.
|
|
|
|
The browser is a Vite application under `devtools/browser/`. Its Button, Badge,
|
|
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.
|
|
|
|
## 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.
|
|
|
|
## Durable transport boundary
|
|
|
|
```text
|
|
TypeScript mock core
|
|
| one-time ticket -> short-lived connection lease
|
|
| PRP v1 hello/welcome, commands, events, cumulative ACKs
|
|
v
|
|
paperclip-runnerd (Rust WebSocket client)
|
|
| atomic private JSON state
|
|
+-- durable outbox and processed-command cache
|
|
+-- stable runner/session/turn/item identities
|
|
+-- Local runner fake-harness process for restart proof
|
|
```
|
|
|
|
The runner initiates the loopback WebSocket. The bootstrap ticket is present
|
|
only in the runner process environment. The returned connection-lease token is
|
|
kept only in runner memory. The mock core stores SHA-256 digests of capabilities
|
|
and the runner state stores neither capability. The runner writes each event and
|
|
command result before network delivery, then removes outbox events only after a
|
|
valid cumulative ACK.
|
|
|
|
The TypeScript peer is a package-local control-plane implementation, not
|
|
production Paperclip. Its focused tests cover lost ACKs, socket loss, malformed
|
|
input, process restarts, lease expiry, storage pressure, drain, and revoke.
|
|
|
|
## Capability-model boundary
|
|
|
|
```text
|
|
Paperclip skill + 7 references Paperclip Evals corpus (106 cases)
|
|
| |
|
|
+-------------------+------------------+
|
|
v
|
|
generated capability contract (258 rows, 41 MCP aliases)
|
|
|
|
|
+-----------------------+-----------------------+
|
|
v v v
|
|
semantic tool catalog authorization engine eval conformance suite
|
|
\ | /
|
|
\ v /
|
|
+-----> in-process mock ControlPlanePort <--+
|
|
|
|
|
v
|
|
read-only browser scenario explorer
|
|
```
|
|
|
|
Capability is a package-local model of a native Paperclip run. It classifies every
|
|
capability as control-plane-owned, always-agent-tool, or optional-agent-tool,
|
|
exposes the always/optional set as a transport-neutral semantic tool catalog,
|
|
gates optional tools behind grants, and proves 106 eval-derived cases against an
|
|
in-process mock `ControlPlanePort`. The mock adapter is the only coupling point,
|
|
so a real adapter can replace it later without touching the catalog,
|
|
authorization rules, or conformance suite. Capability contacts no Paperclip
|
|
service, database, ACPX session, or provider credential; the
|
|
[forbidden-imports checker](#forbidden-dependencies) keeps it that way. Real
|
|
integration is future upload integration (ACPX) and requires separate approval; see
|
|
[the future binding boundary](capability-future-binding-boundary.md).
|
|
|
|
## Capability live process topology
|
|
|
|
The live surface adds a real provider turn loop over the same mock core. The
|
|
package server owns every credential and every child process; the browser holds
|
|
none.
|
|
|
|
```text
|
|
browser (issue thread + evidence panel; no credential)
|
|
| HTTP/SSE over the trusted-proxy boundary
|
|
v
|
|
package server ── projectCapabilityIssueThread ──> CapabilityIssueThreadSnapshot
|
|
| (one contract, two producers)
|
|
| owns session, tool loop, provider auth
|
|
v
|
|
paperclip-runnerd (real binary, owns the Codex process group)
|
|
| newline-delimited JSON-RPC over stdio
|
|
v
|
|
codex app-server (real session)
|
|
| tool request
|
|
v
|
|
CapabilitySemanticDispatcher ── typed command ──> in-process mock ControlPlanePort
|
|
^ |
|
|
+──────── typed result / typed denial ─────+
|
|
```
|
|
|
|
Three actors stay separate at all times: **Real Codex** (the app-server
|
|
session), **Real runnerd** (the package-local binary), and **Mock Paperclip**
|
|
(the in-process `ControlPlanePort`). The same
|
|
`CapabilityIssueThreadSnapshot` is produced by deterministic `fake` fixtures for the
|
|
screenshot matrix and by the server-side projection for a live session; the
|
|
projection reads only durable records and decides nothing, so UI-side state math
|
|
is a defect by construction. The scripted (`fake`) mode drives the conformance
|
|
suite and replay offline; the Codex (live) mode requires a locally authenticated
|
|
Codex. See [execution modes and identity](capability-execution-modes.md) for the
|
|
mode and eligibility rules, and [the live runnerd/Codex loop](capability-live-runnerd-codex.md)
|
|
for the session API. The package server blocks every request to a real
|
|
Paperclip API, and the evidence suite proves no such request occurred. The same
|
|
topology serves the [clean-room chat](capability-clean-room-chat.md): the only
|
|
difference is a mock tenant seeded with a company, an agent, and one blank issue
|
|
instead of a recorded eval case.
|
|
|
|
## Integration rule
|
|
|
|
Paperclip core may implement these contracts behind a separately reviewed
|
|
adapter, but this package must remain independently buildable, testable, and
|
|
runnable against the mock adapter.
|
|
|
|
The proposed Standalone seam is recorded in
|
|
[Standalone Thin Paperclip Adapter Boundary](design/standalone-thin-paperclip-adapter.md).
|
|
It keeps one dependency direction, branches only after Paperclip workspace and
|
|
environment realization, composes a package-owned `NativeSessionBackend` with
|
|
a server-bound `ControlPlanePort`, and returns to the existing Paperclip
|
|
finalization path. The core seam contains no runner behavior. The proposal is
|
|
design-only until the CTO gate accepts it.
|