← Blog
20 Temmuz 2026 · living memory · AI memory staleness · drift detection

Living memory: why memory that isn't tied to your code rots, and how we keep it honest

A note bound to a function should die the moment that function does, not quietly lie to your next agent. Here's how SecondOS anchors memory to real symbols, flags it stale on drift, and revives it when the code comes back.

The tempting way to give an AI tool memory of your codebase is to write it down: a summary, a note, a pile of embeddings. It works for a while, then it rots. Prose describes code at a moment in time, and code moves. A note that says "auth lives in hashPassword" is wrong the instant someone renames that function, and nothing tells you. Your next agent reads the stale note and answers confidently about code that no longer exists.

That is the actual hard problem with codebase memory. Retrieval is the easy half. Staying correct as the code changes underneath you is the half that matters, and it is only solvable if the memory is anchored to your code's real structure, not to text.

A memory bound to a symbol dies when the symbol dies

In SecondOS every memory carries a scope: it is bound to the whole repo, to a file, or to a specific symbol. That anchor is what makes staleness possible.

After each sync, SecondOS re-resolves every file-scoped and symbol-scoped memory against the current map. We call it the K-S34 mechanic, and the rule is simple:

  • a memory whose anchor is no longer present in the map flips to stale
  • a stale memory whose anchor comes back flips to active
  • repo-scoped memories are never stale, because their anchor is the repo itself

So a decision recorded against hashPassword is flagged stale the moment hashPassword is refactored away, instead of quietly describing code that is gone. Rename it back, or restore the file, and the memory revives on the next sync. The matching is tolerant (it resolves path:name or a bare name) so a memory anchors and un-stales consistently however it was written.

Two things make this trustworthy rather than annoying. First, staleness is a status, not a deletion: the memory stays in history, is de-prioritized in recall by decay, and can revive. SecondOS accretes, it does not overwrite. Second, the flip is never silent: /sync/commit returns how many memories staled and how many revived, and the CLI prints it. You see the change, you are not surprised by it.

A concrete story: a memory sourced from a pull request

Here is the mechanic end to end. SecondOS can (opt-in, manual, read-only) ingest the discussion on a GitHub issue or PR and distill it into a memory proposal. The proposal is grounded against the stored map, so the memory binds to the file the PR actually touched, and it carries the source link, so when your agent later recalls it, it shows from #N pointing at the real discussion. Nothing about your source leaves the machine: the GitHub App holds no contents permission and reads only the issue and PR text.

Now that memory lives its life like any other. It is anchored to a real file. Months later someone refactors that file away. On the next sync the anchor no longer resolves, the memory flips to stale, and it stops surfacing in recall as if it were current. The decision is not lost, it is just no longer presented as live truth. If that code path returns, the memory revives. That is what "living" means here: the memory tracks the state of the code it describes, instead of freezing at the moment it was written.

Three-axis drift: the bug in the gap between two files

Anchoring memory to structure buys a second thing: SecondOS can see mismatches that live between files, where the nastiest bugs hide. It surfaces three, all read-only alerts (K-S24) that never touch your code, and each reports both sides of the mismatch.

| Axis | Compares | Flags | |---|---|---| | Code vs DB schema | Drizzle writes in code | a column written in code the schema does not have | | Env vars vs .env.example | process.env reads in code | a var read but documented in no example or schema file | | Imports vs package.json | imports in code | a package imported but declared in no manifest on its path |

The design principle across all three is the same one that makes staleness trustworthy: prove it or stay quiet. Dynamic or spread writes are skipped and counted, never guessed. A dynamic process.env[expr] read is counted, not flagged. If there is no .env.example at all there is no baseline, so the used-but-undeclared check is skipped entirely rather than crying wolf. The dependency check is monorepo-aware: an import resolves against the nearest package.json and its ancestors, so a hoisted root dependency counts as declared. A memory or an alert you cannot trust is worse than none.

Why vectors can't do this

A vector store can tell you a file mentions authentication. It cannot tell you that hashPassword was renamed, because a vector does not know a symbol from a substring: it just knows some numbers used to sit near some other numbers. A symbol knows. A call graph knows. A schema knows. That is the whole difference between memory that decays gracefully and memory that lies with confidence.

And the trade to be honest about: to anchor memory this way, SecondOS stores your code as a structural map (symbols, signatures, routes), never the full body (K-S23). For code that is a privacy win, the source never leaves your machine. It is a genuine trade, and it is the one we chose on purpose.

See how the structure fits together, the drift axes, or reproduce the benchmark.

FAQ

Why does AI memory about a codebase go stale?

Because most memory is prose or embeddings that describe the code at one moment. Code moves. A note that says auth lives in hashPassword is wrong the second someone renames that function, and nothing tells you. Memory only stays honest if it is anchored to the code's actual structure, not to a bag of text.

How does SecondOS know a memory is stale?

Every file-scoped or symbol-scoped memory is re-resolved against the current map after each sync (the K-S34 mechanic). If its anchor is no longer present in the map, the memory flips to stale. If the anchor comes back, it flips back to active. Repo-scoped memories are never marked stale. The sync reports exactly which memories staled or revived, so nothing flips silently.

What is three-axis drift?

Three read-only checks for bugs that live in the gap between two files that were supposed to agree: code writing a DB column the schema does not have, an env var read in code but not documented in .env.example, and a package imported but declared in no package.json. All three report both locations and never modify your code.

Does a stale memory get deleted?

No. Staleness is a status, not a deletion. The memory is kept in history, de-prioritized in recall, and revived automatically if its anchor returns to the map. SecondOS accretes rather than overwrites, so you never lose the record of a decision.

SecondOS — one memory for your codebase, every AI tool reads it. Get started free →