Repository navigation
levain 0.6.0
Team context: one project's decisions shared across a team of engineers, with git as the wire and a Claude Code hook that puts a recorded decision in front of the agent at the edit it governs.
Added
-
levain team(init,join,record,retire,sync,status,verify,consolidate,export,install,doctor,member add,pack-sync). An orphanlevain-ledgerbranch in the project's repository holdsteam.toml, one append-only SHA-256-chained JSON-lines file per author per clone (ledger/<author>/<device>.jsonl, so two clones never write one file), and the owner-generatedPROJECT.md. Every read and write goes through a private, lockedgit worktreeof that branch under<git common dir>/levain-team/. Entry schema v1:v id ts author agent session type kind mode paths owner words summary reason recheck supersedes refs pack rule_id prev hash; typesdecision constraint finding question tension ack retire; a ruling needs an owner and the decider's own words. Refused at write: missing fields, unknown supersede targets, text that looks like a secret, field sizes above the limits anneal-memory's import enforces. -
A Claude Code
PreToolUsehook (Edit|Write|MultiEdit|NotebookEdit) and aSessionStartline, wired bylevain team init/join/installinto.claude/settings.local.jsonof every working tree of the clone (git-excluded; it names your interpreter). Someone else's ruling denies the first edit per session with the full record as the reason, and the retry is allowed and recorded as anack;blockmode always denies; atensionon the path always denies; practices, findings, questions and your own rulings are context only. The hook never emitsallow(that would bypass the user's own permission prompt). Every failure is fail-open with one visible[team] ledger unavailable: ...line.LEVAIN_TEAM_SESSIONSTART_RULINGS=offdrops the per-ruling lines from the session-start context so enforcement rests on the edit-time hook. -
Authority. Only an entry's own author, or the
team.tomlowner, may supersede or retire it, and replacing a ruling takes a ruling (or a retire) carrying the decider's words; links that break these rules are reported and ignored, whether written through the CLI or pushed by hand. Only the owner creates the ledger, consolidates the canon, seeds pack rules or changes membership. -
The ledger is read from git history, not the working tree: every line ever added under
ledger/on thelevain-ledgerref, in the order it was added, each attributed to the author email of the commit that added it. Removing or rewriting a line changes nothing and is reported; a line added to a file that belongs to someone else (the owner, for pack files) is not enforced and is reported. Reads take no lock and always see a completed commit. Ifteam.tomlat the tip does not parse, the newest version that does is used, with a warning.levain team verifyalso reportsteam.tomlandPROJECT.mdchanges not made by the owner of the version before them. -
judgment.tomlin a pack:[pack] name/versionplus[[rule]] id paths kind owner words reason recheck mode.levain team pack-sync(andinit --pack) seeds the rules as entries authoredpack:<name>; an upgrade supersedes changed rules and retires dropped ones; a rule the team retired is not resurrected by re-syncing unchanged text. -
levain team export --jsonl: every verified entry, per file in file order, for anneal-memory's team import (--in-forcefor the current view).tests/test_team_golden.pypins the canonical-JSON and hash bytes. -
Optional anneal import. At session start, if the installed anneal-memory ships
anneal_memory.teamand a store is configured (--anneal-db, or<repo>/.levain/memory.db), the ledger is imported once per ledger tree with--link-authority <owner>; otherwise it is skipped andlevain team doctorsays which.LEVAIN_TEAM_ANNEAL_IMPORT=offdisables it. -
A forged ledger line is one reported problem, never a failed read. Lines are parsed through one bounded entry point: nesting past 32, lines past 256K characters, stray closers, CPython's integer-digit limit and lone surrogates are each refused for that line alone, its neighbours verify exactly as if it were absent, and the PreToolUse hook therefore cannot be stopped by one forged line on the
levain-ledgerbranch. (A line nested ~200,000 brackets deep otherwise raises RecursionError out of every read, and has run for minutes injson.loadson Windows.) The misfiled-entry filter is linear in a hostile file. Review: three lineages over three rounds, each round's pre-registration in the fan-in log.
Changed
- Levain now requires anneal-memory 0.9.36 or later (
anneal-memory>=0.9.36,<0.10), and 0.9.36 is the version it is tested against: it is the first published release carryinganneal-memory team-import, the other end oflevain team export --jsonl. No migration-manifest entry and no schema changed, so existing installs' seeds are unchanged.
Known limits
- Identity is the author email on each commit, mapped through
team.toml. Anyone can set it, so levain detects and reports; it does not authenticate. A teammate who sets their git email to the owner's can act as the owner. Authentication is the git host's job (branch protection, required signed commits); levain does not check signatures. - Any member can record a
tensionon a broad path, which stops every edit there until its author or the owner retires it. That is deliberate (a stop is cheap to lift and expensive to miss) and visible inlevain team status. levain team initmust be run by the owner named in--owner. A clone whose.gitwas copied from another machine relinks its ledger worktree to itself, and two clones sharing a device id refuse to rebase rather than drop an entry: runlevain team join --new-devicein the copy.- Bash writes are not intercepted. Two rulings that disagree are not detected; a
tensionentry is how a team marks one. A nested repository or submodule is governed only by its own ledger. - Freshness is a fetch at session start and at most every
fetch_interval(default 300 s) from the hook. - POSIX only (
fcntl). Claude Code started with--bareloads no settings hooks, so the team layer is absent there. levain doctordoes not yet check the team layer;levain team doctordoes.- Needs git 2.31 or later (
rev-parse --path-format). - The ledger branch is kept linear (levain only rebases and cherry-picks). A merge commit pushed to it by hand is reported, and lines that exist only in a merge resolution are not read.
- If two clones that share a device id have BOTH committed entries,
levain team join --new-devicedoes not move the copy's already-committed entries to its new file: reset the copy's ledger to the remote (git -C .git/levain-team/worktree reset --hard origin/levain-ledger) and record them again. - Two
pack-syncruns from two different owner clones at the same time can leave two versions of one pack rule in force until the nextpack-sync(one run at a time per clone is enforced). - If a member's email changes in
team.toml, lines they added under the old email stop being enforced until the change is reverted or the entries are recorded again. - When a sync must replay local commits after a
team.toml/PROJECT.mdconflict: in a clone whose.gitwas copied from another (shared device id) AND that hasrerere.enabledwithrerere.autoupdate, a local entry can be skipped as "already upstream". Runlevain team join --new-devicein a copied clone, and do not enable rerere autoupdate for the ledger worktree. - In that same replay, commits whose clocks were skewed can replay out of order, and a cherry-pick that fails for an unrelated reason (a stale
index.lock) is reported with the wrong cause. The sync stops and nothing is lost;levain team doctorand the worktree at.git/levain-team/worktreeshow the state. - A member with push access who recommits another member's line under their own name makes that line count as theirs, so it is not enforced, and reports it. Part of the trust boundary above.