Structure You Decide Later, Not Sooner

An agent doesn't hand you a clean history. It hands you a firehose. It tries three approaches to the same problem and keeps one. It fixes a typo it introduced four changes back. It refactors something unrelated because it happened to be nearby. What comes back is a batch of work, and the real shape of that work only shows up once you can see all of it at once. Deciding which part is a real line of work, which part was an experiment worth setting aside, and which part belongs on a different track entirely: that's a decision you can only make after the changes exist.

Git requires you make it before. A commit's hash includes its parent's hash, so the moment you record work you've also recorded its position in a single lineage, and that position becomes part of its identity. If the shape you picked turns out wrong, there's one tool for fixing it, and it's a rewrite. Rebase, squash, or reset all produce new SHAs for everything downstream. They sever the identity of the commits they touch and take the provenance sitting next to those commits down with them. I made the case in the previous post that an ancestry graph was never the right substrate for agents. This is what that costs you in practice: in an ancestry graph, the structure of your work is frozen the moment you write it, and the only way to change it is to rewrite it.

Atomic separates the two things Git fuses together. Content is one graph: a single canonical set of changes, each content-addressed, each carrying its own intent and provenance. Grouping is something else. It's a view, and a view is just a filter that says which changes are visible together and in what order. Views don't own changes and they don't duplicate them. They point at the one graph. Because grouping is a filter and not a lineage, you can change your mind about it as many times as you want without ever touching the content underneath.

// live workshop · aug 26
Do we have your attention yet?

Come learn more on August 26 at 11 AM EST, where we'll answer all your questions about Atomic, live.

Save your seat →

Pulling a Change Out of the Middle

Here's an operation an ancestry graph can't do cleanly. Say you've got a line of work:

dev: [1, 2, 3, 4, 5, 6, 7]

Changes 4 and 5 were an experiment. They're independent of the work in 6 and 7, they touched different files, and you've decided they belong somewhere else while dev keeps moving. What you want is:

dev:  [1, 2, 3, 6, 7]
wip:  [4, 5]        (with everything they depend on)

In Atomic that's one command:

$ atomic view split wip 4a3f… b901…   --from dev
✓ Split 2 change(s) out of dev into draft wip
ℹ 'dev' now has 5 change(s); draft 'wip' has 2 own change(s).

Nothing gets re-applied here. No diff gets computed and replayed onto a new base. The edges for 4 and 5 still live in the same canonical graph they always did. dev's filter just stops including them, and a new view picks them up instead. It's an O(1) metadata operation per change. 6 and 7 keep their exact hashes. So do 4 and 5. There's no 6', no 7', no rebase.

The operation is guarded by the thing that actually matters: real dependencies. Before it moves anything, Atomic computes the reverse-dependency closure of {4,5} inside dev and checks whether anything staying behind depends on what's leaving. If 6 had edited a line that 4 introduced, 6 depends on 4, and the split gets refused unless you pass --cascade to pull 6 along with it. You can preview the whole analysis with --dry-run before you commit to anything. The operation is defined so that it's impossible in exactly the cases where it would leave a dangling reference, which is why it always exits clean.

See It in Action

Here's the split running end to end — pulling changes out of the middle of a view, no rebase, no rewritten hashes.

The Part That Trips Up a Git Brain

Here's the question everyone asks, because it's the question the ancestry model trains you to ask: once 4 and 5 are gone from the middle, doesn't 6 now depend on 3? And if you re-insert 4 later, doesn't it end up depending on 7, since 7 is what it now sits behind?

No, and that's the whole point of the design.

In Atomic, a change's dependencies get computed once, at record time, from the content it actually touched. When a change edits or deletes a vertex, it records the change that introduced that vertex as a dependency, and that set gets frozen into the change's identity. It never gets re-derived. 6's dependencies are whatever 6 genuinely built on, some subset of the changes before it, and that set is identical whether 4 and 5 are sitting in the view or not. Removing them doesn't "re-link" 6 to 3. There's nothing to re-link, because 6 → 3 was never an edge unless 6 actually touched 3's content, and if it did, that edge already existed before the split and nothing about the split changes it.

The sequence [1, 2, 3, 6, 7] is just the order of references in the view's log. It isn't the dependency graph. Sequence position and dependency are different things, and treating them as the same thing is exactly the habit an ancestry graph trains into you, because in Git they are the same thing: the parent pointer means both "what came before" and "what this is built on" at once. Atomic pulls those apart. The only invariant a view has to hold onto is that a change's real dependencies show up somewhere before it in the sequence. Since 4 and 5 were never dependencies of 6 and 7, dropping them can't violate that invariant.

Re-insert 4 and 5 later and the same rule still holds. 4's dependencies are still its original ones, some subset of {1,2,3}. It doesn't acquire a dependency on 7 just because 7 happens to already be sitting in the target view. Insert pulls in 4's forward dependencies, which are already present, and appends the reference:

dev: [1, 2, 3, 6, 7, 4, 5]

4 now sits after 7 in the sequence, and there's still no edge from 4 to 7. Because 4,5 and 6,7 are independent, they commute, and this sequence materializes byte-for-byte the same tree as the original [1..7]. You reordered the references and the content never moved, because the content was never a function of the order to begin with.

Why This Makes Agent Workflows Dynamic

Dynamic is the right word for it. In an ancestry graph, structure is a write-time property. You commit into a shape and you live in that shape until you rewrite it, and rewriting destroys identity and provenance by construction. In a dependency graph, structure is a read-time property instead. The causal edges between changes are fixed and honest. Everything above them, how the work is grouped, which track it lives on, what counts as an experiment versus mainline, is a filter you compose now and can recompose later, as many times as the work demands, without rewriting a hash or breaking the link back to the agent turn that produced it.

For agents this is the difference between fighting the tool and using it. Say an agent returns fourteen changes. Some of them are the actual feature you asked for. A few are a dead-end experiment it tried and abandoned. A couple are an unrelated fix that belongs on a different track altogether, and the rest are cleanup it did along the way. In Git your options are to squash the whole thing into a fiction that throws away which decision produced what, or to rebase it into shape by hand and rewrite every SHA downstream, orphaning whatever provenance was keyed to them. In Atomic you split the experiment into a scratch view, insert the unrelated fix onto its own track, and leave the feature on the mainline, and every one of those changes keeps its hash, its recorded intent, and its provenance edge back to the turn that generated it. The regrouping is a decision about views. The content, and the chain of custody attached to it, never moved.

That's the model agents actually need. They don't produce linear history and they never will, so the substrate has to let you impose structure after the fact instead of demanding it up front, and it has to do that without laundering identity every time you change your mind. An ancestry graph can't give you that. Its whole premise is that position is identity. This is set-based patch theory made literal.

Common questions

Can you remove a change from the middle of a view without rewriting history?

Yes. In Atomic, grouping is a filter over a content-addressed change graph, so pulling a change out of the middle of a view is an O(1) metadata operation. The surrounding changes keep their exact hashes and nothing is re-applied or rewritten, unlike a git rebase.

Does reorganizing history in Atomic rewrite commit hashes like git rebase?

No. A git rebase produces new SHAs for every downstream commit and severs their provenance. Atomic never rewrites hashes when you reorganize: a change's identity and its dependencies are fixed at record time, so splitting, moving, or regrouping work only changes which view references it.

How is Atomic different from Git for AI coding agents?

Git records each change's position in a single lineage, so restructuring an agent's messy output means rewriting history and losing provenance. Atomic stores changes in one content-addressed graph and treats grouping as a read-time view, so agent work can be reorganized after the fact without rewriting anything or breaking the link back to the agent turn that produced it.

Build on a foundation that remembers.

Install the CLI and start recording from the next agent turn. No account required.

Install Atomic → Read the docs