Mullet 2, coding jobs you can leave running
Mullet 2 plans a coding job, runs it unattended in a sandboxed clone, and passes each step only on evidence. Why I rebuilt it, and how to try it.
I have rebuilt Mullet. The old version sent coding tasks to the cheapest model that could do them. The new one runs a whole coding job while you are away, and only tells you a step is done when its own checks say so. I wrote about the old version in Checking a delegate’s edits before trusting them and Picking the cheapest model that can do the job. This post is about why it needed replacing.
What was wrong with the old one
I stopped using Mullet’s delegation once a bigger Claude subscription made working in Claude Code quicker. An audit of the old code then showed why it had become untrustworthy.
Every “done” and “passed” in the orchestration modes came from words in a model’s reply. The checks were substring matches for PASS and APPROVAL, so a reply saying “NO APPROVAL” passed. The agent runner treated a model that stopped calling tools as finished. The swarm tool returned applied: true without applying anything.
There was no isolation. A delegate could write anywhere under the home directory and run any shell command. opencode ran with --auto, and it was handed keys from the .env of whatever directory it started in.
The overnight loop could mark failed work as done, commit it and push to origin master. Autopilot had Mullet’s own test suite hardcoded as the thing to run.
It also only really worked from its own checkout. Paths came from __file__, settings came from the checkout’s .env, and hooks were registered by absolute path. The install script installed an unrelated PyPI package that happens to be called mullet.
Last, the test suite was about 17,900 tests run in series. That took about 30 minutes, so nobody ran it often.
What Mullet 2 does instead
Mullet 2 is a job runner. You install it once, on Linux or macOS, and then use it in any repo.
mullet job plan "<goal>"
mullet job show <id>
mullet job approve <id>
mullet job run <id>
mullet job status <id>
mullet job report <id>
mullet job merge <id>
plan starts a read-only Claude session that proposes a task tree. show prints it. approve is the first of two points where a person decides: you approve the plan, not the code. run starts the job in the background and you can close the terminal. status, tail, stop and resume do what they say.
The planner breaks a goal down until each leaf fits one pass. The depth is at most 3 and the tree has at most 40 nodes. A parent step passes only when its children pass and its own integration checks pass on their combined change, with the full test suite. Workers never start other workers. Only the orchestrator creates nodes, so the budgets and checks live in one place.
Each node runs in a private clone of the base commit it was planned against, inside a sandbox. I cover that in Sandboxing an AI coding worker. When the worker stops, Mullet snapshots the tree, runs its gates over it, and commits exactly the tree it judged. The gates are in Gates, not a model’s word.
If a node fails, it is reset to where it started and retried with the gate output, up to 3 attempts. After that it is BLOCKED. It is never marked done because the model said so.
Status lives in a SQLite ledger of jobs, nodes, attempts, gate results and events. A node’s status can only be set by one function, judge(), from gate results. mullet job report builds the evidence report from that ledger. Anything the worker says about itself appears in the report, but it is labelled as a claim.
The second decision is merge. It refuses a branch name that already exists, fetches the job’s branch into your repo as a new branch, and stops. It never pushes. You read the report and the diff, and you merge it yourself.
Stopping is safe. The runner is detached with setsid and holds a lock. After a kill -9, resume puts the clone back at the last commit the ledger recorded and carries on.
What it ran
I have two runs to report so far. A 2-node job on a small sample Python repo passed end to end in 32 seconds. A 5-node tree made by the planner, with the reviewer switched on, passed in about two minutes, and the ledger audit was clean.
Those are small. The first real ones were on Mullet’s own repo: over the night of 24 October it built its own night mode, the mullet local watchdog and the gated version of mullet_opencode, each a planned tree of six or seven steps, each merged after I read the diff. Along the way the reviewer stopped real bugs from the cheaper models, including a test class defined twice so that half its tests never ran. They were also slow, because of how cheap models climbed the ladder, which is the subject of the delegation ladder.
Where the worker comes from
The worker is your own claude -p, Claude Code in print mode, logged in as you. Mullet never reads, stores or passes Claude credentials. Running your own Claude subscription unattended covers what that means in practice, including a trap with ANTHROPIC_API_KEY. One worker runs at a time across all jobs, because they share one subscription’s quota.
Trying it
Install from GitHub with uv or pipx. Do not run pip install mullet: that is a different project.
git clone https://github.com/tomgould/mullet-public.git
cd mullet-public
scripts/install.sh
mullet doctor
mullet doctor checks Claude Code, bubblewrap, socat and whether a stray ANTHROPIC_API_KEY is set. mullet paths prints where everything lives. Settings go in ~/.config/mullet. An older ~/.mullet is still read, and mullet migrate-state moves it.
mullet setup-claude registers the MCP server and skills with claude mcp add --scope user. It records each change, so mullet setup-claude --uninstall undoes exactly what it did. Hooks are opt-in.
Then, in a repo you can afford to experiment on:
mullet job plan "add a --json flag to the export command"
Read the tree it proposes before you approve it.
What it is not
It is not a way to spend less on its own. Cheaper workers came next: opencode with Kimi, OpenRouter, free or local models, under the same sandbox and gates, off unless a repo’s profile allows it. How that went, with the numbers, is in the delegation ladder.
The old orchestration modes are gone, and the swarm no longer claims to apply anything. The Trawl knowledge search and the MCP server are still there, and so are the older delegation tools with their model routing and presets. MULLET_DELEGATION=off turns those off, which is how I run it.
The code is at https://github.com/tomgould/mullet-public.