Executing Graphs#
A GaP graph is a directory: a workflow.json plus optional scripts/ (script-node
bodies) and checkpoints/ (postcondition sidecars). You execute one of two ways —
gap run from the shell, or gap.execute() from Python. Both drive the same
executor; the CLI is a thin wrapper that builds a connector, parses inputs, and
picks a trace directory for you.
# Tools-only (no robot, runs anywhere):
gap run my_graph
# Against a LIBERO sim task:
MUJOCO_GL=egl gap run my_graph --sim libero_object/0
# Validate without executing:
gap run my_graph --validate-only
Run with the CLI#
The graph argument#
gap run GRAPH accepts either a workflow directory or a direct path to a
workflow.json. A directory has workflow.json appended automatically:
gap run examples/libero_quickstart/graph # directory
gap run examples/libero_quickstart/graph/workflow.json # same thing
Script-node paths inside the workflow resolve relative to the workflow directory,
so always keep workflow.json next to its scripts/.
Choosing a connector#
By default gap run executes tools-only: no robot, no sim — nodes run against
the resolved skill registries and GaP’s core tools. This is how you run pure
codegen/test graphs like build_a_graph on any machine.
--sim SUITE/TASK builds a LIBERO simulation connector, e.g.
--sim libero_object/0 or --sim libero_object_all_variance/3. The value is a
suite name plus a task index; see Simulators and Environments
for every suite and the task-numbering rules.
Note
Requirements
--sim needs the [libero] extra (Linux + NVIDIA GPU + EGL) and MUJOCO_GL=egl
for headless rendering — see Installation.
--real {franka,ur_zed} builds a real-hardware connector instead:
franka— full motion control over the robots_realtime msgpack bridge. By default GaP spawns therr-sessionclient itself;--no-rr-autostartrestores the two-terminal debug flow, and--rr-config YAMLpicks an rr-session config (relative tothird_party/robots_realtime; defaultconfigs/franka/franka_robotiq_client.yaml).ur_zed— perception-only UR + ZED. No motion tools are ever registered.
Warning
Read the safety notes before driving hardware.
--sim and --real are mutually exclusive — passing both aborts the command.
See Connectors for the hardware setup behind both real backends.
Passing inputs#
--inputs takes K=V pairs and seeds the workflow’s initial inputs (the values
nodes reference as {"$ref": "in.<name>"}). Each value is parsed as JSON when
possible and kept as a plain string otherwise:
gap run my_graph --sim libero_object/0 \
--inputs target_label="alphabet soup" max_attempts=3 use_depth=true offsets='[0.0, 0.02]'
Here max_attempts arrives as an int, use_depth as a bool, offsets as a
list, and target_label as a string (it is not valid JSON, so it falls back).
An entry without = aborts the command with an error.
Checkpoint enforcement#
--checkpoints {off,warn,raise} (default warn) controls how postcondition
checkpoints in checkpoints/<subgraph>.py are enforced after each subgraph:
off— skip evaluation entirely.warn— evaluate, log failures, keep running.raise— abort the run at the first failedvalidate=Truecheckpoint.
Checkpoints need ground-truth world snapshots, which only sim connectors provide; on tools-only and real runs enforcement degrades to a one-shot warning and the run proceeds. See Checkpoints and Verification.
Traces and video#
Tracing is on by default: every run writes per-node inputs/outputs, model
calls, and rendered frames into ./outputs/run_<timestamp> (e.g.
outputs/run_20260612_153000). --trace-dir DIR redirects it; --no-trace turns
the timestamped directory off. Browse recorded runs with gap viz — see
Traces and the Trial Browser.
Note
The executor always records a trace. With --no-trace, it falls back to
$GAP_TRACE_DIR if set, otherwise the workflow directory itself — so the trace
artifacts land next to your workflow.json rather than disappearing.
--record-video (sim only) saves the run as <trace-dir>/run_video.mp4, plus one
run_video_<camera>.mp4 per camera when the env buffers per-camera frames. It
requires both a sim connector and tracing — --record-video without --sim, or
combined with --no-trace, exits with code 2. On success the command prints
video: <path> (<n> frames). Videos are watched from disk; the gap viz UI
renders the trace’s image assets, not the mp4.
Skill registries#
--skills PATH (repeatable, precedence-ordered) overrides the resolved registry
set entirely. Without it, GaP resolves registries via $GAP_SKILLS_PATH, the
project’s [tool.gap] table, the user config, or an open-robot-skills checkout
next to the GaP checkout — see Registries.
Flag reference#
Flag |
Default |
Meaning |
|---|---|---|
|
— |
Workflow directory or |
|
off (tools-only) |
LIBERO sim connector, e.g. |
|
off |
Real-hardware connector |
|
|
franka only: rr-session config, relative to |
|
autostart |
franka only: don’t spawn rr-session; run it yourself |
|
resolved registry set |
Skill registry root(s); repeatable, full override |
|
off |
Validate the graph without executing |
|
tracing on |
Don’t create the timestamped trace directory |
|
|
Trace output directory |
|
off |
Sim only: save |
|
|
Checkpoint enforcement mode |
|
none |
Initial workflow inputs, JSON-typed |
|
off |
Debug logging |
Output and exit codes#
A run prints a status line, the trace path, one line per checkpoint result, and the error when the workflow failed:
$ MUJOCO_GL=egl gap run examples/libero_quickstart/graph --sim libero_object_all_variance/0
SUCCESS (exit=success, 42.3s)
trace: outputs/run_20260612_153000
checkpoint: CheckpointResult(name='target_held', subgraph='grasp_sg', passed=True, ...)
Exit code |
Meaning |
|---|---|
|
Run succeeded (or |
|
Run failed, validation found error-severity issues, or bad usage ( |
|
|
This matches the CLI-wide convention (0 success, 1 operation failed, 2 usage error) documented in the CLI reference.
Validate without executing#
--validate-only loads the workflow, resolves the active skill registries, and
runs the structural validator — no connector, no execution, no trace:
$ gap run my_graph --validate-only
OK: 0 errors, 2 warning(s)
Every issue prints on its own line; the command exits 0 when there are no
error-severity issues and 1 otherwise (including when workflow.json fails to
load). Tools that aren’t in any resolved registry degrade to warnings — connector
robot.*/sim.* tools only exist at run time, so a graph that uses them
validates clean with warnings. Validation rules are listed in the
workflow schema reference.
Run --validate-only after every hand edit and in CI; it catches dangling edges,
unreachable nodes, bad $ref targets, and type mismatches before you spend
sim time.
Run from Python#
gap.execute() is the one-call facade behind gap run:
import gap
conn = gap.connector.sim("libero", task="libero_object_all_variance/0", seed=1)
try:
result = gap.execute(
"examples/libero_quickstart/graph",
conn,
trace_dir="outputs/my_run",
checkpoints="warn",
)
finally:
conn.close()
print(result.success, result.exit_status, result.trace_path)
The full signature:
gap.execute(
graph, # dir / workflow.json path / dict / builder Workflow
connector=None, # None = tools-only
*,
skills=None, # registry root(s); default: resolved registry set
inputs=None, # initial inputs, addressable as {"$ref": "in.<name>"}
trace_dir=None, # default: $GAP_TRACE_DIR, else the workflow dir
checkpoints="warn", # "off" | "warn" | "raise"
max_node_workers=8, # thread budget per parallel super-step
) -> ExecutionResult
Accepted graph forms#
graph accepts four forms:
a
str/Pathto a workflow directory,a
str/Pathto aworkflow.jsonfile,a materialized workflow dict,
any object with a
to_dict()method — i.e. agap.builder.Workflow.
Dicts and builder objects are written to a temporary directory before execution.
Only workflow.json is materialized there, so graphs containing script nodes
must be passed as a directory path (or saved with Workflow.save() first) so
their scripts/ resolve.
Connector, skills, and tracing#
connector=None runs tools-only against the default @tool registry — useful for
tests and dry runs (see Testing bundles). The
connector argument is duck-typed: anything exposing tool_registry,
get_observation, and optionally world_snapshot/capabilities works.
skills takes one path or a precedence-ordered sequence of registry roots. When
omitted, the same resolution chain as the CLI applies ($GAP_SKILLS_PATH →
project [tool.gap] → user config → sibling open-robot-skills checkout); when
nothing resolves, execution proceeds without a skill registry.
Unlike the CLI, gap.execute() does not invent a timestamped directory: with
trace_dir=None the trace goes to $GAP_TRACE_DIR or the workflow directory
itself (for dict/builder graphs, that is the temp dir). Pass trace_dir= when you
want the CLI-style layout.
ExecutionResult#
gap.execute() never raises on workflow failure — exceptions are caught and
returned in the result:
Field |
Type |
Meaning |
|---|---|---|
|
|
True iff no exception and the run reached a |
|
|
|
|
|
|
|
|
Directory the trace was written to |
|
|
|
|
|
The exception when the workflow crashed |
|
|
Wall-clock execution time |
With checkpoints="raise", a failed validate=True checkpoint raises
VerificationFailed inside the run — it aborts the workflow and lands in
result.error like any other failure. Safety guards are the one exception to
“never raises”: GuardLimitExceeded inherits from BaseException and propagates
out of gap.execute() so nothing can swallow it.
See the executor reference for what happens between START and END.
Next steps#
Simulators and Environments — suites, seeds, cameras, video
Traces and the Trial Browser — what lands in the trace dir
Checkpoints and Verification — writing postcondition sidecars
Build a graph in Python — author what you just ran