Handoffs
How one task hands its result to the next: by outcome, by condition, after a delay, and back to you when a decision is needed.
What a handoff is
A task can name the tasks that run after it: its successors. When a run ends, the OS starts every successor whose rule matches how it ended. The successor's run joins the same execution, so one piece of work can move from task to task, member to member and team to team, and still read as one thing.
Successors are part of the task's definition, so changing them publishes a new version. A successor can be any task in your enterprise, on any team. Its run is done by that task's member.
A handoff happens when a run has ended. The run that hands off does not wait for its successor, and does not resume afterwards.
Each successor is one rule:
| Field | What it says |
|---|---|
slug |
The task to start. |
on |
The ending it follows. succeeded if you leave it out. |
when |
A condition on the input the successor would receive. Optional. |
delayMinutes |
Wait this long before starting it: 1 to 1440 minutes. Optional. |
maxRunsPerIssue |
The loop cap for this rule. Optional; see Loops. |
resumeConversation |
For a rule that follows your decision back to the same task: carry on the conversation that asked you. See Your answers resume the work. |
By outcome: on
on |
Fires when |
|---|---|
succeeded |
The run succeeded and named no outcome, or named plain success. |
failed |
The run failed. |
terminal |
The run ended, however it ended. |
| A named outcome | The run succeeded and named that outcome, such as needs_changes or complete. |
ceo_approved, ceo_rejected, ceo_changes_requested |
You decided on the run's question or gate. Only your decision sets these. |
Named outcomes
A task's skill can end its run on a word that says which way the work went. The model ends its final message with a JSON object:
{ "outcome": "needs_changes" }
The OS compares that word with each rule's on. A named outcome is a branch: it replaces succeeded, it does
not add to it. A run that ends needs_changes starts the rules on needs_changes and on terminal, and not the
rules on succeeded.
- Words that only mean "finished" (
success,successful,okay,done,finished) count as plain success, unless the task has a rule on that exact word. - A run that failed follows
failedandterminal, whatever outcome it named. - An outcome is only ever the model's word.
ceo_approved,ceo_rejectedandceo_changes_requestedcome from your decision; a run whose model types one fails instead.
The task's instructions to the model list the outcomes its rules route, so a skill ends on a word that goes
somewhere. A rule on a word the OS does not recognise is saved with a warning (successor_on_unknown): it is
usually a typo.
By condition: when
A condition is checked on the input the successor would receive:
when |
True when |
|---|---|
has_issue |
The input carries an issue number (issue). |
has_pr |
The input carries a pull request number (number). |
no_pr |
It does not. |
Any other when is refused when you save the task.
What a successor receives
A finished run builds one input, and every successor it starts receives the same one:
- The run's own input, carried forward. What the OS uses to control a run (retry and resume markers) is left out.
- From the run's result:
issue,repoand the pull requestnumber, if its final JSON names them. A run that opens or finds a pull request names itsnumberthere: that is the only way its successors learn it, and a run whose result names none hands nonumberon (sono_prholds, unless its own input already carried one). A result that names a pull request but no issue only fills inrepowhen the input has none; it never moves the work to another repository. If the result names a different issue or repository, the branch the work was on is not carried forward. - Your decision, if the run was decided by you: your note as
ceoNote, and your answers asceoAnswers.
Nothing else in the result is passed on. Your note and answers reach the next task only: a task that needs them later has to write them somewhere lasting, such as a comment on the issue.
Delayed successors
A rule with delayMinutes starts its successor later: it waits as pending and starts once the delay has passed.
Use it for a check that should look again in a while, such as re-reading a build that is still running.
When the successor is busy
A task has one run under way at a time, and so does a member. If the successor task already has a run under way,
or every member holding its role is busy with other work, the successor waits as pending and starts when the task
and one of them are free. A run waiting for your decision at a gate does not count as busy. The same successor is
not queued twice for the same input.
Loops and the loop cap
A rule may point back to an earlier task: review, fix, review again. A loop made only of terminal rules would
never stop, so it is refused when you save it (unconditional_loop).
Every other loop is capped, for work about one issue or pull request (an issue or number in its input): each
rule may start its successor 4 times by default for that issue or pull request, and maxRunsPerIssue changes the
cap for one rule. The count starts again after each decision of yours, and rules on ceo_approved or
ceo_changes_requested are never capped. Work with neither an issue nor a pull request in its input is not capped,
so give a loop over anything else its own way to stop.
At the cap, the OS does not start another lap. It records a blocked run with the reason loop_cap, which
appears on Blockers for a person to retry (another lap) or cancel. See Runs.
When a pipeline stops
A run that ends on an outcome no rule routes is a dead end. The run itself still reads succeeded, but the OS
records that the pipeline stopped there, and which successors it did not start, and Blockers lists the work as
stopped so it is not lost.
A run that names no outcome at all is a dead end only when the task has neither a succeeded nor a failed rule to
fall back on. Before it ends like that, the model is asked once to name its outcome. A task whose only rules are on
terminal declares no outcomes, and a run of it is simply the end of the pipeline.
Rules that name a task your enterprise does not have are saved with a warning (successor_unknown_slug): the
pipeline ends there.
Your answers resume the work
A task that needs a decision from you asks it and stops. It puts one or more questions to you, each with a recommended answer, and waits at a gate. When you decide:
| Your decision | The run's outcome | What its successors receive |
|---|---|---|
| Submit answers | ceo_approved |
ceoAnswers, one per question, and your note as ceoNote |
| Reject | ceo_rejected |
Your note as ceoNote |
| Request changes on a gate | ceo_changes_requested |
Your note as ceoNote |
Route those outcomes like any other: an approved plan on to the task that carries it out, a request for changes back to the task that proposed it. Rejecting a gated tool call is different: it cancels the run, and nothing follows. See Gates.
A rule back to the same task can set resumeConversation: true. The successor then carries on the conversation
that asked you, instead of starting fresh: the model sees what it did before, then your decision, your note and each
answer, and is told how long the question waited.
Planned
Waiting on another team
A run will be able to hand part of its work to another team's task and wait for it (waiting_on_run): the other
team's run does its part, and the first run resumes when it finishes, or fails with its reason. Today a handoff
starts its successor when the run has ended, and nothing waits for it.