Advanced Metal Research
GitHub Contact AMR

Program format (robot.v4.program.v2)

On this page
  1. Example
  2. Validate a document
  3. Units
  4. Top-level fields
  5. Freespace policy
  6. Nodes
  7. The single line
  8. Weld nodes
  9. Freespace nodes
  10. Move nodes
  11. Forbidden keys
  12. Digests
  13. Related pages

A robot.v4.program.v2 document is an authored robot program: an ordered list of nodes that say what the robot should do, not how. Welds carry their seam geometry, freespace moves carry constraints, taught moves carry a destination, and events (I/O and dwell) do not move. It never contains a solved trajectory. The weld planner turns it into joint trajectories and a .rdt file.

OLP, the Steam Deck v5 pendant and the weld planner all read and write this format. The TypeScript validator is offline-programming/v1/ui/src/contracts/robot-v4-program-v2.ts. Its twins are the Go test steamdeck/real/v4/contracts/programs/program_v2_test.go, the Python module weld_planner/v1/python/weldplan/program_v2.py and the JSON Schema steamdeck/real/v4/contracts/programs/robot.v4.program.v2.schema.json.

Example#

A valid program with one weld: Home, approach, weld, retract, a dwell, Home.

bracket_fillet.program.jsonJSON
{
  "schema": "robot.v4.program.v2",
  "program_id": "bracket_fillet",
  "program_name": "Bracket fillet, one pass",
  "program_revision": 1,
  "base_program_revision": 0,
  "program_state": "saved_unplanned",
  "execution_mode": "simulation",
  "created_utc": "2026-09-29T09:00:00Z",
  "units": {"length": "m", "angle": "rad", "time": "s",
            "joint_order": ["J1", "J2", "J3", "J4", "J5", "J6", "J7", "J8", "J9"]},
  "producer": {
    "component": "offline-programming-v1",
    "source_revision": "local",
    "inputs": [{"kind": "project", "ref": "bracket",
                "sha256": "4f6c2d7a0b3e9f18c5a6d2e7b1f0c3a9d8e4b7f2a1c6d5e0f9b8a7c6d5e4f3a2"}]
  },
  "workpiece": {"frame_id": "bracket_origin"},
  "placement": {"cell_id": "rosie_1400_v3", "anchor_xyz_m": [0, 0, 0],
                "offset_xyz_m": [0.1, 0, 0.1], "rpy_rad": [0, 0, 0]},
  "robot": {"model_id": "rosie_1400_v3",
            "robot_description_sha256": "sha256:b2103adffd53cb9a66db1edf842ae1cb34173f1c8ff83c3b30ff7c8dacee9b0a"},
  "defaults": {"clearance_min_mm": 20, "speed_scale": 0.5, "torch_policy": "free", "positioner_policy": "hold"},
  "nodes": [
    {"id": "home-start", "op": "home", "enabled": true, "label": "Home", "comment": "", "row_hash": "sha256:…"},
    {"id": "approach-1", "op": "approach", "enabled": true, "label": "Approach", "comment": "", "row_hash": "sha256:…",
     "arrive_standoff": {"direction": "torch_axis", "distance_mm": 30}},
    {"id": "weld-1", "op": "weld", "enabled": true, "label": "Fillet A", "comment": "", "row_hash": "sha256:…",
     "geometry": {"kind": "manual_pose_wp", "frame_id": "bracket_origin", "points": [
       {"xyz_m": [0.0, 0.0, 0.01], "rpy_rad": [3.14159, 0.0, 0.0], "continuity": "C0"},
       {"xyz_m": [0.1, 0.0, 0.01], "rpy_rad": [3.14159, 0.0, 0.0], "continuity": "C0"}]},
     "travel_speed_mm_s": {"kind": "constant", "value": 8.0},
     "standoff_mm": {"kind": "constant", "value": 15.0},
     "weave": {"shape": "none"},
     "weld_parameters": {}},
    {"id": "retract-1", "op": "retract", "enabled": true, "label": "Retract", "comment": "", "row_hash": "sha256:…",
     "depart_standoff": {"direction": "torch_axis", "distance_mm": 30}},
    {"id": "settle", "op": "dwell", "enabled": true, "label": "Settle", "comment": "", "row_hash": "sha256:…",
     "duration_s": 0.5},
    {"id": "home-end", "op": "home", "enabled": true, "label": "Home", "comment": "", "row_hash": "sha256:…"}
  ],
  "planner_owns": [
    "the joint-space solution of every freespace node -- this document states constraints, never a path",
    "time parametrization and dynamics"
  ]
}

row_hash is shown elided. A real one is the node's digest; see Digests.

Validate a document#

The TypeScript module exports the validator. With Node 22 or later, from offline-programming/v1/ui:

check-program.mjsJavaScript
import { readFileSync } from "node:fs";
import { programV2Problems } from "./src/contracts/robot-v4-program-v2.ts";

const doc = JSON.parse(readFileSync(process.argv[2], "utf8"));
console.log(programV2Problems(doc)); // [] when the document is accepted
node --experimental-strip-types check-program.mjs bracket_fillet.program.json

The validator stops at the first problem and states it as a sentence, for example weld "weld-1" is not preceded by an approach or transit (an approach is missing). programV2LineProblems returns every ordering problem at once.

Units#

units is fixed: lengths in m, angles in rad, time in s, and joint_order is J1…J9. Fields that use another unit say so in their name: _mm, _mm_s, _deg and _s. The UI shows poses in mm and degrees, but the document stores m and rad.

Top-level fields#

FieldTypeRequiredDescription
schemastringyesrobot.v4.program.v2
program_idstringyesStable program identity
program_namestringyesDisplay name
program_revisioninteger ≥ 1yesThis revision
base_program_revisioninteger ≥ 0yesThe revision this one was edited from
program_digeststringnosha256:<64 hex>; see Digests
program_statestringyesdraft, saved_unplanned, needs_plan, planning, planned, accepted_simulation, accepted_physical, execution_ready, running, completed, stopped or faulted
execution_modestringyessimulation, dry_run or production
created_utcstringyesRFC 3339 timestamp
unitsobjectyesSee Units
producer.componentstringyesoffline-programming-v1 or weld-planner-v1
producer.source_revisionstringyesRevision of the producing code
producer.inputs[]arrayyesAt least one {kind, ref, sha256}. kind is project, weld_program, cad, cell or tool; sha256 is 64 hex characters without a prefix.
sourceobjectnoWhere the geometry came from, for example a STEP file and its hash
workpiece.frame_idstringyesThe workpiece frame. Seams and hand-placed weld poses are in this frame.
workpiece.solids[]arraynoSolids from the CAD import
workpiece.weld_joints[]arraynoWeld joints that a seam's weld_joint_ref resolves against. IDs must be unique.
placement.cell_idstringyesMust equal robot.model_id
placement.anchor_xyz_m, offset_xyz_m3 numbers, myesWhere the workpiece sits in the description's work frame
placement.rpy_rad3 numbers, radyesWorkpiece orientation
robot.model_idstringyesRobot model, for example rosie_1400_v3
robot.robot_description_sha256stringyessha256:<64 hex> identity of the robot description. The all-zero value means the producer had no description to pin to.
toolobjectno{id, sha256} of the torch asset
speed_scalenumbernoWhole-program speed multiplier applied after planning, 0.01–1. Default 1.
defaultsobjectyesThe freespace policy; see below. All four keys are required.
nodes[]arrayyes1–4,096 nodes
planner_owns[]array of stringsyesAt least one sentence naming something this document deliberately leaves to the planner
extensionsobjectnoProducer-specific data

Freespace policy#

defaults sets the policy for every freespace node. A node's overrides replaces any subset of it.

KeyTypeDescription
clearance_min_mmnumber ≥ 0, mmMinimum clearance for freespace motion
speed_scalenumber in (0, 1]Freespace speed fraction
torch_policystringfree, hold_last or torch_down
positioner_policystringfree (the planner may move the positioner) or hold

Nodes#

Every node has these fields, and only the fields its op allows. An unknown key is refused.

FieldTypeDescription
idstringUnique within the program
opstringweld, approach, transit, retract, home, move, dwell or io
enabledboolean
label, commentstringMay be empty
row_hashstringNon-empty; the node digest
extensionsobjectOptional
opKindExtra fields
weldMotiongeometry, travel_speed_mm_s, standoff_mm, weave, weld_parameters, optional weld_preset_id
approach, transit, retract, homeFreespace motionOptional depart_standoff, arrive_standoff, overrides
moveTaught motionmotion, target, speed_scale, speed_mm_s, capture; optional target_space, via, acceleration_scale, constant_tcp_speed
dwellEventduration_s (≥ 0, s)
ioEventchannel (non-empty string), state (boolean)

The single line#

The motion nodes, ignoring dwell and io, must form one line:

home? approach weld (transit weld)* retract home?
  • A weld is preceded by an approach or transit and followed by a retract or transit.
  • A transit sits between two welds.
  • An approach is followed by a weld, and may follow nothing, a home, a move or a retract.
  • A retract follows a weld, and may be followed by nothing, a home, a move or an approach.
  • A home may only start or end the program.
  • move nodes may appear before, after or between complete weld blocks.

Weld nodes#

FieldTypeDescription
geometryobjectseam, posed_polyline or manual_pose_wp; see below
travel_speed_mm_sscalar function, mm/sTravel speed along the weld
standoff_mmscalar function, mmContact-tip standoff. Required, never defaulted, and positive everywhere.
weaveobject{shape, …}; shape is required, for example none
weld_preset_idstringOptional label of the preset the values started from. The values are the authority.
weld_parametersobjectReserved. Must be present and empty in v2.

A scalar function is a value over normalised arc length s in [0, 1]:

kindFields
constantvalue
piecewise_linearknots: [s, value] pairs from s = 0 to s = 1, strictly increasing
bsplinedegree, knots_u, control_values; knots_u has control_values + degree + 1 entries
sampless and values, the same length, at least two

Geometry kinds#

seam is the weld planner's seam. It needs id, reference and orientation (work_angle_deg and travel_angle_deg as scalar functions, in degrees). Its line is either an inline curve or the weld_line of the joint named by weld_joint_ref. An inline curve is a list of B-spline segments, each with degree, control_points_xyz_m and clamped, non-decreasing knots_u (poles + degree + 1 entries), and optional weights. Optional fields include span (start_s, end_s; wrapping through 0 only on a closed curve), direction (forward or reverse), lead (lead_in_mm, lead_out_mm) and search_provenance, the record of the angle search. See the weld program format for the seam model.

posed_polyline is the classic flow's weld: frame_id and at least two points, each {xyz_m, rpy_rad}. The RPY on each point is the authority.

manual_pose_wp is a weld placed by hand as torch poses. frame_id must be the workpiece frame. Each point has xyz_m (the work point, m), rpy_rad (torch orientation, URDF fixed-axis; tool +Z points from the gun into the work) and continuity (C0, C1 or C2; ignored on the first and last point). Consecutive points must be more than 1 µm apart. shape is spline (the default: chord-length cubic Hermite segments) or arc (exactly three non-collinear points on one circle).

Freespace nodes#

approach, transit, retract and home state constraints only. A standoff is {direction, distance_mm}, where direction is torch_axis, workpiece_z or a 3-vector, and distance_mm ≥ 0.

  • depart_standoff is not allowed on approach or home, since nothing is welded before them.
  • arrive_standoff is not allowed on retract or home, since nothing is welded after them.
  • A freespace node may not carry path vocabulary: knots, knots_u, times_s, control_points_xyz_m, segments, samples, points, poses, rpy_rad or path.

Move nodes#

A taught destination, with the observation it was taught from.

FieldTypeDescription
motionstringjoint (MoveJ), linear (MoveL) or circular (MoveC)
targetTeachPoseThe destination
viaTeachPoseOptional; the through point of a circular move
target_spacestringOptional. cartesian re-solves IK from the pose at planning; joint or absent keeps the recorded joint values.
speed_scalenumber in (0, 1]Joint speed fraction
speed_mm_snumber > 0, mm/sTCP speed for linear and circular moves
acceleration_scalenumber in (0, 1]Optional joint acceleration fraction. Absent, it follows speed_scale.
constant_tcp_speedbooleanOptional, default true: linear and circular moves cruise at TCP speed, with ramps
capture.sourcestringmachine (taught on a connected cell) or preview (taught in the viewer)
capture.observed_atstringRFC 3339 timestamp
capture.model_id, capture.robot_description_sha256stringThe robot it was taught on
capture.machine_planning_calibration_sha256stringOptional, sha256:<64 hex>
capture.cell_idstringRequired when source is machine
capture.poseTeachPoseThe pose as observed

A TeachPose is {frame_id: "world", xyz_m, rpy_rad, joint_names, joint_values_rad}. xyz_m and rpy_rad are the TCP pose in m and rad. joint_names lists 1–9 unique names and joint_values_rad the matching values in rad. No other keys are allowed.

See Teach waypoints and moves for how the UI records them.

Forbidden keys#

The document states intent, never a solution. These keys are refused anywhere in it, at any depth: joints, joint_positions_rad, target_joints_rad, q_rad, qd_rad_s, target_tcp_m, tcp_pose_m, trajectory, planned_path, accepted_plan, preloaded_plan, waypoints and rows. Freespace nodes and defaults also refuse the path vocabulary listed under Freespace nodes.

Digests#

Both digests are sha256:<64 hex> over canonical JSON: keys sorted at every level, no whitespace, non-finite numbers refused.

  • row_hash: the node with row_hash removed.
  • program_digest: the whole document with program_digest removed. The validator runs first.

The TypeScript module computes them with computeProgramV2NodeDigest(node) and computeProgramV2Digest(program).

OLP sends the program digest with a plan request and echoes it in the plan response. It is not the program_digest in the resulting .rdt header: there the planner writes sha256: + the SHA-256 of the .weldplan request it was given, and plan_id is <program_id>:<first 12 hex of that digest>. So replanning the same program gives a new plan identity, and resending the same request gives the same one.