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.

Already using Git? You're 30 seconds away.

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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

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.

Graf

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.

Graf

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.

Graf

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".

Graf

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

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

.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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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.

Graf

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

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

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

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:

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.

Try It

curl -fsSL https://graf.tools/install.sh | sh
graf init myproject
graf checkpoint "the beginning"