---
title: 'The delegation ladder, or who does each step'
url: 'https://www.dealmobile.co.uk/writing/the-delegation-ladder'
type: Post
published: '2026-10-25'
summary: 'How Mullet 2 hands each step of a coding job to the cheapest worker that can pass its checks: free and local models first, then Kimi and OpenRouter, Claude last, with the order set by how much of my Claude week is left.'
---

# The delegation ladder, or who does each step

Published 2026-10-25

![A ladder of five rungs, free, local, Kimi, OpenRouter and Claude, beside the figures from the first real jobs: steps passed below Claude and the dollars spent.](https://www.dealmobile.co.uk/sites/default/files/styles/almanac_shot_md/public/content/images/posts/the-delegation-ladder/hero.png.webp?itok=1nXqeECk)

I pay for a big Claude subscription and I still ran out. A large part of a week’s allowance went in about a day of building [Mullet 2](https://www.dealmobile.co.uk/writing/mullet-2-coding-jobs-you-can-leave-running.md). Most of that was Claude doing work a cheaper model could have done: writing tests, updating docs, wiring a command into a CLI.

The old Mullet tried to fix this by guessing which model a task needed and trusting what came back. That failed for the reason I gave in [Gates, not a model’s word](https://www.dealmobile.co.uk/writing/gates-not-a-models-word.md): a model’s “done” is not evidence. Mullet 2 has the evidence now. Every step of a job has to pass the same gates whoever did it, so I can let a cheap model try first and only pay for Claude when the cheap attempt fails.

That is the delegation ladder.

## The rungs

There are five, cheapest first.

- **Free**: an OpenRouter `:free` model. Only for repos that GitHub confirms are public, because free tiers can keep what you send them. It hasn’t run yet, because Mullet’s own repo is private.
- **Local**: a model in LM Studio on my laptop’s 12 GB GPU. Costs nothing but electricity and fan noise, and only runs when the machine guard says there is room. I now keep it for night mode (more on that below).
- **Kimi**: Kimi K2.7 Code on Moonshot’s own API, paid per token.
- **OpenRouter**: GLM 5.3 on Z.AI’s own host, then Kimi again through OpenRouter if Moonshot is down. Every OpenRouter request asks for hosts that don’t keep prompts and aren’t serving a 4-bit copy of the model.
- **Claude**: my own logged-in Claude Code, the last resort for most steps.

Each step gets one attempt per rung. If the gates fail, the gate output goes to the next rung up. A provider error (a timeout, a rate limit, a model that has been withdrawn) moves the step up without using one of its attempts. A model that failed a step’s gates is never tried again on another provider, so Kimi on Moonshot failing a step doesn’t mean Kimi on OpenRouter gets the same step.

## Not every step starts at the bottom

The planner, still Claude, marks each step as mechanical, design, analysis or integration. That decides where it starts.

Mechanical steps (add a test file, update the docs, add a flag) start at the bottom. A design step starts low only if it is checkable: it has acceptance commands, a narrow scope and nothing risky in it. Anything that deletes, kills or sends data gets Claude’s review (G7) whatever rung did it. Planning and review themselves always stay on Claude.

## The order depends on my week

Claude Code’s status line knows how much of the 5-hour and weekly allowance is used. Mullet records those figures, and every `claude -p` stream a job runs reports them too. From them it works out a paced weekly pressure and picks one of three orders:

| Claude’s week | Mechanical steps | Analysis and integration |
|---|---|---|
| Room | free, local, Claude, Kimi, OpenRouter | Claude, Kimi |
| Medium | free, local, Kimi, OpenRouter, Claude | Claude, Kimi |
| Tight | free, local, Kimi, OpenRouter, Claude | Kimi, OpenRouter, Claude |

When there is room, Claude comes before the paid rungs, because the subscription is already paid for. When the week is tight, Claude is only the last resort. Each job has a spending cap on the paid rungs. If a job reaches it while my week is tight, it pauses and tells me rather than quietly handing every remaining step to Claude.

## Keeping the cheap rungs honest

A cheap model running inside my repo is the part that needs the most care.

The worker runs in the same bubblewrap sandbox as Claude workers, with no network. It reaches its provider through a key proxy on a Unix socket: the sandbox never sees the API key, and the proxy is pinned to the one model chosen for that attempt. A review found a gap there. A request that named no model at all went through, and OpenRouter would have answered it with the account’s default model. The proxy now refuses any request that doesn’t name exactly the pinned model, and any body with a second model list, an encoded body or a repeated key.

The job’s clone holds only the base commit and its history, so a worker never sees my other branches, tags or stashes. Spend is recorded as it happens, so an attempt that goes over the job’s remaining budget is stopped part way through.

## The first real jobs

The first job on the ladder was a rework of Mullet itself. It went badly, and usefully. Every cheap attempt at the docs step failed G3, the static check, because a check written for code was flagging reworded Markdown as risky edits. Then Claude’s own attempt failed because the Bash rules I gave Claude workers contained `$MULLET_JUNIT`, and Claude Code won’t match a rule with a variable in it, so it couldn’t run a single test. Then G5 blamed the docs step for tests an earlier step had been allowed to delete. Three bugs in my gates, all found because a cheap model’s attempts went through them more often than Claude’s ever had.

With those fixed, I left three more jobs running overnight: `mullet reap`, `mullet ladder report` and night mode. Across all five:

| Job | Steps passed below Claude | Spent on paid rungs |
|---|---|---|
| `mullet_opencode` as a gated job | 3 of 5 | $3.54 |
| `mullet local` watchdog | 5 of 7 | $1.21 |
| `mullet reap` | 3 of 6 | $4.70 |
| `mullet ladder report` | 3 of 5 | $3.83 |
| Night mode | 2 of 5 | $4.01 |
| All five | 16 of 28 | $17.28 |

`mullet ladder report`, one of the jobs above, now adds this up from the ledger. By its estimate, the 16 steps that passed below Claude would have cost about $66 at Claude’s API prices. On a subscription that isn’t money saved, it is allowance I didn’t use.

## What the first night taught me

The jobs were slow. Three things caused it.

- Every worker waited for every other. I had a single machine-wide lock so that Claude workers wouldn’t fight over my subscription, and Kimi and OpenRouter workers were queuing behind it too, for no reason. They now have three slots of their own, so a job’s cheap attempts run alongside Claude’s.
- The local model never passed. Qwen 3.6 35B in LM Studio made 13 attempts across the five jobs and passed none. Most timed out or stopped answering while the GPU was busy with other jobs, and each one held its step up for 7 to 21 minutes before it moved on. Local now runs only in night mode, when the machine is otherwise idle.
- Steps climbed too many rungs. A step that failed on local, Kimi and OpenRouter before reaching Claude spent well over an hour getting there. Qwen3 Coder on OpenRouter passed 1 step in 11, so it has gone.

The paid money was spent unevenly too: 73% of that first night’s paid spend went on attempts that failed. That is the price of trying cheap first, and it is why the ladder needs good models on its paid rungs rather than the cheapest ones.

The gates earned their keep. Claude’s review (G7) blocked a test class defined twice, so half its tests never ran; a placeholder test that asserted `True`; a function called with a clock of `None`; and a check for a recycled process ID that checked nothing, in the code that decides which processes to kill. None of those would have failed a test run.

## Choosing the paid models

I’d heard good things about GLM 5, so the next morning I replayed the night-mode job’s plan on five paid models, each on its own, with two attempts per step and the same gates:

| Model | Steps passed | Paid | Time |
|---|---|---|---|
| Kimi K2.7 Code (Moonshot) | 5 of 5, all first try | $3.60 | 49 min |
| GLM 5.3 (Z.AI, through OpenRouter) | 3 of 3, all first try | $1.83 | 86 min |
| Kimi K3 (Moonshot) | 2, one after a failed review | $4.54 | 47 min |
| DeepSeek V4 Pro | 0 of 1 | $0.38 | 21 min |

GLM ran out of road when my OpenRouter key reached the spending limit I’d set on it, so it only did three steps, and I have no fair result for MiniMax M3. On the three steps both did, GLM cost $1.83 to Kimi’s $2.48 but took 86 minutes to Kimi’s 32. Kimi K3 cost more and did worse than the model a version below it. DeepSeek twice wrote its tests outside the files the step was allowed to touch.

So Kimi K2.7 Code stays the first paid rung, GLM 5.3 replaces Qwen3 Coder on OpenRouter, and K3 isn’t on the ladder. The bench itself found a bug of mine: an hour earlier I had changed how workers take their slot, and Claude’s review crashed for every job that picked up the new code. Three of the five models had to be run again.

## What I don’t know yet

These are a handful of jobs on one repo, mine. The free rung has never run, because my repo is private. The local rung has run and failed, and night mode is its last chance. The bench is one plan, two attempts per step, on one morning. `mullet ladder report` adds up every job’s attempts by rung, model and step kind, so the defaults can be tuned from evidence rather than my guesses.

The code is at <https://github.com/tomgould/mullet-public>.

- **Products:** [Mullet](https://www.dealmobile.co.uk/products/mullet.md)
- **Tags:** [Python](https://www.dealmobile.co.uk/writing/tag/python.md), [AI](https://www.dealmobile.co.uk/writing/tag/ai.md)

---

Index of this site: [llms.txt](https://www.dealmobile.co.uk/llms.txt)
