lazy documentationv0.91

Cluster tasks

A cluster task is a task whose agent drives other tasks instead of writing code itself. You give it a set of subtasks; its agent — the driver — decides which of them can run at the same time, starts them, reviews each one as it comes back, sends it back with feedback or accepts it, and hands back when there is nothing left it can usefully run.

It is the shape you want when you have a pile of related work that should land in one branch and be reviewed as one change: everything a review turned up, every follow-up from a release, every item on a checklist.

Creating one

lazy create --type cluster --goal "Fix everything the API review found"

The type is available everywhere a type is: lazy create --type cluster, lazy edit <task> --type cluster, the type field of the lazy_create MCP tool, and the type dropdown on the dashboard's create form.

Give the cluster a prompt that says what the children are for. You can list the work in the prompt and let the cluster create the subtasks itself, or create the subtasks up front and let it find them.

Adding existing backlog tasks to a cluster is a reparent:

lazy reparent <task> --parent <cluster>

What the driver does

Every turn a cluster takes, it runs under a contract lazy injects for you:

How many children run at once

That is the driver's call, and lazy does not second-guess it. There is no cap, no queue and no setting.

Running children one at a time would avoid ever merging siblings' work, but those merges are mostly trivial, and serialisation is slow enough to dominate everything else. The judgement about when running children side by side is safe lives with the agent that can actually see the work.

How many rounds one child may have

A cluster runs unattended, so one child that keeps not-quite-passing review could otherwise absorb any number of agent turns with nobody watching the spend. [cluster] max_child_fix_rounds (default 3) bounds it: after that many rounds on the same child, lazy refuses the driver's next send-back and tells it to accept the child, close it, or set it aside with a question for you.

The count is per child, so one stubborn child never eats another's budget, and children running at the same time never interfere with each other's.

The bound is on the driver's own judgement, never on yours. Unblocking that child yourself is never refused, and it starts a fresh budget — as does starting the child again, accepting it, or reopening it. Set the key to 0 to remove the bound entirely.

This key is one of the levers in the review paradigm, which explains what each one costs you when you turn it down.

It keeps its own branch current

A cluster can sync itself: it merges its own branch on origin, then its parent, into its branch. This matters on a long run. Work keeps landing on the parent while the cluster runs, and without the sync every child would branch from the base the parent had when the cluster began, so a fix that landed an hour ago would never reach any of them. Children started after the sync branch from the cluster's current state.

The driver is told to do this before starting a new wave rather than after every accept, because a sync costs a merge for every child currently running.

The sync merges everything outstanding and stops at the first conflict, which the driver resolves itself before carrying on — so a conflict costs one cycle, not the cluster. A cluster is the only thing that can sync a running task, and it can only do it to itself: see surface asymmetries.

Children it cannot finish

Some tasks turn out to be impossible as written, or far bigger than the prompt suggests. A driver is expected to recognise those, set them aside, and carry on with the rest rather than grinding to a halt on the first hard one. A child it sets aside is tagged deferred-by-<cluster>, so you can find them all:

lazy search 'tag:deferred-by-my-cluster'

When the cluster stops, it raises one blocking item per deferred child saying what it needs from you. Blocking means accepting the cluster is refused until you have answered them, so a cluster can never quietly hand back a pile of work it never did.

Which agent the children run on

A child created without an agent runs on the driver's own agent profile, and so on that profile's model. [agent.by_type] in lazy.toml is not consulted for children, so a cluster on an expensive profile keeps every child there. To run a child on a cheaper profile, create it with an explicit agent (--agent <profile>, or the agent argument over MCP); the driver can do this itself, choosing from the profiles the project offers. Children also do not inherit the driver's task type: with no type given they are plain tasks.

Watching it

lazy show <cluster> prints the cluster's progress: how many children have been accepted out of how many are still expected to land, which are running, which are deferred, and how many were closed. The same line is at the top of the cluster's page on the dashboard, with each child linked, and the dashboard's Clusters page lists every cluster with its progress and its children. lazy show <cluster> --json carries the same numbers, under cluster_progress. That key was called loop_progress before the type was renamed, and there is no second spelling: a script that reads the old name finds nothing there, exactly as it would for a task that is not a cluster.

In Lazy Teams the same line is on the cluster's own page, and the project nav has a page listing every cluster with its progress and its children — including which ones were set aside — so you can see what all of them are doing without opening each one.

All of it is derived from the tree, so it is correct whether the driver accepted a child or you did.

Stopping and resuming

A cluster that has stopped is blocked, like any other task, and you resume it the usual way:

lazy unblock <cluster>

With one exception: if you add a child to a cluster that is parked after its own turn, lazy starts it again by itself, telling it which child arrived. That is the only case in lazy where something other than you starts a turn on a local event, and it is limited to cluster tasks — adding work to a cluster should not need a second step to take effect.

Three things to know about it:

What ends a stop. Any turn the cluster completes does — not only one you meant as a restart. Unblocking it obviously does, but so does asking it a question with lazy ask, or running a review on it: both run the agent, and a completed turn clears the stop. From then on the cluster is an ordinary parked cluster again, so the next child added to it starts a turn.

This matters if you are relying on a stop. A cluster you stopped, read, and then handed more work to is no longer stopped — it will pick that work up. If you need it to stay parked, leave it alone, or close the children you are not ready for it to run.

If a cluster's turn is killed

A cluster can be cut off mid-turn — you upgrade lazy, the daemon restarts, the machine reboots, the agent crashes. That is not "stopped": nothing was handed back to you and nothing was decided. Such a cluster is marked interrupted and lazy starts it again for you, out of the same automatic-turn budget as above. It picks up by re-reading its tree, which is where the state of every child lives, so a child that finished while the cluster was down is simply there when it looks.

You do not need to unblock a cluster in that state, and you should not see it waiting for you.

Cluster tasks and the lazy loop command

They are two answers to the same question, and you pick by who is doing the reviewing. lazy loop drives a queue of tasks with you at the keyboard: it starts each one, waits, and presents a review gate where you choose. A cluster task is the unattended version — its agent makes those calls itself, and you review the whole thing once at the end.

What a cluster does not do