Running work in worktrees¶
mini-ork makes most of its changes to a repository, and concurrent agents make most of its mistakes. A worktree per task keeps each change on its own branch and its own directory, so two runs cannot edit the same file at the same time or leave one checkout in a half-merged state.
There are two worktree surfaces, and they are related but distinct:
- Run workspaces — where a
mini-ork runapplies its edits. - Developer worktrees — where a human (or an agent acting as one) edits and merges the mini-ork repository itself.
Run workspaces¶
mini-ork run chooses where to apply changes:
mini-ork run code-fix ./kickoff.md --workspace worktree
mini-ork run code-fix ./kickoff.md --workspace in-place
| Choice | Behaviour |
|---|---|
worktree |
The run gets a fresh mini-ork/<run> worktree; the main checkout is untouched |
in-place |
Edits are applied to the current working tree |
The default is policy-driven: worktree when the review policy requires
approval before merge, in-place otherwise. Override the default with the
flag above or with MO_WORKSPACE_MODE.
Use worktree mode when you are not watching
In-place is fine for a single run on a clean tree. For unattended or concurrent work, worktree mode gives you a directory to inspect and a clean checkout to fall back to.
A worktree run reports the path it used, so you can diff or inspect it before merging. Its verdict refers to the change in that worktree, not to main.
Developer worktrees¶
mini-ork keeps main clean: implementation work never happens in the primary
checkout. The driver is scripts/mini_ork_worktree.py, wrapped by make:
make worktree SLUG=<slug> # create worktree + branch wt/<slug>
make worktree SLUG=<slug> OWNS="mini_ork/foo.py tests/bar"
make worktree-merge SLUG=<slug> # rebase, gate, push HEAD:main
make worktree-clean SLUG=<slug> # remove worktree + branch
make worktree-list # git worktree list
Under the hood:
scripts/mini_ork_worktree.py create <slug> [--owns <path>...] [--branch <name>]
scripts/mini_ork_worktree.py merge [<slug>]
scripts/mini_ork_worktree.py clean <slug>
scripts/mini_ork_worktree.py owners [--json]
scripts/mini_ork_worktree.py release <slug>
scripts/mini_ork_worktree.py list
Worktrees live under $MINI_ORK_WORKTREES_DIR (default: a sibling
mini-ork-worktrees/ directory). Branches are wt/<slug>.
Ownership claims¶
--owns <path> claims a file surface in the project's ownership registry:
- Claims are path-prefix aware: claiming
docs/clicovers everything under it. - Creation is refused when a new claim overlaps a live worktree's claim, so two agents cannot race the same file.
- Claims are released by
clean, byrelease <slug>, or when the worktree directory disappears.
Inspect current claims with:
One command is one critical section¶
create and merge both take a lock around their check-then-act pairs:
- create: claim check + claim registration;
- merge: fetch + rebase + push.
The fetch happens inside the lock, so a rebase never targets a ref cached
before a long green gate. The green gate itself runs outside the lock, so a slow
test suite does not block other worktree commands. The push is a bounded retry:
MO_MERGE_PUSH_ATTEMPTS (default 5).
Merge discipline¶
This is the part that goes wrong. Follow it literally.
Never stage everything¶
git add -A sweeps other sessions' work into your commit. Stage explicit paths,
or git add <dir> for a directory you own end to end.
Assert the diff before committing¶
If the diff contains files — especially deletions — that you never touched or authored, your base is stale. Rebase; do not merge.
After a push, refresh local main¶
Local main is stale until you do. A later worktree created from it starts
behind.
If --ff-only refuses, stop¶
A refused fast-forward means local main and origin/main diverged — someone
pushed while you worked. Do not:
- run a plain
git merge origin/mainand call it done; git reset --hard;- push with
--no-verifyto get past it.
Reconcile so that every commit on both sides survives. A merge commit keeps all of them; a rebase rewrites them. Never rewrite a commit that is not yours.
On a failed smoke test, fix forward¶
Create a new worktree from the latest main and fix the defect there.
Never revert main, never git reset --hard.
Green gate¶
merge runs a test command before pushing. By default it is the project suite;
scope it per task with MINI_ORK_TEST_CMD:
MINI_ORK_TEST_CMD="python3.11 -m pytest -q tests/unit/test_docs_cli.py" \
make worktree-merge SLUG=docs-cli
A scoped gate is faster, but it is not CI. Choose the scope so the files you touched are covered, and let CI run the broad suite after the push.
Two globs can double-resolve a file
Under pytest-asyncio, passing the same test file twice in one invocation
(including via two overlapping globs where one is a subset of the other)
fails at collection with RecursionError. Keep each path in exactly one
glob per command.
Worktree lifecycle¶
flowchart LR
C[create worktree<br/>claim files] --> E[edit + commit]
E --> M[merge<br/>rebase origin/main]
M --> G[green gate]
G -- pass --> P[push HEAD:main]
G -- fail --> F[fix in a new worktree]
P --> R[ff-only refresh main]
R --> X[clean worktree<br/>release claims]
A finished, verified change is shipped, not parked: commit, merge and push are part of the job, not an optional follow-up.
Cleaning up¶
This removes the worktree directory, deletes the branch and releases the claims. If you need to drop claims without deleting anything:
Next¶
- Development: worktree workflow — the same loop from a contributor's perspective.
- Recovery — what to do when a run or a merge fails.
- Tests and CI