Each task is in one state. An edge is a permitted change from one state to another.
| State | Meaning |
|---|---|
provisioning |
tasker clones the repo, makes the worktree and runs bootstrap. |
workspace_ready |
The worktree exists. The task waits for plan. |
planning |
An assistant writes the plan. |
awaiting_plan_review |
The plan waits for you to read it. |
refining |
An assistant revises the plan with feedback. |
approved |
You approved the plan. The task waits for execute. |
executing |
An assistant does the work in the worktree. |
executed |
The work is done. The task waits for verify. |
verifying |
tasker runs the verify commands. |
awaiting_work_review |
The work passed verification and waits for you. |
reviewing |
An assistant reviews the work. |
reviewed |
The review found findings. They wait for you. |
accepted |
You accepted the work. The task waits to land. |
pushing |
tasker pushes the task branch to origin. |
pushed |
The task branch is on origin. |
opening_pr |
tasker opens a pull request. |
in_peer_review |
The pull request is open for review. |
merging |
tasker merges the work into the base branch. |
landed |
The work is on the base branch. |
finishing |
tasker removes the worktree and the task branch. |
cleanup_failed |
finish stopped before the cleanup ended. |
finished |
Terminal. The task is done. |
updating_branch |
tasker moves the worktree onto a newer base. |
quota_blocked |
A subscription limit stopped a run. |
awaiting_quota_approval |
The quota use passed the threshold. The task waits for your approval. |
interrupted |
The process of a run died, or tasker passed it a signal. |
failed |
An operation failed. |
cancelled |
Terminal. You aborted the task. |
finishedandcancelledare terminal. No edge leaves them.- A self-transition (an edge from a state to the same state) is never legal.
- Each non-terminal state has an edge to
failedand an edge tocancelled.failedhas no edge to itself, because the self-transition rule wins.
The overview. Each box with a double border is a section below.
flowchart LR
S((start)) --> P[provisioning]
P -->|bootstrap ok| W[workspace_ready]
W -->|plan| PL[["Plan"]]
PL -->|execute| B[["Build"]]
B -->|pass| WR[["Work review"]]
WR -->|accept| LA[["Landing"]]
LA -->|finish| F([finished])
classDef terminal fill:#b91c1c,color:#fff,stroke:#7f1d1d;
classDef section fill:#f3f4f6,color:#111827,stroke:#6b7280,stroke-width:2px;
class F terminal;
class PL,B,WR,LA section;
tasker start creates the task in provisioning, then clones, makes the worktree and runs bootstrap. Success moves it to workspace_ready.
Two loops go back to Plan, and the overview does not draw them: refine from Work review, and reopen from Landing. Each one writes a new plan revision, which goes through approve, Build and Work review again.
In every diagram, an edge label names the command that will take this edge. "done" means that the spawned run finished. Blue states spawn a process: an assistant, the verify commands, git or the CLI of the forge (pushing, opening_pr, merging, finishing, updating_branch). These are the "spawning states". A run marker covers them, so tasker can find a crash in them. Grey "back to" boxes show a state a second time, so that a loop does not stretch the layout.
Two things that the diagrams do not show:
failedandcancelledcan be reached from every non-terminal state.tasker abortgoes tocancelled, and an operation that fails goes tofailed. See "Failure and retry".tasker update-branchcan run from 11 states. See "Update branch".
There is no awaiting_input state, because tasker start asks about an ambiguous base branch while it runs.
flowchart LR
W[workspace_ready] -->|plan| PL[planning]
PL -->|done| R[awaiting_plan_review]
R -->|refine| RF[refining]
RF -->|done| R
R -->|approve| A[approved]
A -->|unapprove| R
classDef spawn fill:#2563eb,color:#fff,stroke:#1e3a8a;
class PL,RF spawn;
flowchart LR
A[approved] -->|execute| E[executing]
E -->|done| EX[executed]
EX -->|verify| V[verifying]
V -->|pass| WR[awaiting_work_review]
classDef spawn fill:#2563eb,color:#fff,stroke:#1e3a8a;
class E,V spawn;
flowchart LR
WR[awaiting_work_review] -->|review| RV[reviewing]
RV -->|no findings| WR2[back to awaiting_work_review]
RV -->|findings| RD[reviewed]
RD -->|dismiss-review| WR2
RD -->|refine| RF[back to refining]
WR -->|review FEEDBACK| RF
WR -->|accept| AC[accepted]
classDef spawn fill:#2563eb,color:#fff,stroke:#1e3a8a;
classDef exit fill:#f3f4f6,color:#111827,stroke:#6b7280;
class RV spawn;
class WR2,RF exit;
refine from reviewed promotes the findings. review FEEDBACK sends your own feedback. Both go to refining in Plan. dismiss-review can decline only some findings, with --finding. The task goes back to awaiting_work_review only when no finding is left unhandled.
accept goes to accepted. The command you run picks how the task lands: merge for the trunk path, or push and open-pr for the pull request path. mark-landed is for work that landed outside tasker.
flowchart LR
AC[accepted] -->|merge| MG[merging]
MG -->|done| L[landed]
AC -->|mark-landed| L
L -->|finish| FN[finishing]
FN -->|done| F([finished])
FN -->|a step failed| CF[cleanup_failed]
CF -->|finish| FN2[back to finishing]
classDef terminal fill:#b91c1c,color:#fff,stroke:#7f1d1d;
classDef spawn fill:#2563eb,color:#fff,stroke:#1e3a8a;
classDef exit fill:#f3f4f6,color:#111827,stroke:#6b7280;
class F terminal;
class MG,FN spawn;
class FN2 exit;
mergefromacceptedfast-forwards the base branch onoriginto the task sha, with one push. If the push is not a fast-forward, the task fails frommerging.mergerefuses a stacked task, and a base that is a local branch of the tasker clone.- tasker does not wait for CI. The CI wait is a step of the landing process of each project, outside the state machine. There is no
waiting_for_ciorci_failedstate. finishruns underfinishing. It removes the worktree and the local branch ref. It deletes the task branch onoriginif tasker published one. It marks the issue done whenupdateOnFinishis set. If someone moved the branch onorigin,finishdoes not delete it. It stops incleanup_failedand skips the tracker write. You decide what to do with the extra commits, then runfinishagain. A crash infinishinggoes tointerrupted, andresumeruns the cleanup again.
flowchart LR
AC[accepted] -->|push| PG[pushing]
PG -->|done| PU[pushed]
PU -->|open-pr| OP[opening_pr]
OP -->|done| PR[in_peer_review]
PR -->|merge| MG[back to merging]
PU -->|mark-landed| L[back to landed]
PR -->|mark-landed| L
classDef spawn fill:#2563eb,color:#fff,stroke:#1e3a8a;
classDef exit fill:#f3f4f6,color:#111827,stroke:#6b7280;
class PG,OP spawn;
class MG,L exit;
pushpublishes the task branch only. It never pushes the base branch.open-prdoes not push. It needsforge. It adopts a PR that is already open from the task branch, so a resume never opens a second one.mergefromin_peer_reviewmerges the PR through the forge withforge.mergeMethod, only at the sha thatpushpublished. It lands the task only when the forge then says that the PR is merged. It does not wait for CI.- After
landed, the path is the same as the trunk path.finishrefuses a task inin_peer_review. reopen, fromacceptedorpushed, sends a bug found after accept back torefiningin Plan. It resyncs the worktree onto the base tip first.- To update an open PR, run
update-branchfromin_peer_review. It demotes the task toexecuted. Then runverify,acceptandpush.open-pradopts the open PR and returns the task toin_peer_review.
flowchart LR
subgraph Assistant["States that run an assistant"]
PL[planning]
RF[refining]
E[executing]
RV[reviewing]
end
subgraph NoAssistant["Other spawning states"]
V[verifying]
PG[pushing]
OP[opening_pr]
MG[merging]
FN[finishing]
UB[updating_branch]
end
Assistant -->|subscription limit hit| Q[quota_blocked]
Assistant -->|under ask: run ends with utilization ≥ quotaApprovalThreshold| QA[awaiting_quota_approval]
Assistant -->|run died: detected, or tasker interrupted, or a signal that tasker passed| X[interrupted]
NoAssistant -->|run died: detected, or tasker interrupted| X
Q -->|tasker resume| T{{resume target}}
Q -->|tasker approve-credits: only a stop with the cause overage and a known window| T
X -->|tasker resume| T
QA -->|tasker approve-credits| T
classDef pause fill:#d97706,color:#fff,stroke:#92400e;
class Q,QA,X pause;
run died: detectedmeans that a command found a dead run and moved the task tointerrupted. Therun_markerstable says which commands find a dead run.- Here is what each part of the label of the edge from the states that run an assistant means.
run died: detectedhas the meaning of the bullet before this one.tasker interruptednames the commandtasker interrupted. A later milestone adds this command, and it marks a dead run that the dead-run step cannot see.a signal that tasker passedmeans that tasker passed SIGINT or SIGTERM to the assistant run, and the run ended as an interruption (see The result of an assistant run). verifying,pushing,opening_pr,merging,finishingandupdating_branchspawn no assistant. Of the pauses, the state machine allows onlyinterruptedfrom these states.overagePolicypicks the quota pause. Underdecline(the default), a run stops at the overage boundary intoquota_blocked, andapprove-creditscan continue it there when the stop named a window. Underask, a run whose use of the quota window reachesquotaApprovalThresholdpauses intoawaiting_quota_approvalwhen the run ends, unless the operator approved that window withapprove-credits.- A resume moves the task to the state before the spawn, then runs the same command again, except for
updating_branch, which runs nothing. So the task goes straight back into a spawning state, except afterupdating_branch.
| Paused from | Resumes to | Then runs |
|---|---|---|
planning |
workspace_ready |
plan (continues the session) |
refining (plain refine, or from failed) |
awaiting_plan_review |
refine, with the recorded feedback |
refining (from tasker reopen) |
accepted |
reopen |
refining (promoted from reviewed) |
reviewed |
refine, with the recorded feedback or none |
refining (from tasker review FEEDBACK) |
awaiting_work_review |
review, with the recorded feedback |
executing |
approved |
execute (continues the session) |
verifying (interrupted only) |
executed |
verify |
reviewing |
awaiting_work_review |
review (in a new session) |
pushing (interrupted only) |
accepted |
push. No second push if origin already has the sha. |
opening_pr (interrupted only) |
pushed |
open-pr. It adopts an open PR and does not open a second one. |
merging (interrupted only) |
the recorded merge origin (accepted) |
merge. No second push or merge if the work is already on the base. |
finishing (interrupted only) |
landed |
finish. Each cleanup step skips what is already done. |
updating_branch (interrupted only) |
its origin state | nothing. Run update-branch again. |
flowchart LR
ANY[any non-terminal state] -->|operation fails| FAIL[failed]
ANY -->|tasker abort| C([cancelled])
FAIL -->|tasker retry| T{{retry target}}
FAIL -->|tasker refine FEEDBACK| RF[refining]
classDef terminal fill:#b91c1c,color:#fff,stroke:#7f1d1d;
class C terminal;
tasker retry picks the target from the state that the task failed in, then runs the command again. See Code.
| Failed in | Retries to | Then runs |
|---|---|---|
provisioning |
provisioning. Retry does not move the task. start moves it from failed. |
start |
planning |
workspace_ready |
plan (new session) |
refining (refine from awaiting_plan_review or failed) |
awaiting_plan_review |
nothing runs. Retry prints a hint, and you give the next feedback to refine. |
refining (from tasker reopen) |
accepted |
reopen |
refining (promoted from reviewed) |
reviewed |
nothing runs. Retry prints a hint: refine again to act on the findings, or dismiss-review. |
refining (from tasker review FEEDBACK) |
awaiting_work_review |
nothing runs. Retry prints a hint, and you give the next feedback to review. |
approved |
approved |
execute |
executing |
approved |
execute (new session). A done todo gets no turn. |
verifying |
executed |
verify. With --resume-execute: approved, every todo done, then execute continues the session with one fix turn. |
reviewing |
awaiting_work_review |
review (new session). Retry deletes the review session of the next review number. |
accepted (a failed tasker push) |
accepted |
push |
pushing (the push ended before tasker recorded it) |
accepted |
push. No second push if origin already has the sha. |
opening_pr |
pushed |
open-pr. It adopts an open PR and does not open a second one. |
merging |
the recorded merge origin (accepted) |
merge. It lands with no second push or merge if the work is already on the base. |
quota_blocked, awaiting_quota_approval or interrupted |
after planning, executing, verifying or reviewing: the target of that row. After each other state, retry refuses. |
the command of that row. Nothing runs after a refusal. |
- If git refuses a merge because the base moved, the task also fails from
merging. Do not retry it. Runupdate-branch, which demotes the task toexecuted(see "Update branch"). - A stopped
finishdoes not go tofailed. It rests incleanup_failed, andfinishruns again from there. tasker refine FEEDBACKfromfailedgives the assistant an instruction for a failed task. That sends the task through the plan-revision loop.retrynever takes this edge.tasker aborthas no failure state. If teardown fails, the task stays where it was. Runabortagain, with--forceif the worktree is dirty.
tasker update-branch enters updating_branch before any git work, then always leaves it.
flowchart LR
O[origin state] -->|tasker update-branch| UB[updating_branch]
UB -->|worktree did not move| O
UB -->|worktree moved: demote| D{{demotion target}}
UB -->|run died| X[interrupted]
X -->|tasker resume| O
- The 11 origin states:
workspace_ready,awaiting_plan_review,approved,executed,awaiting_work_review,reviewed,accepted,pushed,in_peer_review,failed,cleanup_failed. - A task stacked on a parent moves onto the tip of the branch of the parent while the parent is in flight. After the parent landed, it moves onto the base of the parent, and the task is no longer stacked. If the parent changed and the strategy is
rebase, only the commits of the task are replayed. - If the worktree moved, the old plan approval or verify result no longer describes the tree. So the task is demoted:
| Origin | Demoted to | Why |
|---|---|---|
approved |
awaiting_plan_review |
The approval is cleared. The plan was written against the old tree. |
awaiting_work_review, reviewed, accepted, pushed, in_peer_review |
executed |
Landing goes back behind a real verify run. |
failed, from a failed push, merge, open-pr or review |
executed |
The same reason. Without the demotion, a retry can push, merge, open a PR or review the moved tree with no verify. |
| any other | its origin | Nothing to demote. |
- A failure after the move to
updating_branchrestores the worktree. If the head is the head of the start, the worktree is on the task branch and the worktree is clean, the task goes back to its origin state. Otherwise the task goes tofailed. tasker approverefuses a plan that is older than the last move of the worktree. The plan was written against the old tree, so the operator runstasker refinefirst.
tasker show, tasker status and the wrong-state error name the commands that are valid from the state of a task. They read one table. The table lives in src/state/commands.ts.
| State | Primary command | Commands, in order |
|---|---|---|
provisioning |
start |
start, abort |
workspace_ready |
plan |
plan, update-branch, abort |
awaiting_plan_review |
none | refine, approve, update-branch, abort |
approved |
execute |
execute, unapprove, update-branch, abort |
executed |
verify |
verify, update-branch, abort |
awaiting_work_review |
none | review, accept, update-branch, abort |
reviewed |
none | refine, dismiss-review, update-branch, abort |
accepted |
merge |
merge, mark-landed, update-branch, abort |
pushed |
none | mark-landed, update-branch, abort |
in_peer_review |
none | mark-landed, update-branch, abort |
landed |
finish |
finish, abort |
cleanup_failed |
finish |
finish, update-branch, abort |
failed |
retry |
retry, refine, update-branch, abort |
quota_blocked |
resume |
resume, approve-credits, abort |
awaiting_quota_approval |
approve-credits |
approve-credits, abort |
interrupted |
resume |
resume, abort |
each other state that is not terminal: planning, refining, executing, verifying, reviewing, pushing, opening_pr, merging, finishing, updating_branch |
none | abort |
finished, cancelled |
no row | no row |
The rules of the table:
- The primary command is the clear next step. A state with two or more choices for the user has no primary command.
abortis the last command of each row.- A terminal state has no row.
- The table is the one home of the states that each command accepts. A command accepts a state when the row of that state names the command. The wrong-state error lists these states in the order of the states in
src/state/machine.ts. - A command can have a rule that the table cannot hold. That rule stays next to the table check of the command. For example,
startalso continues a task infailedthat failed inprovisioning. The rule lives insrc/tasks/start.ts. - A later ticket that adds a command or an edge adds its row.
data-root.md gives the transaction rule of a move and of an illegal edge.
The event of a failure records the state that the task failed in. tasker retry reads that state to pick its target. For the origin refining, it also reads the from of the last move into refining. For the origin merging, retry and resume also read the from of the last move into merging. For the origins quota_blocked, awaiting_quota_approval and interrupted, retry also reads the from of the last move into the pause, and only planning, executing, verifying and reviewing give a row.