Skip to content
Orbital

Add a loop

Send work back to an earlier step, and stop the loop after a set number of rounds

Send work back to an earlier step when a review rejects it. A loop is any edge that points back at an earlier step, and nothing else marks it.

bounded-retries.dot
review -> repair [condition="verdict == 'reject'", weight="3", repair_budget="answer", repair_round="retry"]
repair -> review

Writing a loop is easy. The work is in stopping it.

Orbital gives you two ways to stop a loop, and two ways to make a long loop cheaper.

A repair budget limits how many times one piece of work goes back for correction. It is the bound to reach for when an agent may need several corrections but must not correct forever.

You name the budget on three kinds of edge:

Round Edge What it does
retry The edge that sends work back for repair. Starts a repair round.
exhausted A partner with the same condition and budget, and a different weight. Runs once after five unsuccessful rounds, instead of the retry.
reset The edge taken when the work has passed. Clears the budget, so the next piece of work gets five rounds again.

Put the retry and exhausted edges after the review that decides. Never put them on a fan-out or inside a parallel branch. The repair_round entry gives the full rules.

In the editor, each edge that loops back shows its condition and weight.

The workflow editor shows a pull-request workflow in which conditioned edges such as review_verdict == 'reject' lead from later steps back to earlier ones.
Edges that loop back carry a condition and a weight like any other edge.

max_visits on a node caps every visit to that node for the life of the run, whatever the reason. max_visits on the workflow caps the run’s agent turns, 200 by default. Commands and waits do not spend it.

Limit reached Result
The workflow’s limit The run ends Failed.
A step’s own limit That visit fails, and the run is Paused with “Waiting on you”, whatever failure edges the step has.

Visit caps stay independent of repair budgets. Treat them as the outer guard, not as the way the loop is meant to stop. See max_visits.

A poll loop watches something outside the run, such as a pull request, a build or a queue. Put an insulator wait in it. Waiting spends no agent visits, and you can choose Skip to check again now. Add gates and waits covers the wait step itself.

The run page's Waterfall tab shows a poll loop: probe, route and wait steps repeat as probe@2, route@2, wait@2 and so on, each with its duration.
Each pass of a poll loop adds a new visit to the probe, route and wait steps.

loop_restart="true" on the edge back clears the values the loop wrote to context, the history of finished steps that later prompts show, and the agents’ sessions. It keeps the original inputs, the run’s identity, the visit counts and the repair budgets.

Use it when the next pass is a new piece of work, not another attempt at the same one. Without it, each prompt in the loop carries the history of every earlier pass. See loop_restart.

A failed step takes a matching conditioned edge and never the ordinary fallback. When the workflow knows how to recover, route outcome == 'failed' to a repair prompt that reads {{ context.failure_reason }}. Then return to the observation or review that proves the recovery worked.

Make exhaustion visible with a terminal or a step that reassesses the work. A failed terminal ends the run as Failed. A failed step with no edge for the failure leaves the run Paused, with “Waiting on you”. Troubleshooting explains how to tell them apart in the app, and Error handling gives the exact rules.

You need a harness you have signed in to, and a project in Orbital. Any project works. It does not need to be a Git repository, unless you run the example with Codex, which needs one. See Projects and folders.

This workflow reviews an answer and sends rejections to a repair step. It gives up after five unsuccessful rounds instead of looping forever. Download the example and unpack it into ~/.orbital/workflows/. Keep every relative path.

review passes to done or rejects into repair; repair returns to review. Five unsuccessful repair rounds lead to exhausted.

bounded-retries.dot
digraph retries {
graph [version="1", entry="review", max_visits="30"]
review [tool_access="Read only", prompt="Review the goal and any previous repair. Choose pass or reject.", outputs="verdict:choice"]
repair [tool_access="Read only", prompt="Revise the proposed answer to the goal."]
done [shape="Msquare"]
exhausted [shape="Msquare", outcome="failed"]
review -> repair [condition="verdict == 'reject'", weight="3", repair_budget="answer", repair_round="retry"]
review -> exhausted [condition="verdict == 'reject'", weight="2", repair_budget="answer", repair_round="exhausted"]
review -> exhausted
review -> done [condition="verdict == 'pass'", weight="1", repair_budget="answer", repair_round="reset"]
repair -> review
}

Four edges leave review, and three of them name repair_budget="answer":

Edge Condition What it does
retry verdict == 'reject', weight 3 Starts a repair round.
exhausted The same condition, weight 2 Runs once after five unsuccessful rounds.
reset verdict == 'pass', weight 1 Clears the budget, so a later review that rejects gets five rounds again.

The fourth edge, review -> exhausted, has no condition, so it is the fallback. Validation does not count the retry and exhausted edges as answering reject, so review needs a fallback for the case where neither round is open. It points at exhausted, so a rejection that no round can take still ends the run Failed.

Both steps have the Read only tool access, so the repair revises its answer without changing files or running commands.

Download bounded-retries.dot

Terminal window
orbital validate ~/.orbital/workflows/bounded-retries.dot

Choose New task, pick any project, and choose the bounded-retries workflow and your harness. One project serves every example. Write the goal the review step should judge, and start the run.

The first passing verdict ends the run successfully. A rejection starts up to five repairs. A sixth rejecting review ends at exhausted with a failed outcome.

You want to Use
Allow a few corrections of one piece of work A repair budget with retry, exhausted and reset edges.
Guard against a loop that never ends max_visits on the node or the workflow.
Check on something outside the run A wait in the loop.
Start each pass as new work loop_restart="true" on the edge back.
Recover from a failed step An outcome == 'failed' edge to a repair prompt.