Jujutsu Agent Protocol
Use this protocol whenever jj root succeeds from the current directory. Do not translate a Git workflow command-for-command; reason from jj's working-copy commit and graph.
Non-negotiable rules
- Use
jjexclusively for VCS operations. Do not run rawgitcommands in a jj repo, including a colocated.jj/+.git/repo. - Never run
jj git pushunless the user explicitly said push. Preparing, committing, shipping, or opening a PR does not imply permission to push. - Never use interactive/editor forms (
-i,--interactive, bare commands that open an editor,jj resolve, orjj diffedit). Supply messages with-m. - Before editing or mutating history, inspect the full status, graph position, and relevant diff. Do not assume
@is where a previous agent left it. - Prefer stable change IDs (letters such as
nmwwolux) over changing hexadecimal commit IDs. - After every history mutation, verify with
jj status,jj log, and the relevantjj diff/jj show. - If syntax or behavior is uncertain, use
jj helpinstead of guessing; the integrated help matches the active jj version. - If a mutation has an unexpected result, stop and inspect
jj op log. Prefer an explicitjj op revert <op-id>orjj op restore <op-id>over the implicit target selected byjj undo.
Built-in documentation
Prefer jj's integrated, version-correct help over remembered syntax or web examples:
jj help # command list, global options, short descriptions
jj help <command> # usage, arguments, options, behavior, and examples
jj help <command> <subcommand> # nested help, e.g. `jj help git push`
jj help -k <keyword> # conceptual and language reference
Use command help before running an unfamiliar command or when flags may have changed. jj <command> --help is equivalent, but jj help ... composes naturally for nested subcommands.
Keyword topics:
| Keyword | Covers |
|---|---|
bookmarks |
Bookmark semantics, remotes, tracking, and Git branch mapping |
config |
Configuration files, scopes, precedence, values, and settings |
filesets |
Selecting files with patterns, operators, functions, and quoting |
glossary |
Canonical jj terminology |
revsets |
Selecting revisions with symbols, operators, functions, and patterns |
templates |
Customizing command output with -T/--template |
For example, run jj help -k revsets. Run jj help --help to list all supported keyword values.
Mental model
- The working copy is a mutable commit named
@. jj snapshots file edits at the start of most jj commands; there is no staging area. jj newcreates a new empty child commit. After completing and reviewing a described change, use it as a boundary so the completed content is in@-and future edits land in a clean@.jj commit -m ...is equivalent to describing the current commit and then runningjj new; completed content is then in@-.- Bookmarks are named pointers, not current branches. They follow rewrites of the change they point to but do not advance to newly created child changes.
- Conflicts are stored in commits. A rebase may finish successfully while leaving conflicted commits.
- Rewrites preserve the change ID and replace the commit ID. The operation log makes repo-level recovery possible.
Required preflight
Run from the repository, before making edits:
jj root
jj status
jj show
Classify @ before touching files:
State of @ |
Action |
|---|---|
| Empty and undescribed | Safe starting point; describe it for this task. |
| Empty but described | It may reserve another task. Continue only if its description matches; otherwise jj new. |
| Non-empty | Inspect the full diff and description. Continue only when it belongs to this task; otherwise preserve it and jj new. |
| Conflicted | Resolve or deliberately work above it; never silently treat it as clean. |
When existing work is ambiguous or unrelated, preserve it in place and start a fresh @ with jj new. Never squash, abandon, restore, or redescribe existing work merely to obtain a clean state.
Blessed coding workflow: describe first, close with new
Use one workflow consistently:
# After preflight confirms @ is safe for this task
jj describe -m "<description>"
# Edit files and run project checks
# Review the completed change while it is still @
jj status
jj show
# Add more context to description after change is done
jj describe -m "<description>\n<more description>"
# Only after checks and review pass, close it by moving to a clean child
jj new
jj status
jj show @-
- Keep one logical change in
@. - Review the complete diff before closing the change; do not use
jj newto hide unfinished or unchecked work. - End the task with the completed, described change at
@-and a new empty, undescribed@for future edits. - If the user explicitly requests
jj commit, usejj commit -m ...instead of the separatejj describeand finaljj new. Do not move bookmarks without inspecting them. - Do not use a destination bookmark as shorthand until
jj logproves which commit it resolves to.
Safe mutation pattern
For any rewrite or destructive-looking operation:
- Inspect
jj status,jj log, andjj show <change-id>/jj diff. - State exactly which change(s) will move and where.
- Use explicit revisions and non-interactive flags.
- Verify graph, status, diff, bookmarks, and conflicts afterward.
Stack cleanup recommendations
Before the final response, inspect the nearby mutable history. If adjacent commits would be clearer as one logical review unit, suggest a cleanup plan; do not perform it without approval.
Good squash candidates include fixups, tests or documentation inseparable from an implementation, and successive commits editing the same behavior. Keep commits separate when they are independently reviewable or revertible, even if they are small.
A recommendation must include:
- The shortened, unambiguous source and destination change IDs (as rendered by
change_id.short()) with their current descriptions. - Why they belong together.
- The proposed combined description.
- An explicit statement that no rewrite has happened yet.
Treat direct approval such as “do it” as authorization for exactly the proposed cleanup. Then re-run preflight, inspect every affected commit's full diff and graph relationship, confirm they are mutable, and apply the plan with explicit change IDs and non-interactive messages. For example:
jj squash --from '<source1> | <source2>' --into <destination> \
-m "<combined description>"
# For description-only cleanup:
jj describe -r <change-id> -m "<new description>"
Afterward, verify status, graph, destination diff, descriptions, bookmarks, and conflicts. If the result differs from the approved plan, stop and inspect jj op log.
Do not use a bare jj squash when both descriptions may be non-empty; it can request message editing or produce the wrong description.
Splitting
For splits, prefer jj-hunk-tool: inspect selections with jj-hunk-tool hunks -r <revision>, then run jj-hunk-tool split <hunk-id>... -r <revision> -m "<first description>". It supports line ranges such as <hunk-id>:1-3,7-9; verify the rewrite as usual.
Splitting with a constructed intermediate state
jj-hunk-tool works when a split can be made by partitioning existing changed lines. If the split needs intermediate content absent from both the parent and final states—such as overlapping same-line edits or a transitional implementation—construct it manually and recover the combined state from the change's evolution log:
# Snapshot the original combined state before editing it.
jj status
# Edit into the intermediate state containing only change A.
jj diff
jj commit -m "<change A description>"
# Find and inspect the older incarnation that contains both A and B.
jj evolog -r @-
jj show <combined-state-commit-id>
# Restore only the affected path from that incarnation; this reapplies B above A.
jj restore --from <combined-state-commit-id> path/to/file
jj diff
jj commit -m "<change B description>"
jj status
jj log -r 'ancestors(@, 4)'
Use the hexadecimal commit ID from jj evolog here because the incarnations share a change ID. Confirm with jj show that it contains the intended combined state before restoring. If the revision contains other separable work, split that out first so it does not accidentally land in change A.
Push gate
Only after the user explicitly says push:
jj status
jj bookmark list --all
jj log -r '<bookmark> | present(<bookmark>@origin)' --no-graph
jj show <bookmark>
jj git push -b <bookmark>
Push one named bookmark; never use bare jj git push or --all. Never move or push main/the default branch unless the user explicitly names that exact target.
Conflicts and recovery
- Resolve conflicts by editing markers directly, then verify with
jj statusandjj log -r 'conflicts()'. - For an unexpected mutation, inspect
jj op logand the relevant historical state withjj --at-op=<op-id> logbefore recovering. - Use
jj op revert <op-id>to invert one specific operation while preserving later operations. Revert additional explicitly selected operations separately if needed. - Use
jj op restore <op-id>to return the repository to that operation's state, intentionally discarding the effects of all later operations. - Avoid
jj undo: its implicit “last operation” target is less precise than either explicit recovery command. - For a stale workspace, run
jj workspace update-stale, then inspect for divergence. - Never use raw Git as a recovery fallback.