Skip to content
Zit
Esc
↑↓navigate↵open⌘Jpreview
On this page

Zit 101

Install Zit, add it to a repository, make a change by hand, accept a teammate's branch and an agent's work, and publish to git.

Ten minutes, one repository. Every command and its output below is from a real session; home-directory paths are shortened.

The example is a small Python project, shop, with one function under test. You need git 2.38 or newer, and Rust 1.88 or newer to build Zit.

1. Install

From crates.io:

cargo install zit --locked

--locked builds with the exact dependency versions the project was tested with; without it, Cargo picks the newest ones, which may need a newer Rust. This installs two binaries into ~/.cargo/bin:

Binary Use it as
zit zit status, zit accept …
git-zit git zit status, git zit accept …

They are the same program. Git runs any git-<name> on your PATH as git <name>, so Zit works wherever git does. This page uses git zit.

$ git zit -h
Usage: git zit [OPTIONS] <COMMAND>

Commands:
  init         Start the graph in this git repository; REV becomes the current state
  materialise  Create a disposable workspace holding a state; prints its path
  …
  claim        Claim resources you intend to write; refused if other unaccepted work holds them
  record       Snapshot a workspace into a change
  status       Everything that exists: current, speculative changes, workspaces
  accept       Make a change part of the current state
  …
  web          Live graph in the browser: accepted line, speculative changes, workspaces, diffs

2. Start the graph

In any git repository with at least one commit:

$ git zit init
current is e6d498aea2

HEAD is now the current state. Nothing in your working tree, your branches or your history changed. Zit added one ref, refs/zit/current.

3. Tell Zit how to check a change

A zit.toml at the root of the repository lists the checks a state must pass before it can become current. It is a normal committed file, so commit it with git and bring that commit in:

# Files tools leave behind that should never be part of a change.
ignore = [".coverage"]

[[check]]
name = "tests"
run = "python3 -m pytest -q"
inputs = ["shop", "tests"]

Top-level keys such as ignore go before the first [[check]]; anything after it belongs to that check. Zit rejects keys it does not know.

$ git add zit.toml && git commit -m "Add zit checks"
$ git zit sync --branch main
main is 997778b6b8

sync accepts the branch’s new commits into current, then moves the branch to current. inputs says which paths the check depends on; a change that touches none of them reuses the earlier result (Checks).

If your repository has a generated file, such as a registry or an index built by a script, declare it too. Concurrent edits to it are then never conflicts; Zit rebuilds it on the combined state:

[[derive]]
path = "registry.json"
run = "python3 scripts/generate_registry.py"

If it has dependencies to install, declare that as well. Zit installs them once, and every workspace starts with them, as a copy-on-write clone that takes almost no extra disk:

[prepare]
run = "npm ci"
inputs = ["package.json", "package-lock.json"]

Every section, including [accept], is described in zit.toml.

4. Make a change by hand

$ git zit materialise --agent ana --intent "Tax is 20%"
~/.zit/shop-8bdbd4d96eb6bdd8/ws/cfeffbdb/tree

That directory is a disposable copy of the current state. On APFS (macOS), and on btrfs or XFS with reflink (Linux), it is a copy-on-write clone: it took no extra disk for files you do not edit. It is a working git directory, but not a worktree and not a branch. Edit there with anything, then record from inside it:

$ git zit record --dispose
c5dd0df418354c66c8d66ed3354a21f519d02649
  wrote shop/pricing.py#tax
  wrote tests/test_pricing.py#test_tax

The change is in the graph; the directory is gone. Zit worked out what was written, function by function.

$ git zit status
current  997778b6b8  Ana  1s  Add zit checks

CHANGES (1)
  c5dd0df418  speculative        ana          0s  Tax is 20%

WORKSPACES (0)

$ git zit accept c5dd0df418
current is c5dd0df418 (fast-forward; checks: 1 run, 0 reused)

accept ran the tests on the change’s state, then made it current. Had they failed, current would not have moved and the change would be marked invalid with the reason.

5. Publish to git

$ git zit export --branch main
main is c5dd0df418

$ git log --oneline -3
c5dd0df Tax is 20%
997778b Add zit checks
e6d498a Shop

From here it is ordinary git: git push.

6. Accept a teammate’s branch

Teammates do not need Zit. Bo uses plain git:

$ git switch -c readme-currency
$ git commit -qam "Document integer amounts"
$ git push -q origin readme-currency

In the clone where you integrate:

$ git fetch -q origin
$ git zit accept origin/readme-currency
current is 55fdcf6902 (fast-forward; checks: 0 run, 1 reused)

$ git zit export --branch main
main is 55fdcf6902
$ git push -q origin main

checks: 0 run, 1 reused: Bo changed only README.md, and the tests depend on shop/ and tests/, so the earlier passing result still holds.

If Bo’s branch had been built on an older main and changed the same function as someone else, accept would refuse it and say which function and whose change.

7. Let an agent do it

git zit run gives an agent CLI its own workspace, runs it, and records whatever it leaves behind, however it ends:

$ git zit run --agent claude --intent "Add a discount(amount, percent) function with a test" \
    --timeout 300 -- claude -p "Add a discount(amount, percent) function to shop/pricing.py … Do not commit." \
    --permission-mode acceptEdits --allowedTools "Bash(python3:*)"
zit: recorded 6212a64f71 (Add a discount(amount, percent) function with a test)

$ git zit show 6212a64f71
status   speculative
agent    claude
wrote    shop/pricing.py#discount
wrote    tests/test_pricing.py#             (today: tests/test_pricing.py#(imports); recorded before imports became their own unit)
wrote    tests/test_pricing.py#test_discount
check    tests: no evidence

$ git zit accept 6212a64f71
current is 6212a64f71 (fast-forward; checks: 1 run, 0 reused)

zit show also prints what the agent reported at the end of its session (reported …): Zit keeps the agent’s final account as the body of the change’s commit, so it is in the history you export.

With --agent autohand, claude, codex or pi and no command, Zit uses that agent’s headless mode with --intent as the prompt. The session above used Claude Code with an explicit command; with a preset it is just git zit run --agent autohand --intent "…". For agents that should coordinate with each other, give them the MCP server or the claim rules in Agents.

8. Watch it

$ git zit web
zit web: http://127.0.0.1:4747  (read-only; Ctrl-C to stop)

The browser opens on the live graph: accepted changes in a line, speculative ones above it, open workspaces below, and for any selection, what it wrote, why it stands where it does, its diff and the command to run next.

9. Share the graph

The graph is git refs. Push and fetch them like branches:

git push origin 'refs/zit/*:refs/zit/*'
git fetch origin 'refs/zit/*:refs/zit/*'

Undo everything

git zit clean                                                          # every local copy: workspaces, caches
git for-each-ref refs/zit --format='delete %(refname)' | git update-ref --stdin   # the graph

Your branches, history and working tree were never touched. What remains is the evidence ledger in the git directory (rm -rf "$(git rev-parse --git-dir)/zit") and the objects Zit wrote, which git gc removes once nothing refers to them.

Next

Was this page helpful?