Development¶
Tatbot is a collection of independently buildable components. You can work on the web app or simulator without access to robot hardware.
Prerequisites¶
Git
Python 3.12 and
uvA C++ toolchain and CMake for teleoperation work
Rust and Cargo for the vision service
Node.js 22 or newer for Inkmap
Clone the repository, then choose the component you need. There is no required root-level install.
Component builds¶
Component |
Directory |
Check |
|---|---|---|
Teleoperation |
|
|
Vision service |
|
|
LeRobot integration |
|
|
Simulator |
|
|
Inkmap |
|
|
E-stop firmware |
|
see the component README |
Working on a development machine¶
Every session works in its own worktree on its own pushed branch, and lands with one command; see Work. In short:
tatbot work start --task fix-the-thing # ~/tatbot-work/<session>, branch agent/<node>/<session>
# ... edit, commit as usual; every commit is pushed to the agent branch ...
tatbot work land # rebase, fast tier, push to main, delete the branch
The shared checkout is a mirror that refuses commits. tatbot work status
shows what is unlanded on this node; tatbot work park "why" leaves work
unlanded on purpose. tatbot work init sets a machine up once.
Repository checks¶
Run the same checks used by the project before opening a change:
scripts/check
The bare command is the fast tier and finishes in minutes: lint, the
complexity and disclosure ratchets, the CLI and configuration contracts, the
docs build, and the light test profile without slow-marked tests. Pytest jobs
use every core by default, except the fast and light scripts tests, which use
four workers; PYTEST_WORKERS=N picks a count and 0 forces one.
Each process defaults OMP_NUM_THREADS, OPENBLAS_NUM_THREADS and
MKL_NUM_THREADS to one inner thread, preserving explicit settings. This
avoids multiplying pytest concurrency by a separate numerical worker pool.
scripts/check --full runs every job this node can except the simulator ones;
scripts/check rust (one workspace build, every crate’s tests once) and
scripts/check sim-fast are the ones to run by name for a change in those
roots. A job whose inputs are
unchanged since its last PASS on this machine reports PASS (cached ...);
TATBOT_CHECK_NO_CACHE=1 reruns it.
Light scripts tests (the fast tier and scripts/check --light tests) default
OpenBLAS, OpenMP and MKL to one thread per native pool, since pytest workers
already run in parallel. The defaults apply before Python starts, including
collection and test subprocesses. Explicit OPENBLAS_NUM_THREADS,
OMP_NUM_THREADS and MKL_NUM_THREADS values take precedence. Full offline
tests and direct pytest invocations use the caller’s native thread settings.
The command reports unavailable optional toolchains as SKIP; a FAIL is an
actionable defect. For docs-only work, run scripts/check docs as well.
A passing test whose call phase exceeds the per-test budget (60 s of wall time, set by
TATBOT_TEST_BUDGET_S) fails its job. Mark it @pytest.mark.slow – the fast
tier then skips it and --full still runs it – or make it cheaper.
Expensive shared fixture setup also belongs in the slow tier: mark every test
that consumes it. The two-arm calibration capture/solve/adoption tests use this
tier; shorter owner and fault checks remain fast. Named scripts/check tests
and CI include the slow tests.
Workflow boundaries¶
Use the public docs for reproducible, hardware-independent work. Deployment
configuration, private topology, experiment evidence, and powered acceptance
remain in the private internal/ tree and are not prerequisites for a public
contribution.
Test profiles and coverage¶
scripts/check --light tests installs scripts/tests/requirements-light.txt
for core offline tests. scripts/check tests installs the full offline profile
from scripts/tests/requirements.txt, including vision, dataset and
training dependencies. A named profile fails if a required dependency is absent.
scripts/check tests-integration runs the LeRobot-dependent scripts test
modules and validates the patch catalog against copies of the installed sources
in the locked plugin environment. It is a heavy job and is omitted by
--light. Cloud CI runs both the offline and integration jobs. The plugin’s own
suite remains scripts/check plugin.
Omitted collection coverage is printed even with quiet pytest output and appears
as separate SKIP entries in the check summary. A passed test job with omitted
coverage is not a complete acceptance run. Tool geometry and wrist-tag generation
also report separately when NumPy is unavailable.
LeRobot patch ownership¶
The ordered compatibility catalog lives in scripts/lib/lerobot_patches.py.
scripts/il_patch_lerobot.py remains the launcher entry point after environment
sync; scripts/lib/lerobot_patch_engine.py owns application and verification.
It plans all available targets in memory, resolves historical overlapping patches,
rejects ambiguous sites and invalid Python, and only then replaces installed
files without modifying uv’s shared cache. Missing targets are recorded; broken
imports inside an installed target fail validation.
A successful CLI application writes
<venv>/share/tatbot/lerobot-patches.json, containing the catalog hash, upstream
version, final module hashes, and omitted targets. Compare this manifest alongside
the locked dependency revision when diagnosing differences between environments.
The manifest is local source verification, not hardware qualification or a
replacement for behavior tests. Apply patches before starting consumers; replacing
multiple module files is not atomic for an already running process.
Rust production profiles¶
scripts/check rust is one workspace build: locked Clippy (warnings are
errors) and every crate’s tests once, with every feature whose native
dependency this node has. A feature whose dependency is absent is left out and
named as a SKIP: GStreamer (gstreamer-1.0 and gstreamer-app-1.0 through
pkg-config) for PoE capture, realsense2 for the wrist cameras, the pinned
vendor SDK for the arm adapter. A compilation or test failure remains FAIL.
Tests use offline fixtures; no capture command is launched.
Under scripts/check --full, or when TATBOT_RUST_PROFILES selects a
comma-separated subset, visiond is also built on its own for each profile in
rust/check-profiles.json (cargo build --release --bins with only that
profile’s features), so another workspace member cannot silently enable a
feature a deployment lacks. Profile builds are compile checks; nothing is
re-tested. The profiles cover core, viewer, PoE capture and wrist capture,
including the Zenoh-enabled deployment variants, and deployment inventory tests
fail when a service or the visualizer launcher introduces a feature combination
absent from the matrix.
A profile whose native dependency is missing is a named SKIP, unless
TATBOT_RUST_REQUIRED_PROFILES names it: then it fails, as does a missing Rust
toolchain. Unknown names and required profiles excluded from the selection fail
validation. For example, on a capture build host with the RealSense SDK:
TATBOT_RUST_PROFILES=vision-wrist,vision-wrist-bus \
TATBOT_RUST_REQUIRED_PROFILES=vision-wrist,vision-wrist-bus scripts/check rust
Passing these checks establishes build and offline-test coverage, not camera or robot qualification.
Hosted CI builds the core, viewer and both PoE profiles once a day, and for pull requests that touch the Rust tree, after installing the GStreamer development libraries; it is a daily cross-check, not a per-commit gate. The two wrist profiles run on SDK-equipped fleet hosts; they are not silently counted as covered by the hosted job.
Cargo defaults native C compilation to GNU C17 through .cargo/config.toml.
The pinned AprilTag sources use pre-C23 function declarations; this keeps newer
GCC defaults from breaking their bundled build. An explicit CFLAGS overrides
the default.