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
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.
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:
// 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:
{
"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
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 suiteNot git log. Agent log. Every tool call, in causal order, with the files it touched.
2. 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
3. Rewind
$ rgt rewind 8d91e2f1This 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.
$ 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.
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
Reach out at shaylivni@outlook.com or connect on LinkedIn.
Let's build the missing layer.