Skip to content
rev0Docs

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.

TypeWhat it does
triggerEntry 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.
sourceReads the outside world: GitHub, Jira, Slack, or the board itself. Deterministic code, no agent.
transformA pure filter, map or passthrough over a list of items. No agent.
scriptArbitrary 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.
shellRuns 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.
agentOne 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.
subticketDispatches 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.
gatehuman_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.
sinkTerminal 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:

baseThe step starts from
omitted or chainThe previous step’s branch, inheriting its commits.
orchestratorThe 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.

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.

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.

NameHolds
inputsEvery upstream node’s output, by node id.
inputThe output of the only upstream node.
itemsinput as a list.
paramsThe node’s own params.
resultNone until the script sets it.

How it is isolated is Loop script node sandbox in Settings > Automation > Loops (REV0_LOOP_SCRIPT_SANDBOX seeds it):

ModeIsolation
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.
subprocessA separate process with the limits below, and no filesystem or network isolation.
offRuns inside the board’s process with no isolation. Not recommended.

Under srt:

ResourcePolicy
NetworkDenied. 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.
ReadsNothing under your home directory, except the node’s throwaway workspace and the Python installation.
WritesOnly the throwaway workspace.
EnvironmentOnly PATH, HOME, TMPDIR, TMP, TEMP, LANG and LC_ALL are passed through. No tokens.

In every mode but off:

LimitValue
Wall clock30 seconds, or the node’s timeout_s. The whole process group is killed on timeout.
CPU time20 seconds.
Memory512 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.

  • groups draw visual phases in the editor.
  • params.resource_caps bounds 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.goal turns the Loop into a Goal; see Loops and Goals.