A drop-in governance protocol for AI-assisted iteration. harness init lays a harness CLI plus the .harness/ state machine into any repo so AI sessions (Claude Code, Codex, Cursor) follow a coordinator → implementer two-session split with checkpointed commits.
Language-agnostic. Three AI-platform integrations + GitHub. Designed so target repos can pull upstream updates without losing their own state.
- Two-session protocol — coordinator session writes a task brief at
.harness/active/<id>.md; implementer session executes one phase at a time, stops at every commit point and session boundary. - Three triage paths — coordinator routes each request to scaffold (greenfield bootstrap), light (small change), or full (non-trivial change). Greenfield is auto-detected at
harness init(no language manifest + no source dirs + no architecture doc) or forced via--greenfield. - TODO gate — if
CONTRIBUTING.mdstill has unfilled🛠 TODO (project maintainers)blocks, the coordinator first runs afill-contributingprep task that scans manifest/CI/formatter configs, proposes candidate answers, and writes only after you confirm. No surprise auto-fills. - Memory-import gate — adopting harness into a project an agent already worked on? On the first
harness startthe coordinator looks for prior agent memory about this project — both in the repo and in your user-level store for it (Claude Code's~/.claude/projects/<repo>/, Codex/Cursor session stores) — and, only if it finds some, asks which source to import, then distils it into.harness/MEMORY.md(relocating any architecture doc intodocs/ARCHITECTURE.md, rebuilding prior task history into.harness/archive/, reconciling same-name conflicts). Detection is the agent's job, not the CLI's. Gated by[adoption] on_init. - Architecture doc as global basis —
docs/ARCHITECTURE.md(path configurable) is read at every entry point — coordinator, implementer, the Cursor rule, and the SessionStart hook — as the project map / "change X → which files" lookup. On scaffold, the coordinator asks whether you'll provide one and records the choice (harness arch). - Spec gate — for tasks that span cross-cutting dirs, introduce new external dependencies, change external interfaces, or affect the data model, the coordinator decides whether to produce a standalone design spec at
docs/specs/<task-id>.md(in addition to the task brief). Phase 0 of the brief writes the spec; subsequent phases use it as the design authority. - State on disk — task state lives in
.harness/files, so sessions can crash and resume with no information loss..harness/CURRENT.mdis per-clone local state (which task this working copy is driving; git-ignored); the shared truth across a team is the committed.harness/active/brief set..harness/SCAFFOLD-PENDINGis the marker that drives the scaffold gate. - Cross-task memory —
.harness/MEMORY.mdaccumulates conventions, pitfalls, and decisions over time, surfaced to every new session at every entry point (SessionStart hooks for Claude Code / Codex, the Cursor rule, and the coordinator/implementer prompts) so any tool reads the same memory. - Planning backlog —
.harness/TODO.mdis the coordinator's cross-session todo list: tasks scoped/split/prioritised but not yet briefed. The in-chat plan dies with a session; this file survives it, so any fresh coordinator reads the backlog on startup, proposes a recommended order, and asks which to start. The coordinator is a resident planner across the whole project; phase-to-phase hand-offs are the implementer's own and always emit the next kickoff in full. - Three AI-platform hooks — Claude Code (slash command +
SessionStart/PreToolUsehooks), Cursor (alwaysApplyrule), Codex (MCP skill +SessionStart/PreToolUsehooks). - GitHub PR template with a required protocol-status field.
Every code block below has a label telling you where to run it:
bashblocks → run in your terminal (zsh / bash). Copy the whole block; comments (# ...) are fine to keep.textblocks → paste into your AI tool (Claude Code / Cursor chat / Codex CLI prompt) as a chat message.- File-edit instructions are spelled out in prose ("open
harness.config.tomlin your editor and …") — no code block.
If a block has no explicit "Paste into your AI tool" callout, treat it as a terminal command.
CommonHarness is distributed as release tags. Every install pins to a specific version and lands in its own directory; a current symlink picks which version is active. Multiple versions can co-exist for safe upgrades and rollback.
In your terminal:
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.7.2/install.sh | bashWhen this finishes, the harness CLI is at ~/.local/bin/harness (symlinked through ~/.commonharness/current/).
Check whether it's already there:
echo $PATH | tr ':' '\n' | grep -F "$HOME/.local/bin"-
If it prints a path (e.g.
/Users/you/.local/bin) → already set, skip to Step 3. -
If it prints nothing → add it to your shell rc and reload it:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc source ~/.zshrc
macOS default shell since Catalina is zsh (
~/.zshrc). If you're on bash, use~/.bashrcinstead. Ifsourcedoesn't seem to take effect, open a fresh terminal window.
harness --versionExpected: harness 0.7.2. If you see command not found: harness, redo Step 2 (most likely the PATH change didn't propagate to your current shell — open a new terminal window).
Note: env vars must come before bash, not before curl — otherwise the variable only reaches curl and is dropped before install.sh runs.
# Example: pin to an older release for rollback.
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.1.0/install.sh \
| HARNESS_VERSION=v0.1.0 bashcurl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/main/install.sh \
| HARNESS_VERSION=main bashgit clone --depth 1 --branch v0.7.2 \
https://github.com/Libr-AI/CommonHarness.git \
~/.commonharness/v0.7.2
~/.commonharness/v0.7.2/install.shTo use SSH instead, set HARNESS_REPO_URL (note: env var goes before bash, not before curl):
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.7.2/install.sh \
| HARNESS_REPO_URL=git@github.com:Libr-AI/CommonHarness.git bashRequires: bash, git, python ≥ 3.9.
~/.commonharness/
├── v0.7.1/ ← pinned snapshot (shallow tag clone, can't switch branches)
├── v0.7.2/ ← later, after upgrade — old versions kept for rollback
└── current → v0.7.2 ← which version is active
~/.local/bin/harness → ~/.commonharness/current/bin/harness
harness --version reads from the active install's VERSION file. Each project records the version it was init'd against in its harness.config.toml (harness_version = "..."), so team consistency is auditable.
In your terminal, from the project's repo root:
cd /path/to/your/project
harness init # prompts for a preset; pick the one matching your stack
# or pass it directly, e.g.:
# harness init --preset python-uv (Python + uv + ruff + pytest)
# harness init --preset node (JavaScript/TypeScript)
# The preset is only a starting template — it does NOT auto-detect. Whichever you
# pick, the coordinator adapts [verify]/[paths] to your real stack on first start.That writes AGENTS.md, CONTRIBUTING.md (with TODO sections), harness.config.toml, the .harness/ state directory, and the AI integrations. The CLI prints "Next steps" telling you what to do next.
The preset is just a starting point. On the first
harness start, the coordinator inspects your actual project (manifest, scripts, source layout) and proposes corrected[verify]and[paths]for you to confirm — so picking the "wrong" preset still converges on a config that matches your project. Runharness initwith no--presetto see the list of available presets.
Greenfield projects. If you're initializing into an empty repo (no language manifest, no source dirs, no architecture doc), harness init auto-detects this and offers to bootstrap with the scaffold path. You can also force the decision:
harness init --preset python-uv --greenfield # force greenfield bootstrap
harness init --preset python-uv --no-greenfield # force brownfield (skip detection)
harness init --preset python-uv --non-interactive # auto-confirm detectionWhen greenfield is on, a marker file .harness/SCAFFOLD-PENDING is written; your first harness start will route to a 4-phase scaffold task (architecture decisions → directory skeleton → first runnable module → CONTRIBUTING fill + CI). The marker clears automatically when the scaffold task archives.
The first harness start will offer to fill CONTRIBUTING.md's 🛠 TODO (project maintainers) blocks for you (scans your manifest/CI/formatter configs, proposes candidates, writes only after you confirm). You can still hand-edit if you prefer — but the coordinator's TODO gate will detect unfilled blocks and offer the prep task either way.
For greenfield projects: same — but the TODO fill happens as Phase 4 of the scaffold task, not a separate prep task.
In your terminal:
harness startThis prints a long block of text — the coordinator session opening prompt. The next step happens in your AI tool.
Paste the printed text into your AI tool (Claude Code / Cursor / Codex chat) as your first chat message:
You are the **coordinator** for an iteration on this repository. Follow the harness governance protocol strictly.
…
(everything `harness start` printed)
The AI then walks you through the protocol: asks what you want to change, runs triage, writes a task brief at .harness/active/<task-id>.md, and tells you how to start the implementer session.
In your terminal:
harness implement <task-id> # resume / enter the implementer session (also prints a prompt to paste into AI tool)
harness status # show CURRENT.md + the active brief (+ team view of all active briefs in team mode)
harness mode [solo|team] # show or set the collaboration mode
harness lang [<bcp47>] # show or set the language for repo artifacts (specs/briefs/docs)
harness arch [yes|no] # show or set whether an architecture doc is provided (scaffold)
harness end # archive a finished task → CURRENT.md back to idle
harness remember "<text>" # append a convention/pitfall/decision to MEMORY.md
harness curate-memory # quarterly MEMORY.md cleanup (opens it in $EDITOR)The full protocol (triage paths, commit-point markers, anti-garbage rules) is rendered into your repo as AGENTS.md and .harness/workflow.md. Read those.
The coordinator routes each request through three sequential gates (.harness/triage.md in target repos):
| Gate | When | What happens |
|---|---|---|
| Step 0 — Scaffold gate | .harness/SCAFFOLD-PENDING marker exists |
Force scaffold path (4 pre-named phases). Skip the three-question rubric. Marker is removed when the scaffold task archives. |
| Steps 1–3 — Light/Full rubric | Marker absent | Three checks (schema / cross-cutting / tests). All NO → light (single phase). Any YES → full (multi-phase, fresh session per phase). |
| Step 4 — Spec gate | After path decision | Decide if the task needs a standalone design spec at docs/specs/<task-id>.md. Triggers: cross 2+ cross-cutting dirs, new external dep, external-interface change, data-model change, or explicit ask. Adds a Phase 0 to the brief. |
There's also a TODO gate in the coordinator opening (before the rubric): if CONTRIBUTING.md still has unfilled 🛠 TODO (project maintainers) blocks and you're not in scaffold mode, the coordinator runs a fill-contributing prep task first — scans your manifest/CI/formatter configs, proposes candidates, writes only after you confirm.
| Preset | Format / test commands | Default forbidden_without_brief |
Default spec_dir |
|---|---|---|---|
python-uv |
uv run ruff format . / uv run pytest |
["src", "tests"] |
docs/specs/ |
Adding a new preset is one TOML file under presets/ — copy presets/python-uv.toml and edit. After running init, harness.config.toml is yours to tune; the preset only seeds the defaults.
Key config keys you'll want to know about:
paths.architecture_doc— where the project's directory-layout doc lives (defaults todocs/ARCHITECTURE.md); coordinator/implementer always read this.paths.spec_dir— where standalone design specs land when the spec gate fires (defaults todocs/specs/).paths.cross_cutting— directories whose 2+-span triggers full path and spec gate.
CommonHarness sorts files in the target repo into three layers, and each behaves differently on re-runs / upgrades:
| Layer | Files | Behavior on harness upgrade / harness init --force |
|---|---|---|
| Managed | AGENTS.md, .harness/{workflow,triage,README,templates/*}, .claude/{commands,hooks}/*, .cursor/rules/harness.mdc, mcp/skills/harness/SKILL.md, .codex/hooks/check-harness-state.sh |
Overwritten cleanly from upstream templates |
| Fenced fragment | CONTRIBUTING.md, CLAUDE.md, .github/PULL_REQUEST_TEMPLATE.md |
Marker-aware merge: only the <!-- harness:begin --> … <!-- harness:end --> block is replaced; everything outside (your business content) is preserved |
| Hooks-merge JSON | .claude/settings.json, .codex/hooks.json |
Only the hooks key is replaced from upstream; every other top-level key you add (permissions, env, …) is preserved — even under --force |
| Owned | .harness/CURRENT.md, .harness/MEMORY.md, .harness/TODO.md, .harness/active/*, .harness/archive/*, .harness/SCAFFOLD-PENDING, docs/specs/* |
Never touched. The scaffold marker is created by --greenfield init and removed by harness end of a path: scaffold brief. Spec files are created by the implementer in Phase 0. |
This is what makes the protocol upgradable without clobbering project-specific work.
# Install a new version alongside the old one + flip 'current'.
# (Replace v0.7.2 with whichever release you're upgrading to.)
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.7.2/install.sh \
| HARNESS_VERSION=v0.7.2 bash
harness --version # confirms the new version is now active
# Roll back any time by flipping the symlink (older versions stay on disk):
ln -sfn ~/.commonharness/v0.7.1 ~/.commonharness/currentOld versions stay on disk; switching is a single symlink. Because each version dir is a shallow tag clone, you can't accidentally git checkout a different ref and produce inconsistent behavior across the team.
From the project root:
cd /path/to/target/project
harness upgrade
git diff # review what changedharness upgrade re-renders the Managed protocol/template files (AGENTS.md, .harness/{workflow,triage,templates}, the Claude/Cursor/Codex integrations) to the installed version, while:
- preserving
harness.config.toml— your tuned[verify]/[paths]/[branch]/ mode / lang values are kept; new sections from the new version are added with defaults;harness_versionis bumped. (It reads the preset from your config, so no--presetneeded.) - never touching your work —
.harness/{CURRENT,MEMORY,TODO}.md,active/,archive/, anddocs/specs/are left as-is. An in-progress task survives the upgrade.
Caveat: Managed files are refreshed from upstream, so manual edits to
AGENTS.md/workflow.md/ templates / integration hooks are replaced. Keep project-specific content out of those — put it inCONTRIBUTING.md(outside theharness:begin/endfence), your own.harness/docs, ordocs/ARCHITECTURE.md. Fenced fragments (CONTRIBUTING.md,CLAUDE.md, PR template) keep your out-of-fence content;settings.json/.codex/hooks.jsonkeep every key excepthooks.
What --force touches:
- Managed files (
AGENTS.md,.harness/{workflow,triage,README,templates/*},.claude/*,.cursor/rules/*,mcp/skills/*) → re-rendered from upstream templates (your edits to these files are overwritten — they're not meant to be edited locally). - Fenced fragments (
CONTRIBUTING.md,CLAUDE.md,.github/PULL_REQUEST_TEMPLATE.md) → marker-aware merged. Only the<!-- harness:begin --> … <!-- harness:end -->block is replaced; your business content outside the fence is preserved. - Owned files (
.harness/CURRENT.md,.harness/MEMORY.md,.harness/TODO.md,.harness/active/*,.harness/archive/*,.harness/SCAFFOLD-PENDING,docs/specs/*) → never touched. In-progress tasks, accumulated memory, and existing specs all survive. harness.config.toml→ depends on the command:harness upgradepreserves it (overlays your tuned values onto the new structure, adds any new sections with defaults, bumpsharness_version);harness init --forceregenerates it from the preset (resetting[verify]/[paths]/ etc.). For upgrades, preferharness upgrade.
The renderer prints + wrote / ~ merged / · skipped for each file so the diff is auditable.
Whoever runs Step 2 then commits + pushes, and the rest of the team gets the new protocol on git pull of their project — they don't all need to run harness upgrade themselves.
harness upgrade --dry-run— show the diff before writing (today: just review withgit diffafterwards).harness doctor— reports drift, broken hooks, version mismatch (harness --version≠ project'sharness_version), and unfilled🛠 TODO (project maintainers)markers (note: the v0.2.0 TODO gate already detects these atharness starttime, but doctor surfaces them outside any coordinator session).
bin/
harness # bash CLI (init, start, implement, status, end, remember, curate-memory)
_render.py # template engine + marker-aware merge
_toml.py # zero-dep TOML loader (Python 3.9+ fallback)
_cfg_get.py # CLI: read one key from harness.config.toml
templates/ # .tmpl files; directory layout mirrors the target repo
presets/ # one .toml per preset
install.sh # curl-pipe-bash bootstrap
.claude-plugin/ # Claude Code plugin manifest
tests/ # (placeholder for bats-core suite)
MIT.