A task plan that knows what depends on what
Mullet 2.3.0’s planner can say which step comes after which, mark a step as a one-way door, and list the questions it couldn’t answer from the code. A plan that breaks its own order is refused before anyone approves it.
A Mullet job starts with a plan. Claude reads the repo, read-only, and answers with a tree of steps, each with a goal, a kind, a scope and the commands that must pass when it’s done. I approve the tree, and nothing runs until I do.
Until 2.3.0 that plan had no way to say one step waits for another. The prompt asked for siblings “in the order they should be done”, and that was all. When I read 101 attempts, 15 of the 40 failures were in the gates where the shape of a step matters most: no change, edits outside scope, risky edits in a mechanical step. So the plan got three new fields, and the prompt got stricter.
after
A step can now name the steps that must be finished before it. The prompt gives the planner three tests, in these words:
- after: ids of nodes that must be finished first. B is after A when B needs
code that A adds, when A and B touch the same files or modules, or when B
depends on a decision or API shape that A settles. List A (and its children)
before B: Mullet runs nodes in list order, children before their parent.
The first test is the obvious one. The second is about collisions: two steps that edit the same module see each other’s work, so one of them has to go first, and the plan should say which. The third is the one plans most often leave unsaid. A step that writes a client for an API depends on the step that settles the API’s shape, even when the two share no files.
Mullet checks the links before the plan is shown to me. It refuses an after that names a step that doesn’t exist, a step that names itself, a cycle, and a forward reference: a step that would not be finished yet in the order the runner actually uses, which is list order with children before their parent.
after is checked, not scheduled. The runner still works through the list in order. It doesn’t reorder steps or run them side by side. What the field buys is that a plan whose order contradicts its own dependencies is refused instead of run.
door: one-way
The second field marks a step that is hard to walk back once it’s merged:
- door: "one-way" for a step that is hard to walk back once merged: a public
API, a default users build on, a removal, persisted formats (files, schemas,
stored data). Mullet treats one-way steps as risky: each gets an independent
review that must pass, even in a job with review off. Otherwise leave it out.
A one-way step is always risky. On the delegation ladder it starts on Claude rather than on a cheaper model. It always gets the independent review, G7, in every kind of job, ladder or not, even when I planned the job with --no-review. Turning review off is a choice about cost on ordinary steps. It isn’t a choice about the steps that are expensive to undo.
That review has to give its own verdict. A block fails the attempt, as usual. A review that gives no verdict pauses the job for me, and so does one that only gave a verdict when it was asked a second time. The reviewer that can only say no explains why a verdict given on a second asking is trusted less.
Risk only goes up
Before this, risk came from a word match: a step whose goal, acceptance commands or scope mentioned code that deletes, kills or sends data was treated as risky. The door is a second way in, not a replacement. A one-way step is risky, and every other step is still checked for the words, whatever the plan says.
So the planner can raise a step’s risk and never lower it. The planner is a model, and a flag that lowers risk is a flag a model can get wrong in the expensive direction. A wrong “one-way” costs a Claude attempt and a review. If a plan could mark a step safe, a wrong “safe” could send a removal to a cheap worker without the forced review. The same thinking keeps size limits out of the plan altogether: a plan may set a step’s id, parent, goal, kind, door, after, scope, acceptance commands, allowed protected paths and test removal, and nothing else. A plan that raised the limits would weaken the scope and static checks without me seeing it.
The prompt also closes the two gaps the ledger showed. A step that deletes, kills, sends data or changes a public API or persisted format is never mechanical. And any new test file a step will add has to be in its scope, so the scope gate doesn’t fail a step for writing the test it was asked to write.
open_questions
The plan can carry up to ten open questions, of up to 400 characters each. The prompt allows them only for “things you could not verify in the code; never guess.”
mullet job plan and mullet job show list the one-way steps and the open questions under the tree, before I approve. That’s the point of them. A plan with an assumption buried in a step’s goal looks exactly like a plan with none, and I’m the only one who can answer a question about intent that the code doesn’t settle. Better to be asked before the job runs than to find the guess in a diff the next morning.
When the reply is unusable
Sometimes the planner’s reply has no usable plan: no structured answer at all, or one that fails the checks above. It is now asked again in the same session, with the reason, so it doesn’t start reading the repo from scratch:
jobs:
reask_attempts: 1 # times to ask again, in the same session, for a reply without its structured part (0 turns it off)
Plans stored before 2.3.0 load unchanged, because both new step fields have defaults: no after, and a two-way door.
What I don’t know yet
Whether this reduces the G1, G2 and G3 failures needs new ledger figures, and the decision record for it says the same. A field the planner can fill in is only as good as the planner’s reading of the code. What I do know is that the plan I approve now shows me which steps are hard to undo and what the planner didn’t know, before anything runs.
The code is at https://github.com/tomgould/mullet-public.
More on Mullet
-
Cheap models only for the work that is safe to be wrong about
Mullet tries free and cheap models first only on steps its gates can judge, and keeps risky and one-way steps for Claude and its reviewer. Why…
-
Decision records for a solo project
Mullet now keeps eleven short decision records beside its code, each a context, a decision and its consequences. Why a one-person project bothers…
-
Mullet from Python
Mullet 2.3.0 adds mullet.api, so a script or CI can plan, approve, run and merge coding jobs without the CLI. It was built by splitting the CLI’s…
-
A container backend stricter than the usual Docker recipe
Mullet 2.3.0 can run a job’s commands in Docker: one throwaway container per command, no network, no capabilities, secrets on stdin and a lifeline…
More writing
-
A Markdown version of every page, for AI agents
Why every page on this site now has a Markdown version for coding agents, the three ways an agent finds it, what goes into it, and how it stays out…
-
A TV remote from the command line
Pairing a Linux computer with a Google TV over the protocol phone remotes use, what the six-character code on the screen really checks, and why a…
-
The packages I kept on a Google TV
The other half of decluttering a TCL Google TV: a keep-list of 35 packages that debloat lists remove or that only look disposable, how each one got…
-
Letting an agent drive the TV
The rules a coding agent worked under while it changed a TV we were still watching: only changes that can be undone, small batches with a person…