Back to Blog

How I Wrote Git for AI Agents

Git tracks what developers do. Nothing tracks what agents do. So I built re_gent - blame, log, and rewind for AI coding sessions.

I was three hours into a Claude Code session, deep in refactoring auth middleware, when it happened again.

"Actually, can you revert that last change?"

Claude tried. It sort of reverted it. The function signature looked right, but something was off. Tests failed. I couldn't tell if I was looking at the version from an hour ago or a Frankenstein merge of three attempts.

I opened a fresh chat and copy-pasted the "good" version I'd saved in a scratch file. This is absurd, I thought. We have git. Why doesn't the agent?


The Missing Layer

I've been building with AI agents for over a year now. Claude Code, Cursor, custom SDK scripts—they're genuinely transformative. An agent can scaffold a feature in minutes that would take me hours. But there's a fundamental piece missing.

The Problem

Git tracks what developers do. Nothing tracks what agents do.

When you ask an agent to refactor code, it might touch fifteen files across twenty tool calls. Git sees one blob of uncommitted changes. The commit message? Whatever you write afterward. The context for each line—whichprompt caused it, which intermediate step it came from? Gone.

You end up with workarounds:

  • /compact and pray the agent remembers
  • Trying to /rewind (which barely works)
  • Copy-pasting code into fresh chats
  • Screenshotting the "good version" before changes
  • "That was working five minutes ago"
  • "Go back to before the refactor"
  • "Why did you change that file?"

These aren't edge cases. This is every day for people working with agents at any scale.


What Should Exist

Three primitives are missing:

  • blame — which prompt produced this line of code?
  • log — what did this session actually do, in causal order?
  • rewind — restore the workspace + conversation to step N, without losing the audit trail

If git didn't exist, developers would have the same problems. Every refactor would be a one-way door. Code review would be "here's a tarball, good luck." Collaboration would mean emailing zipfiles.

Git gave us branches, diffs, merge, bisect, blame. It didn't just solve version control—it unlockednew ways of working. Pull requests, CI/CD, code review tools, bisect for debugging, git-blame-driven archaeology.

We need that layer for agents.

Building It

I started with the obvious approach: could I just use git underneath?

Attempt 1: Abuse git commits

What if I hooked into Claude Code and auto-committed after every tool call? Commit message = the prompt. File changes = the diff.

I prototyped it. It worked, kind of. But:

  • Git commits weren't designed for this—I couldn't store the full conversation, tool arguments, or results without jamming JSON into commit messages
  • Blame worked for files, but not for which message in the transcript caused a line
  • Rewind meant git reset, which destroyed the audit trail

I was fighting git's data model. It wanted me to think in terms of developer commits. I needed to think in terms of agent steps.

The Breakthrough: Reimplement from Scratch

I read through isomorphic-git's source one weekend. The whole thing is ~10k lines of TypeScript. I looked at Jujutsu (a from-scratch VCS in Rust). Same story: the core data model—blobs, trees, commits, refs—is small.

Git's genius isn't the code. It's the model: content-addressed objects, a DAG of immutable history, and mutable refs pointing into it.

I could keep that model and change what the objects mean.

Here's the core:

Core Data Model
// Blob: raw bytes, identified by content hash
type Blob struct {
    Hash    string // blake3(content)
    Content []byte
}

// Tree: workspace snapshot at one moment
type Tree struct {
    Entries map[string]TreeEntry
}

type TreeEntry struct {
    BlobHash  string
    BlameHash string // per-line provenance
    Mode      os.FileMode
}

// Step: like a git commit, but auto-generated per tool call
type Step struct {
    Parent          string      // previous step hash
    SecondaryParent string      // for sub-agent merges
    TreeHash        string      // workspace state
    TranscriptHash  string      // conversation delta
    Cause           *ToolCall   // what triggered this step
    SessionID       string      // which agent session
    AgentID         string      // which agent (sub-agents get their own ID)
    Timestamp       time.Time
    Effects         []Effect    // uncompensated side effects
}

// ToolCall: the cause of a step
type ToolCall struct {
    ID         string
    Name       string
    ArgsHash   string // blob hash of the arguments
    ResultHash string // blob hash of the result
}

This is the whole model. Four objects. Three immutable and content-addressed (Blob, Tree, Step), one mutable pointer (Ref). Steps form a DAG through parent pointers. Each session gets its own ref.


The Hook

Claude Code has a feature called hooks—shell commands that fire after tool calls. I added this to my.claude/settings.json:

.claude/settings.json
{
  "hooks": {
    "PostToolUse": "rgt hook"
  }
}

Every time Claude calls a tool, my rgt hook command runs. It:

  • Reads the tool call payload (tool name, arguments, result)
  • Snapshots the workspace (every file, respecting .gitignore)
  • Grabs the conversation delta from the JSONL transcript
  • Computes a Step object with all of the above
  • Writes it to .regent/objects/ (content-addressed)
  • Updates .regent/refs/sessions/<session-id> to point at the new step

All of this happens in ~20ms. Claude never notices.


What It Unlocks

Once I had the hook working, I built the three primitives:

1. Log

rgt log
$ rgt log

step a7f3b2c4 (2 minutes ago)
  Edit internal/api/auth.go
  tool: Edit
  session: claude-2024-05-09T14:22:31
  
  Added JWT validation middleware

step 8d91e2f1 (5 minutes ago)
  Read internal/api/auth.go
  tool: Read
  session: claude-2024-05-09T14:22:31
  
  Investigating auth flow

step 3c4d5e6f (8 minutes ago)
  Bash: go test ./...
  tool: Bash
  session: claude-2024-05-09T14:22:31
  
  Ran test suite

Not git log. Agent log. Every tool call, in causal order, with the files it touched.

2. Blame

rgt blame
$ rgt blame internal/api/auth.go

a7f3b2c4  15  func ValidateJWT(token string) error {
a7f3b2c4  16      claims := jwt.MapClaims{}
a7f3b2c4  17      _, err := jwt.ParseWithClaims(token, claims, keyFunc)
8d91e2f1  18      if err != nil {
8d91e2f1  19          return fmt.Errorf("invalid token: %w", err)
8d91e2f1  20      }
a7f3b2c4  21      return nil
a7f3b2c4  22  }

Each line is annotated with the step hash that introduced it.rgt show a7f3b2c4 tells me the full context: which prompt, which tool call, what the conversation was.

The Aha Moment

I was debugging a broken auth check. rgt blame told me line 19 came from step 8d91e2f1. I ran rgt show 8d91e2f1 and could see exactly which message caused that line to exist.

3. Rewind

rgt rewind
$ rgt rewind 8d91e2f1

This restores the workspace and the conversation to step 8d91e2f1. Every file goes back. The transcript is reconstructed from the chained delta objects.

The DAG doesn't change—it's immutable. The rewind is just a new branch point. If I change direction, that becomes a new timeline. Non-destructive time travel.


The Wow Moment

Last week, I was in a gnarly debugging session. Claude had refactored a handler, introduced a bug, tried to fix it twice, made it worse, then partially reverted.

I was lost. So was Claude.

Finding the bug
$ rgt log --files internal/api/handler.go

step f1a2b3c4 (2 min ago)  - Edit: "revert validation check"
step d9e8f7c6 (5 min ago)  - Edit: "fix off-by-one error"  
step c5d6e7f8 (8 min ago)  - Edit: "add bounds checking"
step b3c4d5e6 (12 min ago) - Edit: "refactor validation logic"
step a1b2c3d4 (15 min ago) - Edit: "extract handler helper"

Five edits, all tangled. I ran rgt blame internal/api/handler.go and saw that the broken lines came from step d9e8f7c6—the "fix off-by-one error" attempt.

I rewound to c5d6e7f8 (right before the attempted fix), described the actual bug clearly, and let Claude try again from a clean state.

Worked on the first try.

This is what git does for developers. re_gent does it for agents.


The Vision

This is just the foundation. Once you have blame, log, and rewind, the next layer becomes possible:

  • Multi-session collaboration: Two agents working in parallel, like git branches. Merge points where their work integrates.
  • Agent-to-agent sharing: "Here's the branch where I solved X" becomes a shareable artifact. Not a tarball. A step hash.
  • Session archaeology: rgt log --author=claude --tool=Bash --since=yesterday to audit what an autonomous agent did overnight.
  • Diff and review: rgt diff <step1> <step2> shows file changes and conversation changes.
  • Bisect for agent sessions: The agent introduced a bug somewhere in the last 30 steps. rgt bisect to binary-search where it broke.
  • Branching strategies for agents: "Explore two approaches in parallel, keep the one that works." In the same DAG, with proper merge points.

Git didn't just solve "save my code." It unlocked GitHub, CI/CD, pull requests, bisect, archaeology, collaboration at scale.

Version control for agents will do the same.

Looking for Contributors

I've been running re_gent on my own projects for a few weeks. It works. The architecture is validated. But this is too big for one person.

The Go implementation is straightforward—10-15k lines for the core—but there's a long tail:

  • Adapters for Cursor, Cline, Aider, Continue, Claude SDK
  • Performance work (monorepo snapshots, incremental diffs)
  • Garbage collection for orphaned steps
  • Multi-repo / monorepo subtree handling
  • UIs for browsing the DAG

Join the Project

If you're building with AI agents and you've hit these problems—if you've ever screenshotted code before a refactor, or copy-pasted into a fresh chat, or wished you could actually rewind—I want to hear from you.

Reach out at shaylivni@outlook.com or connect on LinkedIn.

Let's build the missing layer.