Canonical harness model — one definition, many control planes
Status: open/accepted
Summary
Section titled “Summary”- The agentic-harness surface is held as a single canonical, host-neutral model — capabilities, commands, hooks, MCP servers, permissions, and context documents — and exporters project it onto each host’s native shape. One definition drives Claude Code, Codex, Cursor, and Gemini; no target is privileged.
- The model is the source of truth. An importer bootstraps it from the existing Claude surface so no capability is hand-transcribed, but once imported the model — not any host — is authoritative.
- Markdown bodies validate with markdown-contract; the model is a plain JSON-serializable Zod shape that is itself the portable interchange form. Adding a host is a new exporter module behind a registry, never an op edit.
Status
Section titled “Status”Accepted as the harness architecture, superseding this decision’s own first draft (a Claude-derived, skills-only model with Claude as the reference target). Realizes D-VSLI-distributed-work-runner-architecture’s “the Claude plugin ends as one surface among several.”
Context
Section titled “Context”The harness is the skill-layer surface D-VSLI-distributed-work-runner-architecture installs into
a host: today .claude-plugin/ manifest + skills/<name>/SKILL.md + conventions/*.md +
.claude/settings*.json (hooks, permissions, MCP servers), wired in by harness install claude
(D-0014-cli-primary-npm-distribution). That surface is authored ONCE, in Claude Code’s native
shape, and there is no seam that lets the same capabilities reach a different host — a second host
means a second hand-maintained copy of every capability, hook, and server. Different hosts (Codex,
Cursor, Gemini) express the same concepts in different layouts and vocabulary; nothing models those
concepts independently of Claude.
Decision
Section titled “Decision”Adopt a canonical harness model and a set of equal-footing exporters over it.
The model
Section titled “The model”plugin/lib/services/harness/model.ts — a plain, JSON-serializable Zod shape whose every field is
host-neutral:
| Field | Is | Rendered by each host as |
|---|---|---|
metadata | identity (name, version, description, author, homepage) | plugin/config manifest |
capabilities[] | invocable units: kind (skill | command), name, description, instructions, allowedTools, argumentHint, resources[] | SKILL.md / slash command / custom prompt / rule |
hooks[] | lifecycle hooks: event, matcher, action | settings hooks / config notify |
mcpServers[] | MCP servers: transport, command/url, args, env | .mcp.json / config.toml / mcp.json |
permissions | tool allow/deny/ask rule sets | settings permissions / approval policy |
documents[] | shared context docs | conventions / AGENTS.md / GEMINI.md / rules |
Capability and document bodies validate through a markdown-contract contract, so a malformed
body fails at the model seam, not in a downstream host. The model IS the interchange format — the
generic exporter emits it verbatim as harness.json.
The importer (bootstrap, not truth)
Section titled “The importer (bootstrap, not truth)”plugin/lib/services/harness/import/ — reads an existing Claude surface (skills, conventions, and
settings*.json/.mcp.json hooks, permissions, MCP servers) into the canonical model. This seeds
the model faithfully without hand-transcription; it is a migration aid, not the ongoing source of
truth.
The exporters (equal targets)
Section titled “The exporters (equal targets)”plugin/lib/services/harness/exporters/ — one pure model → files module per target, registered by
name behind a registry:
- claude —
.claude-plugin/+skills/+commands/+conventions/+settings.json(hooks, permissions, mcpServers). - codex —
AGENTS.mdroster +prompts/<name>.md+config.toml([mcp_servers], approval). - cursor —
.cursor/rules/*.mdc+.cursor/commands/+.cursor/mcp.json. - gemini —
GEMINI.md+.gemini/commands/*.toml+.gemini/settings.json(mcpServers). - json — the model verbatim, the host-independent interchange form.
Where a host lacks a concept (Codex has no hard tool-permission gate), the exporter renders the
model’s field in that host’s idiom (informational guidance) rather than dropping it. sdlc harness export <target> runs an exporter; both it and sdlc harness model are ops on the deterministic
substrate (D-0007-deterministic-op-substrate).
Consequences
Section titled “Consequences”- A single edit to a capability, hook, or server reaches every host on the next export — the drift trap closes across four control planes.
- Claude loses its privileged position: no model field is Claude-specific, and the Claude exporter
is peer to the others.
harness install claudestays as the settings-registration path; a later slice may route it through the exporter. - The model grows by adding host-neutral fields, never host-specific ones. A host that needs a concept the model lacks motivates a new field every exporter can render.
Out of scope
Section titled “Out of scope”- Round-trip importers for non-Claude hosts (export-only for codex/cursor/gemini).
- Re-authoring the corpus by hand against a native model manifest — the importer makes that a later, optional migration.