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 failed and terminal, whatever outcome it named.
  • An outcome is only ever the model's word. ceo_approved, ceo_rejected and ceo_changes_requested come 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, repo and the pull request number, if its final JSON names them. A run that opens or finds a pull request names its number there: that is the only way its successors learn it, and a run whose result names none hands no number on (so no_pr holds, unless its own input already carried one). A result that names a pull request but no issue only fills in repo when 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 as ceoAnswers.

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.