Expand description
§tapes-harnesses
Client-side knowledge about coding-agent harnesses, for Tapes capture.
This is the half of capture that changes because a harness was added. Its
membership test is one question: would adding one more harness change this?
If yes it belongs here; if no it belongs in
tapes-capture, which this crate depends on and
which may never depend back.
Consumed by tapesctl and by closed-source daemon clients alike, so every
client launches and attributes sessions with the same code rather than with
separate implementations kept in agreement by review.
§Public seams
harness— the registry, and the vocabulary the rest of the crate speaks. One declaration per harness bundles its id, User-Agent rule, launch support, attribution strategy, transcript location, and plugin needs. Every other module takes its harness ids from here, and consumers derive their supported-agent lists from it rather than hard-coding one.launch— per-harness environment and config injection for running a harness under a capture proxy. Recipes are pure: they plan an argv prefix, an environment overlay, and any config documents a harness reads from disk. Spawning, materialisation, and cleanup stay with the consumer.attribution— session-file reads, fork-parent recovery, peer-PID lookup, and the session watchers, grouped per harness (attribution/claude/,attribution/codex/, …) with the harness-agnostic pieces shared. The composition itself lives inattribution/pipeline.rs.plugin— the plugin and extension artifacts a harness needs on disk, with the vendor-neutrality bar enforced by the module’s own tests.transcript— discovering and packaging harness transcripts for the transcript ingest lane. Delivery, auth, and retry are the consumer’s.config— the config-document patch grammars the launch recipes plan against.
No feature flags: everything above is always compiled.
§Adding a harness
Start at src/harness.rs, then follow
docs/adding-a-harness.md at the repository
root — it covers the registry declaration, when a launch recipe is needed, which
attribution strategy applies, and what the tapes deriver needs on its side.
§Stability
This crate is supported public API, meant to be depended on directly. So
are its two siblings — tapes-capture (the
capture protocol) and tapes-client (the read
client) — and all three version independently on crates.io.
Pre-1.0, 0.x versions carry the usual Cargo meaning: a breaking change bumps
the minor (0.2.0), anything compatible bumps the patch (0.1.1). What counts
as breaking is the boundary in the repository README,
not just the signatures: knowledge that stops being harness-specific belongs in
tapes-capture, and moving it is a break here even when nothing stops
compiling.
Adding a harness to the registry is additive, and it is the change most worth
reading about — consumers derive their supported-agent lists from that registry
rather than hard-coding one, so a new entry appears in their surface without
their doing anything. This crate also requires tapes-capture at a version, so
an upgrade may carry one; the changelog says when it does.
Changes are recorded in CHANGELOG.md.
§License
Dual-licensed under MIT OR Apache-2.0; see the repository root.
§Module map
Exactly three places hold harness knowledge; this crate is one of them (the deriver and the envelope spec/fixtures are the other two). It owns these responsibilities:
harness— the registry: one declaration per harness, bundling its id, User-Agent rule, launch support, attribution strategy, transcript location, and plugin needs. The other modules take their harness ids from it and consumers derive their supported-agent lists from it, so adding a harness starts in exactly one place.launch— per-harness env/config injection to run a harness under a capture proxy.config— persistent harness-config patch grammars: how an installer patches a capture provider into a harness’s own config file, idempotently and preserving the user’s content. Wherelaunchplans per-process config that dies with the process, this module owns the durable install a desktop app or long-lived integration needs.plugin— the artifacts a harness with no base-URL knob needs installed into it before capture is possible at all, and the environment contract those artifacts read. Consumers are installers; the bytes live here so every client installs the same ones.attribution— session-file reads, fork-parent recovery, peer-PID lookup, the peer-trust ancestry walk, and the codex session watcher, grouped per harness.transcript— discovering and packaging harness transcripts for thePOST /v1/ingest/transcriptlane.
§The three ways a harness gets captured
This is the distinction to hold on to, because it decides which modules
apply to a given harness. It correlates with
harness::AttributionStrategy but is not the same axis: that enum says
how a request acquires an identity, this says how the traffic is reached at
all.
| mechanism | when it applies | plan it with |
|---|---|---|
| Launch redirect — point the harness’s base-URL knob at a proxy | the harness has such a knob (claude, codex, opencode) | launch |
| Installed plugin — code runs inside the harness and stamps its own envelope | the harness has no such knob (pi) | plugin |
| Lifecycle hooks — a hook plugin reports allowlisted evidence at session boundaries | the harness is configured rather than launched (codex-app) | plugin::codex_app and config |
A harness declares which of these it needs through
harness::LaunchSupport and harness::PluginDelivery; nothing here
infers it.
§What is not here
Everything above changes when a harness is added. The parts of capture that
do not live in tapes_capture, which this crate depends on: the
X-Tapes-* envelope producer and its harness-id vocabulary, the
capture-gateway environment contract and launch-nonce protocol, peer-PID
lookup, and the peer-trust ancestry check. The edge runs one way by
construction: a harness module may reach for a capture primitive, and
nothing over there can reach back, because the moment a capture primitive
knows a harness’s name it stops being the thing every harness shares.
The envelope is the sharpest case, because the arrow points the way that
first looks backwards. Harness ids are envelope vocabulary — they are what
goes on the wire — so harness takes its ids from tapes_capture rather
than declaring them and having the envelope import them back. Reading them
the other way is what used to make the two mutually dependent, and it is why
the producer now asks for a tapes_capture::HarnessSession instead of
naming any harness’s session type.
§Provenance
attribution is extracted from a daemon client’s proxy session layer —
the code that validated peer-PID attribution and fork-parent discovery
against real Claude and Codex traffic.
launch is extracted from the same client’s per-agent env/config
injection, with the Go tapes start opencode/codex knowledge folded in —
including opencode, which that client never supported. Its recipes are pure:
they plan argv, environment, and config documents, and the consumer owns
process spawning and cleanup.
transcript is extracted from that client’s transcript uploader — its
discovery/packaging half, the push trigger, and the ingest payload shape —
and adds a startup sweep of the transcript tree, which closes a gap every
daemon client has: a session that began and ended while the daemon was down
is never re-registered by live traffic, so its fork skeleton was previously
lost. Delivery, auth, and retry stay in each client.
§Names
The repository is tapes-crates; this crate is one of its four members.
There is no tapes-harness crate — the singular spelling is reserved as a
stub redirect so the near-miss cannot be claimed by someone else. tapes is
a different repository entirely: the server a capture client ships to.
Modules§
- attribution
- Session attribution.
- config
- Persistent harness-config patch grammars.
- harness
- The harness registry — the one place a harness is declared.
- launch
- Launch recipes.
- plugin
- Plugin artifacts — the files that must be installed into a harness before its traffic can be captured at all.
- transcript
- Transcript tailer.