You do not hand-write a trailer for every commit. Most commits should carry no record at all. Add one only for a decision the diff cannot recover: an external constraint, a rejected alternative, a warning, or a verification gap.
CommitLore never invents a record. The commit-msg hook validates a record that is already present; it does not create one, and it does not silently add one.
Ask the agent to commit normally and preserve only the decision context the diff cannot explain:
Commit this change. Add a CommitLore record only if the diff cannot recover an important constraint, rejected alternative, warning, or verification gap.
Most commits should still carry no record. The MCP server states the prepare →
verify → stage procedure in its instructions on every connection, so any host
that surfaces those receives it without a file being written into the
repository. commitlore init --agents-md also writes a marked CommitLore
section of AGENTS.md, for a host that reads that convention and not MCP
instructions; it replaces nothing else in the file. Claude Code also receives the fuller
skills/commitlore-commits/ workflow. The
hook validates any record the agent adds.
Capture normally runs with the agent in the loop: it prepares, drafts and
verifies, and the policy's mode decides whether anyone is asked before
staging. A repository can go one step further and consent once, for every
commit, to capture with nobody in the loop at all:
commitlore auto on
writes the setting to .commitlore-policy.json (commitlore init asks about
it once where no policy file exists yet, and commitlore auto status reports
what is set). Where that is set — mode "auto" beside unattended: true —
an agent host may call commitlore capture --unattended (or the MCP prepare
tool with its unattended argument) to prepare, verify and stage without any
prompt. The Git hooks then attach the staged record. They do not begin capture:
an ordinary git commit has no session transcript, so it creates no pending
transaction unless the host initiates capture before the commit. Anywhere else
the declaration is refused at prepare: consent is a repository setting, not a
caller's say-so (ADR-0030, #511). The setting is honoured in auto mode only
— suggest exists to ask, and off captures nothing; commitlore auto on
sets both coherently rather than producing a file the resolver rejects.
The file is committed with the repository: turning it on applies to everyone who clones it.
Because that file is committed, a contributor who needs a different answer used to have one route — edit it — and their worktree stayed modified forever, which is enough to stop a release script that refuses a dirty tree. So a second file may sit beside it:
.commitlore-policy.local.json per key, wins
.commitlore-policy.json repository default
built-in defaults
commitlore auto on --local and auto off --local write it, and once it
exists it is the file commitlore auto writes — the tracked file is left as
the repository wrote it. Keep it out of version control; nothing adds a
.gitignore entry for you.
Precedence is per key. An overlay setting only unattended leaves mode and
max_records_per_commit as the repository set them, so a later change to the
committed file still applies. It may set a value in either direction: the
consent that matters is expressed at commit time by a person on their own
machine, and what keeps an unattended record from directing is the drafted
stamp and the claim cap in grading, not this switch.
Two things make the precedence answerable rather than ambiguous. The policy
identity hash is computed over the effective policy whenever an overlay is
present, so a record prepared under one is stamped with the policy that
actually produced it — a repository with no overlay keeps exactly the digest it
had. And commitlore doctor reports the disagreement, naming both files, both
values and the one in force.
Because the setting lives with the capture policy, the policy identity covers it: a file edited between stage and commit is detected like any other policy change (ADR-0021 §7). It is off unless a repository sets it; shipping the switch is not flipping it.
What it does not change: every record staged without a person reading it is
stamped Provenance: drafted and served as [claim], never [directive],
and the commit-msg hook's credential scan still runs before the commit exists.
commitlore harvest builds a prompt contract from a session transcript and a
staged diff, and commitlore harvest-verify checks a draft against them. They
support drafting, not automatic commits. Interactive record building is not
implemented.
# 1. Build the prompt contract for the session and hand it to the agent.
commitlore harvest --transcript session.jsonl --prompt-only
# 2. The agent answers with a draft. Check what survives the transcript and diff.
commitlore harvest --transcript session.jsonl --draft draft.json
commitlore harvest-verify --transcript session.jsonl --draft draft.jsoncommitlore harvest --diff <file> reads a diff other than the staged one, and
--out <file> writes the output somewhere other than stdout.
commitlore capture runs the same idea as one transaction — prepare, verify,
then stage a record from a transcript and draft, with no trailer syntax to
write. commitlore pending inspects capture transactions that have not reached
a commit yet, and commitlore capture gc removes expired ones.
Whatever route produces the draft, the record only becomes real when the commit-msg hook validates it on the commit.
As an escape hatch, a human can write ordinary Git trailers by hand. The trailer
grammar is in protocol.md; the normative rules are in
spec/SPEC.md.
- It lives in Git, with an identity (
Record-Id:) that survives a rewritten commit hash, and a lifecycle (Supersedes:,Expires:). - Before a path is edited, the next agent receives only the decisions still in
force — through MCP, or through the
PreToolUsehook. - A decision that was later superseded or expired does not reach the agent as if
it still stood.
commitlore stalelists the ones that no longer apply.
A record marked Provenance: drafted was produced by the capture pipeline and
staged without a person reading it. Every quote it cites is checked to occur,
character for character, in the transcript or the diff it came from — so a
drafted record cannot cite something nobody said.
That is a narrower guarantee than it sounds, and worth stating exactly: the
check establishes that the quote is present, not that it supports the
trailer it is attached to. Limit: Redis is forbidden citing the sentence
Redis is required passes, because the sentence is really there. Reading the
quote against the claim is judgment, and nothing in this pipeline performs it.
That is why no drafted record is ever delivered as [directive], however
trusted its author is (ADR-0030): it arrives as [claim], information to weigh
rather than an instruction to follow.
Endorsing one is a new record, never an edit. A commit message cannot be changed without rewriting history, so the way to stand behind a drafted record is to write one that supersedes it:
feat: stand behind the cache decision
Warn: session entries must stay under 4KB
Ruled-out: shared Redis cache | ops refuses another stateful dependency
Supersedes: r-promo01
Record-Id: r-promo02
Provenance: authored
The lifecycle fold retires the drafted record, and the endorsement is graded on its own author:
before [claim] r-promo01 session entries must stay under 4KB
after [directive] r-promo02 session entries must stay under 4KB
Nothing was added for this — Supersedes: and the fold already did it.
Never endorsing anything is the supported path. A repository where nobody
promotes serves every record as a claim, which is what an installed CommitLore
serves today unless a directive author string is configured; that default match
is not identity proof. Signature mode additionally requires Git verification
and a repository-local commitlore.trustedSigner match on Git's %GF
fingerprint; an absent, empty, or unreadable allowlist authorizes nobody.