Traces and the Trial Browser#
Every GaP run records a full trace by default: the executed workflow, every
nodeβs resolved inputs and outputs, every tool sub-call, and auto-extracted
visual assets (camera frames, masks, point clouds). The on-disk layout is a
stability contract consumed by two tools β the gap viz browser and
gap trace-diff β so you can debug a rollout long after the process exited.
Where traces go:
gap runwrites to./outputs/run_<timestamp>unless you pass--trace-dir PATH;--no-tracedisables tracing entirely.Programmatically, the precedence is the
trace_dirargument > theGAP_TRACE_DIRenvironment variable > the workflow directory itself.
Trial directory anatomy#
A trial is one executed run. Its directory looks like this:
outputs/run_20260612_141503/
βββ workflow.json # snapshot copy of the executed workflow
βββ scripts/ # snapshot copy of its script files
βββ dag_trace.json # node timings, statuses, edges, event log
βββ run_video.mp4 # sim only, with `gap run --record-video`
βββ node_data/
βββ target_sg.perceive/ # one dir per node, id = <subgraph>.<node>
β βββ resolved_inputs.json
β βββ output.json
β βββ request.json # recorded tool request (tool nodes)
β βββ request.meta.json # {"tool": "<name>"} β replay metadata
β βββ assets/ # auto-extracted visual data
β β βββ output_rgb.png
β β βββ output_mask.png
β β βββ output_cloud.npz
β βββ calls/ # ctx.tool(...) sub-calls from scripts
β β βββ 000_query/ # <seq:03d>_<short tool name>
β β βββ request.json
β β βββ request.meta.json
β β βββ response.json
β β βββ assets/
β βββ stream_reads/ # observation-stream .latest() reads
β βββ 000_observation_stream/
β βββ value.json
β βββ meta.json
βββ grasp_sg.close/
βββ ...
The pieces:
workflow.json+scripts/β a copy-once snapshot of what actually ran, so the trace stays interpretable after you edit the source graph.dag_trace.jsonβ the structured trace: anodes[]list (name, node type, start/finish timestamps,duration_ms, statuspending|running|ok|error|skipped, condition results, error messages, asset names), anedges[]list, and a sequencedevents[]log (node_started,node_finished,inputs_recorded,output_recorded,subcall_recorded,stream_read,condition_evaluated,error_recorded, β¦).node_data/<id>/β per-node payloads. Node ids inside subgraphs are fully qualified as<subgraph>.<node>.request.json+request.meta.jsoncapture the exact tool request for replay; eachctx.tool(...)call made by a script gets its own numbered directory undercalls/.Assets β image-, mask-, depth- and pointcloud-shaped numpy values are extracted structurally to files:
*_rgb.png(uint8 HxWx3),*_mask.png,*_depth.npy(float32),*_cloud.npz(positions + optional colors).
Warning
dag_trace.json is flushed when the run reaches an end node. On a
crash path (an exception that never reaches an end node), node_data/
contents are present but dag_trace.json is not written β gap viz will
show the trial with an empty trace, falling back to the static graph.
Large-array summarization#
Trace JSON stays human-sized: any ndarray with 100 or more elements is
collapsed to the string "<ndarray shape=(480, 640, 3) dtype=uint8>", and
bytes values to "<bytes len=N>". Real pixel and point data lives only in
the extracted assets/ files. Arrays under 100 elements (poses, joint
vectors, OBB corners) are inlined as lists.
For high-rate observation streams, GAP_TRACE_STREAM_SAMPLE_N (default 1)
persists every Nth .latest() read; 0 disables stream-read recording. See
Environment variables.
The trial browser: gap viz#
gap viz # scan ./outputs recursively, serve on :9432
gap viz --root path/to/runs --port 9432 --no-browser
gap viz --services ../open-robot-skills # enrich port schemas (see below)
gap viz starts a local FastAPI server (default 127.0.0.1:9432) and opens
your browser. Anything under --root that contains a dag_trace.json (an
executed trial) or a workflow.json (a static graph with an empty trace) is
discovered as a trial; copies under codegen directories are skipped.
Three views#
The workflow graph: subgraphs expand and collapse on click, with control edges, data edges, and typed ports. Best for βwhat is this graph?β
Swimlanes β one column per subgraph, sequence-ordered step cards with status dots and transition arcs. Best for βwhat happened, in what order?β
An embedded viser replay of the rollout: animated arm(s), camera frusta,
point clouds, OBB wireframes. Requires the trial to contain scene_log/
data.
The browser opens on the Execution view when the trial has recorded steps, otherwise on the State Machine. If a trace has no event stream (e.g. a crashed run), a degraded banner appears and step ordering falls back to coarse timestamps.
Per-node inspection#
Clicking a node or step card opens the inspector with tabs for:
Summary β sequence, duration, asset counts, plus clickable data provenance (βinputs sourced fromβ / βoutputs consumed byβ the producing and consuming steps).
Inputs / Output β the recorded
resolved_inputs.jsonandoutput.json; static port schemas are shown when no runtime JSON exists.Script β the script or router source as it ran.
Calls β expandable cards per
ctx.toolsub-call with request/response JSON and inline image assets (zoomable lightbox).
Note
Videos are not played in the browser. gap run --record-video saves
<trace-dir>/run_video.mp4, but the viz UI renders PNG/JPEG assets only β
open the mp4 from disk with your video player.
Node replay (POST /api/node/{id}/replay) re-issues a nodeβs saved tool
request through the live tool registry. It is best-effort: requests whose
large arrays were summarized in the trace are replayed with placeholder
strings, so replay works best for tools with compact inputs (poses, names,
parameters). The 3D Scene replay runs as a separate viser server on port
8890; only one replay server runs at a time.
Multiple trials#
When the root contains more than one trial, a sidebar rail lists them and
the browser loads the first. If you call the REST API directly
(/api/trial, /api/workflow, β¦), you must disambiguate with
?trial=<path> β with multiple trials and no trial parameter the API
returns HTTP 400 (βMultiple trials available β specify ?trial=dag_trace.json counts.
Port schemas with βservices#
Node tooltips and the inputs/output fallback views show typed port schemas
resolved from the tool registry. Without --services, only built-in @tool
plugins are registered β connector tools (robot.*, sim.*) and bundle
tools show no schemas, and replaying their nodes fails with a tool-not-found
error. Point --services at your
open-robot-skills checkout to register bundle
tools and get full schemas.
Generation traces#
gap generate records its own trace inside
the emitted workflow directory, under agent_traces/ β every prompt and raw
LLM response from the pipeline:
<workflow_dir>/agent_traces/
βββ _coordinator/
β βββ system_prompt.md
β βββ llm_response_attempt_0.md # one file per retry attempt
βββ <subgraph_name>/ # one dir per generated subgraph
β βββ system_prompt.md
β βββ subgraph_spec.json
β βββ llm_response_attempt_0.md
βββ _checkpoint_agent/
β βββ system_prompt.md
β βββ user_prompt.md
β βββ llm_response_attempt_0.md
βββ _validation_fix/
βββ attempt_0/ # per validation-fix round
βββ <script>_prompt.txt
βββ <script>_response.txt
βββ <script>_fixed.py
When a generated graph misbehaves, read these alongside the execution trace:
the execution trace tells you what the graph did; agent_traces/ tells
you why the LLM authored it that way (and what the validator made it fix).
Comparing runs: gap trace-diff#
gap trace-diff aligns two dag_trace.json files by node name and
reports where they agree β the backbone of the rehearse-in-sim,
run-on-hardware workflow:
$ gap trace-diff outputs/rehearsal outputs/real_run --out diff.json
$ echo $?
1
Both arguments may be trace directories or the dag_trace.json files
themselves. Per matched node it reports:
verdict agreement β both
ok, or both not-ok;status agreement β exact status string match;
condition agreement β same
condition_result.metoutcome (or both absent).
The summary includes the aggregate agreement rates, the first divergence
(in trace Aβs DAG order), and any nodes present in only one trace
(rehearsal-only / real-only β reported, never matched). Because alignment is
by node name, both runs must come from the same workflow.json; comparing
different graphs yields zero matched nodes.
Exit codes are CI-friendly:
Code |
Meaning |
|---|---|
|
At least one matched node and verdict agreement is 1.0 |
|
Some matched node disagreed on verdict |
|
Zero matched nodes (different graphs; a missing |
--out PATH writes the structured diff as JSON; --quiet suppresses the
human-readable summary. The same machinery is available programmatically as
gap.runtime.trace_diff.diff_trace_dirs(a, b) with format_summary() /
to_json_dict() renderers.
Next steps#
Executing graphs β how runs produce these traces.
Checkpoints β ground-truth verification recorded alongside the trace.
CLI reference β full flag listings for
gap run,gap viz, andgap trace-diff.