Spingi is Italian for "push!", the word you shout at someone who should keep going. Pronounced speen-jee.
Spingi is a Physical Agent Runtime for humanoid robots: it turns a robot into a reliable executor of simple physical tasks (go there, look at this, fetch that, put it here), with safety limits, retries and human escalation built in. It is developed sim-first: everything runs and is tested in MuJoCo with the Unitree G1 model before any real robot is involved. Every run produces an episode that the Spingi Viewer replays in the browser.
This repository is a neutral open-source toolkit. Applications built on top of it live elsewhere; this repo keeps sample plans and scenes that show how to use the system.
Status: runtime M3 complete · viewer v0.2 · 2026-10-04
| Folder | Project | What it does |
|---|---|---|
| runtime/ | Spingi runtime (Python) | Executes a declarative plan made of skills on a robot adapter. Ships with a fake adapter for unit tests and a MuJoCo adapter with the Unitree G1. Writes an episode for every run. |
| viewer/ | Spingi Viewer (web, Three.js) | Loads an episode .zip and replays it: robot and objects moving in the 3D scene, event timeline, head-camera frames, safety and operator events. Live at spingi-viewer.netlify.app. |
| docs/episode-format.md | Episode format | The contract between the two: a folder with manifest.json, scene.yaml, plan.yaml, events.jsonl, trajectory.jsonl, frames/. JSON schemas in docs/schemas/. |
Requirements: Python 3.11+ with uv, Node 20+ for the viewer. No GPU, no robot.
cd runtime && make setupmake demo-simThis runs the inspection round on the G1 in MuJoCo as fast as the CPU allows and leaves an episode in runtime/runs/<run_id>/. To watch it live in the MuJoCo viewer (macOS uses mjpython, already included):
make demo-sim-viewTo fetch a box from a shelf and deliver it to a workstation, routing through the aisle of a small warehouse:
uv run spingi run plans/demo_material_runner.yaml --scene sim/scenes/warehouse_small.yaml --adapter sim --record --zip--record adds a third-person video, --zip packs the episode for sharing.
Open https://spingi-viewer.netlify.app and drop the .zip onto the page, or pick one of the bundled samples. Nothing is uploaded: the episode is read in your browser. To run the viewer locally, from the repository root:
cd viewer && npm install && npm run devThen open http://localhost:5173. Space plays and pauses, the arrow keys step one second, clicking an event jumps to it.
From runtime/ again. Answer the robot's requests yourself instead of a fixed policy, with Ctrl+C as the stop button:
uv run spingi run plans/demo_material_runner.yaml --scene sim/scenes/warehouse_small.yaml --adapter sim --operator consoleRun the same plan 100 times with noisy perception and check the sim-to-real gate (success rate, operator requests, fatal runs, safety violations):
uv run spingi bench plans/demo_material_runner.yaml --scene sim/scenes/warehouse_small.yaml --adapter sim --runs 100 --noise 0.2 --gateTurn episodes into a LeRobotDataset v3.0 for training tools:
uv run spingi export lerobot runs/r-*/ --out runs/datasetWith an Anthropic API key in runtime/.env (copy runtime/.env.example; the file is ignored by git), Claude turns a request into a plan that is validated against the skills before anything moves, and can run it straight away under your supervision once you confirm it (--yes skips the question):
uv run spingi plan "Bring the red box from shelf A to workstation B" --scene sim/scenes/warehouse_small.yaml --run --adapter simspingi eval-planner measures the planner on ten golden requests. The model never controls the robot: it can only write steps made of whitelisted skills, and a plan that does not validate is never executed.
A plan is a YAML list of skills with a failure policy per step. No branches, no loops: when a decision is needed, a new plan is generated.
id: fetch_box
description: "Fetch the red box from shelf A and bring it to workstation B"
steps:
- skill: navigate
params: { to: shelf_A, via: [aisle_in] }
on_failure: { retry: 2, then: needs_human }
- skill: detect
params: { cls: red_box, expect: 1 }
- skill: pick
params: { object_id: "$detect.objects[0].id" } # reference to the previous step's evidence
- skill: navigate
params: { to: workstation_B, via: [aisle_out] }
- skill: place
params: { at: workstation_B }Skills available today: navigate, detect, pick, place, inspect, wait_for_human, say. uv run spingi skills prints their parameters. A scene is a YAML file too: named locations, obstacles, objects and safety limits (geofence, speed cap, battery minimum). See runtime/sim/scenes/.
request ──► LLMPlanner ──► plan.yaml ──► Executor ──► skills ──► RobotAdapter ──► FakeAdapter | SimAdapter (MuJoCo) | real robot (M4)
│ │
│ └── Perceiver (ground truth in simulation; markers and detector later)
├── SafetyMonitor: independent task on robot time, geofence (e-stop), speed cap, battery, watchdog
└── EventLog ──► console · metrics · episode (events, trajectory, frames) ──► Viewer, LeRobot
Eight principles drive the design, written down in docs/runtime-spec.md and in the ADRs. The two that matter most: a language model may propose a plan but never controls the robot directly, and every component is testable without hardware.
Safety layers run on the robot's own clock (simulated time in MuJoCo), so a simulation faster than real time is checked as often as the real robot would be. An e-stop ends the run (status estop), leaving the geofence e-stops the robot, and any failure inside the runtime stops the robot and still records the episode (ADR-0011).
| Document | Content |
|---|---|
| docs/runtime-spec.md | Principles, architecture, contracts, execution model, safety layers, testing strategy, milestones |
| docs/episode-format.md | The episode folder, manifest, trajectory, frames, compatibility rules |
| adr/ | Architecture decisions 0001–0011 and the ones still open |
| runtime/README.md | CLI reference, scenes, plans, skills, episodes, tests, how to add a skill or an adapter |
| viewer/README.md | Running, loading episodes, deploying |
| SECURITY.md | Threat model and how to report a security or safety problem |
make testruns the runtime suite (unit, adapter contract, MuJoCo scenarios, golden episodes, architecture and licensing rules): 176 tests in about 12 seconds. The viewer has npm test (21 tests) and npm run build. CI is GitHub Actions (.github/workflows/ci.yml), on every push to main and every pull request, without a GPU: runtime lint, format check and tests (with offscreen MuJoCo rendering through EGL), viewer install, audit of the production dependencies, tests and build.
Conventions: every architectural decision is an ADR, changed by writing a new one; the runtime never imports the viewer and the viewer never imports the runtime, they only share the episode format; spingi.core imports nothing from adapters, skills, planner or perception, and a test enforces it.
- M2 (done): operator console,
spingi benchwith the sim-to-real gate, LeRobot v3.0 export,wait_for_human. - M3 (done): LLM planner with structured output, golden episodes replayed on every commit. On the ten golden requests
claude-opus-5-5scored 10/10, nine at the first attempt. - M4 (next, needs the robot): adapter for the real Unitree G1 on
unitree_sdk2_python, the same contract tests on hardware, sim-to-real gates per skill.
Apache License 2.0, see LICENSE. The Unitree G1 model in runtime/sim/models/unitree_g1, and the viewer's g1.glb derived from it, are redistributed under their own BSD 3-Clause license, see NOTICE; the viewer serves the license text at /NOTICE.txt, and the Python package ships copies of LICENSE and NOTICE.