Your AI Agent Has Amnesia. Mine Keeps Receipts.
Months of shared memory across Claude, Codex and Antigravity — versions, retractions, and why append-only notes keep failing TL;DR. Scattered Markdown notes kept sending my agents down paths we had already rejected. I
Months of shared memory across Claude, Codex and Antigravity — versions, retractions, and why append-only notes keep failing
TL;DR. Scattered Markdown notes kept sending my agents down paths we had already rejected. I built a shared Git-backed store (KYB) with versioned records and lifecycle states — active, archived, retracted. Query current state, history, or a time slice separately; replace wrong text instead of appending; separate finished work from wrong knowledge. Pinned to KYB v0.3.3, no controlled benchmarks — mechanisms and field notes below.
Before KYB, my coding agents already had memory: Markdown files scattered across different places. The flaw showed up whenever another agent returned to a workspace, found an older note, and mistook it for the active plan. In my experience, an agent would sometimes spend hours pursuing an approach we had already investigated and rejected. The file still held a perfectly reasonable case for the old approach, while the decision to abandon it lived somewhere else.
I am Alexander Panasenko, the author of KYB (Know Your Business), so I have a stake in this story: it is my open-source project. This is an account of my engineering experience with Claude Code, OpenAI Codex, and Google Antigravity from summer through October 2026. The text, its translation, and the diagrams were prepared with AI assistance, based on my own experience, the public source code, and saved development records.
A fresh session does not automatically inherit the conversation that just ended. A human had to step in and explain which file still mattered, why we changed course, and where the outcome was recorded. An obvious temptation is to write one more summary note, but that note needs remembering too: without updates, it goes stale like the rest.
So here is the question this piece addresses: how do you preserve operational knowledge across sessions without letting an outdated note send the next agent down a path you already abandoned? My short answer is to save a little, with versions and lifecycle state, and keep three things apart: what is true now, what was recorded then, and how an attempt ended. The rest of this piece shows where each boundary came from.
The mechanics below are pinned to the public KYB v0.3.3 release from October 27, 2026 (commit 06f63b5). The episodes below keep their dates: new features are not attributed to old versions, and the current main branch may differ. Our internal records are not public. I have not run a controlled comparison of agents with and without KYB, so the hours lost to stale notes are my personal recollection, not a measured saving after adoption.
Why Files Were Not Enough
In hindsight, the problem had three parts. First, there was no single shared entry point: notes lived in different places, and none was canonical across sessions. Second, a note had no explicit lifecycle state: a file describing a rejected plan looked just like one describing the active plan. Third, separate files do not by themselves establish which version of a decision is authoritative: even when notes are already tracked in Git, an agent reading a file in a working tree or a neighboring note cannot tell without a separate check whether that text superseded an earlier approach or was superseded later.
My first real fix was simple: a shared service for operational facts, incidents, and tasks that every session could reach. Its first interface was a CLI on top of an HTTP API; agents know how to run commands, and that was enough to start. Later, the server gained Model Context Protocol (MCP) support so agents could call its tools directly. The CLI, HTTP API, and MCP all lead to the same records: three doors into one room, not three separate memories.
What KYB did not become matters as much. It does not save hidden model reasoning or archive whole conversations. A record exists only when a human or agent explicitly saves it: knowledge under a stable key, an incident with detection and outcome, or a task with owner and status. Each record is a Markdown file in a Git repository. The search index is built on top and can be rebuilt; the repository stays canonical. I chose small, verifiable records over an automatic chat archive on purpose: an archive has everything, but finding the active conclusion in it is as hard as in a pile of files. One more distinction: the public repository holds the engine, while our working records live in a separate internal store. Installing KYB gives you your own store under your own access rules, with none of our notes.
What to Record So Nobody Repeats a Rejected Path
The shared server solved place, not content. Pour the same old notes into a database and search just finds stale text faster. So the next question was: what should survive a session?
The most useful record usually answers a question source code cannot: why was it built this way? Code shows the chosen implementation, not which options were tried, where they fell short, or which constraint decided the outcome. Without that, the next agent may carefully rebuild a rejected design while sincerely believing it is an improvement—exactly my original problem.
Material therefore falls into three kinds. Knowledge is a state or rule worth returning to: a service's role, an interface agreement, a way to verify configuration. An incident is a problem that happened: symptom, impact, how to tell whether it continues, and how it ended. A task is work to be done, or the reason it was closed. The split keeps you from confusing an intention with a fact, or a finished attempt with an active recommendation.
Take an example record about choosing a message queue. "We use option B" says too little. The next agent does not need to obey an old conclusion blindly; it needs to tell whether the conditions behind it still hold. I would write such a record roughly like this (a template, not a real benchmark):
Decision: Use option B for this workload.
Reason: Option A failed requirement X under conditions Y.
Verification: Link to test, configuration, or report with revision SHA.
Revisit if: X or Y changes.
Related: Task key where the options were compared.
The "Revisit if" line turns an old decision from a prohibition into a condition: if X or Y has changed, revisiting is legitimate, and the agent does not have to guess whether the record expired.
The same logic produced habits I now treat as part of the record. A claim without a source soon becomes one more text asking to be trusted, so note how and when a fact was checked: not "the API listens on 9090," but "at revision X, checked the service-api listener, port 9090." Before creating a record, search for an existing key; the same knowledge under two names recreates the old chaos inside the new system, and Git will not clean it up. Hunches and long logs of competing hypotheses belong in an open task or incident, not standing knowledge. Secrets do not belong in the store at all; a pointer to where an authorized person or tool can fetch them suffices.
In v0.3.3, there is also a guard against silently losing tags: rewriting a task or incident keeps any tags the write omits. An explicitly empty list clears them. This is a narrow rule for metadata, not a partial update of any record: replacing the knowledge under an existing key still requires keeping the full current text.
One Port, Different Questions
Even a clean record serves different questions. In a toy example, Monday's entry records that service-api listens on port 8080. On Wednesday, the service moves to 9090, and the record is updated under the same key. "Where should I connect right now?" and "What was recorded on Monday?" need different answers, and both are fair questions.
Overwrite the row without history and Monday's answer is gone. Return every version without dates or states and an agent may pick the wrong one. Git offers a third path: the key stays the same while its edits form a history. Still, history alone answers nothing until you say which slice you need.
Each KYB query mode matches one question. A regular query searches current state: "where do I connect now." --history searches matching text across all saved content versions, including the current one; kyb history <key> shows a record’s complete chain of changes. --as-of builds a slice of the store at a chosen moment and searches inside it, telling you what the store knew when a release went out. --changed-between searches versions written within an interval and returns, for each key, the latest matching version from that window.
Figure 1. A toy example with synthetic ports; R1 and R2 are Git commits, not real-world events.
Figure 1 maps those answers onto one key. The current query gets R2 and port 9090; a cutoff between the commits gets R1 and port 8080; history may return both if both match; and export, discussed below, writes one latest row per key.
The old port appears in historical results by design, not by mistake. Pick the question and mode first; judge the hits second.
A note for readers checking this against the code. In
--as-of, KYB first selects, for each key, the latest version recorded no later than time T—in revision order, not by the largest timestamp—and only then tests it against the query. A retraction recorded by T hides the key if the selected version is not newer than that retraction. In--as-ofand at the upper bound of--changed-between, a bare date means 23:59:59 UTC; at the start of--changed-betweenit means 00:00:00 UTC. A SHA passed instead of a date becomes its commit timestamp rather than selecting an exact Git tree, and versions written within the same second cannot be distinguished by a time cutoff. Seeindex.rsandmain.rsin v0.3.3.
The Correction That Did Not Replace the Mistake
The "slice first, match second" order came from practice: we hit the same problem twice, first in notes, then in code.
A shared server did not cure the habit of appending clarifications to the end of a document. A September internal record examined a case where a correction was added below an incorrect instruction while the instruction stayed on top. A human reading the whole page might catch the contradiction; an agent taking the first matching passage still got the old advice. It was the stale-Markdown trap again, except both versions now shared one page instead of sitting in different folders.
After that, our working rule got specific: edit the active text, then search for the retired wording. Checking that "the right paragraph was added" is too weak; you must confirm the wrong one no longer reads as an instruction. Git removes the main reason to keep the old paragraph in the body, since earlier wording survives in history. And if the same instruction was once copied under another key, searching for a distinctive old phrase after the fix can help surface a duplicate within the searchable store—KYB does not deduplicate automatically or guarantee that every copy is found.
In October, we found a software version of the same mistake. Historical cutoff search first collected versions containing the query term and only then picked the latest. When a newer version no longer contained the old term, it dropped out of candidates too early, and the query returned old text even for a moment after replacement. In effect, the code did what the agent did with the page: it matched words without checking whether the passage still applied.
The fix in PR #17 of October 11 reversed the order: first select each key's state at the target moment, then test for a match. This is the order in effect in v0.3.3, the version this piece is written against. For full history search, old text is appropriate; for the state after a correction, it is the wrong answer. The patch and regression test are public; we did not measure how many real agent decisions the bug affected.
I covered both cases in more detail in a HackerNoon article. The lesson for shared memory is simple: verify the whole path from correction to the next read. A successful write says nothing on its own about which passage the next query will return.
Write Time Is Not Event Time
Time slices tempt you to read the store's history as an event log. The same example shows why that is wrong. Say the service switched ports at 15:50, but the record was updated only at 16:20. An --as-of query for 16:00 returns the old port even though the cutover had already happened. The store was not wrong; it simply answers about write time, not event time.
KYB's temporal filters use Git commit time. That tells you what the store recorded and when; it does not replace deployment logs, metrics, or checking the running system. Incident fields do not prove event time on their own either. A client can supply started_at and detected_at; if detected_at is empty, the server keeps a stored value or fills in the current time. The server records the closing timestamp, resolved_at; clients do not supply that field. In an investigation, the store's history is one source, not the referee.
Finished, or Wrong?
Another boundary appears once a record stops being current: "this record no longer applies" can mean two very different things.
First, the work is finished. An incident was resolved, or a task was done or dropped, yet the next agent still needs to know how the attempt ended, especially if it failed. KYB archives such records: they leave the active tree, while their final version stays in regular current-state search. Closing a task as done or dropped requires resolution text, including why dropped work was abandoned. The field guarantees only that text exists, not that it is true or useful; a bare "done" tells a future reader nothing.
Second, the knowledge turned out to be wrong. Then the record is retracted: it stops answering regular queries while its Git history remains. To an agent, these are distinct signals: "work is finished and its outcome is worth reading" versus "this is no longer knowledge." Collapse both into a single delete or overwrite, and a reader cannot tell whether the old record disappeared because the job was done or because it was wrong—the exact ambiguity my old Markdown files created.
States also matter for the store as a whole. Version 0.3.1 (October 15) added kyb export, which writes a commit snapshot: one row per key, with its latest content and state (active, archived, or retracted). Export answers "which keys existed at that commit, and in what state?" without including earlier versions; for those, use history.
Interface details. By default, export uses the current commit;
--atpins another. The command is available in the CLI and over HTTP (GET /export), but is not an MCP tool in v0.3.3; seeexport.rsandmcp.rs.--changed-betweenreturns content versions written inside the window, not retraction events. Whether a key was retracted can be checked from its history or an export snapshot.
A Task You Can Find Too Late
Keeping a history of decisions still leaves another problem: open commitments. Even a correctly saved task is useless if you remember it after its deadline. The store may hold an exact date while an ordinary search for a service never shows it: the human or agent was asking about something else.
Deadlines arrived on October 27: v0.3.2 added them, and v0.3.3, released the same day, refined them and added their display in the console. A task can now carry a due date, a marker for external consequences of missing it, and a reminder window. If the date depends on a future event, it can be kept as a provisional one.
Task fields. The due date is stored in
due, the marker for external consequences inhard, the reminder window inremind_days. A provisional date is set throughexpectedtogether withwaits_on. A deadline field omitted on rewrite keeps its previous value; moving a date remains a version in Git.
Take an example task about renewing a certificate. Writing "by December 15" in its body is not enough: to the search engine, that is just text. The due field lets a date be compared with a calendar, and hard explains why missing it matters. Successful search results, record reads, and task and incident lists can carry a deadlines block from the server: up to five overdue or soon-due open tasks. This selection is not limited to records matching the original question. Overdue items come before future ones; within each group, hard deadlines come first. The count of remaining records is shown separately. In the CLI, the reminder goes to stderr while the JSON answer stays on stdout. The block can be switched off, and if computing it fails, the main answer may arrive without it: the absence of a reminder does not prove there are no commitments.
For a "today" deadline, the reader's calendar matters. The CLI and the console send their own date; without one, the server uses its own. Historical queries work the other way: in --as-of, a bare date means the end of the day in UTC.
In the v0.3.3 web console, deadlines appear in a strip under the header, in the task queue, and in a separate by-date view. You can open a task, move its date, or close it with an outcome. But neither the strip nor the JSON does the work: KYB does not run a task on a schedule, repeat it, or send notifications. An agent still has to call the tool, read the reminder, and react — a limited way to make commitments more visible while reading, not proof that we miss deadlines less often.
Finding Is Not Knowing
Clear states still leave the last stretch: finding the right record. KYB searches current records by words and, if a model is loaded, by meaning. Historical versions in v0.3.3 are searched by words only: vectors exist only for current versions. Comparing the quality of current and historical results without accounting for that difference would mislead. The API response reports whether semantic search took part in a query.
How search works. Tantivy performs BM25 keyword search, and the multilingual-e5-small model, via ONNX Runtime, adds semantic candidates. The two lists are merged by reciprocal rank fusion. Without a model, only keyword search remains.
Figure 2. One canonical store, several interfaces, two search paths.
Figure 2 puts the pieces together: three client interfaces lead to one server, Git holds canonical versions, the index derives from them, and current and temporal queries take different paths. The web console uses the same HTTP API; its on-screen queues are not shown here. No diagram can guarantee that an agent will ask at all. A hit shows what was saved, not that it is true, and an uncalled tool never helps. How much that matters, I learned from the agents themselves.
What the Agents Told Us
On October 10, 2026 (PDT, UTC−7), I asked five internal coding agents how the shared store fit into their work. Four replied in writing; the replies live in an internal record committed October 11 UTC. This is a small qualitative survey, not a representative sample or usage telemetry. Still, one issue recurred: KYB works on demand, whereas some memory files load into context automatically at session start. The tool can be available while the agent forgets to call it, so part of our durable knowledge kept living in separate files instead of the shared store.
My original problem returned from the other side: before, an agent found an old file and trusted it; now it had a path to the current record but no prompt to take it. The agents asked for a short digest of workspace records at session start and an explicit verification status: who confirmed a fact, when, from what source, and at which revision. They also noted that behavioral rules for agents are awkward to model as ordinary knowledge. This is user feedback, not proof that automatic context injection improves accuracy. When that record was written, automatic kyb context was only a proposal, and the v0.3.3 server does not provide it. The deadline reminders described above arrived only after this survey, in late October. They return part of the current commitments, not the whole project context.
Separately, I tested provenance: when different agents write records, you need to know which one did. On October 11, I ran trial writes via the CLI from Claude Code, Codex, and Antigravity, and via MCP from Claude Code, using the attribution mechanism from PR #16. Git history recorded labels for agent, session, workspace, and write transport, confirming those paths. In v0.3.3, the CLI refines this attribution: it walks up the process tree and picks the nearest agent with its own session. For example, Claude Code inside an Antigravity terminal should not be recorded as Antigravity; an explicit KYB_AGENT takes priority. Because clients supply the labels, they help audit changes but do not provide cryptographic identity or prove regular store usage.
An episode before the survey showed what a shared dependency costs. On October 8, an agent reported that several kyb query calls exited with no output after an external timeout expired. On October 11, repeat queries answered in under a second. Logs did not cover the first report, and nobody checked stderr or server status then, leaving the cause unknown—server, network, or client call. I treat this as an agent's report, not a reproduced defect or benchmark: when agents rely on a shared service, availability and clear timeout diagnostics become part of memory. The v0.3.3 CLI makes those diagnostics explicit: the exit code distinguishes success, a server rejection, a server error, and an unreachable server; the error goes to stderr, and stdout stays empty on failure. It still does not explain the October episode after the fact. If an agent checks only for text and ignores the exit code, even a good store history will not help.
A Loop You Have to Practice
The cases above and the agents' feedback point to Figure 3: a record must be written explicitly in one session and looked up explicitly in the next.
Figure 3. A record survives the session, but it does not enter a new context window on its own.
In Figure 3, Session A at the top learns something: the agent observes a change, checks a source, separates fact from hypothesis, and explicitly saves a version under a stable key. Git keeps the revision, and the index derives from it. Session B below is the next session. Because it does not automatically inherit the prior chat, it starts with a specific question, queries in the right mode, checks the result against a current source, and only then uses, corrects, or retracts the record. This is the intended order, not observed behavior of every agent.
Three points matter most. First, the question: "Tell me everything about the project" returns too much text, while "Why did we choose option B for this operation?" or "How did the last outage on this service end?" gives a clear target. An empty result may just need a component name, key, tag, or different wording. Second, verification: code at the right revision for an interface decision, configuration or observed responses for a running service. A record does not replace checking the primary source; it helps you locate what to check and how. Third, the update: an agent that learns a recommendation no longer applies and leaves the fix only in the current chat sets the same trap for the next session. Updating the record under its existing key, with reason and source, belongs in a task's definition of done.
What I Can Show, and What I Can't
Everything above rests on public code or saved internal records. Back to an earlier version of the system — v0.2.0 from August 16. One field's history shows what such records preserve. An internal task described a backlog of over fifty open tasks with no explicit priority, forcing urgency into tags. Status filtering existed; importance ranking did not. The initial request named the field severity, by analogy with incidents. Discussion refined that: severity describes damage that already happened, while priority fits future work. priority shipped in KYB v0.2.0—a concrete chain from friction to product change preserved in the store, not a measurement of agent speed.
What I lack is a reliable count of reads per agent, a link between reads and task outcomes, or a controlled comparison with and without KYB. That requires fixed tasks and models, a log of when each agent queried the store and what it got, and blind grading, knowing an available tool is not always called. Until such data exists, I describe mechanisms and observations, not causal effects.
This approach is not for everyone. A Git-backed shared store fits when several sessions or machines read and update changing knowledge and humans want history and diffs. For a single agent in one repository, a good README and a few project files are often enough. Git itself needs no server, but this design adds a shared server and an index to maintain, and the records still have to be verified and kept current. If your knowledge rarely changes, it may be one system too many.
Back to the Old Note
Recall the opening scene: an agent finds a well-written note and heads down a path we abandoned long ago. What would change if that note lived in a shared store?
The agent would not get smarter. Instead, the record would have one shared entry point, an explicit lifecycle state, and a queryable version history under a single key. If the plan was abandoned, its task can be closed with the reason and wrong knowledge can be retracted from current search, separating the record's status from an active instruction. If the plan evolved, the earlier text sits in history and the body holds the current one. If the question is about the past, a slice at the right moment answers about the past without mixing it into the present. None of this guarantees that an agent will interpret a hit correctly, and it helps only when the agent asks the question and checks the answer against the source.
The practical takeaway does not require installing KYB. Next time you correct a note meant for agents, in a file, a wiki, or a store of your own, check three things. Did you replace the wrong text instead of appending a fix below it? Can a reader tell whether the plan was canceled, completed, or turned out to be wrong? Did you record why the decision was made and when it should be revisited? Three yes answers will not guarantee that the next agent never takes a wrong turn, but they give the next reader—human or session—an explicit status, the reason behind the decision, and a source to check instead of a stale note masquerading as the active plan.
To see how KYB works, start with the v0.3.3 release and the README of that version, which cover prebuilt binaries, installation, and Docker Compose. For a first check, complete that setup, save one decision with a "Revisit if" line, then query it from a fresh session. For Docker, use a separate fresh checkout: Compose keeps its data in that checkout's ./data directory.
Originally published on prod.codes.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.

