Conflicts as data¶
In Git, a conflict is a halt. The merge stops, your working tree fills with <<<<<<< markers, and you
are stuck in a special, anxious mode until you fix it — you can't easily switch tasks, your teammates'
merges hit the same wall, and the operation you ran reports failure.
TOVIO treats a conflict as data, not an event. When two changes can't be merged automatically, TOVIO stores the conflict as a first-class object and the merge completes. The conflict becomes an open question the repository holds for you — visible, tracked, resolvable later — instead of a wall you have to clear before doing anything else.
✓ landed `feature/cgm-sync` into `my-lane` with 3 conflict(s) 9f2c1a
⚠ content src/integrations/cgm/dexcom.ts
⚠ content src/integrations/cgm/types.ts
⚠ delete/modify tests/integrations/cgm.test.ts
nothing is blocked — the conflicts are stored as data.
resolve them: tovio resolve
list them: tovio status --conflicts
Note the glyph: that is a success line, not an error. The land succeeded. There are open questions attached to its result.
flowchart LR
M["tovio land / sync"] --> A{"auto-merges?"}
A -- yes --> OK([✓ clean merge])
A -- no --> S[["✓ merge completes<br/>+ typed conflict object stored"]]
S --> W["Keep working:<br/>commit · switch · sync · stack"]
W --> R["tovio resolve when ready"]
R --> G[["Landing it on requires<br/>conflict-free tips<br/>(and the protected-lane gates)"]]
G --> OK2([✓ done])
More diagrams: Conflict workflows.
What "conflict object" actually means¶
A conflict object holds both sides of the unresolved region, plus the merge base, as structured data inside the object graph. It is a normal versioned object — content-addressed, syncable, part of history. Because it's data:
- The merge that produced it completed, so the operation didn't fail.
- You can commit, switch, sync, and stack on top of a state that contains conflicts.
- When someone resolves the conflict, the resolution propagates everywhere that same conflict appears, survives history rewriting (amend / rebase), and does not reappear — rather than being re-fought in every lane that inherited it.
Mental model
A conflict is like a type error, not a runtime crash. It doesn't stop you from editing other files, saving, and committing — it's right there in the output, tracked — but the affected file isn't "clean" until you resolve it. The compiler analogy is exact: a type error is information the system is holding for you, not a failure of the build system.
This rests on TOVIO's typed three-way merge over the commit DAG.
The shape of a conflict¶
A conflict object is file-granular: each one is anchored at a single path and carries that file's base / ours / theirs sides. Two files in conflict are two independent objects — there is no umbrella "conflict set" that bundles a merge's conflicts together. A merge or land simply completes and leaves however many single-file conflict objects it produced, each content-addressed on its own.
Underneath a single file, resolution is region-granular. A text conflict isn't one indivisible blob — it's a sequence of conflicted regions (hunks) with clean content between them, and each region is resolvable on its own. That's why a file can be partially resolved: some regions settled, others still open. Those regions are computed on demand from the three sides plus the two changes in conflict; they aren't stored as child objects, so every replica re-derives the identical breakdown instead of trusting a second copy.
And the whole thing is keyed on the change pair — the two changes that
diverged, by their stable identity, ordered canonically so an A-into-B merge and the same conflict
met later as B-into-A carry the same key. That key is what makes a conflict's identity independent of
the operation that surfaced it: a land, a merge, a rebase, a pull, or a re-sync all produce the same
conflict — which is exactly why a resolution recorded once (next section) re-applies everywhere it
recurs, rather than being tied to the one operation that happened to expose it.
The model in one line
File-granular first-class objects, region-granular resolution computed underneath, with the whole thing keyed on the change pair — so a conflict's identity is stable and operation-independent by design.
"Non-blocking" has a precise scope — and hard limits¶
This is the part it's important to get right, because "non-blocking" is easy to over-read. A repository with open conflicts is always a consistent VCS state: you can commit, switch, sync, and stack on top of it. But it is not necessarily a buildable state — a conflicted source file holds both sides as data, so it won't compile and CI will fail on it. Keep three things separate:
Non-blocking — within your own stack and for independent work
A conflict in chg-003 never stops you from starting chg-004. A teammate's conflict never stops
your unrelated work. Conflicts don't quarantine the repository; they sit where they are.
Strictly blocked — landing a change that still holds conflicts
tovio land and the Forge refuse to land when the source tip, the target tip, or the merge base
still contains unresolved conflicts — resolve first. Onto a protected lane like main or release
the land gates enforce conflict-freedom as an explicit gate on top of that. Non-blocking does not
mean "ship broken code." Land is the build gate. A conflict may sit in your private work; it may
not travel onward through a land.
Gated — when you pull someone else's conflicted change as a dependency
If a teammate's change that you build on top of still has unresolved conflicts, TOVIO will not
silently materialize those conflict markers into your working tree and break your build. You wait
for them to resolve — or you opt into tovio sync --isolate to build against the last-known-clean
version of their change.
The boundary that matters¶
"Non-blocking" is about your stack — it does not propagate unresolved conflicts across developers' build trees.
A worked example makes the line concrete:
- Alice lands a change that forces a conflict into Bob's unlanded
chg-002. That conflict is Bob's to resolve. It doesn't block Alice — her land succeeded — and the conflict is now stored onchg-002. - Carol's UI work depends on
chg-002. When Carol runstovio sync, TOVIO does not drop Bob's conflict markers into her tree. It tells herchg-002is conflicted and offers--isolateso she can keep building against the last clean version.
So the conflict is real, it's tracked, and it's assigned to exactly one person's stack — without
spraying broken markers across everyone who happens to depend on the conflicted change. You can always
check where you stand with tovio change health <id> and tovio status --conflicts.
Resolve once — and it stays resolved¶
Because a conflict is stored data with a stable identity, a resolution you record once is something
TOVIO can carry forward. Three guarantees follow, and together they replace Git's rerere ("reuse
recorded resolution") with something that just works by default:
- Resolve once, applies everywhere. The same conflict appears with the same identity wherever it shows up. Resolve it in one place and TOVIO applies that resolution to every other context where the identical conflict recurs — instead of asking three developers to make the same edit three times.
- Survives history rewriting. A resolution is keyed to the changes
in conflict, and a change's identity persists through every rewrite. So a resolution recorded before
you
amendorrebasestill applies after — those paths, and the descendant cascade a land triggers, run the replay. (change absorbstill folds through the pure core path, so a resolution does not yet replay there; routing it is a tracked follow-on.) - Doesn't reappear. Once resolved, a conflict stays resolved across a re-sync or a rebase. It won't
silently come back and make you re-fight it. (Changed your mind? The specified
tovio conflicts reopenverb for deliberately reopening a resolution is not yet shipped.)
This is a direct consequence of conflicts being durable data rather than ephemeral working-tree state.
And you can record why you resolved it that way. A resolution captures what won; TOVIO can also
capture the reasoning behind it. tovio resolve --why "<reason>" (with optional --rejected /
--dead-end / --confidence) attaches a short, structured rationale to the resolving commit — the same
signed "why" record TOVIO already keeps for agent commits, surfaced by tovio log --why — so the
thinking that settled the conflict travels with the resolution instead of evaporating into a commit
message. On a protected file that rationale is sealed to the file's readers, exactly like the resolved
content, so the reasoning never leaks. It is descriptive only: it explains the choice, it never grants or
gates anything.
Why, not just what
A resolution answers what won; --why answers what you weighed and why the other side lost. It
reuses TOVIO's existing rationale record, so the reasoning is attested and — on protected paths —
sealed, never a strippable side-note.
The sound merge engine has landed
The merge engine supports typed conflicts, whole-file and region rerere replay, and rewrite-aware
resolution propagation — clear, region, and sealed protected-path caches replay and reseal across
rebase and land cascades. Two follow-ons stay open: routing change absorb's cascade through the
same replay pipeline, and protected sealed-cache hit/reseal integration evidence with an OS keychain
on each desktop platform (a tracked Phase 5 item). The user-facing behavior remains the contract it
implements.
A note on divergence¶
There's a second kind of "conflict" worth distinguishing. When two replicas advance the same lane to
concurrent commits and then sync, TOVIO reconciles the two on the lane — it never picks a pointer winner
and strands the loser, and it never asks you to force-push. It runs the same three-way merge as land: if the
sides merge cleanly the lane advances to a two-parent merge commit; if they don't, it advances to a
two-parent commit that carries conflict objects — the very objects described above. Both diverged tips
become parents of the new lane tip, so nothing lands off-lane and no work is stranded. A content fork on sync
is just an on-lane merge-or-conflict commit. The CRDT refs still run underneath,
but only to propagate that reconciliation pointer between replicas. Both are covered on
Offline & distributed.
Where to go next¶
- Why divergence isn't an error in the first place: Offline & distributed.
- How stacking interacts with conflicts: The change model.
- The feeling this is part of: Fearlessness.
Last reviewed September 9, 2026
Suggest an improvement to this page Not for security reports — see disclosure