diff --git a/.agents/README.md b/.agents/README.md index 3cd3a8b18..fb1335061 100644 --- a/.agents/README.md +++ b/.agents/README.md @@ -24,11 +24,12 @@ This directory contains Agent Skills following the [Agent Skills standard](https | [attach-gossip-payload](skills/attach-gossip-payload) | Broadcast custom ROS messages to peers via PeerProfile gossip payloads | | [bump-version-and-release](skills/bump-version-and-release) | Bump `.env` VERSION + CHANGELOG to clear the version-check gate | | [capture-discovered-knowledge](skills/capture-discovered-knowledge) | Persist hard-won discoveries to AGENTS.md or a skill | -| [configure-multi-robot](skills/configure-multi-robot) | Multi-robot setup: replicas, ROBOT_NAME, ROS_DOMAIN_ID | +| [configure-multi-robot](skills/configure-multi-robot) | Multi-robot setup, fleet-first: fleet files under `config/fleets/` (`--fleet`) primary; `NUM_ROBOTS` is the legacy homogeneous knob | | [create-module](skills/create-module) | Author a thin module repo (module.yaml manifest, CI, test_stack) | | [create-stack](skills/create-stack) | Create a stack folder: `airstack stack new`, wiring bootstrap, split stacks + bridge.yaml, doctor | | [debug-module](skills/debug-module) | Systematic autonomous debugging of ROS 2 modules | | [docker-build-profiles](skills/docker-build-profiles) | Build-time validation for Docker compose profiles and build args | +| [extract-module](skills/extract-module) | Extract an in-tree capability into a standalone module repo (TRUNK_REMOVAL.md pattern, sequencing rules, module CI) | | [integrate-module-into-layer](skills/integrate-module-into-layer) | Integrate a module into a **stack** (stack entry include, canonical defaults, wiring.md regen, lint) — the layer-bringup workflow is legacy | | [run-system-tests](skills/run-system-tests) | Run/extend the pytest system-test harness (marks, MetricsRecorder, /pytest) | | [test-in-simulation](skills/test-in-simulation) | End-to-end module testing in Isaac Sim | @@ -73,4 +74,4 @@ old pattern. - **Integration Checklist:** [docs/robot/autonomy/integration_checklist.md](../docs/robot/autonomy/integration_checklist.md) - **Stacks Guide:** [docs/development/stacks.md](../docs/development/stacks.md) - **Modules Guide:** [docs/development/modules.md](../docs/development/modules.md) -- **AI Agent Guide:** [docs/development/ai_agent_guide.md](../docs/development/ai_agent_guide.md) +- **AI Agent Guide:** [docs/development/advanced/ai_agent_guide.md](../docs/development/advanced/ai_agent_guide.md) diff --git a/.agents/skills/add-behavior-tree-node/SKILL.md b/.agents/skills/add-behavior-tree-node/SKILL.md index 376307c47..36fb7d730 100644 --- a/.agents/skills/add-behavior-tree-node/SKILL.md +++ b/.agents/skills/add-behavior-tree-node/SKILL.md @@ -1,7 +1,7 @@ --- name: add-behavior-tree-node description: Create behavior tree nodes for high-level mission logic and decision-making. Use when implementing actions, conditions, or decorators for behavior trees. Covers BT node types, registration, and integration with behavior executive. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/add-ros2-package/SKILL.md b/.agents/skills/add-ros2-package/SKILL.md index 0e344f9ab..bb4a4cd92 100644 --- a/.agents/skills/add-ros2-package/SKILL.md +++ b/.agents/skills/add-ros2-package/SKILL.md @@ -1,7 +1,7 @@ --- name: add-ros2-package description: Create a new ROS 2 package for the AirStack autonomy stack. Use when implementing a new algorithm module (planner, controller, perception, world model, behavior node). Covers package structure, CMakeLists.txt, package.xml, launch files, and configuration. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/add-ros2-package/assets/package_template/README.md b/.agents/skills/add-ros2-package/assets/package_template/README.md index f8207befe..b8d228260 100644 --- a/.agents/skills/add-ros2-package/assets/package_template/README.md +++ b/.agents/skills/add-ros2-package/assets/package_template/README.md @@ -254,7 +254,7 @@ airstack up isaac-sim robot # Run test scenario... ``` -See [test_in_simulation.md](../../.agents/skills/test_in_simulation.md) for detailed testing procedures. +See the [test-in-simulation skill](../../../test-in-simulation/SKILL.md) for detailed testing procedures. ## Visualization diff --git a/.agents/skills/add-task-executor/SKILL.md b/.agents/skills/add-task-executor/SKILL.md index d5570c591..db525d0cb 100644 --- a/.agents/skills/add-task-executor/SKILL.md +++ b/.agents/skills/add-task-executor/SKILL.md @@ -1,7 +1,7 @@ --- name: add-task-executor description: Implement a new task executor as a ROS 2 action server in AirStack. Use when adding a new goal-directed task (coverage, search, counting, trajectory following, etc.) that a user can trigger with parameters, monitor via feedback, and cancel. Reference implementation is random_walk_planner (ExplorationTask). -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/add-unit-tests/SKILL.md b/.agents/skills/add-unit-tests/SKILL.md index a71b4e33b..37a0108f0 100644 --- a/.agents/skills/add-unit-tests/SKILL.md +++ b/.agents/skills/add-unit-tests/SKILL.md @@ -1,7 +1,7 @@ --- name: add-unit-tests description: Add Python or C++ unit tests to an AirStack ROS 2 package. Covers the co-location pattern (test source in package/test/), registering the package in colcon_unit_test_packages.yaml so pytest tests/ and airstack test -m unit collect it, and how to extend to sim components. -license: MIT +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/attach-gossip-payload/SKILL.md b/.agents/skills/attach-gossip-payload/SKILL.md index 0e726648b..86e5ef7a0 100644 --- a/.agents/skills/attach-gossip-payload/SKILL.md +++ b/.agents/skills/attach-gossip-payload/SKILL.md @@ -1,3 +1,12 @@ +--- +name: attach-gossip-payload +description: Broadcast any ROS message to all peer robots via the gossip protocol by attaching it as a PeerProfile payload — config-driven via gossip_payloads.yaml, with the peer_profile.py add_payload/get_payload API for consumers. Use when a module needs to share data (frontier maps, sensor summaries, task status) across robots on the gossip domain. +license: BSD-3-Clause-Clear +metadata: + author: AirLab CMU + repository: AirStack +--- + # Skill: Attach Custom Payload to PeerProfile (Gossip Protocol) ## When to use diff --git a/.agents/skills/bump-version-and-release/SKILL.md b/.agents/skills/bump-version-and-release/SKILL.md index a4a74c77e..e925a5452 100644 --- a/.agents/skills/bump-version-and-release/SKILL.md +++ b/.agents/skills/bump-version-and-release/SKILL.md @@ -1,7 +1,7 @@ --- name: bump-version-and-release description: Bump the AirStack VERSION in .env (semver) before merging a PR that changes Docker image content, and update CHANGELOG. Required to pass the check-version-increment gate and to trigger the docker-build release workflow. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack @@ -17,7 +17,7 @@ Bump VERSION when the PR touches: - Any `Dockerfile` under `robot/`, `simulation/isaac-sim/`, `simulation/ms-airsim/`, `gcs/`, `common/`, or `tests/docker/` - `docker-compose.yaml` or any included sub-compose file (when the change affects what is built or installed into images) -- Code that is **baked into** an image (i.e., copied during build, not bind-mounted at runtime). For `DOCKER_IMAGE_BUILD_MODE="prebuilt"` this includes `robot/ros_ws/src/**`. For `DOCKER_IMAGE_BUILD_MODE="dev"` (the current default in `.env`) the workspace is bind-mounted, so source-only changes there do not strictly require a rebuild — but bumping is still safer if you are unsure. +- Code that is **baked into** an image (i.e., copied during build, not bind-mounted at runtime). The workspace is bind-mounted (`DOCKER_IMAGE_BUILD_MODE` is a tag discriminator only — a real `prebuilt` workspace-baked stage is future work), so source-only changes under `robot/ros_ws/src/**` do not strictly require a rebuild — but bumping is still safer if you are unsure. - Apt packages, pip requirements, ROS package manifests installed during the build - Entry-point scripts, tmux configs, or `.bashrc` snippets copied into images - Submodule pointer updates that affect image contents diff --git a/.agents/skills/capture-discovered-knowledge/SKILL.md b/.agents/skills/capture-discovered-knowledge/SKILL.md index d362a62ad..3fa2c0396 100644 --- a/.agents/skills/capture-discovered-knowledge/SKILL.md +++ b/.agents/skills/capture-discovered-knowledge/SKILL.md @@ -1,7 +1,7 @@ --- name: capture-discovered-knowledge description: Persist hard-won discoveries to AGENTS.md or a new/existing SKILL.md so future agents don't repeat the discovery cost. Trigger after any long context-discovery task (multi-minute grep / file-reading session, parallel research agents, debugging that took several iterations) or whenever you learn something critical, surprising, undocumented, or that contradicted prior assumptions in AGENTS.md or a skill. Decides between updating AGENTS.md, updating an existing skill, or creating a new skill. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/configure-multi-robot/SKILL.md b/.agents/skills/configure-multi-robot/SKILL.md index 1dbec3094..76f441545 100644 --- a/.agents/skills/configure-multi-robot/SKILL.md +++ b/.agents/skills/configure-multi-robot/SKILL.md @@ -1,7 +1,7 @@ --- name: configure-multi-robot description: Configure, name, and isolate multiple robots in AirStack — fleet files (config/fleets/, airstack up --fleet) first, legacy NUM_ROBOTS second. Use whenever launching multi-robot, multiple robots, swarm, or fleet scenarios; mixing different stacks/vehicles per robot (heterogeneous fleets, split placement via hosts:); setting ROBOT_NAME; debugging cross-robot topic collisions; choosing a ROS_DOMAIN_ID; or namespacing topics, TF frames, and DDS bridges across robots. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/create-module/SKILL.md b/.agents/skills/create-module/SKILL.md index 0874fdd34..c1e298f6c 100644 --- a/.agents/skills/create-module/SKILL.md +++ b/.agents/skills/create-module/SKILL.md @@ -1,7 +1,7 @@ --- name: create-module description: Create a new AirStack module repository by hand — author the thin module.yaml manifest, lay out colcon packages with canonical-default launch args (never remaps), add test_stack/ and CI, and validate with tools/validate_module.py. Use when packaging a capability (planner, estimator, sim extension, vehicle data) as a standalone module repo per RFC #379. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/create-stack/SKILL.md b/.agents/skills/create-stack/SKILL.md index 8a6ab6001..b3e3ce84d 100644 --- a/.agents/skills/create-stack/SKILL.md +++ b/.agents/skills/create-stack/SKILL.md @@ -1,7 +1,7 @@ --- name: create-stack description: Create a new AirStack stack folder — copy a reference stack with `airstack stack new`, edit the entry launch file(s), bootstrap wiring.md, and validate with doctor and the unit lints. Covers stack anatomy, split stacks (multiple entry points + bridge.yaml), the control/trajectory placement hard gate, and gen_dds_router. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/debug-module/SKILL.md b/.agents/skills/debug-module/SKILL.md index 5d4dff05a..468cb6106 100644 --- a/.agents/skills/debug-module/SKILL.md +++ b/.agents/skills/debug-module/SKILL.md @@ -1,7 +1,7 @@ --- name: debug-module description: Systematically debug ROS 2 modules with autonomous diagnostic strategies. Use when a module is not working as expected. Covers node status, topic connections, data flow analysis, parameter checking, and performance profiling. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack @@ -497,8 +497,8 @@ Solution: Added scipy dependency - [Debugging with GDB](https://docs.ros.org/en/jazzy/Tutorials/Debugging/Debugging-CPP.html) - **AirStack:** - - [Integration Checklist](../docs/robot/autonomy/integration_checklist.md) - - [System Architecture](../docs/robot/autonomy/system_architecture.md) + - [Integration Checklist](../../../docs/robot/autonomy/integration_checklist.md) + - [System Architecture](../../../docs/robot/autonomy/system_architecture.md) - **Related Skills:** - [add-ros2-package](../add-ros2-package) diff --git a/.agents/skills/docker-build-profiles/SKILL.md b/.agents/skills/docker-build-profiles/SKILL.md index 9304be3a4..99f7e8be4 100644 --- a/.agents/skills/docker-build-profiles/SKILL.md +++ b/.agents/skills/docker-build-profiles/SKILL.md @@ -1,24 +1,74 @@ -# docker-build-profiles SKILL +--- +name: docker-build-profiles +description: Build-time validation and guidance for AirStack Docker compose profiles and build args — adding a robot profile, quoting numeric-like YAML args, the L4T/Jetson build chain, and how module-owned dependencies enter images via module layers (airstack module lock --build) now that per-capability SKIP_* build args are gone. Use when adding/updating a compose profile or debugging a Dockerfile.robot build. +license: BSD-3-Clause-Clear +metadata: + author: AirLab CMU + repository: AirStack +--- + +# Skill: Docker Build Profiles and Build Args Summary -- Purpose: Provide actionable build-time validation snippets and YAML guidance for AirStack Docker builds. Designed for Claude/GPT-style agents that automate repo changes, CI checks, or PR review suggestions. +- Purpose: Provide actionable build-time validation snippets and YAML guidance for AirStack Docker builds. Designed for agents that automate repo changes, CI checks, or PR review suggestions. - Location: .agents/skills/docker-build-profiles/SKILL.md When to use -- When adding or updating a `docker-compose` profile that passes `PYTHON_VERSION`, `ROS_DISTRO`, or other numeric-like build args. -- When an automated agent needs to verify a new profile will produce a correct `PYTHONPATH` and avoid YAML float-parsing bugs. +- When adding or updating a `docker-compose` profile / service that builds from `robot/docker/Dockerfile.robot`. +- When a build arg looks numeric (`PYTHON_VERSION`, versions like `36.4.0`) and YAML float parsing could corrupt it. +- When deciding where a new dependency belongs: the trunk Dockerfile vs a **module layer**. + +## Current profile landscape (robot/docker/docker-compose.yaml) + +Robot services select by compose profile: `desktop` (robot-desktop, the default dev +target), `desktop_split` / `offboard` (split-stack pairs), `simple`, `voxl` / +`voxl_onboard`, `l4t` (Jetson: `robot-l4t` + `robot-l4t-stack-base` + `zed-l4t`), +`l4t_lite`, and `test`. Build args passed by compose today are `BASE_IMAGE`, +`ROS_DISTRO` (jazzy), `REAL_ROBOT`, and (l4t) the stack-base image ref. +`PYTHON_VERSION` is an `ARG` **defaulted inside `Dockerfile.robot`** (currently +`3.12`) — compose does not pass it; only quote-and-pass it if a new profile genuinely +needs a different Python. + +## Module-owned dependencies: NOT build args anymore + +The per-capability skip args are **gone**: `SKIP_MACVO` and `SKIP_TENSORRT` no longer +exist in `Dockerfile.robot` or any compose file. MAC-VO (and with it the TensorRT apt +blocks, torch/onnx wheels, and model weights) was extracted to the external +`asm_macvo` module, whose own `Dockerfile.module` owns those deps. + +Module deps enter images via **module layers** (RFC #379 §6, `docs/development/modules.md`): + +- **Tier 1** — `module.yaml` `deps: {apt: [...], pip: [...]}` → one generated `RUN` + layer per module in `.airstack/generated/layers//Dockerfile.composed`. +- **Tier 2** — the module's `Dockerfile.module`, built with + `--build-arg BASE_IMAGE=` (always `ARG BASE_IMAGE`, never a + fixed base). +- **Tier 3** — a prebuilt `overlay_image` ref. + +`airstack module sync` (and `airstack module lock`) is **plan-only**: it writes +`.airstack/generated/layer_plan.json`, the composed Dockerfile, and `modules.lock`, +and never calls docker. To actually build the chain: + +```bash +airstack module lock --build # = tools/compose_module_layers.py --build +``` + +Rule of thumb: a dependency used by exactly one optional capability belongs in that +capability's module (tier 1 or 2), not in `Dockerfile.robot`. Trunk Dockerfiles thin +out as deps migrate into the modules that own them. Actions the agent can perform -1. Validate `docker-compose.yaml` args are quoted when numeric-like (e.g. `PYTHON_VERSION: "3.10"`). +1. Validate `docker-compose.yaml` args are quoted when numeric-like (e.g. `PYTHON_VERSION: "3.12"`). 2. Insert a build-time validation `RUN` into `robot/docker/Dockerfile.robot` to fail early when the ROS Python path does not exist. 3. Add or update a short test in documentation showing how to build the `builder` stage and check `ament_package` import. 4. Suggest `network: host` under `build:` for L4T/Jetson profiles only when necessary (kernel iptables workarounds). +5. Route new capability-specific deps to a module layer instead of a trunk build arg (see above). Snippets (copyable) - YAML-check rule (agent pseudocode): - - If a `build.args` key named `PYTHON_VERSION` exists and the value matches `/^\d+\.\d+$/`, ensure it's a quoted string in YAML; otherwise update to `""`. + - If a `build.args` key named `PYTHON_VERSION` (or any version-shaped value) exists and the value matches `/^\d+\.\d+$/`, ensure it's a quoted string in YAML; otherwise update to `""`. - Dockerfile validation snippet (recommended, place before using `PYTHON_VERSION` to compose `PYTHONPATH`): @@ -33,16 +83,17 @@ RUN test -d /opt/ros/${ROS_DISTRO}/lib/python${PYTHON_VERSION} \ DOCKER_BUILDKIT=1 docker build --target builder \ -f robot/docker/Dockerfile.robot \ --build-arg BASE_IMAGE= \ - --build-arg ROS_DISTRO= \ - --build-arg PYTHON_VERSION="" \ + --build-arg ROS_DISTRO=jazzy \ + --build-arg PYTHON_VERSION="3.12" \ -t airstack-builder-test:local robot/docker -docker run --rm -it airstack-builder-test:local bash -c "python3 -c 'import ament_package; print(ament_package.__file__)'" +docker run --rm airstack-builder-test:local bash -c "python3 -c 'import ament_package; print(ament_package.__file__)'" ``` Guidance for agents when editing the repo - Prefer making minimal, reversible changes: add the `RUN test -d ...` check early in the Dockerfile and gate it with informative message text. - When updating `docker-compose.yaml`, only quote the numeric-like values; do not change unrelated fields. +- Every service with a `build:` section needs **both** `cache_from` entries (the versioned tag and the floating `${CACHE_TAG:-cache}` tag) or its CI builds will always be cold (see AGENTS.md "Docker layer cache"). - If creating PRs, include a short note in the PR description instructing maintainers to run the builder-stage sanity build on both an amd64 desktop profile and an arm64 L4T profile. Troubleshooting notes @@ -51,10 +102,11 @@ Troubleshooting notes - Jetson **`robot-l4t`** builds from **`robot-l4t-stack-base`** (`robot/docker/Dockerfile.l4t-stack-base`), not raw dustynv, so **`Dockerfile.robot` stays Ubuntu-shaped.** `airstack image-build --profile l4t robot-l4t` triggers **`robot-l4t-stack-base`** first (`airstack.sh`); bare `compose build robot-l4t` can still parallelize badly, so list stack-base explicitly if not using AirStack CLI. - **dustynv `/ros_entrypoint.sh` shadows the apt Jazzy runtime (mavros symbol-lookup crash).** The dustynv base sources a prebuilt *source* ROS at `$ROS_ROOT/install` from PID 1, prepending its older libs (e.g. `fastcdr` 2.2.5) ahead of the apt Jazzy (2.2.7) that `Dockerfile.robot` layers on top — apt-built nodes like mavros then die with symbol-lookup errors under tmux autolaunch. `Dockerfile.l4t-stack-base` neutralizes it by overwriting `/ros_entrypoint.sh` with a `exec "$@"` passthrough; shells get ROS from `/opt/ros/jazzy/setup.bash` via `.bashrc`. If a Jetson node suddenly can't resolve symbols after a base-image bump, check whether the entrypoint passthrough is still in place. - **ZED SDK version is pinned across `zed/Dockerfile.zed-l4t`** — the `ZED_SDK_URL` (e.g. `.../zedsdk/5.2/...`) and the ROS dep args (`ZED_MSGS_VERSION`, `POINTCLOUD_TRANSPORT*_VERSION`, `BACKWARD_ROS_VERSION`) must move together; a mismatched `zed_msgs` vs SDK breaks the driver build. Bumping the SDK is camera-firmware-coupled, so confirm the target camera runs that SDK line before merging. -- **`pytest` is pinned to `7.4.*` in `Dockerfile.robot` — do not remove or bump it.** The builder-stage `pip3 install` pulls `pytest` transitively into `/usr/local` (copied into the runtime image), which shadows Jazzy's apt `python3-pytest` 7.4. `pytest` 8 removed the `path` argument from the `pytest_pycollect_makemodule` hook, which apt's `launch_pytest` plugin still declares — so an unpinned (>=8) pytest aborts **every** pytest run in the container at plugin registration. That breaks `colcon test` for `ament_python` packages (e.g. `lidar_point_cloud_filter` in `test_colcon_test_robot`), while `ament_cmake` gtest packages are unaffected. Keeping the pin at Jazzy's version keeps `launch_testing` / `launch_pytest` usable for launch-based tests. The `tests/docker` runner is a separate interpreter and is free to use a newer pytest. +- **`pytest` is pinned in `Dockerfile.robot` — do not remove or bump past 8.0.** The builder stage installs `pytest==7.4.*` and a later `RUN` constrains `pytest>=7.4,<8.1`: ROS Jazzy's `launch_testing` still implements `pytest_pycollect_makemodule(path=...)`, which pluggy rejects after pytest 8.1 removed the `py.path` hook argument — an unpinned pytest aborts **every** pytest run in the container at plugin registration, breaking `colcon test` for `ament_python` packages while `ament_cmake` gtest packages are unaffected. The `tests/docker` runner is a separate interpreter and is free to use a newer pytest. +- **Robot build suddenly missing a MAC-VO / TensorRT dep?** Those deps left trunk with the `asm_macvo` extraction. Add the module (`airstack module add https://github.com/castacks/asm_macvo --version `), then `airstack module lock --build`. Examples of agent prompts -- "Check `robot/docker/docker-compose.yaml` for `PYTHON_VERSION` entries and quote any unquoted numeric values; open a PR with the fixes and include a test log from a builder-stage build." +- "Check `robot/docker/docker-compose.yaml` for numeric-like build-arg values and quote any unquoted ones; open a PR with the fixes and include a test log from a builder-stage build." - "Insert a build-time validation `RUN` in `robot/docker/Dockerfile.robot` that ensures `/opt/ros/${ROS_DISTRO}/lib/python${PYTHON_VERSION}` exists; push as a separate small commit." Notes @@ -74,12 +126,12 @@ This section shows the minimal, recommended steps an agent or maintainer should 1. Pick a sensible service name and base image - Choose a service name that clearly indicates the platform, e.g. `robot-desktop`, `robot-l4t`, or `robot-myboard`. - - Select an appropriate `BASE_IMAGE` (amd64 desktop base or `nvcr.io/nvidia/l4t-jetpack:...` for Jetson). + - Select an appropriate `BASE_IMAGE` (amd64 desktop base or an L4T/JetPack base for Jetson; Jetson goes through `robot-l4t-stack-base`). 2. Add the profile with quoted numeric args - Add a service block in `robot/docker/docker-compose.yaml` (or an override file) and set `build.args` for the profile. - - Always quote `PYTHON_VERSION` values (e.g. `"3.10"`) so YAML does not convert them to floats. + - Quote any numeric-like values (e.g. `PYTHON_VERSION: "3.12"` if you must override it) so YAML does not convert them to floats. Example snippet to add: @@ -89,12 +141,11 @@ This section shows the minimal, recommended steps an agent or maintainer should context: ./robot/docker dockerfile: ./Dockerfile.robot args: - BASE_IMAGE: nvcr.io/nvidia/l4t-jetpack:r36.4.0 - ROS_DISTRO: humble - PYTHON_VERSION: "3.10" + BASE_IMAGE: + ROS_DISTRO: jazzy REAL_ROBOT: true # module-owned deps (e.g. MAC-VO's torch/TensorRT) are NOT build-args: - # they layer on via `airstack module lock --build` (RFC #379 S6) + # they layer on via `airstack module lock --build` (RFC #379 §6) # for L4T builds only when necessary # network: host ``` @@ -105,22 +156,11 @@ This section shows the minimal, recommended steps an agent or maintainer should 4. Run the builder-stage sanity build - - Run the builder-target build locally (or in CI) to verify the image picks up the correct Python/ROS paths and that `ament_package` imports: - - ```bash - DOCKER_BUILDKIT=1 docker build --target builder \ - -f robot/docker/Dockerfile.robot \ - --build-arg BASE_IMAGE=nvcr.io/nvidia/l4t-jetpack:r36.4.0 \ - --build-arg ROS_DISTRO=humble \ - --build-arg PYTHON_VERSION="3.10" \ - -t airstack-builder-test:local robot/docker - - docker run --rm airstack-builder-test:local python3 -c "import ament_package; print('ok', ament_package.__file__)" - ``` + - Run the builder-target build locally (or in CI) to verify the image picks up the correct Python/ROS paths and that `ament_package` imports (see the snippet above). 5. Smoke-run the full compose build (optional but recommended) - - Use `docker compose -f robot/docker/docker-compose.yaml build robot-myboard` to ensure compose passes the args correctly. + - Use `airstack image-build robot-myboard` (or `docker compose -f robot/docker/docker-compose.yaml build robot-myboard`) to ensure compose passes the args correctly. 6. Prepare the PR with clear validation notes @@ -135,4 +175,4 @@ Agent implementation tips - When automating the change, produce a single commit that updates only the new service block and, if needed, a second commit that adds the `RUN` check to `Dockerfile.robot`. - If the target is Jetson/L4T, add `network: host` under `build:` only when prior builds show iptables/kernel errors; do not enable it by default. -- If you detect a pre-existing unquoted `PYTHON_VERSION` in the repo, prefer to update that entry in-place and include an explanatory commit message about YAML float parsing. +- If you detect a pre-existing unquoted numeric-like build arg in the repo, prefer to update that entry in-place and include an explanatory commit message about YAML float parsing. diff --git a/.agents/skills/extract-module/SKILL.md b/.agents/skills/extract-module/SKILL.md new file mode 100644 index 000000000..8c9e9dbd7 --- /dev/null +++ b/.agents/skills/extract-module/SKILL.md @@ -0,0 +1,185 @@ +--- +name: extract-module +description: Extract an in-tree AirStack capability into a standalone module repo — choose history extraction vs plain copy, author and validate module.yaml, build test_stack/ from a reference stack, handle submodules and host-setup hooks, write a TRUNK_REMOVAL.md checklist, sequence the trunk-removal PR against the module overlay, and wire module CI. Use when graduating a trunk package (or fork research) into an asm_* module per RFC #379. +license: BSD-3-Clause-Clear +metadata: + author: AirLab CMU + repository: AirStack +--- + +# Skill: Extract an In-Tree Capability into a Module Repo + +Distilled from three completed extractions: `asm_macvo` (heavy Docker deps + +submodule), `asm_dfm2_disturbances` (Isaac extension from a fork), and +`asm_optitrack` (proprietary SDK via hooks). This skill covers the **extraction +procedure**; for authoring the module repo itself (manifest fields, +canonical-defaults launch rule, repo anatomy) defer to +[create-module](../create-module/SKILL.md) — don't duplicate it here. + +> `airstack module extract` automation is **future work** (RFC #379 §11 names it +> as the graduation step for `module create --in-tree` research). Today the +> extraction is manual; this skill is the manual. + +## 0. Scope the extraction + +- [ ] List every trunk artifact the capability touches — not just the package: + Dockerfile blocks and build args, compose args, bringup launch wiring, + keepalive/foxglove rows, wiring snapshots, docs pages, mkdocs excludes, + `.devcontainer` launch entries, rviz layouts. (The macvo extraction touched + all of these.) This list becomes `TRUNK_REMOVAL.md` (§6). +- [ ] Record the exact trunk ref and `VERSION` you extract from — `module.yaml` + `airstack_compat` and the checklist's line references pin against it. +- [ ] If the source lives in a **fork**, run `airstack module doctor --drift` + there first: module-contained changes move with you; trunk edits are + extraction debt to upstream, carry, or turn into a convention. + +## 1. History: `git filter-repo` vs plain copy + +- **`git filter-repo`** when the capability lives in one or two clean trunk + paths, its history is worth keeping, and the code moves mostly as-is: + + ```bash + git clone --no-local /path/to/AirStack asm_ && cd asm_ + git filter-repo --path robot/ros_ws/src// \ + --path-rename robot/ros_ws/src//: + ``` + +- **Plain copy into a fresh repo** when sources are scattered (the dfm2 case: + files spread across multiple fork branches, some only in dangling commits), + contain unresolved conflict markers, or need heavy rewriting to trunk + conventions anyway. Cite the source commits in the README / a friction log + instead of carrying history. +- Either way, keep a **FRICTION_LOG.md** (or notebook entry) of every step that + needed manual invention — it is the requirements list for the future tooling. + +## 2. Author + validate the manifest + +- [ ] Follow [create-module](../create-module/SKILL.md) for `module.yaml`. Key + extraction-specific choices seen in practice: + - Heavy image deps → put **everything** in `Dockerfile.module` (tier 2) and + keep `deps: {apt: [], pip: []}` empty so the layer story is unambiguous + (asm_macvo). + - Large pinned files (model weights) → `assets:` with `url` + `sha256` + + `dest`, no Git LFS. + - `airstack_compat`: a real semver range against the trunk `VERSION` you + tested (e.g. `">=0.19.0-alpha.18 <0.20.0"`), never a branch. +- [ ] Validate: + + ```bash + python3 tools/validate_module.py /path/to/asm_ # exit 0, {"valid": true} + airstack module add /path/to/asm_ # local-path add + sync + airstack module doctor # manifests + overlay integrity + ``` + +## 3. Host-side SDK installs → `hooks.host_setup` + +Proprietary or license-gated SDKs (asm_optitrack's NatNet SDK) never go in the +image or in git: + +- [ ] Ship a download script and declare it: + + ```yaml + hooks: + host_setup: /scripts/download-.sh + ``` + +- [ ] The hook contract: **idempotent, no sudo, writes only inside the module + checkout** (gitignore the landed files). `airstack module sync` runs it; + `--no-hooks` skips it. + +## 4. Git submodules + +If the trunk package embeds a submodule (macvo's MAC-VO network): + +- [ ] The **module repo** carries the submodule now; `airstack module sync` uses + `vcs import --recursive`, so it clones automatically. +- [ ] Put the trunk-side removal in the checklist: `git submodule deinit -f + ` **before** `git rm -r`, plus deleting the `.gitmodules` entry. + +## 5. `test_stack/` from the reference stack + +- [ ] Copy the closest trunk reference stack (`airstack stack new ` + or copy by hand) into the module's `test_stack/` and wire the module in its + `launch/stack.launch.xml` — the **one** place the module is wired + (single-locus rule; the module's own launch file keeps canonical defaults + and zero remaps). +- [ ] Pin the module in `test_stack/modules.repos`. Before the repo has a + remote/tag the pin is a **placeholder** — track updating it on first + push/tag as a precondition in the checklist. +- [ ] Smoke it end-to-end from a trunk checkout: + + ```bash + airstack module add /path/to/asm_ + airstack module lock --build # only if the module has a Dockerfile.module + airstack up --sim isaac --robots 1 --headless --play --wait + airstack test -m liveliness --sim isaacsim --num-robots 1 -v + ``` + +## 6. Write `TRUNK_REMOVAL.md` in the module repo + +The pattern that made the macvo extraction reviewable: a checklist **in the +module repo** enumerating every trunk file/block the trunk-removal PR must +delete, written for the orchestrator of that PR. Include: + +- [ ] The package `git rm` (+ submodule/.gitmodules steps). +- [ ] Dockerfile blocks and build args to delete, **with a pip/apt consumer + audit**: for every dep you remove, `git grep -Iil ` outside the module + proves nothing else in the image uses it. Mark shared deps (numpy, matplotlib) + **keep** explicitly, with reasoning. +- [ ] Compose build args, bringup launch gates/remaps (the wiring moves into + stacks/module launch files), keepalive/foxglove rows, wiring snapshots to + regenerate (`airstack test -m wiring --stack `), docs/skills/mkdocs + references. +- [ ] Verification: clean-cache image build with before/after size, a + `git grep -in ` residue check, the trunk test suite without the module, + and a module dogfood run (add → sync → lock --build → up → liveliness). + +## 7. Sequencing rules (the ones that bite) + +- **Never have the same package in trunk and the module overlay at once.** A + duplicate colcon package makes builds nondeterministic. Remove the trunk copy + in the same PR that consumers start pinning the module — or gate the overlay + (don't `module add` until the trunk-removal PR merges). The macvo order: + trunk-removal PR merges → module CI can go green → trunk's stack pins the + module tag. +- **Stale colcon cache:** removed packages linger in the container's `install/` + (and `build/`) until a clean rebuild — a "deleted" package that still launches + is cache, not magic. `airstack clean` (host) or remove `build/ install/ log/` + in the container, then `bws`. +- **Placeholder pins rot silently:** every `modules.repos` pin written before + the first push/tag must be updated and is a checklist precondition, not a + footnote. + +## 8. CI for the module repo + +- [ ] Add `.github/workflows/ci.yml` calling trunk's reusable workflow — see + [docs/development/module_ci.md](../../../docs/development/module_ci.md): + + ```yaml + jobs: + system-tests: + uses: castacks/AirStack/.github/workflows/module-system-tests.yml@v0.19.0 + with: + airstack_ref: v0.19.0 + marks: liveliness # per module category — see module_ci.md + sim: msairsim # the cheap bring-up + ``` + + Pin the workflow ref and `airstack_ref` **together**. First-party policy: the + workflow hard-fails outside the `castacks` org. +- [ ] Known gap (tracked): the reusable workflow runs `module add`/`sync` + (layer *plan* only) but not `airstack module lock --build`, so a + `Dockerfile.module` is not built in CI — verify tier-2 builds manually until + that lands. +- [ ] Register the module in the index repo + (`castacks/airstack-modules-index`) once CI is green. + +## References + +- [create-module](../create-module/SKILL.md) — manifest, anatomy, canonical-defaults rule +- [create-stack](../create-stack/SKILL.md) — stack folders and wiring.md +- [docs/development/modules.md](../../../docs/development/modules.md) — overlay, dep tiers, `module lock` +- [docs/development/module_ci.md](../../../docs/development/module_ci.md) — the reusable CI caller +- Worked examples: `asm_macvo/TRUNK_REMOVAL.md` (trunk-removal checklist), + `asm_dfm2_disturbances/FRICTION_LOG.md` (fork archaeology + port decisions), + `asm_optitrack` (hooks.host_setup) diff --git a/.agents/skills/integrate-module-into-layer/SKILL.md b/.agents/skills/integrate-module-into-layer/SKILL.md index f02efcaf1..c328b8705 100644 --- a/.agents/skills/integrate-module-into-layer/SKILL.md +++ b/.agents/skills/integrate-module-into-layer/SKILL.md @@ -1,7 +1,7 @@ --- name: integrate-module-into-layer description: Integrate a ROS 2 module into a stack. Use after creating a package to add it to a running topology. Covers the stack entry launch file, canonical-default topic args (usually zero include args), the single-locus wiring rule, wiring.md regeneration, and the launch lint. The old layer-bringup workflow this skill used to teach is legacy. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/run-system-tests/SKILL.md b/.agents/skills/run-system-tests/SKILL.md index c2260d29e..1fe41d2d6 100644 --- a/.agents/skills/run-system-tests/SKILL.md +++ b/.agents/skills/run-system-tests/SKILL.md @@ -1,7 +1,7 @@ --- name: run-system-tests -description: Run, interpret, and extend AirStack's pytest system test suite (build_packages, build_docker, liveliness, sensors, takeoff_hover_land, autonomy), trigger runs via /pytest PR comments, and read run_meta.json/metrics.json reports. Use for invoking tests, distinguishing infrastructure failures from policy regressions, or adding a new system test. -license: Apache-2.0 +description: Run, interpret, and extend AirStack's pytest system test suite (build_packages, build_docker, liveliness, wiring, sensors, takeoff_hover_land, autonomy, waypoint_flight), trigger runs via /pytest PR comments, and read run_meta.json/metrics.json reports. Use for invoking tests, distinguishing infrastructure failures from policy regressions, or adding a new system test. +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack @@ -24,8 +24,9 @@ This skill is about the **test harness itself** — pytest marks, fixtures, the The suite lives at `tests/` (repo root) and is fully pytest-based. Configuration is in `tests/pytest.ini` and shared infrastructure in `tests/conftest.py`. -- **`tests/system/`** — Docker stack integration tests. Marks: `build_docker`, `build_packages`, `liveliness`, `sensors`, `takeoff_hover_land`, `autonomy`. +- **`tests/system/`** — Docker stack integration tests. Marks: `build_docker`, `build_packages`, `liveliness`, `wiring`, `sensors`, `takeoff_hover_land`, `autonomy`, `waypoint_flight`. - **`tests/integration/`** — Cross-component tests (`integration` mark): robot container + a host-side component, no sim/GPU. +- **`tests/meta/`** — fast **contract tests** (`unit` mark, no Docker) that pin the CLI/docs/stack contracts: module manifest schema, module overlay, Docker layer plan, launch-intent flags, stack layout, single-locus launch rule, bridge safety, fleet resolution, docs catalog, doctor, wiring snapshot format, metrics reporting, and test collection. They run with `airstack test -m unit` and in `unit-tests.yml`; if you change one of those mechanisms, expect the matching `tests/meta/test_*_contract.py` to fail first. - **Unit tests** (`@pytest.mark.unit`) — Hermetic. Source is **co-located** with each ROS 2 package in its own `test/` dir (ROS 2 / colcon convention). `tests/colcon_unit_test_packages.yaml` lists which packages have unit tests; `conftest.py` resolves each to its `test/` dir and collects the non-linter `test_*.py` under `--import-mode=importlib`. ### Unit tests vs system tests @@ -57,8 +58,27 @@ For details on the co-located layout and adding new unit tests, see the | `tests/system/test_sensors.py` | `sensors` | Topic Hz (Isaac: batched on sim + robot; LiDAR `echo-once` + cloud sanity), RTF, `test_sensor_streams_stable` | Docker daemon, NVIDIA GPU + `nvidia-container-toolkit`, sim license / Omniverse creds | | `tests/system/test_takeoff_hover_land.py` | `takeoff_hover_land` | 4-phase flight chain per `(sim, num_robots, iteration, velocity)`: `test_px4_ready` → `test_takeoff` → `test_hover` → `test_landing`. Records altitude error, overshoot, hover stability, landing accuracy, odometry drift | Docker daemon, NVIDIA GPU, sim license | | `tests/system/test_fixed_trajectory.py` | `autonomy` | 4-phase flight chain per `(sim, num_robots, iteration, trajectory_type)`: `test_px4_ready` → `test_takeoff` → `test_fixed_trajectory` → `test_landing`. Records cross-track error, path RMSE, trajectory success/time for Circle/Figure8/Racetrack/Line | Docker daemon, NVIDIA GPU, sim license | - -The marks are declared in `tests/pytest.ini`. **Do not invent new marks ad-hoc** — register any new mark there or pytest will warn about unknown marks. +| `tests/system/test_waypoint_flight.py` | `waypoint_flight` | 4-phase flight chain (PX4 ready → takeoff → NavigateTask waypoint route → land); pass/fail judged on the odometry track by `tests/waypoint_checker.py` (in-order corridor arrival, final goal tolerance, per-waypoint timeout) | Docker daemon, NVIDIA GPU, sim license | +| `tests/system/test_wiring_snapshot.py` | `wiring` | Observed wiring snapshot of the running ROS graph, drift-checked against the committed golden / the stack's `wiring.md` (use with `--stack ` to regenerate a stack's wiring) | Docker daemon, NVIDIA GPU, sim license | + +### The full mark set (`tests/pytest.ini`) + +All eleven registered marks — **do not invent new marks ad-hoc**; register any new +mark in `tests/pytest.ini` or pytest will warn about unknown marks: + +| Mark | Meaning | +|------|---------| +| `unit` | Fast hermetic tests (no Docker stack; numpy / pure Python) — includes `tests/meta/` contract tests | +| `build_docker` | Docker image build tests | +| `build_packages` | Colcon workspace build tests | +| `integration` | Cross-component integration tests (robot container + a host-side component; no sim/GPU) | +| `liveliness` | Container and process health (Docker, tmux, sentinel ROS 2 nodes) | +| `wiring` | Observed wiring snapshot of the running ROS graph, drift-checked against a committed golden (`test_wiring_snapshot.py`) | +| `sensors` | Sim and robot sensor topic rates, LiDAR validation, sim RTF | +| `takeoff_hover_land` | End-to-end takeoff / hover / land action tests | +| `autonomy` | Fixed-pattern trajectory path-tracker benchmark (`test_fixed_trajectory.py`) | +| `waypoint_flight` | Ordered-waypoint navigation judged on the odometry track (`test_waypoint_flight.py`) | +| `optitrack` | OptiTrack NatNet end-to-end — registered for `asm_optitrack` module CI (tests live in the module repo) | ### Test ordering (set by `pytest_collection_modifyitems`) @@ -160,6 +180,8 @@ The `airstack_env` fixture is parametrized over `(sim, num_robots, iteration)` t |------|---------|---------|---------| | `--sim` | `isaacsim` | `airstack_env` | One env-tuple per sim (`msairsim` opt-in) | | `--num-robots` | `1,3` | `airstack_env` | Cross-product with sim | +| `--stack` | `None` (= `full_default`) | `airstack_env` | Stack folder under `stacks/` to launch (sets `AIRSTACK_STACK_DIR` for `airstack up`); the `wiring` test drift-checks against `stacks//wiring.md` | +| `--fleet` | `None` (legacy `--num-robots` behavior) | `airstack_env` | Fleet preset under `config/fleets/` (e.g. `sim_three_mixed`); sets `FLEET_CONFIG_FILE` and derives `NUM_ROBOTS` from the fleet's robot count, **overriding `--num-robots`** | | `--stress-iterations` | `1` | `airstack_env` | Up/down cycles per `(sim, num_robots)` | | `--stable-duration` | `120` | `system.test_liveliness::test_stable` and `system.test_sensors::test_sensor_streams_stable` | Total seconds polled | | `--stable-interval` | `10` | `system.test_liveliness::test_stable` and `system.test_sensors::test_sensor_streams_stable` | Seconds between polls | @@ -290,8 +312,9 @@ If your test... - Builds a Docker image → reuse `build_docker` - Builds a colcon workspace → reuse `build_packages` -- Verifies the running stack → `liveliness` (infra); sensor topic rates / LiDAR / RTF → `sensors` -- Drives the autonomy stack to fly → reuse `takeoff_hover_land` +- Verifies the running stack → `liveliness` (infra); sensor topic rates / LiDAR / RTF → `sensors`; ROS-graph topology drift → `wiring` +- Drives the autonomy stack to fly → reuse `takeoff_hover_land`; fixed-pattern path tracking → `autonomy`; waypoint navigation → `waypoint_flight` +- Pins a CLI/docs/stack contract with no Docker stack → a `unit`-marked contract test in `tests/meta/` - Doesn't fit any of these → **register a new mark in `tests/pytest.ini`** before using it. Update the table in `tests/README.md` and the AGENTS.md "System Test Suite" table at the same time. ### 2. File location and naming @@ -428,6 +451,7 @@ python tests/parse_metrics.py \ - `tests/conftest.py` — pytest hooks + the `airstack_env` / `robot_autonomy_stack` fixtures (re-exports the harness API) - `tests/harness/` — helpers split by concern: `session`, `discovery`, `commands`, `containers`, `metrics` (`MetricsRecorder`), `run_meta`, `test_ids`, `sim`, `collection` (ordering) +- `tests/meta/` — fast contract tests (`unit` mark) pinning CLI/docs/stack contracts - `tests/pytest.ini` — mark registration, log format - `tests/parse_metrics.py` — markdown reporter, regression diff - `tests/README.md` — user-facing docs (CLI options, output layout, CI/CD orchestrator) diff --git a/.agents/skills/test-in-simulation/SKILL.md b/.agents/skills/test-in-simulation/SKILL.md index c5882a312..fedda2194 100644 --- a/.agents/skills/test-in-simulation/SKILL.md +++ b/.agents/skills/test-in-simulation/SKILL.md @@ -1,7 +1,7 @@ --- name: test-in-simulation description: Test modules in Isaac Sim simulation environment end-to-end. Use after implementing and integrating a module to verify functionality. Covers test scenarios, monitoring, recording, and analysis of simulation tests. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack @@ -144,11 +144,10 @@ Plan specific tests for your module: ### 7. Execute Test Scenario -#### Method 1: Manual Commands via Behavior Tree GUI +#### Method 1: Manual Commands via the GCS ```bash -# If behavior tree GUI is available -# Send commands through rqt_behavior_tree_command +# Send commands from the Foxglove Robot Tasks panel (see the GCS docs) # Typical test sequence: # 1. Arm drone @@ -551,8 +550,8 @@ fi - [Isaac Sim Testing](https://docs.omniverse.nvidia.com/isaacsim/latest/index.html) - **AirStack:** - - [System Architecture](../docs/robot/autonomy/system_architecture.md) - - [Integration Checklist](../docs/robot/autonomy/integration_checklist.md) + - [System Architecture](../../../docs/robot/autonomy/system_architecture.md) + - [Integration Checklist](../../../docs/robot/autonomy/integration_checklist.md) - **Related Skills:** - [debug-module](../debug-module) - Debugging issues found in testing diff --git a/.agents/skills/update-documentation/SKILL.md b/.agents/skills/update-documentation/SKILL.md index 5144fb3d1..f08c3dd15 100644 --- a/.agents/skills/update-documentation/SKILL.md +++ b/.agents/skills/update-documentation/SKILL.md @@ -1,7 +1,7 @@ --- name: update-documentation description: Document new modules and update mkdocs navigation. Use after implementing any new feature or module. Covers README templates, mkdocs.yml updates, mermaid diagrams, and documentation standards for AirStack. -license: Apache-2.0 +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack diff --git a/.agents/skills/use-airstack-cli/SKILL.md b/.agents/skills/use-airstack-cli/SKILL.md index 564ca5324..b3f42a999 100644 --- a/.agents/skills/use-airstack-cli/SKILL.md +++ b/.agents/skills/use-airstack-cli/SKILL.md @@ -1,7 +1,7 @@ --- name: use-airstack-cli -description: Operate AirStack via the airstack CLI and run commands inside containers using the non-interactive docker exec pattern. Use whenever you need to start/stop services, build the workspace, source the workspace, run ros2 commands, or inspect logs in any AirStack container. -license: Apache-2.0 +description: Operate AirStack via the airstack CLI — up/down with launch-intent flags (--sim, --robots, --stack, --fleet, --headless, --no-autolaunch, --dry-run, --wait), module management (add/list/sync/remove/create/lock/doctor), stack and fleet commands, doctor and ready checks — and run commands inside containers using the non-interactive docker exec pattern. Use whenever you need to start/stop services, build the workspace, source the workspace, run ros2 commands, or inspect logs in any AirStack container. +license: BSD-3-Clause-Clear metadata: author: AirLab CMU repository: AirStack @@ -14,6 +14,8 @@ metadata: Use this skill any time you need to: - Start, stop, or inspect AirStack services (robot, isaac-sim, ms-airsim, gcs, docs) +- Launch a specific **stack** (`--stack`) or **fleet** (`--fleet`) +- Manage **modules** (`airstack module add|list|sync|remove|create|lock|doctor`) - Build or source the ROS 2 workspace inside a container - Run `ros2` commands (node list, topic echo/hz, param get, launch, etc.) - Tail or grep container logs @@ -21,23 +23,29 @@ Use this skill any time you need to: - Run the system test suite or build the docs site This skill is the foundation for almost every other AirStack workflow — `debug-module`, -`test-in-simulation`, `add-ros2-package`, and `integrate-module-into-layer` all rely on -the patterns described here. +`test-in-simulation`, `add-ros2-package`, `create-module`, `create-stack`, and +`integrate-module-into-layer` all rely on the patterns described here. ## Why `airstack`, Not Raw `docker compose` Always prefer `airstack ` over `docker compose ...` directly: -- Runs a **containerized** docker-compose pinned to a known version (consistent across - hosts and CI runners). -- Loads `.env`, propagates host env overrides, and applies the right include set from - the top-level `docker-compose.yaml` (isaac-sim / ms-airsim / robot / gcs / docs). -- Resolves Compose **profiles** (`desktop`, `isaac-sim`, `ms-airsim`, etc.) from - `COMPOSE_PROFILES` in `.env` automatically. +- Resolves **launch-intent flags** (`--sim`, `--robots`, `--stack`, `--fleet`, ...) + into exported env vars before compose sees anything, prints the **effective launch + config**, and saves it under `.airstack/runs//effective_config.env`. +- Runs **preflight validation** (exactly one sim profile, URDF↔sim match, + NUM_ROBOTS↔Isaac-script consistency, missing images, removed `AUTONOMY_ROLE`) + before starting anything. `AIRSTACK_SKIP_PREFLIGHT=1` downgrades errors to warnings. +- Automatically includes the **generated overlays**: module mounts + (`.airstack/generated/docker-compose.modules.yaml`, opt out with + `AIRSTACK_NO_MODULE_COMPOSE=1`) and heterogeneous-fleet services + (`.airstack/generated/docker-compose.fleet.yaml`) — for both `up` and `down`. +- Loads `.env` via `--env-file` and applies the include set from the top-level + `docker-compose.yaml`; resolves compose **profiles** from `COMPOSE_PROFILES`. - Gives partial container-name matching for `connect` and `logs`. -Drop to raw `docker` only for: `docker exec bash -c ""` (CLI does not -wrap exec), `docker logs ` for raw streams, and `docker ps` to discover +Drop to raw `docker` only for: `docker exec bash -c ""` (the CLI does +not wrap exec), `docker logs ` for raw streams, and `docker ps` to discover container names. ## Container Lifecycle @@ -48,86 +56,171 @@ container names. # Install Docker Engine + NVIDIA Container Toolkit (skip if already installed) airstack install -# Configure AirStack: add `airstack` to PATH, set up shell completion, etc. +# Configure AirStack: add `airstack` to your shell profile, git submodules, +# Isaac/Nucleus/git-hooks config, and (if modules.repos exists) a module sync airstack setup ``` -`airstack install` is only needed once per host (and only if Docker / nvidia-container-toolkit -are missing). `airstack setup` is needed once per shell user; rerun if you switch shells -(bash <-> zsh) or if `~/.airstack.conf` is missing. +### Starting services: `airstack up` and its flags -### Starting services +`airstack up` takes AirStack **intent flags** (consumed before compose sees the args) +plus optional compose service names and passthrough flags (`--build`, `--recreate`): -The most common entrypoints: +| Flag | What it does | +|------|--------------| +| `--sim isaac\|airsim` | Selects the simulator: swaps the compose profile (`isaac-sim` / `ms-airsim`) and the matching `URDF_FILE` | +| `--robots N` | Exports `NUM_ROBOTS=N`; on Isaac also keeps `ISAAC_SIM_SCRIPT_NAME` consistent (auto-selects the multi-drone script for N>1) | +| `--stack NAME[:ENTRY]` | Launches `stacks/NAME/launch/ENTRY.launch.xml` (default entry: `stack`). Stacks are the **only** launch dispatch; no `--stack` = `full_default`. `NAME:onboard` / `NAME:offboard` select split-stack entries | +| `--fleet NAME` | Launches `config/fleets/NAME.yaml`: validates it, exports `FLEET_CONFIG_FILE`, derives `NUM_ROBOTS`, selects the Isaac fleet spawner (`fleet_spawn.py`), and for heterogeneous fleets includes the generated per-robot services. Mutually exclusive with `--robots` | +| `--headless` | Sets `ISAAC_SIM_HEADLESS=true`, `MS_AIRSIM_HEADLESS=true`, `QT_QPA_PLATFORM=offscreen` | +| `--play` / `--no-play` | Whether the sim auto-presses Play on start (`PLAY_SIM_ON_START`) | +| `--no-autolaunch` | Containers start **idle** (no tmuxinator launch sequence) — the development mode | +| `--wait` | Block until the stack is flight-ready (runs `airstack ready` after up) | +| `--dry-run` | Print + validate the resolved launch config, start nothing (shadows compose's own `up --dry-run`) | ```bash -# Start the default profile from .env (typically: desktop + isaac-sim) -airstack up +airstack up # default profile from .env +airstack up --sim isaac --robots 2 # Isaac, two robots, consistent sim script +airstack up --sim airsim --headless --play --wait +airstack up --stack lite_default --sim isaac # launch a specific stack +airstack up --stack lite_offload_global:offboard # split-stack ground half +airstack up --fleet sim_three_mixed --sim isaac # fleet launch (RFC #380) +airstack up --dry-run --sim isaac # validate config, start nothing +airstack up robot-desktop # one service only +``` -# Start a specific service (matches docker-compose service name) -airstack up robot-desktop -airstack up isaac-sim -airstack up ms-airsim -airstack up gcs +**Env vars still work.** Flags only export env vars (shell env has highest compose +precedence), so `AUTOLAUNCH=false airstack up` or `NUM_ROBOTS=2 airstack up` behave +exactly like `--no-autolaunch` / `--robots 2`. Prefer the flags — they validate input +and keep derived settings (profiles, URDF, Isaac script) consistent. -# Start multiple services -airstack up isaac-sim robot-desktop -``` +**`AUTONOMY_ROLE` was removed.** A set `AUTONOMY_ROLE` (env, `--env-file`, or `.env`) +is a preflight **error**. Migration: `full` → `full_default` (the no-stack default), +`onboard` → `lite_default`, onboard/offboard split → `lite_offload_global:onboard` / +`:offboard`. -### CRITICAL: `AUTOLAUNCH=false` for development +### CRITICAL: `--no-autolaunch` for development -By default, `AUTOLAUNCH="true"` in `.env`, which means a freshly started robot or sim -container immediately runs its tmuxinator launch sequence. **For development and -debugging, you almost always want this disabled** so the container starts idle and you -can iterate on launch files, rebuild packages, and start/stop nodes by hand: +By default `AUTOLAUNCH="true"` in `.env`, so a freshly started robot or sim container +immediately runs its tmuxinator launch sequence. **For development and debugging you +almost always want it disabled** so the container starts idle and you can iterate on +launch files, rebuild packages, and start/stop nodes by hand: ```bash # Start the robot container without autolaunching the autonomy stack -AUTOLAUNCH=false airstack up robot-desktop +airstack up --no-autolaunch robot-desktop -# Combine with other overrides -AUTOLAUNCH=false NUM_ROBOTS=2 airstack up robot-desktop isaac-sim +# Combine with other flags +airstack up --no-autolaunch --robots 2 --sim isaac ``` -Any variable defined in `.env` can be overridden this way (the wrapper exports each -`.env` key into the compose container). Common ones for agents: - -| Variable | What it controls | -|------------------------------|-----------------------------------------------------------| -| `AUTOLAUNCH` | Whether the container auto-runs the launch sequence | -| `NUM_ROBOTS` | How many robot containers spawn | -| `ROBOT_NAME` | Namespace prefix for ROS topics | -| `VERSION` | Docker image tag to use | -| `COMPOSE_PROFILES` | Which compose profiles are active | -| `ISAAC_SIM_USE_STANDALONE` | Run Isaac Sim as a standalone Python script | -| `ISAAC_SIM_SCRIPT_NAME` | Which Isaac Sim launch script to run | +(The legacy form `AUTOLAUNCH=false airstack up robot-desktop` still works.) -### Inspecting and stopping +### Waiting for readiness ```bash -# Show all running containers (with airstack container names) -airstack status +airstack ready # containers → sim /clock → autonomy nodes → PX4; blocks until green +airstack ready --json # machine-readable, for scripts +``` -# Tail logs for a single container (partial name matching works) -airstack logs robot-desktop -airstack logs isaac-sim +Or pass `--wait` to `airstack up` to run it automatically. -# Stop and remove containers (clean slate) -airstack down -airstack down robot-desktop +### Inspecting and stopping -# Stop, remove containers, and prune volumes/networks -airstack clean +```bash +airstack status # all containers with ROBOT_NAME + ROS_DOMAIN_ID columns +airstack logs robot-desktop # tail logs (partial name matching) +airstack down # stop everything (includes generated module/fleet services) +airstack down robot-desktop # stop one service +airstack clean # remove ALL ROS 2 build artifacts (build/, install/, log/, .egg-info, __pycache__) ``` +Note: `airstack clean` deletes host-side colcon build artifacts (forcing a full +rebuild on next up) — it does **not** touch containers, volumes, or networks. + ### Container naming convention Compose generates names of the form `--`. With the default `PROJECT_NAME="airstack"`: `airstack-robot-desktop-1`, `airstack-isaac-sim-1`, -`airstack-ms-airsim-1`, `airstack-gcs-1`, `airstack-docs-1`. With `NUM_ROBOTS=2` you +`airstack-ms-airsim-1`, `airstack-gcs-1`, `airstack-docs-1`. With `--robots 2` you also get `airstack-robot-desktop-2`. Always confirm with `airstack status` or `docker ps --format '{{.Names}}'` rather than guessing. +## Modules, Stacks, Fleets, Doctor (RFC #379/#380) + +### Modules + +Modules are thin external repos declared in `./modules.repos` (pinned to tags/SHAs — +branch refs are refused), synced into the gitignored `./modules/` dir and overlaid +into the checkout. Guide: `docs/development/modules.md`. + +```bash +airstack module add --version # pin + sync (branches refused) +airstack module add ../asm_optitrack # local path (recorded under x-local-modules) +airstack module add --version v0.1.0 --no-hooks # skip host_setup hooks +airstack module list # NAME / TYPE / VERSION/PIN / TARGETS / VALID +airstack module sync [--no-hooks] # (re)clone, validate, overlay, layer plan, hooks +airstack module remove # drop entry, checkout, overlay artifacts +airstack module create --in-tree # scaffold robot/ros_ws/src/modules// (fork research) +airstack module lock [--build] [--check-conflicts] # recompute layer_plan.json + modules.lock; --build runs the docker layer chain +airstack module doctor # validate manifests + overlay integrity +airstack module doctor --drift # classify fork changes: module-contained vs extraction debt (never blocks) +``` + +After sync, module mounts are included automatically by `airstack up` +(`.airstack/generated/docker-compose.modules.yaml`; opt out with +`AIRSTACK_NO_MODULE_COMPOSE=1`). Isaac module launch scripts are addressable as +`ISAAC_SIM_SCRIPT_NAME=modules//