Skip to content
On this page

Harnesses ​

Every registered harness gets a rule file in its always-loaded layer, and every harness with a hook system gets one hook that runs npx -y @vivswan/maxims sync --quiet on the event the hook column names. The matrix below is rendered from the harness definitions by scripts/render_docs_tables.ts: bun run docs:tables regenerates it, and bun run check fails while the page is behind the registry.

The matrix ​

Ids in the first column are what --agent accepts. A project target is written for a project install, a global target for -g; a harness with no global target skips -g.

idharnesstierproject targetglobal targetstrategyhookstdoutmcp stubmarkersbyte budget
claude-codeClaude Code1, or 2 when disableAllHooks is true.claude/rules/maxims-<source>.md~/.claude/rules/maxims-<source>.mdASessionStart entry in .claude/settings.json or ~/.claude/settings.json, asyncplain-stripped4,194,304 bytes, 40,000 bytes path-scoped
codexCodex1, or 2 when features.hooks is falseAGENTS.md block, written into the first existing of AGENTS.override.md, AGENTS.md~/.codex/AGENTS.md block, written into the first non-empty of ~/.codex/AGENTS.override.md, ~/.codex/AGENTS.mdBSessionStart entry in .codex/hooks.json or ~/.codex/hooks.json, asyncplain-counted-
gemini-cliGemini CLI1GEMINI.md block~/.gemini/GEMINI.md blockBSessionStart entry in .gemini/settings.json or ~/.gemini/settings.jsonjson:hookSpecificOutput.additionalContext-counted-
copilotGitHub Copilot1.github/instructions/maxims-<source>.instructions.md~/.copilot/instructions/maxims-<source>.instructions.mdAmaxims-owned file .github/hooks/maxims.json or ~/.copilot/hooks/maxims.jsonjson:additionalContext-counted-
cursorCursor1.cursor/rules/maxims-<source>.mdcnoneAsessionStart entry in .cursor/hooks.jsonjson:additional_context-counted-
clineCline1.clinerules/maxims-<source>.md~/Documents/Cline/Rules/maxims-<source>.mdAmaxims-owned executable .clinerules/hooks/TaskStart or ~/Documents/Cline/Hooks/TaskStartnone-counted-
opencodeOpenCode1AGENTS.md block~/.config/opencode/AGENTS.md blockBmaxims-owned file .opencode/plugins/maxims.ts or ~/.config/opencode/plugins/maxims.tsnone-counted-
dshDeepSeek Harness1AGENTS.md block~/.dsh/AGENTS.md blockBcustom--counted64,512 bytes
devinDevin Local1AGENTS.md block~/.config/devin/AGENTS.md blockBSessionStart entry in .devin/config.json or ~/.config/devin/config.jsonjson:hookSpecificOutput.additionalContext.devin/mcp_config.json or ~/.config/devin/mcp_config.jsoncounted-
windsurfWindsurf Cascade1.devin/rules/maxims-<source>.md~/.codeium/windsurf/memories/global_rules.md blockA project, B globalpre_user_prompt entry in .devin/hooks.json or ~/.codeium/windsurf/hooks.jsonnone-countedproject 12,000 bytes, global 6,000 bytes
zedZed2AGENTS.md block, written into the first existing of .rules, .cursorrules, .windsurfrules, .clinerules, .github/copilot-instructions.md, AGENT.md, AGENTS.md, CLAUDE.md, GEMINI.md~/.config/zed/AGENTS.md blockBnone-.zed/settings.json or ~/.config/zed/settings.jsoncounted-
ampAmp1AGENTS.md block, written into the first existing of AGENTS.md, AGENT.md, CLAUDE.md~/.config/amp/AGENTS.md blockBmaxims-owned file .amp/plugins/maxims.ts or ~/.config/amp/plugins/maxims.tsnone.amp/settings.json or ~/.config/amp/settings.jsoncounted-
warpWarp2AGENTS.md block, written into the first existing of WARP.md, AGENTS.mdnoneBnone-~/.warp/.mcp.jsoncounted-
piPi1AGENTS.md block, written into the first existing of AGENTS.override.md, AGENTS.md, AGENTS.MD, CLAUDE.md, CLAUDE.MD~/.pi/agent/AGENTS.md block, written into the first existing of ~/.pi/agent/AGENTS.override.md, ~/.pi/agent/AGENTS.md, ~/.pi/agent/AGENTS.MD, ~/.pi/agent/CLAUDE.md, ~/.pi/agent/CLAUDE.MDBmaxims-owned file .pi/extensions/maxims.ts or ~/.pi/agent/extensions/maxims.tsnone.pi/mcp.json or ~/.pi/agent/mcp.jsoncounted-
  • tier is the declared tier; the config key that demotes it to 2 follows when the definition names one.
  • project target and global target are the file written for a project install and for -g; none means that scope is skipped. A block cell that lists files is written into the first of them the harness reads, the first that exists or, where the cell says so, the first that is not empty; the named file is created only when none qualifies.
  • strategy A writes one whole file per source into a rules directory, so removal is a file delete. B writes a managed block into a shared instructions file the user also owns, so removal cuts the block and keeps the rest.
  • hook is the registry entry, hook file, or custom reconcile that runs the sync command, with its path for each scope the harness installs into. A maxims-owned file is written whole and deleted on removal, so nothing else belongs in it; custom means the definition writes its own files, named in the catches below.
  • stdout is how sync's output reaches the agent: plain text becomes context, a json: value names the key inside the one JSON object the harness reads, none means the hook passes nothing of sync's on, and - means no declared stdout variant, a custom hook or no hook.
  • mcp stub is the MCP servers file, per scope, where sync registers the bundled stub server whose start runs one sync; it follows the same --add-hook intent as the hook, and remove takes it back with the last source. - when the definition names none.
  • markers is stripped when the harness drops HTML comments before injection, so the marker pair is free, and counted when they ride into context.
  • byte budget is the largest rule file the writer will produce for the harness; past it the newest source whose block the run changes is held, one at a time, until it fits. - when the definition declares no budget, so the writer enforces none. A second figure marked path-scoped is the smaller limit the harness reads a --paths file under.

Memory bodies do not vary by harness. They live in the maxims store and rule lines point at them; a project install links them into .agents/memories/ for every harness, the convention npx skills set with .agents/skills/.

A harness you declare in harnesses.json has no row here, because it exists only on the machine that declares it; the adding a harness page owns that file.

Tiers ​

tiermeaningfreshness
1a rule file plus a hook that runs the syncloads every session and refreshes itself
2a rule file, no hookfresh via any hooked harness on the machine or the MCP stub; manual otherwise
3unsupportedno file-based always-loaded layer, so nowhere to put a guarantee

The MCP stub keeps a tier 2 harness fresh where the mcp stub column names a file. Tier 3 is web-only agents with UI-stored rules.

The tier a harness achieves is a sync-time result that list reports. It is not stored, so a config edit that demotes a harness is visible the next time you look.

The rule file ​

Generated on every sync, compared to what is on disk, and written only on a difference. Hand edits inside the block are overwritten by design; --dry-run shows what would be lost, and the marker text says where the real edit belongs. The block below carries an illustrative revision and hash.

markdown
<!-- maxims:begin @Octocat/rules sha=77769dc1e2b3a4c5d6e7f8091a2b3c4d5e6f7089 version=1 -->
- Use when a call leaves the process, however small - give it a timeout, since a hang is worse than a clear failure (detail: ~/.agents/maxims/store/octocat/rules/memories/prefer-timeouts-to-hangs.md, a1b2c3d)
<!-- maxims:end @Octocat/rules -->
  • The rule file is a real file on every harness, never a symlink. A rule file that silently never loads is the failure maxims exists to prevent; the design decision records the reported Claude Code behavior behind it.
  • Strategy A writes one file per source, maxims-<source> plus the harness's suffix; strategy B writes one block per source. Removal is a file delete or a block cut, provenance is visible, and two sources never fight over one file.
  • Markers are HTML comments matched at line start only. Every target is markdown; a marker quoted inside someone's fenced code block is not a marker.
  • The begin marker carries the revision installed, whole. A GitHub or git source's is its 40-hex commit id; a local directory's is sha256: and the 64-hex hash of its tree.
  • The begin marker ends in the version of the block grammar, version=1 today. maxims reads only its own version; a block behind a marker of another version, or of none, holds the file with exit 4 rather than being rewritten or deleted.
  • Everything outside the marker pair is preserved byte for byte. A user may keep hand-written rules in the same file.
  • Rule lines are sorted by memory name. Two machines with the same source produce the same file, and "nothing changed" is detectable.
  • --> in a description is escaped, and a token a harness would expand (Claude Code's and Gemini's @path imports) is wrapped in backticks. An unescaped one would end the comment early or read a file into context; an undocumented syntax is escaped conservatively.
  • On Claude Code the marker pair may be verbose; elsewhere it shrinks to one line. Stripping of block-level HTML comments before injection is verified only for Claude Code, so provenance is free there; on every other harness it is not known to be stripped, so the markers are assumed to cost tokens.
  • A description longer than 300 characters is cut with an ellipsis. A rule file is a budget, not a document.
  • Where the format requires frontmatter, maxims owns it and regenerates it with the block. The frontmatter is what keeps the file always-loaded on Cursor and Copilot; the catches below name the keys.

Per-harness catches ​

  • Claude Code. disableAllHooks is read after settings precedence applies, so a true in your user settings silences a project hook unless the project's settings say false. list reports the tier the first of .claude/settings.local.json, .claude/settings.json and ~/.claude/settings.json that sets the key produces, whichever scope you installed into; managed settings and --settings are not read.
  • Claude Code's unreadable settings. Claude Code's settings files are strict JSON: a file with a // comment, a trailing comma, invalid JSON, or disableAllHooks as something other than true or false is skipped, and the other layers stay in effect. So a broken file is hooks off only for a hook registered in it, reported like Codex's unreadable config below; elsewhere in the walk it is left out.
  • Claude Code's JSONC settings. The hook writer refuses a settings file holding a comment or a trailing comma (exit 4, naming the construct and its line) rather than registering a hook Claude Code would never load. Remove the construct, then run sync.
  • Codex. [features] hooks is read from each .codex/config.toml from the directory Codex runs in up to the project root, nearest first, then the user config.toml; the first that sets it decides, and hooks are on when none does. A project-local hook runs once its .codex layer is trusted, a non-managed one only after Codex's /hooks trusts it. list reports tier 2 when the flag is false; maxims never writes it.
  • Codex project trust. A project's .codex/config.toml counts only where the user config.toml marks its directory, or the project root, projects.<path>.trust_level = "trusted", since Codex applies it only then. An untrusted project's layer is skipped whole, broken or not.
  • Codex's unreadable config. A config.toml in the user layer or a trusted project layer that does not parse, or sets hooks to a non-boolean, is read as hooks off whatever the other layers say: doctor says so in a warning, list beside the tier, and sync in a notice on a run that renders a stale Codex block. Nothing is written to it; fix the file by hand.
  • Codex instruction files. Codex prefers AGENTS.override.md over AGENTS.md, but its two loaders treat a blank override differently. Its home loader skips it and reads ~/.codex/AGENTS.md, so the block goes there. Its project loader takes it and reads nothing else from that directory, so the block goes into the blank override. Codex stops reading instruction files past 32 KiB combined by default.
  • Cursor. A plain .md in .cursor/rules is ignored, so the file is .mdc with alwaysApply: true frontmatter; without it the rule is silently conditional. Its sessionStart hook is fire-and-forget.
  • Copilot. applyTo: "**" is what keeps the instructions file always-loaded instead of path-scoped; a missing applyTo silently narrows the rule. The hook belongs to the CLI, so the IDE half stays tier 2.
  • Gemini CLI. A project hook is fingerprinted and must be trusted again whenever it changes. The hook must print nothing to stdout except one JSON object, so the staleness notice goes out as hookSpecificOutput.additionalContext, never as plain text. Its hook timeout is in milliseconds where the others use seconds.
  • Cline. Hooks are on by default, and the hook is an executable file named exactly TaskStart with a shebang. The "Enable Hooks" switch that turns them off lives in the editor's own storage, so maxims cannot read it and list reports tier 1 either way. Windows is not supported by Cline's hooks.
  • OpenCode. The harness targets OpenCode 2.0 (@opencode/cli 2.x), which reads the project's AGENTS.md and ~/.config/opencode/AGENTS.md and no instructions entry, so both scopes are blocks. Freshness comes from a plugin file maxims writes whole into the plugins directory, loaded with no config entry; it runs the sync when the plugin activates and on each later session. 1.x (opencode-ai) cannot load the file and is not targeted.
  • DeepSeek Harness. dsh renders every instruction file it finds into one 65,536-byte block and truncates the most specific file past it, so the writer refuses a rule file over 64,512 bytes (the rest is dsh's own framing) rather than truncating.
  • DeepSeek Harness hook. dsh has no per-project config discovery, so the hook is one @deepseek-ai/dsh-hooks-claude-code bridge row in $DSH_HOME/cordis.patch.yml whatever the install scope, pointing by absolute path at a maxims-owned $DSH_HOME/maxims-hooks.json. The bridge reads that file once at process start, so a new or changed row needs a dsh restart.
  • Codex, Gemini CLI, DeepSeek Harness. The block lands in a file inside the repo, so it is a committed artifact that appears in every diff and PR review; this is the strongest argument for -g on these harnesses.
  • Every hook. A repeated invocation within 60 seconds of the last quiet-mode sync exits as soon as it reads the stamp, so a harness that fires more than once per session does the sync work once.
  • MCP-eager harnesses. maxims bundles an MCP stub server, registered via the hidden maxims mcp-serve command, that exposes zero tools and runs one sync at process start. sync registers it in the file the mcp stub column names wherever the hook is wanted, in the same plan as the hook, so a harness that starts its MCP servers eagerly syncs at launch even with no hook.

Editing a hook registry is surgical everywhere: the writer parses the file, finds the maxims entry by its command prefix npx -y @vivswan/maxims sync, and updates it in place or appends it.

ruleeffect
the write is a temp file, then a renamea half-written registry has no representation
an unparsable config is never rewrittenthe run exits 4
a UTF-8 BOM ahead of the JSON is dropped by the next writeuntil then the file reads as unreadable, as a strict parser sees it
a comment or trailing comma the harness's own parser rejects is refused the same waya hook the harness would skip is never registered
formatting and comments outside the entry survive byte for bytethe user's own hooks and layout are untouched
Built from main at 5f6e985Source: docs/harnesses.md