Appearance
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.
| id | harness | tier | project target | global target | strategy | hook | stdout | mcp stub | markers | byte budget |
|---|---|---|---|---|---|---|---|---|---|---|
claude-code | Claude Code | 1, or 2 when disableAllHooks is true | .claude/rules/maxims-<source>.md | ~/.claude/rules/maxims-<source>.md | A | SessionStart entry in .claude/settings.json or ~/.claude/settings.json, async | plain | - | stripped | 4,194,304 bytes, 40,000 bytes path-scoped |
codex | Codex | 1, or 2 when features.hooks is false | AGENTS.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.md | B | SessionStart entry in .codex/hooks.json or ~/.codex/hooks.json, async | plain | - | counted | - |
gemini-cli | Gemini CLI | 1 | GEMINI.md block | ~/.gemini/GEMINI.md block | B | SessionStart entry in .gemini/settings.json or ~/.gemini/settings.json | json:hookSpecificOutput.additionalContext | - | counted | - |
copilot | GitHub Copilot | 1 | .github/instructions/maxims-<source>.instructions.md | ~/.copilot/instructions/maxims-<source>.instructions.md | A | maxims-owned file .github/hooks/maxims.json or ~/.copilot/hooks/maxims.json | json:additionalContext | - | counted | - |
cursor | Cursor | 1 | .cursor/rules/maxims-<source>.mdc | none | A | sessionStart entry in .cursor/hooks.json | json:additional_context | - | counted | - |
cline | Cline | 1 | .clinerules/maxims-<source>.md | ~/Documents/Cline/Rules/maxims-<source>.md | A | maxims-owned executable .clinerules/hooks/TaskStart or ~/Documents/Cline/Hooks/TaskStart | none | - | counted | - |
opencode | OpenCode | 1 | AGENTS.md block | ~/.config/opencode/AGENTS.md block | B | maxims-owned file .opencode/plugins/maxims.ts or ~/.config/opencode/plugins/maxims.ts | none | - | counted | - |
dsh | DeepSeek Harness | 1 | AGENTS.md block | ~/.dsh/AGENTS.md block | B | custom | - | - | counted | 64,512 bytes |
devin | Devin Local | 1 | AGENTS.md block | ~/.config/devin/AGENTS.md block | B | SessionStart entry in .devin/config.json or ~/.config/devin/config.json | json:hookSpecificOutput.additionalContext | .devin/mcp_config.json or ~/.config/devin/mcp_config.json | counted | - |
windsurf | Windsurf Cascade | 1 | .devin/rules/maxims-<source>.md | ~/.codeium/windsurf/memories/global_rules.md block | A project, B global | pre_user_prompt entry in .devin/hooks.json or ~/.codeium/windsurf/hooks.json | none | - | counted | project 12,000 bytes, global 6,000 bytes |
zed | Zed | 2 | AGENTS.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 block | B | none | - | .zed/settings.json or ~/.config/zed/settings.json | counted | - |
amp | Amp | 1 | AGENTS.md block, written into the first existing of AGENTS.md, AGENT.md, CLAUDE.md | ~/.config/amp/AGENTS.md block | B | maxims-owned file .amp/plugins/maxims.ts or ~/.config/amp/plugins/maxims.ts | none | .amp/settings.json or ~/.config/amp/settings.json | counted | - |
warp | Warp | 2 | AGENTS.md block, written into the first existing of WARP.md, AGENTS.md | none | B | none | - | ~/.warp/.mcp.json | counted | - |
pi | Pi | 1 | AGENTS.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.MD | B | maxims-owned file .pi/extensions/maxims.ts or ~/.pi/agent/extensions/maxims.ts | none | .pi/mcp.json or ~/.pi/agent/mcp.json | counted | - |
tieris the declared tier; the config key that demotes it to 2 follows when the definition names one.project targetandglobal targetare the file written for a project install and for-g;nonemeans 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.strategyA 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.hookis 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;custommeans the definition writes its own files, named in the catches below.stdoutis how sync's output reaches the agent:plaintext becomes context, ajson:value names the key inside the one JSON object the harness reads,nonemeans the hook passes nothing of sync's on, and-means no declared stdout variant, a custom hook or no hook.mcp stubis the MCP servers file, per scope, where sync registers the bundled stub server whose start runs one sync; it follows the same--add-hookintent as the hook, andremovetakes it back with the last source.-when the definition names none.markersisstrippedwhen the harness drops HTML comments before injection, so the marker pair is free, andcountedwhen they ride into context.byte budgetis 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--pathsfile 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
| tier | meaning | freshness |
|---|---|---|
| 1 | a rule file plus a hook that runs the sync | loads every session and refreshes itself |
| 2 | a rule file, no hook | fresh via any hooked harness on the machine or the MCP stub; manual otherwise |
| 3 | unsupported | no 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=1today. 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@pathimports) 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.
disableAllHooksis read after settings precedence applies, so atruein your user settings silences a project hook unless the project's settings sayfalse.listreports the tier the first of.claude/settings.local.json,.claude/settings.jsonand~/.claude/settings.jsonthat sets the key produces, whichever scope you installed into; managed settings and--settingsare 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, ordisableAllHooksas something other thantrueorfalseis 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] hooksis read from each.codex/config.tomlfrom the directory Codex runs in up to the project root, nearest first, then the userconfig.toml; the first that sets it decides, and hooks are on when none does. A project-local hook runs once its.codexlayer is trusted, a non-managed one only after Codex's/hookstrusts it.listreports tier 2 when the flag is false; maxims never writes it. - Codex project trust. A project's
.codex/config.tomlcounts only where the userconfig.tomlmarks 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.tomlin the user layer or a trusted project layer that does not parse, or setshooksto a non-boolean, is read as hooks off whatever the other layers say:doctorsays so in a warning,listbeside the tier, andsyncin 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.mdoverAGENTS.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
.mdin.cursor/rulesis ignored, so the file is.mdcwithalwaysApply: truefrontmatter; without it the rule is silently conditional. ItssessionStarthook is fire-and-forget. - Copilot.
applyTo: "**"is what keeps the instructions file always-loaded instead of path-scoped; a missingapplyTosilently 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
TaskStartwith a shebang. The "Enable Hooks" switch that turns them off lives in the editor's own storage, so maxims cannot read it andlistreports tier 1 either way. Windows is not supported by Cline's hooks. - OpenCode. The harness targets OpenCode 2.0 (
@opencode/cli2.x), which reads the project'sAGENTS.mdand~/.config/opencode/AGENTS.mdand noinstructionsentry, 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-codebridge row in$DSH_HOME/cordis.patch.ymlwhatever 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
-gon 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-servecommand, that exposes zero tools and runs one sync at process start.syncregisters it in the file themcp stubcolumn 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.
| rule | effect |
|---|---|
| the write is a temp file, then a rename | a half-written registry has no representation |
| an unparsable config is never rewritten | the run exits 4 |
| a UTF-8 BOM ahead of the JSON is dropped by the next write | until 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 way | a hook the harness would skip is never registered |
| formatting and comments outside the entry survive byte for byte | the user's own hooks and layout are untouched |