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 short table of what a plan step can now say. After: steps finished first. Door one-way: always risky, always reviewed. Open questions: shown before you approve. Below it, the plans Mullet refuses: an unknown id, a cycle, a forward reference. A short table of what a plan step can now say. After: steps finished first. Door one-way: always risky, always reviewed. Open questions: shown before you approve. Below it, the plans Mullet refuses: an unknown id, a cycle, a forward reference.

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

More writing