Skip to content

harn init

harn init [--adapter claude-code|cursor|codex] [--dry-run] [--check] [--project-root <path>]

One command to make a project harnery-ready. It does the setup steps that are otherwise easy to miss, and that fail silently when missed:

  1. Creates the .harnery/ coord root. Without it, findCoordRoot returns null and every hook no-ops forever. init also drops a .harnery/.gitignore (*) so machine-local runtime state (heartbeats, the event stream) never gets committed.
  2. Wires the agent-hook entries into the chosen adapter’s settings file (see the adapter-specific counts below).
  3. Injects the agent-facing guidance so a fresh consumer’s agent actually knows the coord surfaces exist (design: ADR 0008):
    • A machine-owned instructions block spliced into AGENTS.md (a hash-versioned <!-- harnery:begin instructions --> region; everything outside the markers is untouched). Every command in it renders in your bin name.
    • The generic skills harn-decide and harn-council written to .claude/skills/ (claude-code only; Cursor and Codex read AGENTS.md natively). Suppress one with skills.exclude in .harnery/config.jsonc when you ship your own richer version. The block is exclusion-aware: it only points at a skill it actually wrote, so an excluded skill (or a adapter with no skill files, like Cursor/Codex) gets a <bin> decision --help / <bin> council --help pointer instead of a dangling reference.
    • A CLAUDE.md @AGENTS.md import shim when CLAUDE.md is absent (Claude Code reads CLAUDE.md, not AGENTS.md). A CLAUDE.md that already imports AGENTS.md, or already carries the block, is left alone.
    • Your own coordination policy, if you name a file in instructions.hostAddendumFile. See below.

Harnery’s block is deliberately generic: it ships to every consumer, so it carries engine mechanics and no house rules. A project with real coordination policy of its own (commit conventions, escalation targets, a triage rubric) used to have nowhere machine-managed to put it, and hand-maintained it beside the block where nothing kept the two in step.

Name a file instead:

.harnery/config.jsonc
{ "instructions": { "hostAddendumFile": ".agents/host-instructions.md" } }

init splices that file’s contents into a second managed region (<!-- harnery:begin host-addendum -->) right after its own block. From then on your policy gets the same treatment harnery’s does: refreshed when the file changes, reported by init --check, removed by deinit or by deleting the config key and re-running init.

Harnery never parses or renders what the file says. It is your content, placed and versioned by harnery, which is what lets one generic mechanism serve consumers whose policies have nothing in common.

The path must be repo-relative and inside the project, and the file must exist and be non-empty. Anything else fails the run before the first write, so a mistyped path leaves your AGENTS.md exactly as it found it rather than silently dropping a section you believe is still there.

When you run init through a host CLI that composes Harnery under its own bin name (e.g. mycli init), it also stamps binName into .harnery/config.jsonc so Harnery’s coord binaries and web UI tell agents to run your command (mycli agents status, …) rather than the harn default. The stamp is comment-preserving and idempotent. Standalone harn needs no stamp; it’s the resolver’s default. See the config reference.

init also pins workflow.subscriptionOnly: true into .harnery/config.jsonc so run children can only authenticate via the adapter CLI’s logged-in (subscription) auth, so API-key vars are scrubbed from child envs, making accidental per-token billing structurally impossible in the repo. The pin is written only when no workflow key exists; a committed workflow key of any shape (including subscriptionOnly: false for key-only CI hosts) is a deliberate choice and is never touched. Per-process escape hatch: HARNERY_WORKFLOW_SUBSCRIPTION_ONLY=0.

Terminal window
harn init # wire the current project for Claude Code
harn init --dry-run # preview every change without writing

Safe to re-run. init merges into an existing settings file: it preserves every hook you already have, skips any agent-hook entry that’s already wired (rewriting its command in place when a Harnery upgrade changed the canonical form), and removes Harnery-owned entries retired from that adapter’s native lifecycle. If the settings file exists but isn’t valid JSON, init refuses rather than overwrite.

harn init
root: /path/to/project
· .harnery/ already exists
· all 10 claude-code hooks already wired in .claude/settings.json

Re-run after a Harnery upgrade that changes hooks

Section titled “Re-run after a Harnery upgrade that changes hooks”

Updating the package is enough for code fixes: your wired hooks call the agent-hook binary, so they pick up new code with no re-init. The exception is a release that adds, renames, or retires a hook event, or changes the wired command form: re-run harn init to migrate the Harnery-owned entries. harn doctor’s adapter hooks check flags this, and a one-line nudge appears at your next session start naming the drift.

The same is true of the instructions block and skills: they’re committed by you, and the source of truth is the installed Harnery version’s templates, so a package upgrade shows up as drift until you re-run init.

--check is read-only: it reports whether the instructions block and skills match what the installed version would render, and writes nothing. It’s the exit-code contract you wire into a pre-commit hook or CI:

Exit Meaning
0 Fresh: block and skills are current
2 Drift: a block or skill is stale, missing, or hand-edited
1 Error: a file couldn’t be read
harn init --check: drift found (re-run `init` to refresh)
✗ skill harn-council: stale (re-run init)

A skill you excluded via skills.exclude is not required, so --check never flags it missing.

Defaults to the git top-level (falls back to the current directory). Override with --project-root <path>.

All three adapters are fully auto-wired. Pass --adapter:

--adapter Settings file Notes
claude-code (default) .claude/settings.json 10 events; nested { hooks: [{ type, command }] } entries
cursor .cursor/hooks.json 10 events; flat { command } entries + "version": 1
codex .codex/hooks.json 7 native events; nested { hooks: [{ type, command }] } entries; strict description + hooks top-level schema

Each writes only its own file, so you can wire several adapters in one project by running init once per adapter. The canonical file path + event→subcommand map + entry shape for every adapter lives in src/core/hooks/adapter/events.ts (ADAPTER_SPECS).

Codex requires hooks to be enabled ([features] hooks = true in config.toml, the default on current Codex) and prompts you to trust new hooks via /hooks on first run.