Architecture#
How GaP is put together and why: the principles behind the engine, a map of its modules, the design decisions contributors most often ask about, the acceptance gates that defined the v1 release, and how the project is packaged. This page adapts the release design doc (docs/design.md) for a public audience; for the user-facing vocabulary (graph, node, tool, skill, connector, checkpoint, trace) start with Concepts.
Principles#
GaP is a port of a working research codebase, restructured around five explicit principles:
As easy to use as a Python library.
pip install, four lines of Python, and an LLM API key. The quickstart needs zero self-hosted servers and zero extra terminals.In-process by default. The simulator, the vision models, and IK all run in one Python process. Ray is an opt-in extra for scale-out benchmarking β never a prerequisite.
Plain Python, no protos. gRPC and protobuf were deleted everywhere. Typed dicts + numpy arrays are the data contract. The only wire protocol left in the system is the msgpack bridge to real robots.
Two repos. The engine (graph-as-policy) and the contributable skill library (open-robot-skills, in the Anthropic Agent Skills format) are separate repos. Skills are discovered by filesystem path, not by pip entry points β see Skill registries.
Minimum viable abstraction. Working code was ported with seam changes, not rewritten around speculative interfaces. There is no plugin system where a function call suffices, and no pluggable backend where one implementation is enough (the connectorβs IK is a deliberate example).
The two repos#
βββββββββββββββββββββββββββ gap (engine) βββββββββββββββββββββββββββ
β agent/ instruction ββΊ coordinator ββΊ subgraph agents β
β ββΊ checkpoint agent ββΊ validate / fix ββΊ graph β
β runtime/ executor (super-steps, streaming, Send), validator, β
β tracing, policy loop, verify/ (World, checkpoints) β
β tools/ ToolRegistry: typed schemas, tagsβguards, dispatch β
β connector/ sim()/real(), in-process pyroki IK, rr_launcher, β
β data collector β
β envs/ libero (+perturbed), franka_real, ur_zed, msgpack β
β benchmark/ grid harness (modes Γ families Γ seeds), --gate β
β viz/ FastAPI+React trial browser, replay3d, PDF render β
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ²ββββββββββββββββββββ
β discovers (by path) β registers tools
ββββββββΌββββββββββββββββββββββββββββββββ΄βββββββ
β open-robot-skills (Agent Skills format) β
β tools/ sam3, grounding-dino, gemini-er, β
β molmo, vlm, curobo, geometry β
β skills/ perceiving-objects(+variants), β
β grasping-*, transporting-objects, β
β tracking-objects, pi05-libero, β
β molmoact-libero β
βββββββββββββββββββββββββββββββββββββββββββββββ
Three flows cross this boundary:
execute β graph + connector + skills β
ExecutionResult+ trace (Executing graphs)generate β instruction + skill catalog β validated workflow directory (Generating graphs)
benchmark β config β grid of generate/execute cells β summary + videos (Benchmarking)
The dependency is one-way: open-robot-skills bundles import GaPβs stable
authoring surface (gap.NodeContext, gap.types, gap.errors,
gap.skills, gap.testing); the engine never imports the skills repo β it
discovers bundle directories on disk and loads them.
Module map#
gap/agent/ β the LLM generation pipeline: a coordinator agent decides the subgraph topology, per-subgraph agents write the state machines and inline scripts, a checkpoint agent authors postcondition predicates, and a script-fix loop (β€2 attempts) repairs validation errors.
llm.pyis the provider layer (openrouterdefault,vertex) for generation β the runtimevlmtool bundle carries its own equivalent provider config (GAP_VLM_*);launcher.pyandparallel.pyrun generate/execute trials (including the benchmarkβs multiprocess worker pool). See Generating graphs and LLM providers.gap/runtime/ β the execution engine:
workflow.py(strict v3 schema parsing),validate.py(structural and type rules),executor.py(the super-step scheduler),context.py(NodeContext, the whole skill-facing surface),tracing.py,observation_stream.py, the policy loop (policy.py,policy_manager.py,policy_presets.py), andverify/(theWorld/Body/Robotpredicate vocabulary behind checkpoints). See the executor reference.gap/tools/ β the
ToolRegistry: the@tooldecorator, schema extraction from type hints, name-collision rejection, dispatch, andguards.py(tag-based call-count limits).ray_executor.pyholds the opt-in@ray.remotewrappers.gap/connector/ β the embodiment layer:
sim()/real()factories, theConnectorbase class, in-process pyroki IK (ik.py), the robots_realtime session launcher (rr_launcher.py), the HDF5 data collector (collector.py), and the world-snapshot adapter checkpoints evaluate against.gap/envs/ β concrete environments behind a lazy registry: LIBERO (plus the perturbed variant), the real Franka env, the UR+ZED env, and the msgpack bridge real robots speak. Factories are dotted paths, so importing the registry never pulls in mujoco or pyzed.
gap/benchmark/ β the grid harness: config/family expansion (
modes Γ families Γ variations Γ task_ids Γ seeds), per-cell launch, report math, and the--gate/--resumemachinery. See Benchmarking.gap/viz/ β the FastAPI + React trial browser (
gap viz), the swimlane graph builder, the 3D replay (viser), andrender.py(graph β PDF/PNG). The pre-built frontend ships in the wheel (see Packaging).gap/cli/ β one module per subcommand with lazy-import dispatch, so
gap --helpandgap skills listwork on a bare install without importing heavy dependencies. See the CLI reference.gap/builder/ β the Python authoring API (
Workflow/Subgraph); it emits the identical on-disk artifact the LLM pipeline produces, through the same validation. See The builder API.gap/skills/ β registry resolution and precedence, SKILL.md frontmatter parsing, the
Skillbase class for stateful bundles, prompt loading, and the capability probes behindgap check.gap/testing/ β the public test fixtures, exported for bundle authors and engine contributors alike:
FakeContext,ToolCallRecord,make_test_observation,assert_graph_valid. See Testing bundles.
Three top-level modules round out the package: types.py (the data
vocabulary), schema.py (the type-name registry), and errors.py (the
PipelineError hierarchy).
Key design decisions#
A data vocabulary instead of protos#
gap/types.py replaces the old protobuf message
definitions with numpy-first TypedDicts: Se3Pose, OrientedBoundingBox,
CameraFrame (rgb u8[H,W,3], depth f32[H,W] in meters, intrinsics
f64[3,3]), Mask, PointCloud, JointState, Trajectory, ArmState,
Observation, WorldConfig, GraspCandidates. Nothing is byte-packed
in-process; conversions from simulator buffers happen once, inside the env
classes. Two conventions are load-bearing:
Quaternions are wxyz, scalar-first. LIBEROβs xyzw is converted at the env boundary, nowhere else.
ArmState.proprio_stateis policy-training-exact β its layout matches what learned policies were trained on and is never transformed.
gap/schema.py maps the type-name strings used by
subgraph inputs:/outputs: declarations to these definitions; the
validator and the viz frontend consult it instead of proto descriptors.
One dispatch surface for tools#
Every callable a graph can reach is registered in the ToolRegistry and
dispatched by name β ctx.tool(name, **kwargs) from scripts, or type: tool nodes in the graph. The @tool decorator extracts typed schemas from
type hints; the same schemas drive validation and the LLMβs tool catalogs,
so the model sees exactly what the validator enforces. Names are
namespaced: robot.*/sim.* are reserved for connectors, <model>.<func>
(e.g. curobo.plan_to_pose, sam3.segment_text) for tool bundles; the
loader rejects collisions.
Safety guards hook this single dispatch point: tools carry tags
(perception, planning, sim_step), and per-tag call-count limits β
from safety_limits in a task config or GAP_MAX_* environment variables
β decrement on every call. GuardLimitExceeded subclasses BaseException
on purpose, so a skillβs except Exception cannot swallow it. Ordinary
failures are PipelineError subclasses (PerceptionFailed,
PlanningFailed, GraspFailed, β¦), which the executor wraps and routes
through subgraph on_error edges.
The connector is the embodiment#
A graph never talks to MuJoCo or a robot driver directly. The
connector owns the environment (sim) or the
robot link (real), and registers the robot.*/sim.* tools that form the
embodiment surface β the same tool names whether the body is LIBERO or a
physical Franka (see the
connector tools reference). Inverse
kinematics is built into the connector (pyroki, JAX on CPU, always
in-process) β deliberately not pluggable. Motion planning is a separate
concern: the curobo tool bundle produces collision-aware trajectories
that skills execute via robot.execute_trajectory. All motion tools take
arm_id=0, leaving the door open for multi-arm support post-v1.
Super-step execution#
The executor is a frontier scheduler: each super-step runs every ready node
concurrently, collects results, routes conditional edges on exit values,
and advances the frontier. Subgraphs execute recursively with their own
input bindings and on_error routing; streaming nodes publish snapshots
that downstream nodes consume via $ref; Send fan-out launches dynamic
parallel branches from router nodes; a node-visit cap guards against
runaway cycles; and end nodes can carry best-effort recovery tool calls
(open the gripper, go home). The semantics are specified in the
executor reference and the
workflow schema.
The trace is a stability guarantee#
Tracing is on by default β the trace is the product, not a debug flag.
Every run writes workflow.json, dag_trace.json, and node_data/<id>/
with per-node inputs/outputs and extracted PNG/NPZ assets
(Traces). This on-disk layout is one of the
projectβs two declared stability guarantees (the other is the
skill-authoring import surface): gap viz, gap trace-diff, and usersβ
own tooling depend on it, the deterministic tracing tests pin it, and
changes to it require a migration note β see
Contributing.
Release gates#
v1 was defined by four acceptance gates, and the first remains the regression bar for every release:
G1 β Grocery fulfillment gate.
gap benchmarkinllm_generationmode on the grocery-fulfillment suites must clear the configβsgate_threshold, with CuRobo motion planning and the grasping/transport skills in the prompt. The pinned config is examples/benchmark/grocery_acceptance.yaml;gap benchmark --gateexits non-zero below threshold.G2 β Correct generation.
gap.agent.generateon grocery-fulfillment instructions produces graphs that pass the equivalence suite and execute to success β the generated code clears G1, not just hand-written graphs.G3 β Steered policy works. The hover-then-handover example (perceive β approach above the target β hand control to a learned policy) runs end to end β see Steered policy.
G4 β Quickstart is one command on the stated hardware floor: 1Γ NVIDIA RTX 4090 (β₯24 GB VRAM) + Linux + EGL, an LLM API key, the two repos cloned side by side, and a one-time multi-GB model-weight download on first use (
HF_TOKENfor the gated SAM3 weights). The floor is stated up front β no pretending it runs on a laptop CPU.
Packaging#
Distribution
graph-as-policy, importgap, console scriptgap, Python β₯ 3.10, MIT license.Core dependencies are lean β numpy/scipy, FastAPI/uvicorn, httpx (the OpenRouter / OpenAI-compatible LLM client), pyroki + JAX (CPU) for in-process IK, msgpack, imaging libraries. No grpcio, no protobuf, no torch in core.
Extras gate the heavy stacks:
[libero](MuJoCo/robosuite/the posvar LIBERO fork βmujoco==3.6.0is pinned exactly because the acceptance numbers were measured on it),[ray],[real],[vertex],[policy], plus meta-extras[quickstart],[grocery], and[all]that span both repos. open-robot-skills mirrors this with one extra per bundle. See Installation.uv-first. The committed
uv.lockpins the exact environment the measured results were produced on;[tool.uv.sources]resolves the vendored submodules and the side-by-side open-robot-skills checkout. pip is supported with the submodules named explicitly.The viz frontend ships pre-built.
gap/viz/frontend/dist/is checked into the repo and included in the wheel as package data, so pip users getgap vizwithout Node. CI asserts the wheel containsdist/and leaks nofrontend/src/ornode_modulesβ see Contributing.Pinned submodules under
third_party/: robots_realtime, the Variational-Automation-Benchmark fork, robosuite, and LIBERO-PRO.
Where next#
Contributing β dev setup, test markers, what CI enforces
Roadmap β the v1 line and the named post-v1 cuts
Workflow schema and executor semantics β the normative specs
Authoring bundles β extending the skills repo instead of the engine