Appearance
Design decisions
Each decision is one line, what was decided and why it will not be re-argued. The page the decision shapes owns the details; this page owns the why.
Shape
- The rules file is the carrier, not hook stdout. Stdout is conversation content that cannot be inspected between sessions; the file on disk is what a harness re-reads after a compaction. Stdout is for notices only.
- One canonical home,
~/.agents/maxims/, with links everywhere else. The only arrangement in which a stale body cannot exist;--copyis the escape hatch. Details on the files page. - State drives everything.
state.jsonis the instructionsyncexecutes, not a receipt of whatadddid;syncandupdateare real verbs because state, not a command line, is the source of truth. - State holds intent only. A cached copy of the filesystem or a hook registry can disagree with reality the moment a user hand-edits a file, so nothing of the kind is stored; recovery from any crash is
syncagain. - Hooks are one per harness, not one per source. The hook carries no source and no filter, so the tenth source adds no latency and the selection cannot drift from what
addrecorded. --add-hookdoes not imply--rule. Syncing bodies into a project and publishing one-liners into the always-loaded layer are separate choices, recorded per source.syncowns the cooldown and fetches past it;updateforces the fetch. The alternative, a strictly local sync, needs two hook commands per session to keep the freshness guarantee.- No daemon, no write-back, no skill folders, no vector store. maxims runs at
add,sync,update,remove, and session start; authoring stays in the source repo. - Compatibility lives only in a document's migration ladder. A shape change appends a step to the registry, named for what it changes, with the fixture it migrates away from; code outside the ladder knows only the current shape, so nothing elsewhere is kept for an older version. The migrations section owns the ladder.
- Two scopes only, project and user;
-ois an escape hatch, not a third scope. Every harness has a project target and most have a user target, so two scopes cover the matrix;-oexists for the one rule file no harness owns. The install page owns the shapes. - User defaults live in
config.jsonbeside state, never in state. A default is a preference about future commands, not intent about installed sources; keeping it out ofstate.jsonmeans the intent-only rule needs no exception. This replaces the earlierstate.configshape. The defaults section owns the keys. - A project commits
.agents/maxims.lockandinstallreplays it. State is per machine, so a fresh clone would otherwise start with no rules; the lock is what a teammate'sinstallreads. Sorted keys and no timestamps keep its diff to what changed. The share page owns the file. disablewithholds one memory at one scope andenablerestores it, without touching a source's intent. Removing the memory would lose the selection and the rename; a per-scope disabled list in state keeps both and makes the withholding visible inlist. The verb table owns the verbs.
Identity and refusal
- Name is identity. A content hash as identity would make an upstream one-word edit look like a second rule; the hash is recorded and shown short for change detection only.
- A cross-source name collision is an error with a rename offered. First-wins-and-skip would leave a rule the user believed was loaded and was not; the rename keeps both live and makes the conflict visible in the name.
- Exceeding the rule cap refuses the whole source. Truncating would drop the last rule silently and make the result depend on sort order.
- A dangling
[[wikilink]]is an unmet dependency and aborts the add. Installing a rule whose prerequisite is absent is the same incomplete install as a collision or a cap breach. - Codes 3, 6, 7, and 8 write nothing at all. A partial install is a set of rules the user believes is loaded and is not.
--quietexits 0 on every outcome. A failing session-start hook renders an error notice in the transcript, which the user would see at every start.- Never auto-remove on a fetch failure. A rate limit and a deleted repo look alike from the client; the last good copy stays and a notice is printed.
--rename <upstream>=<local>resolves a collision without a prompt. A scriptedaddthat meets exit 6 has no way forward otherwise; the flag records the same entry the prompt would. The collision section owns it.- Hidden characters in a memory refuse the install unless
--allow-hidden. A character the reader cannot see can carry an instruction past the plan, and the plan is the review gate. The hidden-character rule owns what counts and the exit code. metadata.internal: truehides a memory unless the installer opts in. A source repo can hold rules for its own maintainers without a second repo; the marker is in the file, so it travels with it. The contract owns the field and the opt-in variable.
Sources and layout
memories/is the layout convention,--fromnames any other folder,--full-depthscans wide. Autodetection by frontmatter would turn any README with aname:field into a rule.@owner/repofetches,.or a path installs locally, exactly asnpx skillsdoes. The standing principle is that whereskillshas a settled behavior for the analogous case, maxims copies it rather than inventing.--pin <sha or tag>ships. It sets a field the GitHub source shape already carries, so it is nearly free and needs no special removal path.--linkis opt-in for local sources and default for.. A copy survives a moved dotfiles folder where a dangling symlink feeds nothing; a working-tree install exists to test unpushed edits, so it is live.- A live source has no fetch record. The tree is the record, so the cooldown and
updatehave nothing to act on rather than a special case to guard. --pathsscoping is opt-in, never a default. A scoped rule loads only when matching files are touched, which breaks "every session"; it is stored per source, in the target harness's own syntax.--cooldownand--capwriteconfig.json, not state. They are machine-wide defaults, so they follow the user defaults decision above rather than living beside intent or on a hook command line; unlike the other flags, typing one persists it.- Fetching is anonymous by default;
--authopts a source in and is recorded asintent.auth. Reading aghtoken nobody asked to use turns a public-repo install into an authenticated request, and a token read is a side effect the plan never showed. The fetch section owns the order of attempts and the environment variables. - Any git URL is a source, stored verbatim, cloned as typed, with no tarball fallback. Host-specific resolution would need a resolver per forge; a clone works on all of them. The canonical home owns its store path.
- The default fetch is a sparse, shallow clone of the memories folder; a whole-repo tarball only when
gitis missing. The memories folder is a fraction of most repos, and a clone needs no per-host API.MAXIMS_FETCH_TIMEOUTbounds a fetch so a hung remote cannot block a session start. .agents/memories/holds project bodies on every harness. One convention for every harness, mirroring wherenpx skillsputs project skills.
Harnesses
- Every harness in the matrix ships in the first release. Each built-in is declared from a spec, the shape adding a harness owns, and a user's own
harnesses.jsonentries take the same shape. dsh adds aquirks.tsbeside its spec for what the data cannot say. - Pi ships with a file hook, not a deferred extension package. An extension file in Pi's extensions directory receives
session_startand runs the sync, the plugin-file pattern OpenCode uses. - Codex's hooks flag is read-only detection. Hooks are on by default there; maxims reads
hooks = falseto report tier 2 achieved and never writes the flag. - Cursor gets a session-start hook, not a per-prompt one. Current Cursor exposes
sessionStart, so the earlier per-prompt shape is gone. - Every quiet-mode sync is debounced by 60 seconds, on every harness. A harness that fires more than once per session does the sync work once, and the rule needs no per-harness exception.
-gon a harness with no global target warns and skips. Cursor's user rules are UI-stored; refusing the whole command for one harness would block the other harnesses.- Copilot CLI is tier 1; the IDE half is tier 2. The CLI ships a
sessionStarthook; the IDE has none, and an editor folder-open task to promote it was rejected as too invasive. - OpenCode is tier 1 through a maxims-written plugin file, and the harness targets OpenCode 2.0. Whole-file write and whole-file delete are simpler than a registry edit because nothing else lives in the file. 2.0 loads the file from its plugins directory with no config entry and passes over the 1.x plugin shape without a word, so the 1.x line is not targeted.
- DeepSeek Harness is tier 1 through its Claude Code hook bridge, caveats recorded. The bridge row is mounted machine-wide and its config points at a maxims-owned file, never into
.claude; the dsh catch owns the caveats. - The MCP stub ships behind the hidden
maxims mcp-servecommand. It is the only mechanical tier 2 answer, for harnesses that start MCP servers eagerly and have no hook; the matrix shows where it is registered. - The self-refresh line ships beside the stub. Zero artifact and universal by construction, since every tier 2 harness has an always-loaded layer by definition; not written where a hook exists.
- Shell-rc lines, OS schedulers, editor tasks, and git hooks are rejected as freshness fallbacks. None fires on the agent's session start, each forks per platform or writes into a file the team shares, and the one hook already refreshes every harness. The prior art table places them beside the tools maxims did borrow from.
- The rule file is a real file, never a symlink, on every harness. A rule that silently never loads is the failure the tool exists to prevent. The spec reports, unverified here, that Claude Code skips a symlinked rule file pointing outside the working directory.
- A hook registry, a shared rule file, or a harness config file the user owns is edited through its symlink, never replaced. A registry, a shared
AGENTS.md, or acordis.patch.ymllinked into a dotfiles checkout stays in the checkout, since replacing the link would cut it out silently; the apply step owns the mechanics.
Tooling
- No telemetry.
skillshas a--metadataflag; maxims has no analog, deliberately. - The hook command carries no version pin. Fixes reach hooked sessions without a re-add; the price is that a bad release reaches every session start, which is why the fail-soft last rung requires a dedicated test.
listships in the first release. It is the read command for state, and everything it shows past intent is re-derived at call time, so it reports a hand-edited registry honestly.--jsonand--dry-runare on every verb. Every write goes through one plan, so both are structural rather than per-verb features.doctorchecks what a harness would load;--expectand--jsonmake it a CI assertion.listreports state;doctorreads the harness's own loading rules against disk, which is the only way to catch a rule file at the wrong path. The doctor section owns it.init [name]ships. Scaffolding one contract-valid file is cheaper than explaining the contract; the init section owns what it writes.linkandunlinkedit a source's harness list without a refetch;add -astill replaces. Adding a harness is a one-field intent change, and a refetch for it would spend network and a cooldown for nothing. The verb table owns them.- Non-interactively,
--yesis implied foraddandsynconly;removewithout an explicit-yaborts with exit 1. Both implied verbs are idempotent and reversible; a deletion is not.remove --allpasses because--allspells-yout. The non-interactive table owns the matrix.