Appearance
Project layout & what to commit
mnema init writes this layout into your project:
my-project/
├── AGENTS.md # operating manual for agents (generated by init)
├── .gitignore # pre-seeded: ignores only the local cache
├── .gitattributes # audit log merges append-only (merge=union)
└── .mnema/ # everything Mnema owns
├── mnema.config.json # project configuration (versioned)
├── audit/ # append-only event log (versioned by default)
│ └── current.jsonl
├── keys/ # committed public verification material only
│ ├── project.hmac-id # non-secret fingerprint of the HMAC secret
│ └── <actor>.<fp>.pub # per-machine Ed25519 public key
├── state/ # local cache — gitignored
│ └── state.db # SQLite (FTS, tasks, runs, audit metadata)
├── backlog/ # one .md per task, foldered by workflow state
│ ├── DRAFT/MYAPP-1.md # carries its epic_key / sprint_key link
│ ├── READY/
│ └── …
├── sprints/ # one .md per sprint, mirrored from the DB
├── roadmap/ # one .md per epic and per decision (ADR)
├── memory/ # recorded facts, mirrored to .md
│ ├── <slug>.md # project-global memories at the root
│ ├── <scope>/<slug>.md # scoped memories under a scope folder
│ ├── decisions/ # human-curated ADR notes (own INDEX)
│ └── notes/ # human-curated free-form notes (own INDEX)
├── skills/ # recorded skills, mirrored to .md
│ ├── default/ # tool-shipped seed skills
│ └── authored/ # human/AI-authored skills
├── commands/ # versioned slash-command definitions (.md)
├── config.local.json # optional personal overrides — gitignored
└── workflows/
└── default.json # active state machineWhat to commit, and the dirty-tree question
Mnema is a SQLite-first store with a markdown mirror, and the split between the two decides what belongs in git:
- Commit it. The markdown mirror —
backlog/,roadmap/,sprints/,memory/,skills/— and theaudit/log are the version-controlled record of the work. They survive a fresh clone and are what a teammate (or another machine) reads:mnema syncrebuilds tasks, epics, sprints, decisions, observations and memories and skills from the committed markdown into a fresh database. A few knowledge fields are not carried by the mirror and so are lost on a clone-rebuild — memoryscope, skill version history, andcreated_by/provenance (see Skills & memory).mnema.config.jsonand the activeworkflows/*.jsonare versioned too. So is.mnema/keys/— but note it holds only public verification material (the HMAC fingerprint and per-machine.pubfiles), never a secret; committing it is what lets any clone verify the chain. - Ignore it.
.mnema/state/is local: the SQLite cache, the sync buffer and attachment blobs — all derived and rebuilt from the markdown withmnema sync. The optional.mnema/config.local.json(per-repo personal overrides) is local too.mnema initpre-seeds the.gitignorelines for exactly these — nothing more. - Never in the repo at all. The integrity secrets — the per-project HMAC key and the per-machine Ed25519 private key — live outside the repo under
~/.config/mnema/(mode0600) and are never written into.mnema/. That out-of-repo placement is what puts them beyond an in-repo agent's reach (see the integrity model); back that directory up separately, because a lost private key means a new machine re-mints its own (old signatures still verify from the committed.pub).
Because the mirror is versioned, those .md files do change on every mutation and show up in git status — that is the trail, not noise to suppress. The one place this used to bite was the append-only audit/*.jsonl: two branches that both recorded activity would conflict on the tail. mnema init writes a .gitattributes that gives the audit log git's built-in union merge driver, so a merge keeps both sides instead of raising a conflict (no .git/config setup needed). If two divergent histories ever interleave the hash chain, mnema doctor flags it and mnema sync rebuilds the cache from the markdown. If you would rather treat the whole store as a private local cache, add .mnema/ to your .gitignore instead — you keep a clean tree but give up the cross-clone history that is part of what Mnema offers.
Keeping the trail out of your code diffs
That trail churn is legitimate, but you rarely want it mixed into a code diff. mnema commit keeps them apart without hiding either:
bash
git add src/rate-limit.ts # stage the code you want to commit
mnema commit -m "feat: add rate limiting"
# → commit 1 chore(mnema): update trail (.mnema/ only)
# → commit 2 feat: add rate limiting (your staged code)It makes two commits — the .mnema/ trail first (default message, overridable with --trail-message), then your code. The trail is staged for you; your code is whatever you already staged with git add (or git add -p) and is committed straight from the index, so unstaged edits and partial staging are preserved — the helper never git adds your code, never amends, and never pushes. An empty bucket is skipped rather than committed. Use --trail-only to commit just the trail. It refuses to run mid-merge/rebase so it can't leave a half-finished state.
Log growth and rotation
The audit log rotates by month: current.jsonl receives new events, and at the first write of a new month the previous file is rolled to audit/YYYY-MM.jsonl. The hash chain runs continuously across those segments — the first line of each file links to the tail of the one before it — and mnema doctor verifies the whole chain end to end, segment boundaries included. Deleting or editing an archived month is caught the same as tampering with current.jsonl.
Growth is modest and bounded per month: one compact JSON line per mutation (JSONL compresses well under git's packing), and completed-task markdown stays as small per-task files. Even on a large project history, verifying the full chain (mnema doctor) takes single-digit milliseconds and a mirror rebuild (mnema sync) well under a second — measured by pnpm bench:scale. The audit_strategy and audit_retention_months config keys are reserved for a future compaction pass; they are accepted today but not yet enforced — the chain is append-only, so nothing is dropped regardless of their value.