Loop nodes
A pipeline is a DAG of typed nodes:
{ "version": 1, "nodes": [], "edges": [], "groups": [], "params": {} }Every node is {"id", "type", "title", "params": {...}} and every edge is
{"source", "target"}. It must be a DAG, and it is validated strictly: a cycle, a
dangling edge or an unknown type is rejected with the reason. How to build and fire one is
in Run a Loop.
Node types
Section titled “Node types”| Type | What it does |
|---|---|
trigger | Entry point: a schedule, a webhook or an event. It must have no inbound edges. Optional: a run fired by hand starts at the first source anyway. |
source | Reads the outside world: GitHub, Jira, Slack, or the board itself. Deterministic code, no agent. |
transform | A pure filter, map or passthrough over a list of items. No agent. |
script | Arbitrary Python (the code param), run out of process in a sandbox with the network denied by default. The escape hatch when no other node fits. See The script node sandbox. |
shell | Runs a command. The only node that may leave a detached process running (with detach and a ttl_s) for a later node to use or stop. |
agent | One bounded, tool-less model turn over its upstream inputs. for_each fans it out over a list, bounded by max_items and concurrency; sandbox: false widens it to a multi-turn, fully tooled turn, which then also requires max_turns and timeout_s. |
subticket | Dispatches a real child ticket run with its own worktree, branch, model and permission gate. Params: title, scope (its brief), model, mode, merge_into_parent, base. |
gate | human_approval parks the run for a human; adversarial_review has a reviewer on a different model family sign the work off before it continues; kill_switch aborts. |
sink | Terminal write-back: a comment, created tickets, a published report, or out to GitHub, Jira or Slack; kanban merge lands the run’s children in a branch. It must have no outbound edges. |
Put a human_approval gate before anything irreversible.
Edges are the execution order, and the only thing that creates it. Two subticket nodes
with no path between them are dispatched back to back and their children run
concurrently. To make the pipeline wait, give them a shared downstream node: the run
parks on the join and resumes when the children land, with each child’s status, branch
and summary bound as that node’s input. Parallel by default, sequential where you draw an
edge.
An edge between two subticket nodes also carries the code by default: the later
child branches off the earlier one’s branch, so its worktree already contains its
predecessor’s commits. A step that depends on two earlier steps takes the first as its
base and is told, in its brief, to merge the other. A step with no upstream subticket
starts from the orchestrator’s branch. The base param decides this per step:
base | The step starts from |
|---|---|
omitted or chain | The previous step’s branch, inheriting its commits. |
orchestrator | The orchestrator’s branch. It still receives its predecessors’ summaries as context, but not their code. Use it for independent attempts at one problem, or a review that should read a clean tree. |
{"from": "<node id>"} | The named predecessor’s branch, for a step with several; the others are still listed in its brief to merge. |
A shell or script node runs in the repo checkout, not in a worktree holding the
children’s unmerged work, so it cannot verify the combined result. That happens after the
children’s branches are merged.
Reviewing and landing subticket work
Section titled “Reviewing and landing subticket work”A subticket node’s result is {id, title, status, branch, summary}. It is bound only
once the child is In Review and its verification checks passed at its latest commit
(or were overridden). summary is the report the child passed to finish_ticket, clipped
to 2,000 characters for downstream prompts.
An adversarial_review gate over subticket nodes does not read that clip. Its reviewer
gets an index (each child’s report size, commit count and check status) and four paged,
read-only tools: read_handoff, list_commits, read_diff and read_checks. It holds no
other tools. When it fails and an agent awaits the Loop, the run parks at the gate and the
agent is woken with the findings; it answers with resolve_loop_gate(ticket_id, run_id, action), where action is return (with feedback), accept or review_again.
The kanban merge sink says where the finished work goes:
{ "provider": "kanban", "operation": "merge", "into": "orchestrator" }into is orchestrator (the branch of the ticket that owns the Loop) or a branch name.
Every child of the run in In Review or Done is merged there, in dispatch order, through
the board’s integration pass; a child already contained is skipped. Merging into main or
master needs an approved human_approval gate directly before the sink.
The script node sandbox
Section titled “The script node sandbox”A script node’s code runs with these names bound, and whatever it assigns to result
becomes the node’s output. An exception fails the node.
| Name | Holds |
|---|---|
inputs | Every upstream node’s output, by node id. |
input | The output of the only upstream node. |
items | input as a list. |
params | The node’s own params. |
result | None until the script sets it. |
How it is isolated is Loop script node sandbox in Settings > Automation > Loops
(REV0_LOOP_SCRIPT_SANDBOX seeds it):
| Mode | Isolation |
|---|---|
srt (default) | Anthropic’s sandbox runtime: Seatbelt on macOS, bubblewrap with a network namespace and seccomp on Linux. Falls back to subprocess by itself when the runtime or Node is missing, or fails a test run. |
subprocess | A separate process with the limits below, and no filesystem or network isolation. |
off | Runs inside the board’s process with no isolation. Not recommended. |
Under srt:
| Resource | Policy |
|---|---|
| Network | Denied. The only domains a script may reach are the board’s allow list, loop_script_sandbox_network_domains, which is empty by default (no network at all). It has no control in the Settings dialog: seed it with REV0_LOOP_SCRIPT_SANDBOX_NETWORK_DOMAINS (comma separated, for example api.github.com,pypi.org) or set it through the settings API. |
| Reads | Nothing under your home directory, except the node’s throwaway workspace and the Python installation. |
| Writes | Only the throwaway workspace. |
| Environment | Only PATH, HOME, TMPDIR, TMP, TEMP, LANG and LC_ALL are passed through. No tokens. |
In every mode but off:
| Limit | Value |
|---|---|
| Wall clock | 30 seconds, or the node’s timeout_s. The whole process group is killed on timeout. |
| CPU time | 20 seconds. |
| Memory | 512 MB, best effort (macOS often does not enforce it). |
The node’s result starts with [sandbox= and the mode that actually ran, so a fallback to
subprocess is visible.
Pipeline params
Section titled “Pipeline params”groupsdraw visual phases in the editor.params.resource_capsbounds the Loop: max runs, tokens, cost, and wall clock per run.{"from": "<node id>", "field": "<key>"}in any node’s param reads an upstream node’s output.params.goalturns the Loop into a Goal; see Loops and Goals.