Engineering · Sverklo · 2026-04-29

Git for AI agent memory — version control for what your AI knows about your codebase

2026-04-29 ~7 min read by Nikita Groshin

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.

Git made code safe to change. Sverklo makes the agent's understanding of code safe to change.

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:

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

QuestionFlat memoryBi-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:

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

See also