Adding a Harness
How the automation-harness layer works across engines, and what it takes to support a new one.
Most pages in this section are about skill-bridge discovery: which file a tool reads
(AGENTS.md, .cursor/rules/) so it can find your Knowledge Base. This page is about the layer
above it, harness execution: configuration, authentication, model provider, headless mode, and
who re-invokes the loop.
Bridge, Harness, Engine
Three words that are easy to conflate and mean different things:
| Term | What it is | Example |
|---|---|---|
| Bridge | The file that lets a tool discover .pair/ | AGENTS.md, .cursor/rules/ |
| Harness | An environment configured to execute pair's process, config locations, skill paths, auth, model provider | Claude Code, pi, opencode |
| Engine | A process pair-cli run can drive headlessly, one iteration at a time | pi, opencode, claude -p |
A harness is usually also an engine, but the roles are independent: you may work interactively in one harness and drive unattended runs through another.
Fan-Out Has Three Realizations
Unattended delivery needs a way to run many cards without one context accumulating all of them. That capability has three realizations, in preference order (ADR-021):
- In-harness: the harness's own fan-out primitive (Claude Code's
Workflow, Codex subagents). Cheapest where it exists: no second executable, no stream contract. - External driver:
pair-cli run: a fresh process per iteration on a chosen engine, re-invoked on the continue-token the skill prints. The portable baseline, and a legitimate choice even where subagents exist: a separate process shares no context, no tool state and no crash with its predecessor, which is stricter isolation than a subagent gives you. - Degraded: one card per invocation, re-triggered by a human, CI job or cron. Only for a caller with neither of the above.
Whichever tier runs, the process logic lives in the skills and the policy lives in
.pair/adoption/tech/automation.md. The driver decides how to invoke, never what to work on.
Supporting a New Harness
Two levels, and they are separable: a harness is useful after level 1 alone.
Level 1: Make it a harness (no pair code changes)
- Check skill discovery. If the tool reads
AGENTS.md/.agents/skills//.claude/skills/,pair-cli installalready put pair's skills where it looks. Most tools need nothing here. - Write down what pair needs to know about it: config file locations and which are committable, skill-path declaration, context-file precedence, authentication (and whether it is CI-viable), access paths (MCP or not), model-provider configuration, headless execution, and what the tool explicitly does not support. That is exactly the shape of the per-harness guides in the agent-harness framework; add a sibling file there.
- Declare it, if your project restricts harnesses.
tech/automation.md's## Harnesssection lists the harnesses a project supports (and, optionally,Requires: mcp). Absent that section every harness is presumed supported. - Provision it with
/pair-capability-setup-harness, which verifies fitness against those declarations before writing any configuration.
Level 2: Make it an engine (a small, contained code change)
pair-cli run holds every engine as data: one entry per engine with its executable, headless and
stream flags, how a skill invocation and a prompt are handed over, the autonomy and project-trust
postures, and which stream event is terminal. Adding an engine is an entry in that map: there
are no engine-specific conditionals anywhere else in the driver.
What you must be able to answer, with evidence rather than assumption:
- What is the one-shot headless invocation, and which flag makes it emit machine-readable JSONL?
- Which event ends a run, and how is a failure distinguishable from a success?
pair-cli runreads an explicit terminal event and never the exit code: an unrecognised stream is a failed iteration, and the loop stops. Verify this against the real tool, not its docs alone. - How are confirmations expressed: a flag, or not at all? A tool with no confirmation mechanism
is not treated as "safely confirming": the driver requires an explicit
--autonomousfor it. - Is there a project-trust decision, and where does it live? The driver reads such a store and never writes it.
Keeping the engine map as data is deliberate: it means moving it into the Knowledge Base later is a
source swap rather than a refactor. Until then, adding an engine is a change to apps/pair-cli
and a pull request: a conscious, recorded trade-off, not an oversight.
Related
pair-cli runreference- pi · opencode · Claude Code · Codex
- Configuration reference: the optional
enginekey - Batch engine: the in-harness fan-out realization