This article is the third installment in the "Agentic Software Engineering" series.
Second: Agentic Software Engineering #2 | Rethinking Code Review
Git is a sharp knife designed for humans. Agents need safe power tools.
Introduction: The Implicit Assumptions of Version Control Are Crumbling
In late 2019, Google engineer Martin von Zweigbergk launched a version control system under the guise of a personal project. He named the command-line tool jj (because it's easy to type).
The project is called Jujutsu[1], simply because it matches jj; it doesn't seem to have any actual meaning. Six years later, this project has garnered 25k stars, become a significant direction for Google's internal version control evolution, and its design philosophy is being rediscovered by an unexpected user group: AI Coding Agents.
Just as we stepped into March 2026, several things happened simultaneously.
OpenAI was reported to be building a GitHub competitor[2], triggered by GitHub's frequent service degradations causing unacceptable impacts on teams relying on AI Agent continuous integration.
OpenAI also open-sourced Symphony[3]. An Agent orchestration layer implemented in Elixir, its core philosophy is "letting engineers manage work rather than supervise Agents." It creates isolated workspaces for each Linear issue, launches Codex for autonomous execution, and collects work proofs.
Meanwhile, a startup named JJHub[4] announced the construction of an Agent-Native development platform based on Jujutsu. Their core argument is that "AI Agents make a small startup look like Google in terms of commit volume."
I also noticed that Steve, a former core maintainer of Rust (author of the official Rust Book, who also wrote the foreword for my Rust book), started his own company ersc[5] based on JJ at the end of 2025. Steve also wrote a JJ tutorial[6].
GitHub itself launched a technical preview of Agentic Workflows[7] in early January 2026, attempting to embed AI Agents into existing CI/CD frameworks.
All these actions point to the same fact: The core design assumptions of Git, the version control system that has dominated software engineering for twenty years, are being systematically challenged by AI Coding Agent systems.
However, challenging Git is not the goal. Version control is merely a means; the fundamental goal it serves is: safely managing the history and concurrency of code changes. Within the framework of Agentic Software Engineering, this goal remains unchanged, but the implementation must be fundamentally rethought.
Part 1: The Five Implicit Assumptions of Git
To understand why Git encounters difficulties in the Agent era, we must first dissect its design assumptions. These assumptions were entirely reasonable in the human-coding era but are crumbling one by one in the Agent era.
Assumption 1: The Operator Understands Context
Every interaction design in Git assumes the operator—a human developer—understands what they are doing.
git add -p(interactive staging) assumes you can judge line-by-line which modifications should enter the next commit.git rebase -i(interactive rebase) assumes you can understand the dependency relationships between commits and make correct editing decisions.- When
git mergeproduces conflicts, it assumes you can understand the semantic intent of both parties' modifications and make merge decisions.
Agents do not "understand" context. They make decisions based on statistical inference. They can execute deterministic command sequences like git add -A && git commit -m "...", but when facing the interactive interface of git rebase -i, needing to rearrange pick/squash/edit lines in a text editor, it becomes a fragile operation where every step increases the probability of error.
Assumption 2: Operations Are Sequential
Git's concurrency model assumes only one operator works on a working directory at a time. The existence of git stash is proof. When you need to switch contexts, you must first "stash" the current state. When git rebase encounters conflicts, it enters an "in progress" state; until this state is resolved, you cannot perform other operations.
In the Agent era, concurrency is the norm. Orchestration systems like OpenAI's Symphony may manage 5-10 Agents simultaneously, each handling different issues, and they may need to work on the same codebase. Git's lock file mechanism (.git/index.lock) and "in progress" states become severe concurrency bottlenecks in such scenarios.
Assumption 3: Changes Require Explicit Staging
Git's staging area (index) is an intermediate layer designed for human cognition. Its value lies in allowing you to "curate" which modifications enter the next commit, which is important for writing a well-organized commit history.
But Agents do not need to "curate." They execute a clearly defined task in an isolated workspace; all file modifications are for completing this task. For an Agent, git add is a purely ritualistic action, 100% of the time it is git add -A. Every such ritual consumes tool-calling token budgets and adds context noise without producing any decision value.
Assumption 4: Branches Need Names
Git's branch model requires every branch to have a name. This makes sense in human workflows. You need to communicate with colleagues saying, "I'm on the feature/user-registration branch." But an Agent's working branches are temporary, isolated, and one-off. Assigning a branch name to every Agent session is meaningless overhead.
A deeper issue is: Git's branch concept binds "branch pointers" and "code changes" together. When an Agent amends a commit, the commit hash changes, the branch pointer needs updating, and any system referencing the old hash must follow suit. This reference instability creates massive coordination overhead in automated orchestration.
Assumption 5: Conflicts Must Be Resolved Immediately
Git stops and waits for human input when it encounters a merge conflict. git merge produces conflict markers (<<<<<<<), and git rebase enters an "in progress" state; both require you to resolve the conflict before continuing.
This is reasonable for humans. A conflict means two people have different understandings of the same code segment, requiring human judgment to decide the correct merge. But for Agent orchestration systems, "stopping to wait" is catastrophic. If an orchestrator managing 10 parallel Agents gets blocked by a conflict in any Agent's rebase, it requires intervention. And the intervention itself requires context (understanding why the conflict occurred), which may not be within the Agent orchestrator's scope.
Part 2: How Jujutsu Systematically Solves These Problems
Jujutsu (jj) is not a "better Git CLI," but a fundamental redesign of the version control data model. It happens to use Git as a storage backend (maintaining compatibility), but its user model is completely different from Git. Implemented in Rust, it uses the gitoxide library at the底层, and all core developers use jj to develop jj itself.
Solution 1: Everything is a Commit, Eliminating Ritualistic Operations
jj unifies Git's four different "state containers" (working directory, staging area, stash, commit) into one: commit.
Your working copy (@) is a commit. When you edit files, jj automatically snapshots the changes as an update to this commit. There is no staging area, no stash, and no concept of a "dirty working directory."
┌─────────────────────────────────────────────────────────────┐
│ Git's State Model: 4 containers, manual transfer required │
│ │
│ ┌──────────┐ git add ┌─────────┐ git commit ┌────────┐ │
│ │ Working │──────────►│ Staging │────────────►│ Commit │ │
│ │ Directory│ │ Area │ │ │ │
│ └────┬─────┘ └─────────┘ └────────┘ │
│ │ │
│ │ git stash ┌─────────┐ │
│ └────────────►│ Stash │ (Another temporary container) │
│ └─────────┘ │
│ │
│ Agent Operation: git add -A && git commit -m "..." │
│ = 2 tool calls, purely ritual, zero decision value │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ jj's State Model: 1 container, automatic snapshot │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ @ (working copy commit) │ │
│ │ ← Edits auto-snapshot as update to this │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Agent Operation: Edit files directly, done. │
│ = 0 extra tool calls │
└─────────────────────────────────────────────────────────────┘
For Agents, this means:
Tool calls drop from 2 steps to 0 steps. Git's git add -A && git commit -m "..." is unnecessary in jj. Modifying a file *is* "committing." The Agent only needs to execute jj new (start a new change) and jj describe (describe the change's intent) when it wants to express semantic boundaries.
Blocking errors never occur. Git errors like "Your local changes to the following files would be overwritten by merge" do not exist in jj because there is no concept of "uncommitted changes."
Example:
# jj: After editing files
vim src/auth.rs # Modify file — done.
# jj automatically snapshots current working copy as an update to @ commit
# No add, no commit needed
jj describe -m "Implement login" # (Optional) Add a description to this change
Key Mechanism: Whenever jj runs any command (jj status, jj log, jj new, etc.), it first automatically snapshots all changes in the working copy into the current @ commit. So your "unsaved modifications" are actually saved every single moment.
Git's Mental Model:
File Modification ──is a──> "Untracked dirty state" (Dangerous, may be lost)
jj's Mental Model:
File Modification ──is a──> "Latest content of @ commit" (Safe, snapshotted)
jj has no staging area, but it uses split to do the same thing, more flexibly:
# jj: At this point @ commit already contains all 3 file modifications (auto-snapped)
jj split # Interactively split current @ commit into two commits
# Interface lets you choose which files/hunks go into the first commit
# Remainder automatically becomes the second commit
Git's Approach:
Modify → Select which "enter" staging → commit → Remainder stays in working dir
(Select then store)
jj's Approach:
Modify → All auto-enter @ commit → split to extract desired parts
(Store then split)
Difference: jj's way never "loses things" — all modifications always exist in some commit.
In Git's way, working directory modifications can be lost due to 误 operations.
Therefore, jj's mental model is simpler than Git's.
The core mindset shift is: Git requires you to decide "where does this modification go" (working dir? stage? stash?) before making changes; jj lets you make changes first, then organize later. All modifications safely exist in some commit; you can split, move (squash/move), or describe them afterwards. This "store first, organize later" model naturally eliminates the risk of data loss.
For Agents, this means the Agent only needs to know two operations: jj new (start a new logical change) and jj describe (write a description for it). Everything else, such as saving, staging, and temporary storage, is handled automatically by the version control system.
Solution 2: Change ID, Stable Logical Identification
jj distinguishes between two levels of identification:
- Revision: An immutable snapshot (similar to Git's commit hash); every content modification produces a new revision.
- Change: A stable logical identifier (change ID); no matter how you modify a change's content, its change ID remains unchanged.
┌─────────────────────────────────────────────────────────────┐
│ Git: amend breaks the reference chain │
│ │
│ commit abc123 ──"Implement login" │
│ │ │
│ ▼ (agent amend modifies content) │
│ commit def456 ──"Implement login (fix)" ← New hash! │
│ │
│ Problem: All references to abc123 in CI, PRs, orchestrators break │
│ Orchestrator: "Where did the abc123 I was tracking go??" │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ jj: Change ID remains stable across modifications │
│ │
│ change kkmpptqz ──"Implement login" │
│ (revision: abc123) │
│ │ │
│ ▼ (agent modifies content) │
│ change kkmpptqz ──"Implement login (fix)" ← Same change ID! │
│ (revision: def456) ← revision changed, but change ID didn't │
│ │
│ Orchestrator always tracks kkmpptqz, unaffected by amend │
└─────────────────────────────────────────────────────────────┘
This is crucial for orchestration systems. Agent orchestration systems like Symphony need to track "the code changes corresponding to this issue." Using Git's commit hash, every Agent amend breaks this association. Using jj's change ID, no matter how many times the Agent modifies, the orchestrator can stably track via the change ID.
Solution 3: Conflicts are First-Class Objects, Non-Blocking, Non-Interrupting
This is the deepest innovation jj borrows from Darcs/Pijul.
In Git, a conflict is a blocking state. You are stuck and must resolve the conflict to continue. In jj, a conflict is data; a commit can "contain conflicts," which is no different本质上 than it containing a function or a file.
Technically, jj does not store text conflict markers (<<<<<<<), but maintains a logical representation of the conflict tree: a combination of diffs. This means rebasing a commit containing conflicts does not produce the nightmare of "nested conflict markers."
┌─────────────────────────────────────────────────────────────┐
│ Git: Conflicts block the pipeline │
│ │
│ Agent executes git rebase main │
│ │ │
│ ▼ │
│ commit 1 ── rebase success ✓ │
│ │ │
│ ▼ │
│ commit 2 ── Conflict! ✗ ──── Pipeline stuck │
│ │ Requires interactive resolution │
│ × git rebase --continue │
│ commit 3 ── Cannot continue Agent may not understand context│
│ commit 4 ── Cannot continue │
│ │
│ Status: REBASE IN PROGRESS (Intermediate state, dangerous) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ jj: Conflicts are data, operations always complete │
│ │
│ Agent executes jj rebase -d main │
│ │ │
│ ▼ │
│ change 1 ── rebase success ✓ │
│ │ │
│ ▼ │
│ change 2 ── Has conflict, marked conflicted ⚠ ── Operation continues!│
│ │ │
│ ▼ │
│ change 3 ── rebase success ✓ (Conflict resolution propagates)│
│ │ │
│ ▼ │
│ change 4 ── rebase success ✓ │
│ │
│ Status: Operation complete. change 2 marked conflicted, │
│ can be resolved later. No intermediate state, no blocking. │
└─────────────────────────────────────────────────────────────┘
More profound design implications:
Rebase always "succeeds". There is no git rebase --continue, no "in progress" state. Operations are atomic—they either complete (possibly producing commits with conflicts) or fail for other reasons.
Conflict resolution propagates. If you resolve a conflict in a certain change, jj automatically propagates the solution to all descendant changes. This is similar to Git's rerere (record/replay resolve), but it is default behavior and more reliable.
Conflicts can be deferred. After the Agent completes its work, the orchestrator can check for changes containing conflicts, then decide whether to let the Agent resolve them, start a dedicated conflict-resolution Agent, or escalate to a human. Conflicts are no longer "the pipeline is stuck," but "there is a marker in the output needing extra processing."
Solution 4: Operation Log, Complete Audit Trail
Git has a reflog, but it only records branch tip movements, has an obscure format, and is mainly used for disaster recovery.
jj's operation log records every operation you execute on the repository. Each record is an atomic operation; even if a rebase involves 10 commits, it is still one record in the operation log.
┌─────────────────────────────────────────────────────────────┐
│ Git reflog: Coarse-grained, only records branch tip moves │
│ │
│ abc123 HEAD@{0}: commit: fix login │
│ def456 HEAD@{1}: rebase: updating HEAD │
│ 789abc HEAD@{2}: checkout: moving from main to feature │
│ │
│ Problem: A rebase involving 10 commits appears in reflog as │
│ 10 scattered records, hard to atomically rollback │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ jj operation log: Atomic, one record per operation │
│ │
│ @ kmlprxqv rebase 4 commits onto main │
│ ○ zxsewqpw describe change kkmpptqz │
│ ○ tnrqpaxy new empty change │
│ ○ vqrpmzxn fetch from origin │
│ │
│ Undo entire 4-commit rebase: jj op restore zxsewqpw │
│ One command, atomic rollback. │
└─────────────────────────────────────────────────────────────┘
# View operation history
jj op log
# Undo last operation (atomic)
jj undo
# Revert to state of any historical operation
jj op restore <operation-id>
For Agents, this is a "time machine." Did an Agent perform a catastrophic operation? No need to manually find the correct state from the reflog; one jj op restore precisely returns to the previous moment. Orchestration systems can record the operation ID before every Agent operation, enabling one-click rollback on failure.
Solution 5: Automatic Rebase, Foundation for Multi-Agent Concurrency
When you modify a commit, jj automatically rebases all descendants onto the new version. Coupled with the "conflicts are first-class objects" design, this automatic rebase does not block even if conflicts arise.
┌─────────────────────────────────────────────────────────────┐
│ Scenario: Modifying historical commit B (B → B') │
│ │
│ Before mod: A ← B ← C ← D │
│ │
│ ─── Git ─── │
│ After mod B: A ← B' (manual amend) │
│ A ← B ← C ← D (C, D still point to old B!) │
│ You need: git rebase --onto B' B D │
│ If C conflicts: rebase stuck, manual resolution needed │
│ │
│ ─── jj ─── │
│ After mod B: A ← B' ← C' ← D' (Auto rebase, one step) │
│ If C conflicts: C' marked conflicted, but D' still generates│
│ No manual operation, no intermediate state │
└─────────────────────────────────────────────────────────────┘
This is jj's most core and counter-intuitive design.
Git produces nested conflict markers when conflicts occur—<<<<<<< inside <<<<<<<, which is basically unreadable. I believe this is the most headache-inducing part for every human developer.
jj understands conflicts completely differently. It does not insert text markers into files, but records "this file has multiple versions, they diverge" at the data model level.
The internal representation is a conflict tree, not text, but structured data.
jj's internal representation of a conflict file (conceptual model, not actual format):
file: src/auth.rs
conflict:
base: "fn login(user: &str, pass: &str) -> bool { ... }" # Common ancestor
side_1: "fn login(user: &str, pass: &str) -> bool { # Party 1's mod
validate_credentials(user, pass)
}"
side_2: "fn login(user: &str, pass: &str) -> Result<...> { # Party 2's mod
let valid = check_password(user, pass)?;
}"
When you checkout or view this file, jj renders it into a conflict marker format similar to Git for you to see. But this is just the presentation layer; the underlying storage is structured three-party data, not text markers.
Storage Layer: Structured conflict tree (base + side_1 + side_2)
│
▼ (When you open the file, render as human-readable format)
Presentation Layer: Git-like <<<<<<< markers (Just a view, not data)
This distinction is crucial. Because structured data can be precisely manipulated by programs, while text markers can only be read by humans.
How Conflicts Are Handled During Automatic Rebase
Scenario 1: Rebase Without Conflicts
The simplest case. Two changes modify different files or different areas of the same file:
Before mod:
main: A ← B ← C
↑
You modified B to B' (e.g., changed src/auth.rs)
Auto rebase:
C depends on B. B became B'. jj automatically rebases C onto B'.
Result: A ← B' ← C'
If C modified src/config.rs (no overlap with B''s src/auth.rs)
→ C' applies cleanly, no conflict. Auto-complete, you do nothing.
Scenario 2: Rebase Produces Conflicts, How jj "Succeeds"
Here is the key. Assume B and C both modify the same area of the same file:
Before mod:
A ← B ← C ← D
↑
You modified B to B' (changed lines 10-20 of src/auth.rs)
But C also modified lines 10-20 of src/auth.rs
Git's behavior:
git rebase --onto B' B D
→ Finds conflict when processing C
→ Stop! Insert <<<<<<< markers in src/auth.rs
→ Enter "rebase in progress" state
→ Wait for you to resolve conflict and run git rebase --continue
→ D is not processed yet, suspended
jj's behavior:
jj modifies B → B', automatically rebases C and D
→ Finds conflict when processing C
→ Don't stop! Create C', where src/auth.rs is recorded as "conflict state":
conflict:
base: Lines 10-20 of src/auth.rs in version B
side_1: Lines 10-20 in version B' (your mod)
side_2: Lines 10-20 in version C (C's mod)
→ C' is a "commit containing conflicts" — but it is a valid commit!
→ Continue processing D → D' rebased onto C'
→ If D didn't touch lines 10-20 of src/auth.rs, D' is clean
→ If D did touch, D' is also marked as containing conflicts
→ Operation complete. The entire rebase chain is not interrupted.
┌──────────────────────────────────────────────────────────────┐
│ Git rebase encounters conflict │
│ │
│ A ← B' ← C ← D │
│ ↑ │
│ Conflict! Stop here │
│ Status: REBASE IN PROGRESS │
│ C becomes a half-finished product with <<<<<<< markers│
│ D not processed yet │
│ You must: Resolve → git add → git rebase --continue │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ jj rebase encounters conflict │
│ │
│ A ← B' ← C' ← D' │
│ ⚠ ✓ │
│ Conflict recorded D' clean (if it doesn't touch conflict)│
│ in C' │
│ But C' is a │
│ valid commit │
│ │
│ Status: Operation complete. No "in progress". │
│ C' marked conflicted, you can resolve later. │
└──────────────────────────────────────────────────────────────┘
Scenario 3: How to Resolve Conflicts
When you decide to resolve the conflict in C':
bash
# Check which changes have conflicts
jj log
# Output will mark:
# ⚠ kkmpptqz (conflict) "feature: implement registration"
# Switch to the conflicted change
jj edit kkmpptqz
# Now when you open src/auth.rs, you see rendered conflict markers:
# <<<<<<< Side #1 (your mod B')
# fn login(...) -> bool {
# validate_credentials(user, pass)
# ||||||| Base (common ancestor B)
# fn login(...) -> bool {
# check(user, pass)
# ======= Side #2 (C's mod)
# fn login(...) -> Result<...> {
# let valid = check_password(user, pass)?;
# >>>>>>>
# You (or Agent) edit the file, remove markers, write correct merge
# Save file
# jj auto-detects conflict markers are gone → change no longer conflicted
# And — critically — D' will be automatically rebased onto resolved C'
# If D' was previously连带 conflicted due to C''s conflict,
# now that conflict is resolved, D' might automatically become clean
Scenario 4: Propagation of Conflict Resolution, The Killer Feature
Before resolving conflict:
A ← B' ← C'(⚠conflict) ← D'(⚠collateral conflict) ← E'(⚠collateral conflict)
After resolving C''s conflict: A ← B' ← C''(✓clean) ← D''(?) ← E''(?)
jj automatically propagates conflict resolution to D'' and E''.
If D and E's conflicts were entirely caused by C's conflict,
D'' and E'' automatically become clean, no need to resolve individually.
In Git, you need to manually resolve conflicts for C, D, E individually.
In jj, you only resolve C's conflict, D and E may auto-fix.
This is like Git's `rerere`, but jj's implementation is more fundamental —
it's not "remembering you resolved similar conflicts before and replaying",
but "conflict resolution itself is a diff that can be rebased and propagated".
Why "Conflict is Data" is so important
The essential impact of turning conflict from "state" to "data":
┌──────────────────────────────────────────────────────────────┐
│ "Conflict is State" (Git) │
│ │
│ • Conflict blocks operation — must resolve first │
│ • Conflict cannot be committed — only exists in working dir │
│ • Conflict cannot be propagated — may regenerate on rebase │
│ • Conflict puts repo in "intermediate state" — dangerous │
│ • Resolution time window: during rebase (pressure, blocked) │
│ │
│ Impact on Agent: │
│ Agent hits conflict → Pipeline stuck → Orchestrator intervenes│
│ → But orchestrator may lack context → Escalate to human │
│ → Human needs to understand Agent's work → High cost │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ "Conflict is Data" (jj) │
│ │
│ • Conflict doesn't block — complete rebase, marker in commit │
│ • Conflict can be committed — it's part of the commit │
│ • Resolution propagates — resolve one, descendants auto-fix │
│ • Repo always in "clean state" — some commits just have markers│
│ • Resolution time window: anytime (no pressure) │
│ │
│ Impact on Agent: │
│ Agent hits conflict → Operation done, conflict marked → Continue│
│ → Orchestrator sees "change X is conflicted" → Decide: │
│ a) Start conflict-resolution Agent (give it 3-party data) │
│ b) Skip temporarily, process other non-conflict tasks │
│ c) Escalate to human (not urgent, nothing blocked) │
│ → Resolve anytime, doesn't affect other Agents │
└──────────────────────────────────────────────────────────────┘
Possible Ways for Agents to Resolve Conflicts
Because jj's conflicts are structured data (base + side_1 + side_2), Agents can obtain richer information than Git to resolve conflicts:
# Information seen by Agent
# Git: Text mixed with <<<<<<< markers in a file
# Agent must parse "what is ours, what is theirs, what is base" from text
# Easy to parse wrong, especially nested conflicts
# jj: Structured three-party data
# base: Original version (common ancestor)
# side_1: First party modification
# side_2: Second party modification
# Agent can precisely get full content of all three parties
# A reasonable Agent conflict resolution strategy:
# 1. Extract full content of base, side_1, side_2
# 2. Understand side_1's intent (from change description or associated Contract)
# 3. Understand side_2's intent (same as above)
# 4. Generate merge result
# 5. Verify merge result satisfies both parties' Contracts
If we further bind Contract (some Spec constraints) to changes, conflict resolution becomes more grounded:
change kkmpptqz (side_1) → contract-042 "Implement user registration"
change rrsnntyz (side_2) → contract-057 "Fix login verification"
Conflict occurs in src/auth.rs
Conflict Resolution Agent can get intents of both Contracts:
- contract-042 requires: hash password with bcrypt-12 on registration
- contract-057 requires: verify password on login and return JWT
→ Agent has enough semantic info to judge how to merge:
Both modified different functions (registration vs login),
Merge result should contain both registration logic and fixed login logic.
This is the synergy of Conflict is Data + Spec-Change Binding. Conflicts are no longer "two pieces of code fighting for unknown reasons," but "overlap of two known-intent changes in the same area." With intent information, solutions have a basis for judgment.
Solution 6: Workspace, Advantage for Multi-Agent Concurrency
Git worktree is an important scenario for multi-Agent concurrent work. jj has its own solution, cleaner than Git worktree.
Git worktree's core scenario: You want to work on different branches of the same repo simultaneously without stashing/checkout back and forth.
jj's corresponding concept is called workspace. But its model is simpler than Git worktree because jj has no index/staging area; each workspace's state is just an @ commit, nothing else.
# jj: Create multiple workspaces
jj workspace add ../feature-login
jj workspace add ../bugfix-auth
# Now you have three directories:
# ./repo ← Default workspace, @ = some change
# ../feature-login ← Workspace "feature-login", @ = another change
# ../bugfix-auth ← Workspace "bugfix-auth", @ = yet another change
# They share the same jj repo data
# Each workspace has its own independent @ (working copy commit)
# List all workspaces
jj workspace list
# Output:
# default: kkmpptqz (current change)
# feature-login: rrsnntyz (another change)
# bugfix-auth: ppmmlkqz (yet another change)
# Key Differences
┌──────────────────────────────────────────────────────────────┐
│ Git Worktree │
│ │
│ Each worktree has: │
│ ├── Independent working directory (files) │
│ ├── Independent index / staging area (staging state) │
│ ├── Independent HEAD (points to commit/branch)│
│ └── Shared .git/objects (object store) │
│ │
│ Limitations: │
│ • Each worktree must point to a different branch │
│ (Cannot checkout same branch in two worktrees) │
│ • Each worktree has its own index, state complexity ×N │
│ • If one worktree is in "rebase in progress", │
│ it locks related refs, affecting other worktrees │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ jj Workspace │
│ │
│ Each workspace has: │
│ ├── Independent working directory (files) │
│ ├── Independent @ (working copy commit) │
│ └── Shared repo data (all changes + op log) │
│ │
│ Does NOT have: │
│ • No index (not needed) │
│ • No branch pointing limits (workspace points to change) │
│ • No "in progress" state (conflict is data, no blocking) │
└──────────────────────────────────────────────────────────────┘
Each jj Workspace instance has only **one layer of state**:
State of each jj Workspace:
@ commit (current working copy change)
No staging area, no "in progress" state.
Orchestrator only needs to know "which change this workspace's @ points to".
This is the manifestation of "everything is a commit" in the workspace scenario: The complete state of each workspace is just a change ID. An orchestrator tracking N Agents is tracking N change IDs, not the Cartesian product of N × (working dir + index + HEAD + possible intermediate states).
Significance for Agent Orchestration
┌──────────────────────────────────────────────────────────────┐
│ Complexity of Orchestrator managing 10 Agent workspaces │
│ │
│ Git Worktree: │
│ • 10 branches to name and manage │
│ • 10 staging areas may be in inconsistent states │
│ • Any one could be stuck in "rebase in progress" │
│ • Merge conflicts block the pipeline │
│ • Crash recovery requires judging state of each worktree │
│ State space: O(N × M) N=worktrees, M=possible intermediate states│
│ │
│ jj Workspace: │
│ • 10 anonymous changes, no naming needed │
│ • No staging area │
│ • No "in progress" state │
│ • Conflicts marked but not blocking │
│ • Crash recovery: jj op restore or just continue (mods in @) │
│ State space: O(N) Each workspace is just a change ID │
└──────────────────────────────────────────────────────────────┘
So jj workspace is not just a "Git worktree alternative"; by eliminating staging area and intermediate states, it compresses each workspace's state complexity from multi-dimensional to one-dimensional (a single change ID). For orchestration systems managing multiple parallel Agents, this is an order-of-magnitude simplification.
Part 3: When Spec Meets Version Control, Spec-Change Binding
Review: The Role of Spec in Agentic Software Engineering
In the previous article of this series, we established the core framework of Agentic Software Engineering: Spec is the control plane, Code is the data plane, and Agent is the execution plane. The core human work is writing Specs (specifications); Agents are responsible for translating Specs into code implementations, and verification systems confirm code satisfies the Spec.
In this framework, we call the task-level Spec given to Agents a Task Contract. It is a structured document containing four core elements:
- Intent: What to do, and why.
- Decisions: Already determined technical choices, eliminating Agent decision space.
- Boundaries: Which files can be modified, what is forbidden.
- Completion Criteria: BDD-format acceptance criteria, deterministic pass/fail checks.
The concept of Contract comes from our analysis of the Code Review crisis: When AI code production speed far exceeds human review capacity, the solution is not "review code faster," but shift human review up to the intent layer—humans review whether the Spec/Contract correctly defines intent and acceptance criteria, machines verify if code satisfies the Contract.
Current Break: Spec and Version Control are Two Worlds
In today's toolchain, the association between Spec (whether GitHub Issue, Jira Ticket, or our defined Contract) and version control systems is fragile and convention-based:
┌─────────────────────────────────────────────────────────────┐
│ Current Loose Association │
│ │
│ Spec/Contract │ Version Control │
│ (External System) │ (Git/jj) │
│ │ │
│ "Implement User Reg" ·····via commit message····→ abc123 │
│ Issue #42 "fixes #42" string association │
│ │
│ Problems: │
│ 1. Association relies on convention (forget #42, link breaks)│
│ 2. commit hash unstable (breaks on amend) │
│ 3. How many commits per Spec? Unknown │
│ 4. Which acceptance criteria of Spec did a commit satisfy? │
│ 5. No linked version history for Spec and Code changes │
└─────────────────────────────────────────────────────────────┘
This break is acceptable in human manual development—humans maintain the association "I am writing code for Issue #42" in their minds. But in Agent orchestration systems, this association must be formalized, queryable, and unbreakable by amends.
Spec-Change Binding: The Opportunity Provided by jj
Two features of jj provide the foundation for solving this:
Stable Change ID solves the "amend breaks association" problem. No matter how many times the Agent modifies, the change ID remains unchanged; the orchestration system can always use the change ID to track "the code changes corresponding to this Contract."
External Metadata Storage solves the "association relies on convention" problem. jj already stores metadata like bookmarks outside the Git repository. Contract binding can serve as another type of metadata, becoming a first-class citizen of version control.
┌─────────────────────────────────────────────────────────────┐
│ Ideal Spec-Change Binding │
│ │
│ Contract "User Registration" │
│ ├── intent: "Implement registration API endpoint" │
│ ├── decisions: [bcrypt-12, POST /api/v1/auth/register] │
│ ├── boundaries: [ONLY src/auth/**, DO NOT add deps] │
│ ├── criteria: [Reg success→201, Duplicate email→409, ...] │
│ │ │
│ ├── bound_changes: ← Formal binding, not commit message │
│ │ ├── kkmpptqz "Implement registration endpoint" │
│ │ ├── rrsnntyz "Add registration tests" │
│ │ └── ppmmlkqz "Fix email validation" │
│ │ │
│ └── verification: ← Verification status linked to Contract │
│ ├── kkmpptqz: L1=PASS, L2=PASS, L3=PASS │
│ ├── rrsnntyz: L1=PASS │
│ └── aggregate: all_criteria_met = true │
│ │
│ Queryable: │
│ "All changes for Contract 042" → [kkmpptqz, rrsnntyz, ...] │
│ "Which Contract does change kkmpptqz belong to?" → C042 │
│ "Are all acceptance criteria for Contract 042 met?" → true │
└─────────────────────────────────────────────────────────────┘
This model transforms Spec and version control from "two loosely associated worlds" into "a unified traceability chain":
Human defines Spec/Contract
│
▼
Agent generates Changes (linked to Contract)
│
▼
Verification system checks if Changes meet Contract Criteria
│
▼
Contract Acceptance (Human reviews Contract def + Verification pass)
Mapping of Spec Levels to Version Control
In our Spec conception, Specs are divided into three levels:
- L0 Organizational Spec (Security policies, architecture principles), changes extremely slowly.
- L1 Project Spec (Tech stack, module boundaries, API contracts), changes slowly.
- L2 Task Spec / Contract (Specific task intent, constraints, acceptance criteria), one per task.
The correspondence between these three levels and version control is:
┌─────────────────────────────────────────────────────────────┐
│ Mapping of Spec Levels to Version Control │
│ │
│ L0 Org Spec ──→ Rule files (rules/security-baseline.md) │
│ (Security etc) Versioned in repo, rarely changes │
│ Agent loads on demand via rule routing │
│ │
│ L1 Project Spec ──→ Project config (specs/project.yaml) │
│ (Stack, Contracts) Versioned in repo, Sprint-level changes │
│ Agent loads on startup │
│ │
│ L2 Task Contract → Formally bound to jj changes │
│ (Task Intent/Accept) Contract change triggers regen │
│ Verification results linked to Contract │
└─────────────────────────────────────────────────────────────┘
Key Insight: L0 and L1 Specs themselves should be versioned in the code repository. They are the "constitution" of the code; changing L0/L1 Specs should be as慎重 as amending a constitution, with complete change history. L2 Contracts are "work order" level; their lifecycle starts and ends with their corresponding changes.
Part 4: What Agent-Native VCS Still Lacks
jj solves most friction points of Git in Agent scenarios. But from the perspective of the complete Agentic Software Engineering framework, there are several dimensions current jj does not cover.
Primitive 1: Verification Status as a Change Attribute
Current version control systems do not know if a change passed tests. CI/CD is an external system; verification results exist in GitHub Actions or Jenkins, not in the version control system.
In Agent-Native VCS, verification status should be an intrinsic attribute of the change:
Change kkmpptqz:
revision: abc123
description: "Implement registration endpoint"
contract: contract-042
verification:
L1_type_check: PASS (2s)
L2_contract: PASS (5s)
L3_bdd: PASS (30s)
L4_adversarial: PENDING
status: verified_to_L3
This makes "Can this change be merged?" a question the version control system can answer directly, without checking external CI systems. The merge gate changes from "human clicks approve" to "verification engine confirms all layers passed."
Primitive 2: Agent Identity and Permissions
Git's author/committer fields use name + email. In multi-Agent concurrent work scenarios, we need richer identity information:
Change kkmpptqz:
agent: claude-code-session-7a3b
role: implementer
contract: contract-042
permissions: workspace-write, files=[src/auth/**]
orchestrator: symphony-run-019
This allows audit systems to answer: "Which Agent generated this code under what permissions?" In compliance scenarios, this is far more valuable than "Author: bot@ci.example.com".
Primitive 3: Snapshot Timeline Rather Than DAG
Git's commit DAG (Directed Acyclic Graph) is designed for humans to understand branch/merge history. But when 10 Agents work in parallel, the DAG becomes a bowl of unreadable spaghetti.
Agent-Native history visualization should be a snapshot sequence arranged by timeline, filterable by Contract, Agent, or module. Imagine a timeline where each Agent is a swimlane, Contracts are color-coded, and you can scrub to any moment to see the codebase state at that instant.
jj's operation log already provides part of the foundation. It records all operations by time. What's missing is the association with Contract/Agent identity and the visualization layer.
Part 5: From Code Review to Contract Acceptance
The revolution in version control is not just at the tool level; it 连带 changes the entire collaboration model.
Traditional Model: Branch → PR → Code Review → Merge
GitHub has built its entire collaboration flow around Pull Requests. Developers create a branch, push code, open a PR, colleagues review the code diff, and merge after approval.
This flow faces three fundamental problems in the Agent era:
1. The object of review is wrong. As analyzed in detail in our previous article "Rethinking Code Review," when AI generates code at machine speed, human line-by-line code diff review is not scalable. Faros AI data shows PR merge rates surged 97.8% while Review time surged 91.1%.
2. The granularity of Branch is wrong. An Agent may complete a Contract in minutes; the process of creating a branch → push → open PR → wait for review → merge takes longer than the actual work time.
3. The gate of Merge is wrong. The PR approve button is a human judgment, "this code looks right." But verification should be deterministic, "this code passed all verification layers."
Agent-Native Model: Contract → Changes → Verification → Acceptance
┌──────────────────────────────────────────────────────────────┐
│ Traditional GitHub Flow │
│ │
│ Issue ──→ Branch ──→ Code ──→ PR ──→ Code Review ──→ Merge │
│ (Vague) (Named) (Hand-written) (Open PR)(Human diff) │
│ │
│ Human Involvement: Full process │
│ Gate: Human subjective judgment │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Agent-Native Flow │
│ │
│ Contract → Changes → Verification → Acceptance │
│ (Structured Task) (Anonymous jj) (Deterministic) (Contract-level)│
│ │
│ Human Involvement: Contract def + Final acceptance │
│ Gate: Verification engine confirms all Criteria passed │
└──────────────────────────────────────────────────────────────┘
In this model:
Contract replaces Issue + PR description. Contract is structured, contains acceptance criteria, and is automatically verifiable. It is not a "task memo for humans," but "executable spec for Agents + checklist for verification systems."
Changes replace Branch. jj's anonymous changes + stable change ID mean no branch names are needed; orchestration systems track via change ID.
Verification Pipeline replaces Code Review. Multi-layer deterministic verification from type checking to contract verification to BDD tests to adversarial Agent review provides more comprehensive, faster, and scalable quality assurance than human Code Review.
Contract Acceptance replaces PR Approve. Humans do not review code diffs, but rather: "Are the acceptance criteria for this Contract correctly defined? Do verification results indicate the Contract is correctly satisfied?"
Part 6: Practice Guide - How to Gradually Adopt in Existing Toolchains
Ideals are great, but most teams won't abandon Git and GitHub overnight. Here is a gradual adoption path.
Level 0: Colocate jj in Git Repository (Zero Risk)
jj supports coexisting with Git repositories (colocate mode), meaning you can start using jj in any existing Git repository without affecting others in the team continuing to use Git.
# Initialize jj in existing Git repo (both coexist)
cd your-repo
jj git init --colocate
# Now can use both jj and git
# jj ops sync to Git, git ops visible to jj
Immediate Value for Agents:
jj op logprovides complete audit of Agent operations.- Automatic snapshot means Agents won't "lose" uncommitted modifications.
jj op restoreallows precise rollback when Agents make mistakes.
Level 1: Use jj for Agent Workspaces
In Claude Code / Codex CLI workspaces, replace git with jj as the Agent's version control tool:
# Simplified Agent toolset
# No need: git add, git stash, git rebase --continue
# Only need: jj new, jj describe, jj bookmark set, jj git push
Key Benefit: Agent's version control tool call volume reduces by 30-50% (eliminating ritualistic operations like git add, git stash, git rebase --continue); every saved tool call is more context budget available for actual work.
Level 2: Track Tasks with Change ID
In Agent orchestration systems, use jj's change ID instead of Git's commit hash to track changes. Change ID is stable across amends; orchestrators don't need to re-bind references after every Agent modification.
Level 3: Leverage Conflict-as-Data Characteristics
In multi-Agent concurrent scenarios, no longer treat conflicts as "errors," but as "states needing extra processing": After Agent completion, check for changes with conflict markers. Simple conflicts auto-resolve, complex ones escalate to humans. The pipeline is never "stuck."
Part 7: Comparison Overview
Git vs jj vs Ideal Agent-Native VCS
| Dimension | Git | Jujutsu (jj) | Ideal Agent-Native VCS |
|---|---|---|---|
| Data Model | 4 containers | 1 container (commit) | 1 container + Spec metadata |
| Change ID | commit hash (changes on mod) | change ID (stable) | change ID + Contract binding |
| Conflict Model | Blocking state | First-class data object | First-class object + Auto-resolve strategy |
| Operation Audit | reflog (coarse) | operation log (atomic) | operation log + Agent identity |
| Branch Model | Named branches | Anonymous changes + bookmarks | Anonymous changes + Contract association |
| Auto Rebase | Manual | Default auto | Auto + Conflict propagation |
| Spec Integration | None (commit message convention) | None | Formal Contract-Change binding |
| Verification Integration | None (external CI) | None | Verification status is change attribute |
| Agent Friendliness | Low (many rituals) | High (auto-snapshot, no blocking) | Very High (native identity/permissions) |
| Storage Backend | Git only | Git (extensible) | Extensible (Git / Cloud) |
| Ecosystem Compatibility | Native | Git compatible | Git compatible + Native protocol |
Key Conclusion: jj has already solved 80% of Agent-VCS friction. Through "everything is commit", change ID, conflict-as-data, and auto-rebase. The remaining 20% involves linking with upper layers of Agentic Software Engineering (Spec/Contract, verification pipelines, Agent orchestration).
Conclusion: Version Control Reflects the Way of Work It Serves
Looking back at the history of version control, every generation of systems reflects the working methods of its era:
CVS/SVN Era: Central server, sequential commits. Reflected "one team, one codebase, one integration manager" waterfall development.
Git Era: Distributed, cheap branches. Reflected "distributed teams, parallel development, Pull Request collaboration" agile development.
Agent Era: What is needed? Automatic snapshots, stable identifiers, conflict-as-data, atomic operations, complete audit. Reflects "multi-Agent concurrency, Spec/Contract driven, deterministic verification, machine speed" Agentic Software Engineering.
Git is not "wrong." It served human developers perfectly for twenty years. But its core assumptions—that the operator understands context, operations are sequential, changes need explicit staging, conflicts must be resolved immediately—have become friction rather than features in the Agent era.
Jujutsu offers a pragmatic evolution path: Maintain Git compatibility (don't break existing ecosystems), while introducing Agent-Native data models and operation semantics. It doesn't require you to abandon GitHub, switch the whole team, or rebuild CI/CD, but it gives Agents a safe, deterministic version control interface that won't get "stuck."
And when we link version control with Spec/Contract systems. By implementing Contract-Change binding via Change ID, achieving automated gating through verification status integration, and enabling complete audit via Agent identity, version control upgrades from an isolated "code storage layer" to the core infrastructure of Agentic Software Engineering.
Spec is the control plane, Code is the data plane, Agent is the execution plane, and Version Control is the physical layer carrying the data plane. When the physical layer transforms from "sharp knives designed for humans" to "safe power tools designed for Agents," the design space of the entire upper system will be opened up.
References
[1] Jujutsu: https://github.com/jj-vcs/jj
[2] OpenAI reported building GitHub competitor: https://www.reddit.com/r/technology/comments/1rk8bfa/openai_is_developing_alternative_to_microsofts/
[3] Symphony: https://github.com/openai/symphony
[4] JJHub: https://jjhub.tech/
[5] ersc: https://ersc.io/
[6] JJ Tutorial: https://steveklabnik.github.io/jujutsu-tutorial/introduction/introduction.html
[7] Agentic Workflows: https://github.com/github/gh-aw