post://versioned-memory-for-ai-agents

Versioned memory for AI agents, seven languages deep

read: 6 min words: 1,173
Versioned memory for AI agents, seven languages deep
toc://sections
outline

    The failure mode of a long-running agent is not that it cannot reason. It is that it forgets why it reasoned the way it did. An hour of work fills the context window, the oldest tokens fall off the end, and the agent starts contradicting a decision it made earlier as if it had never happened. The usual answers are to stuff the whole history back in, to summarize it lossily, or to bolt on a vector store that retrieves fragments with no sense of order. Each one is expensive, incomplete, or both.

    A paper I found while working through this argues that the problem is not new, and that version control already solved it. The Git Context Controller, or GCC, applies Git's branching model to an agent's memory. Repo: swadhinbiswas/contexa · Paper: arXiv 2508.00031

    The research and the benchmark results belong to that paper, by Wu and others. My contribution is Contexa, an implementation of the framework that runs in seven languages against a single on-disk format. That constraint, one format across seven runtimes, turned out to be most of the engineering.

    The idea in one diagram

    GCC treats an agent's working history the way Git treats a repository.

    <svg viewBox="0 0 900 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="An agent committing along the main branch and exploring a side branch">
      <g fill="none" stroke="currentColor" stroke-opacity="0.4" stroke-width="2">
        <path d="M120,60 V250"/>
        <path d="M120,140 C300,140 320,90 480,90 V220"/>
        <path d="M120,205 C340,205 360,245 560,245"/>
      </g>
      <g fill="currentColor" font-family="ui-monospace, monospace" font-size="13" opacity="0.65">
        <text x="150" y="66">main</text>
        <text x="500" y="86">branch: experiment</text>
      </g>
      <g fill="#cba6f7">
        <circle cx="120" cy="60" r="8"/><circle cx="120" cy="140" r="8"/><circle cx="120" cy="205" r="8"/>
      </g>
      <circle r="8" fill="#a6e3a1">
        <animateMotion dur="3s" repeatCount="indefinite" path="M120,60 V250"/>
      </circle>
      <circle r="8" fill="#89b4fa">
        <animateMotion dur="3s" begin="1s" repeatCount="indefinite" path="M120,140 C300,140 320,90 480,90 V220"/>
      </circle>
      <circle cx="120" cy="250" r="9" fill="none" stroke="#cba6f7" stroke-width="2">
        <animate attributeName="opacity" values="1;0.2;1" dur="2s" repeatCount="indefinite"/>
      </circle>
    </svg>
    

    Every step the agent takes is an Observation, Thought, Action record, which is the working directory. When a piece of work is done, it commits: a milestone summary that compresses the older OTA steps away. When it wants to try two approaches, it branches, so an experiment cannot corrupt the main line. When an experiment works, it merges. And when it needs to think again, it asks for context at a resolution of K commits.

    The API is small enough to hold in your head.

    from contexa import GCCWorkspace
    
    ws = GCCWorkspace("/path/to/project")
    ws.init("Build a REST API with user auth")
    
    ws.log_ota("saw empty dir", "scaffold first", "create_files()")
    ws.log_ota("files created", "implement user model", "write_code('models.py')")
    ws.commit("Project scaffold and User model complete")
    
    ws.branch("auth-jwt", "Explore JWT authentication")
    ws.log_ota("JWT docs reviewed", "stateless, good for APIs", "implement_jwt()")
    ws.commit("JWT auth middleware implemented")
    
    ws.merge("auth-jwt")
    ctx = ws.context(k=1)
    

    That context(k=1) call returns a formatted summary ready to inject into a prompt: the roadmap, the last commit, and the current OTA log. The paper's ablation is the counterintuitive part. K=1, the most recent commit only, outperforms giving the agent more history. Compression is not a compromise being forced by a small window. It is a better input.

    The part I actually built

    Implementing a single library is routine. Implementing the same library in Python, TypeScript, Rust, Go, Zig, Lua, and Elixir, all producing and reading the same .GCC/ directory, is where the design pressure lives.

    .GCC/
      main.md                    # global roadmap
      branches/
        main/
          commit.md              # milestone summaries
          log.md                 # fine-grained OTA traces
          metadata.yaml          # intent, status, provenance
        experiment/
          commit.md
          log.md
          metadata.yaml
    

    The format is deliberately human-readable, Markdown and YAML on disk. You can open an agent's memory in your editor and read what it decided. That choice made cross-language parity possible, because the contract is text rather than a binary protocol, and it made debugging possible, because a wrong summary is a line you can point at.

    Holding seven implementations to one format is a discipline problem more than a technical one. Each language has its own idioms for files, time, and structured data, and every one of them will happily drift by a field or a timestamp format if you let it. The seven packages are independent, with their own build tooling and tests, and the only thing that truly binds them is that the directory one language writes is the directory another can extend. I added a place for the metadata for exactly this reason: intent, status, and provenance need to survive the trip, or the memory stops being portable.

    The models are small on purpose. An OTA record has a step, a timestamp, an observation, a thought, and an action. A commit record carries the branch, its purpose, the previous progress summary, and this commit's contribution. A branch has a name, a purpose, where it came from, and where it merged. Keeping the schema that small is what lets seven languages agree.

    Why the branching model matters

    The interesting claim from the paper is not that summarization helps. It is that structure helps. The ablation walks from 69.1 percent with the roadmap and commits, to 75.3 with logs and context, to 77.8 with metadata, and to 80.2 with branch and merge on SWE-Bench Verified using Claude 4 Sonnet. The paper also reports 83.4 percent on BrowseComp-Plus with GPT-5, against 26 existing open and commercial agent systems.

    To be precise about what those numbers are and are not: they come from the paper that defines GCC, not from a benchmark I ran on Contexa. What I can speak to is the implementation. The last column of that ablation is the one that stuck with me, because branch and merge is the part that maps least obviously to memory and most obviously to how engineers already work. When an agent tries two approaches, the useful record is not which one won. It is that there were two, what each assumed, and which one the main line adopted.

    There is a real cost to this approach. It adds tool calls and structure to an agent loop that was simpler before, and the paper notes that GCC agents spend more compute while ending up more cost-efficient. If you are building something that runs for a few turns, this is overhead. If you are building something that works on a task for an hour and needs to remember the first decision when it reaches the last, structured memory with a real branching model is worth the tokens.

    Where it lives

    Contexa is published across PyPI, npm, crates.io, pkg.go.dev, Hex, and LuaRocks, with the Zig package built from the repository. All seven read and write the same layout, so a workspace created by the Python package can be extended by the Rust one. The original research citation is in the repository, and the framework belongs to its authors.

    The lesson I keep taking from it is that memory is a data-modelling problem wearing an AI costume. The moment you stop treating context as a buffer to fill and start treating it as a structure to query, the design questions become ones that version control answered years ago.

    Reach me at swadhinbiswas.cse@gmail.com or on GitHub and LinkedIn.

    react://versioned-memory-for-ai-agents
    comments://versioned-memory-for-ai-agents

    No comments yet.