Git for AI agent memory — version control for what your AI knows about your codebase
Your AI coding agent's memory is the file you never wrote, the conversation that never happened, and the architectural decision your team made three weeks ago that nobody can find anymore. Git solved this problem for code 20 years ago. Sverklo solves it for what the agent knows about the code.
The story that doesn't fit in `CLAUDE.md`
Last month, debugging an auth flow with Claude Code, the team made a deliberate decision: JWT verification happens in the middleware, not the route handler. The reason was specific — token validation needed to short-circuit before the route's body parser ran, which mattered for a CSRF protection added two weeks earlier.
Three days later, in a fresh session, Claude Code proposed moving JWT verification into the route handler. It hadn't seen the previous decision. Context was compacted. The agent had no way to retrieve "we decided X for reason Y."
The conventional fix is a CLAUDE.md file with project invariants. That works for stable rules ("never use any in TypeScript") but fails for evolving design decisions. CLAUDE.md either:
- Becomes a wall of every decision the team ever made (unreadable; agents skim past it)
- Gets manually pruned when decisions change (loses the why)
- Gets auto-pruned by some rule (which decision wins on conflict?)
None of these answer "what did we believe at this specific commit?" — and that's the question that comes up when triaging a bug introduced in some past version, or when a new engineer lands on the team and asks why the auth code looks the way it does.
The right axis is the commit graph, not the wall clock
Database researchers solved a closely-related version of this problem 30 years ago for transactional systems where you needed both "what is true now" and "what did we think was true on Date X." Richard Snodgrass's Developing Time-Oriented Database Applications in SQL (1999) is the textbook. The pattern: every row carries two time dimensions, and updating doesn't overwrite — it inserts a new row with new bounds and links it back to the old.
The wrong substitution: pin to wall-clock time. Code doesn't change continuously — it changes at commits. A "2026-03-15 14:32:18 UTC" timestamp on a memory is technically valid but operationally useless; the engineer asking "what did we think back then?" is asking about a code state, not about a date.
Sverklo's substitution: pin to git SHA. Every memory carries valid_from_sha and valid_until_sha. Updates don't overwrite — they insert a new row with the new content, set valid_until_sha on the previous row to the current HEAD, and link the new row back via superseded_by.
The four operations Git made boring, applied to memory
Snapshot
Every memory write captures the current HEAD as valid_from_sha. The memory is a fact-at-this-commit, not a fact-in-the-abstract. sverklo_recall with no flags returns the current view (memories where valid_until_sha IS NULL). sverklo_recall --at <sha> returns what was true at that commit — a literal time-travel query.
$ sverklo recall "auth flow"
[2026-04-15] JWT in middleware. Reason: CSRF ordering.
valid since main@abc123, still current.
$ sverklo recall "auth flow" --at def456
[2026-04-15] JWT in middleware. Reason: CSRF ordering.
valid abc123 → def456.
Branch
Memories pinned to a branch SHA persist with that branch. When you switch branches mid-session, the agent's memory of "what we believe" follows the code state. Branch-aware retrieval isn't a separate feature — it falls out of the bi-temporal model for free, because the SHA already encodes the branch context. (Branch-aware indexing for the search side of sverklo lands in v0.20; bi-temporal memory has been there since v0.10.)
Rollback / time-travel
$ sverklo recall "auth flow" --timeline
abc123 2026-04-15 "JWT in middleware — CSRF ordering"
def456 2026-04-22 "JWT in route handler — middleware ran for SSE
endpoints unnecessarily, +12% latency. CSRF
moved to a separate guard."
↑ superseded the previous memory
The pattern that's underrated in flat-overwrite memory systems: knowing why a previous decision was overturned is often more useful than knowing the current decision. A new engineer joining six months from now sees JWT in the route handler, runs sverklo_recall --timeline auth, and gets both the current truth and the why-it-changed. Conventional flat-overwrite memory deletes the why.
Merge
Two agents working on the same branch independently can both write memories. Sverklo's bi-temporal model makes the merge trivial: both memories enter the table, both get marked active. If they contradict (same pin/symbol, semantically opposed content), sverklo_memories --conflicts surfaces the contradiction so a human picks the canonical one. (Contradiction detection is a v0.20 feature in the works; the lineage tracking that makes it possible has been there since v0.10.)
What this enables that flat-overwrite memory can't
| Question | Flat memory | Bi-temporal git-pinned memory |
|---|---|---|
| "What is true now?" | ✓ | ✓ |
| "What did we believe at commit abc123?" | ✗ (overwritten) | ✓ |
| "Why was the previous decision overturned?" | ✗ (no lineage) | ✓ |
| "Show me the team's evolving understanding of authentication." | ✗ | ✓ (timeline) |
| "Two agents wrote contradicting memories — which is right?" | ✗ (last write wins) | ✓ (--conflicts) |
| "This memory contradicts current code. Which is wrong?" | ✗ | ✓ (drift detection) |
The schema is small enough to fit in one diagram
┌─────────────────────────────────────────────────────────┐
│ memories │
├─────────────────────────────────────────────────────────┤
│ id INTEGER PRIMARY KEY │
│ category TEXT (decision | preference | …) │
│ content TEXT │
│ │
│ valid_from_sha TEXT NOT NULL ← when fact became true │
│ valid_until_sha TEXT (NULL = current) │
│ superseded_by INTEGER REFERENCES memories(id) │
│ │
│ embedding_id INTEGER REFERENCES memory_embeddings │
│ pins JSON (file paths, symbol names) │
│ tags JSON │
│ │
│ created_at INTEGER (provenance, not retrieval) │
│ confidence REAL │
│ access_count INTEGER │
└─────────────────────────────────────────────────────────┘
INDEX idx_memories_current ON memories(valid_until_sha)
WHERE valid_until_sha IS NULL;
The partial index on valid_until_sha IS NULL makes "current truth" queries fast — most queries only want active memories. Timeline queries hit a different access path (full table scan filtered by SHA ancestry). Both are fast on the scale of memories per project (typically <5,000 even on multi-year codebases).
Why not just use git's own log?
Two reasons.
First, git's log is a record of code changes, not a record of beliefs about code. Many decisions are made without code changes ("we decided to migrate to Postgres next quarter, but we haven't started"). Git doesn't capture those. Bi-temporal memory does.
Second, git commit messages are unstructured prose. Searching them for "what did we decide about X?" is the needle-in-haystack problem grep handles badly — which is the same failure mode our retrieval bench documents. Sverklo's memory layer is structured: each memory has a category, a confidence, a tier, embeddings for semantic search, and the lineage. Retrieval is hybrid (FTS5 + cosine over an ONNX embedding) and pinned to the git SHA where the question is being asked.
The upside compounds when teams get bigger
For solo developers, bi-temporal memory is a nice-to-have — most of the value can be replicated with a careful CLAUDE.md. For teams, it's load-bearing:
- New engineer onboarding: the timeline view is a literal training corpus.
sverklo_recall --timeline authreturns the team's evolving understanding in chronological order. New engineers pattern-match faster from real history than from documentation. - Multi-agent workflows: Agent A on a feature branch writes a memory; Agent B in a code review session sees it tagged with the branch SHA. Conflicts surface; lineage is preserved.
- Audit trails: when production breaks because of a decision made six months ago, "we decided this for reason Y at commit abc123" is exactly what the postmortem needs.
Borrowed from databases, applied to LLMs
Bi-temporal modelling isn't novel. The novelty is the application surface: AI coding agents have an ephemeral context window that compacts every few hours, and they need a memory layer that survives compaction and tracks belief change.
Most existing memory MCPs (comparison) are wrappers around a flat key-value store or an external vector DB. They treat memory as a flat searchable bag. The bi-temporal pattern adds a second axis (time), and pinning that axis to git SHAs (rather than wall-clock time) aligns with how engineering teams actually think about decisions.
It's an old idea applied to a new problem. The implementation lives in src/storage/memory-store.ts and src/memory/prune.ts — about 800 lines of TypeScript over SQLite.
Try it
Sverklo is MIT-licensed and runs on your laptop. Memory is local — no cloud, no external services, no API keys.
npm install -g sverklo cd your-project sverklo init
Memory tools available immediately: sverklo_remember, sverklo_recall, sverklo_memories, sverklo_pin, sverklo_promote, sverklo_demote. The agent calls them; you don't have to.
GitHub: sverklo/sverklo · 60-task retrieval benchmark · Deeper dive on the bi-temporal mechanics
References
- Snodgrass, R. T. (1999). Developing Time-Oriented Database Applications in SQL. Morgan Kaufmann.
- Sverklo memory implementation:
src/storage/memory-store.ts - Sverklo memory pruning:
src/memory/prune.ts