Git is the most successful version control system ever built. It won. It's everywhere. And it makes developers miserable on a near-daily basis.
This isn't a hit piece. Git was designed in 2005 for coordinating Linux kernel patches over mailing lists. It was brilliant for that. But in 2026 – with AI agents committing code, teams of 200 working in monorepos, and continuous deployment pipelines running every minute – the cracks aren't cracks anymore. They're canyons.
Graf is used in production by humans and AI agents today. It ships code, distributes packages, and proves integrity – daily. What follows are 35 architectural decisions that explain why we built a new system instead of patching the old one. Not complaints about Git – explanations of why each problem required a structural answer.
Graf is natively Git-compatible. You don't have to abandon your existing repo.
Install graf, run graf init in your git project (it auto-configures the ignore files),
import your history with graf import ., and you're running. Graf becomes your local
VCS; graf sync push distributes to your existing git remotes. Your team doesn't even
need to know. When you're ready to go graf-only, delete .git/. That's it.
Part I: Git Makes These Impossible – Graf Makes Them Default
Daily frustrations. Every developer reading this nods along.
1. git pull Is an Overloaded Nightmare
git pull does fetch + merge. Or fetch + rebase. Depending on your config, your flags,
your .gitconfig, and – if you're unlucky – the last StackOverflow answer you read.
You type the same command and get different behaviour depending on invisible state. That's not a tool;
that's a lottery.
Two verbs, two jobs. graf pull fetches and fast-forwards – nothing more.
graf sync fetches and reconciles, presenting clear options when branches diverge.
No invisible config changes behaviour. No surprises.
2. "Remote Is Ahead" / "Non-Fast-Forward" Push Rejection
You write code, commit, push – rejected. Someone else pushed first. Now you have to pull, resolve, push again. In a busy team, this happens multiple times a day. It's a race condition baked into the protocol.
Per-agent ref namespaces. Your push writes to refs/agents/grf:yourkey/heads/main.
Nobody else writes there. Push rejection from ref races is structurally impossible.
Integration into canonical main happens through intentional merge requests.
3. Merge Conflicts Are Chaotic
Git's merge conflict markers are a wall of text that even experienced developers misread. Renames, moved files, large refactors – Git loses track and dumps the mess in your lap. Tooling helps, but the underlying conflict model is crude: here's ours, here's theirs, good luck.
Three-way merge with base_cid, ours_cid, theirs_cid on every
conflict entry. Agents can resolve programmatically – no interactive prompts needed. When conflicts
can't auto-resolve, graf sync presents structured options: merge, restack, or open an
exploration to resolve manually.
4. Detached HEAD
"You are in 'detached HEAD' state." One of Git's most bewildering messages. You checked out a tag or a commit hash and now your commits will vanish unless you remember to create a branch. Beginners lose work to this. Experienced developers still get tripped up.
HEAD always points to a named ref. graf checkout by CID materialises files into a
directory – it doesn't put your entire repository into a liminal state. You can't accidentally make
commits that float in the void.
5. Cryptic Error Messages
"Your branch and 'origin/main' have diverged." What does that mean? What do I do? "fatal: index.lock exists" – what index? What lock? Git's error messages read like compiler internals because they are compiler internals. They expose implementation details instead of describing the problem.
Structured error output. When branches diverge, graf shows exactly which checkpoints exist locally vs remotely, who authored them, and presents numbered options. When something is locked, it says what operation holds the lock and how to resolve it.
6. Stash Is a Graveyard
git stash is where work goes to die. You stash something "temporarily," forget about it,
stash three more things, and now git stash list is an archaeological dig. No descriptions
by default, no easy way to see what's in each entry without popping.
graf stash uses proper stack semantics with parent-chain refs. Each stash entry is a
full checkpoint – named, timestamped, diffable. graf stash list shows you what's
actually in each stash. Stash entries are CAS objects like everything else – they don't rot.
7. Force-Push Is Dangerous and Routine
git push --force overwrites remote history. --force-with-lease is safer but
still destructive. In teams that rebase, force-push becomes a daily ritual – a destructive operation
normalised through repetition.
Per-agent namespaces mean you can only push to your own refs. There's no mechanism to overwrite
someone else's history. restack replaces rebase, and since restacking creates auditable
RestackRecords, the old CIDs are always recoverable. Destructive push doesn't exist.
8. Inconsistent Defaults
git push pushes only the current branch (unless configured otherwise).
git pull sometimes fetches everything. git clone checks out the default branch.
git checkout can switch branches or restore files, depending on arguments.
Every command has its own personality.
One verb, one job. push pushes. pull pulls. checkout checks out.
diff diffs. No command does double duty. No flags required for sane behaviour.
9. Committing to Shared Branches
Nothing stops you from committing directly to main. Branch protection is a server-side
feature on GitHub/GitLab – Git itself has no concept of it. In local workflows, anyone can push to
anything.
Canonical refs like refs/heads/main are updated through merge requests – signed objects
with policy hooks (approve, reject, hold). The protection is
in the data model, not in a server-side checkbox that only works on one hosting platform.
10. Unreadable History
"fix", "update", "wip", "asdf". Every team has a commit message policy. No team enforces it
consistently. Git doesn't care what your messages say, so the history becomes noise. Tools like
git log --oneline become useless when every third message is "stuff".
Checkpoints are signed with your SoulKey. Every entry in graf log shows the three-clock
timestamp, the author's SoulKeyID, and the message. Explorations (lightweight experimental branches)
let you make "wip" checkpoints in an isolated namespace – they don't pollute the canonical history.
When you merge, you merge clean.
Part II: Git Handles These Badly – Graf Handles Them Structurally
The disasters that cost hours or days when they hit.
11. Rebase Destroys Content Addresses
When you git rebase, every rebased commit gets a new SHA. Links, CI references,
code review comments – anything pointing to the old hash is now broken. In a content-addressed
system, this is the equivalent of renaming every file in your archive.
graf restack creates new CIDs (the parent changed, so the hash changes – that's correct)
but writes a signed RestackRecord linking every old CID to its new counterpart.
The provenance chain is permanent, distributed, and queryable via refs/restacks/*.
Nothing is lost.
12. History Rewriting Kills Provenance
rebase, commit --amend, filter-branch,
filter-repo – Git provides an arsenal of tools to rewrite history. Each one
destroys the true chronology. Who did what, when, and why? After a rewrite, you're trusting
the rewriter's word.
The DAG is immutable. There is no --amend. Instead, graf has --amend on
checkpoint (which creates a new checkpoint and a deadend for the old one), explorations for
experimental work, and prune records for intentional history sanitisation. Every transformation
is itself a signed, auditable object.
13. Noise Merge Commits
Most merge commits in a Git repository document nothing except "two people pushed at roughly the same time." They carry no intentional work. They clutter the log. Teams spend real time debating whether to squash-merge, rebase, or allow merge commits – and none of the options are good.
Merges only happen through intentional merge requests or explicit graf merge.
There is no automatic merge on pull. If you graf sync and branches have diverged,
the default is restack (clean linear history) – not a noise merge. Every merge is deliberate.
14. Large Repos and Monorepos
Git was designed for the Linux kernel – which is large but text-only. Modern monorepos with thousands of packages, generated files, and binary assets push Git to its limits. Operations that should be instant take minutes. Microsoft had to build an entire virtual filesystem (VFS for Git) just to make Windows development bearable.
Prefix-sharded CAS with BLAKE3 hashing (3x faster than SHA-256). M:N fiber scheduler for concurrent operations. Stat cache for sub-100ms status on large trees. The architecture scales with the hardware, not against it.
15. Submodules and LFS
Git submodules are universally hated. They're fragile, confusing, and break in ways that require deep Git knowledge to fix. Git LFS bolts on large file support as a separate system with its own server, its own authentication, and its own failure modes. Neither is a first-class citizen.
CAS stores content by hash regardless of size. Large files are just blobs with large CIDs. Cross-repo dependencies are handled through Hinge (the package manager) – same protocol, same identity, same CAS. No secondary systems bolted on.
16. Lost Commits
git reset --hard, a bad rebase, git push --mirror to the wrong remote –
commits vanish. git reflog might save you, if you know it exists, if you act quickly,
and if the garbage collector hasn't run. For many developers, lost commits are lost forever.
CAS objects are immutable and content-addressed. They can't be overwritten – only new objects can be created. Refs can move forward, but every ref change is logged. There is no garbage collector that silently deletes unreachable objects. If you created it, it exists.
17. Case-Sensitivity Collisions
Linux is case-sensitive. macOS and Windows aren't. Create README.md and
readme.md on Linux, push, and watch your colleagues' checkouts break silently.
Git handles this poorly because the object model doesn't enforce filesystem semantics.
Tree objects store paths as byte sequences. The CAS doesn't care about filesystem case rules – it stores what you give it. Collision detection happens at tree construction time with explicit warnings, not as a silent corruption at checkout.
18. .gitignore Disasters
Secrets, API keys, binary blobs, node_modules – all have been committed because
.gitignore was wrong or missing. Once committed, removing a file from Git history
requires filter-branch or BFG Repo-Cleaner and a force-push to every
remote. The damage is done.
.grafignore + .gitignore dual parsing. But more importantly: if you
accidentally checkpoint a secret, graf prune creates a signed
PruneRecord that marks the object for removal while preserving an auditable record
of what was cleaned and why. No silent history rewriting.
19. Broken Merge/Rebase Recovery
When a merge or rebase goes wrong mid-operation, Git leaves you in a half-finished state. The
recovery path is git reflog + manual reset + prayer. If you don't know
reflog exists, you're starting over from a fresh clone.
Operations are atomic. A merge either completes and creates a new checkpoint, or it doesn't. There's no half-merged state. Explorations provide a safe sandbox for complex reconciliation – if it goes wrong, drop the exploration. Your branch is untouched.
20. Destructive Commands Without Undo
git push --mirror can delete every branch on a remote. git clean -fdx wipes
untracked files permanently. git reset --hard discards uncommitted work. Git provides
powerful destructive operations with no confirmation and no undo.
Destructive operations don't exist in the same way. You can't delete remote refs you don't own. Local destructive operations (like discarding changes) are scoped to your working tree – they can't reach into the CAS or corrupt the DAG. The worst you can do is lose uncommitted edits, same as closing a text editor without saving.
Part III: Git Can't Do These At All
Capabilities that Git would need to become a different system to support.
21. Multiple Writers to the Same Ref
This is the root cause of problems 1, 2, 7, 9, and 13. Git allows anyone to push to any ref
they have write access to. When two people push simultaneously, it's a race. The loser has to pull,
merge, and push again. The entire git pull / merge / rebase ecosystem exists to
manage this fundamental design flaw.
Per-agent ref namespaces. One writer per namespace. Races are structurally impossible. Canonical refs are updated through signed merge requests with policy hooks. The problem doesn't need a solution because it doesn't exist.
22. No Agent Namespaces
In Git, branch names are global. feature/login belongs to whoever pushed it last.
There's no concept of ownership. In the age of AI agents committing code, this is actively dangerous
– an agent can overwrite a human's branch or vice versa with no guardrails.
Every agent – human or AI – has a SoulKeyID (grf:a7fec791). Their refs live under
refs/agents/<soulkey>/. Cryptographically enforced. An agent can't write to
your namespace any more than they can forge your Ed25519 signature.
23. The Rebase vs Merge Dilemma
Git forces every team to pick a religion. Rebase gives you clean linear history but rewrites hashes and loses provenance. Merge preserves history but adds noise commits. Squash-merge is a third option that destroys individual commits entirely. None of these are good. Every team argues about this. Forever.
Restack gives you clean linear history with auditable provenance records. Old CIDs map to new CIDs through signed RestackRecords. No dilemma. No religious wars. Clean history and honest provenance at the same time.
24. Mutable History
Git's history can be rewritten by anyone with push access. force-push,
filter-branch, rebase – the timeline is mutable. There's no way
to prove that the history you see today is the history that existed yesterday. In regulated
environments, this is a compliance nightmare.
Content-addressed, Ed25519-signed, immutable DAG. Every checkpoint is a cryptographic proof. RestackRecords, PruneRecords, and DeadendRecords encode every transformation. History isn't just preserved – it's provable. Institutional memory with cryptographic backing.
25. No Concept of Intentional Merge
In Git, a merge is something that happens to you when you pull. It's not an architectural act – it's a reconciliation side effect. There's no first-class "merge request" object in the Git data model. GitHub invented Pull Requests as a UI feature; they don't exist in the protocol.
mr (merge request) is ObjectKind 0x06 – a first-class CAS object.
Signed, timestamped, content-addressed. Merges are architectural decisions recorded in the DAG,
not UI features on a hosting platform.
26. Byzantine Internal Model
Working tree. Staging area (index). Local refs. Remote-tracking refs. Objects. Packfiles. The reflog. Worktrees. Git's internal model is a layer cake of abstractions that leak into every command. Understanding Git means understanding its implementation – and that's backwards.
Five object types: Blob, Tree, Change, Checkpoint, Release. One store (CAS). One ref system. One working tree. ~26,000 lines of code. The entire system is auditable in an afternoon. You don't need to understand the implementation to use it.
27. Inconsistent CLI
git checkout switches branches and restores files. git reset can
unstage, undo commits, or destroy your working tree depending on flags. git stash
uses pop and apply but git branch uses -d and
-D. Every subcommand was designed in isolation.
35+ commands, each with one job. checkout checks out.
branch manages branches. stash manages stashes.
diff diffs. Consistent flag patterns across commands.
You learn the system once.
28. Documentation Written for Computer Scientists
Git's man pages are technically accurate and practically useless for most developers. "Rebase reapplies commits on top of another base tip" – what does that mean? The Git documentation assumes you understand the object model, the DAG, and the index. Most users don't and shouldn't have to.
Commands explain what they do in terms of your work, not in terms of internal data structures. Error messages include actionable next steps. The system is simple enough that the documentation doesn't need to be a textbook.
29. Designed for 2005
Git was built for Linux kernel development: a single maintainer pulling patches from a web of trust via email. It excels at that. But modern development is teams of humans and AI agents, continuous integration, monorepos, package distribution, and deployment pipelines. Git serves none of these use cases natively – they're all bolted on through external tools.
Agent-native from day one. Per-agent branches, nursery-supervised concurrency, policy hooks for merge governance, SoulKey identity for humans and AI alike. Built-in package distribution via GTP. CI/CD can verify release certificates cryptographically – no more "trust the tag." Designed for how teams actually work in 2026.
30. No Audit Trail
git reflog is local, temporary, and not distributed. When someone rewrites history,
the reflog on their machine might record it – but nobody else's does. There is no permanent,
distributed, cryptographic record of what happened to your history. In compliance terms:
Git has no audit trail.
Every transformation – restack, prune, deadend, merge – is a signed CAS object stored in the
DAG and discoverable via refs. refs/restacks/*, refs/deadends/*,
refs/prunes/*. Distributed, permanent, cryptographically signed. Not a reflog
that expires – a provenance chain that's part of the protocol.
31. No Negative Knowledge
Git has no concept of "this direction was explored and rejected." When you delete a branch or abandon an approach, the knowledge vanishes. The next person – or the next AI agent – might waste hours re-exploring the same dead end.
DeadendRecords (ObjectKind 0x09) preserve institutional memory. When --amend
supersedes a checkpoint or an exploration is dropped, the old direction is recorded as a signed
CAS object under refs/deadends/*. It's negative knowledge – proof that a direction
was tried and abandoned. Your agents learn from each other's failures instead of repeating them.
32. No Cryptographic Identity
Git commits have an "author" field – a plain text string that anyone can set to anything.
git commit --author="Linus Torvalds <[email protected]>" works. GPG signing
exists but is optional, cumbersome, and used by almost nobody. Authorship is claimed,
not proven.
Every checkpoint is Ed25519-signed by the author's SoulKey. Authorship is a cryptographic
proof, not a text string. graf log shows the SoulKeyID for every entry.
You can't forge a checkpoint – the signature is verified against the content hash. In a world
where AI agents commit code autonomously, knowing who actually authored a change
isn't a nice-to-have. It's a requirement.
33. No Package Distribution
Git distributes code. npm distributes packages. pip distributes packages. Cargo distributes packages. They're separate systems with separate identities, separate authentication, separate integrity models. Your Git identity is not your npm identity. Your signed Git tag says nothing about your signed npm package.
Graf distributes code and packages through one protocol (GTP), one identity (SoulKey),
one manifest (graf.kdl). Hinge (Janus packages), Nip (Nexus OS packages), and
Graf itself all publish through the same forge, the same CAS, the same signature chain. One
identity proves everything you ship.
34. No Trust Scoring
Git treats all contributors equally. A first-time contributor's commit has the same structural weight as a ten-year maintainer's. GitHub adds stars, sponsors, and green dots – but these are UI decorations on a server, not properties of the data. They vanish if you change platforms.
Graf's traffic light system (green/yellow/orange/red) computes trust from objective signals:
license clarity, build reproducibility, community vouches, and publisher history. The
registry section in graf.kdl carries the license class and
publisher score. Trust is earned and computed, not assumed or decorated.
35. No Agent Governance
Git was designed for humans typing at terminals. AI agents that commit code use Git through
wrappers – shelling out to git CLI, parsing text output, hoping nothing goes
wrong. There is no concept of agent identity, agent namespaces, supervised concurrency, or
policy-gated merges in the Git protocol.
Graf is agent-native from day one. Per-agent ref namespaces (refs/agents/<soulkey>/),
nursery-supervised concurrency (M:N fiber scheduler), merge requests as signed CAS objects
with segment-based approval gates, and WorkFn interfaces for structured agent operations.
AI agents are first-class participants – not afterthoughts bolted on through shell wrappers.
The Summary
Git's problems aren't bugs. They're architectural decisions from 2005 that were reasonable for Linux kernel development and became liabilities for everything else. The flexibility that made Git powerful became the rope that teams hang themselves with. Rebase vs merge. Force-push culture. Detached HEADs. Cryptic errors. Mutable history with no audit trail.
The first 30 points show where Git breaks. The last 5 show what Git can't even conceptualise. Negative knowledge. Cryptographic identity. Unified package distribution. Computed trust. Agent governance. These aren't features you can bolt onto Git. They require different axioms.
Graf starts over with those axioms:
- One writer per namespace – no ref races, no push rejection, no noise merges
- Immutable DAG – CIDs never change; transformations are auditable objects
- Cryptographic identity – SoulKeys for humans and agents; signatures, not trust
- Separated concerns – pull fetches, sync reconciles, push publishes
- Merge requests in the protocol – first-class CAS objects, not server-side UI features
- Agent-native – per-agent namespaces, nursery concurrency, policy gates
- ~22K lines of Janus – auditable in an afternoon, maintainable by humans
Git won the 2005 war. Graf isn't fighting the 2005 war. It's building for the 2026 reality where AI agents commit more code than humans and package distribution is inseparable from version control.
curl -fsSL https://graf.tools/install.sh | sh
graf init myproject
graf checkpoint "the beginning"