Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agents/skills/add-task-executor/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,9 @@ int main(int argc, char* argv[]) {

Only switch to `MultiThreadedExecutor` if you have independent callbacks that genuinely need concurrent execution **and** all shared resources are thread-safe. Nodes that use OpenGL, CUDA, or other thread-affine resources **must** use `rclcpp::spin()` to keep callbacks serialized.

### 3. Add remap to bringup launch
### 3. Add remap in the stack entry file

In the layer bringup launch file (e.g., `global_bringup/launch/global.launch.xml`):
In the module's canonical launch file, declare the action endpoint as a topic arg with the canonical default; any deviation is wired in the stack entry file (e.g., `stacks/full_default/launch/stack.launch.xml` — the single-locus rule):
```xml
<remap from="~/your_task" to="/$(env ROBOT_NAME)/tasks/your_task_name" />
```
Expand Down
23 changes: 11 additions & 12 deletions .agents/skills/configure-multi-robot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ robot-desktop:
replicas: ${NUM_ROBOTS:-1}
```

So `NUM_ROBOTS=3 airstack up` produces **three** robot containers (`airstack-robot-desktop-1`, `-2`, `-3`), each with its own `ROBOT_NAME` and its own `ROS_DOMAIN_ID`. Each container runs the full autonomy stack independently. Cross-robot communication, when needed, goes through the DDS router (see [`onboard_all/config/dds_router.yaml`](../../../robot/ros_ws/src/autonomy_bringup/onboard_all/config/dds_router.yaml)) which bridges allowlisted topics from each per-robot domain into a shared GCS domain.
So `NUM_ROBOTS=3 airstack up` produces **three** robot containers (`airstack-robot-desktop-1`, `-2`, `-3`), each with its own `ROBOT_NAME` and its own `ROS_DOMAIN_ID`. Each container runs the full autonomy stack independently. Cross-robot communication, when needed, goes through the DDS router (see the shared allowlist [`autonomy_bringup/config/dds_router.yaml`](../../../robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml)) which bridges allowlisted topics from each per-robot domain into a shared GCS domain.

```bash
airstack up --sim isaac --robots 3 # sets NUM_ROBOTS and the multi-drone Isaac script together
Expand All @@ -203,18 +203,17 @@ docker ps --format '{{.Names}}' | grep robot-desktop

The simulator side has to spawn matching vehicles — see [Sim-Side Robot Spawning](#sim-side-robot-spawning).

### `onboard_all` vs `onboard_local_offboard_global` (legacy roles)
### Full vs. lite vs. split topologies (stacks)

The stack-shaped successors: `--stack full_default` / `--stack lite_default` replace the role values, `--stack lite_offload_global:onboard|:offboard` replaces the split pair, and a fleet entry's `hosts: {offboard: <ground>}` replaces choosing the split *placement* by hand (see Fleet-First above). The role dispatch below still works without a stack/fleet.
Topology is selected by **stack** — the legacy `AUTONOMY_ROLE` role dispatch was removed (a set `AUTONOMY_ROLE` is now a preflight error): `--stack full_default` (the no-stack default) runs everything on the machine, `--stack lite_default` runs the lite set, `--stack lite_offload_global:onboard|:offboard` is the split pair, and a fleet entry's `hosts: {offboard: <ground>}` declares the split *placement* (see Fleet-First above).

[`autonomy_bringup`](../../../robot/ros_ws/src/autonomy_bringup/) ships two layouts, selected by the `role` arg / `AUTONOMY_ROLE` env var:
| Stack | What runs onboard | What runs offboard | When to use |
|-------|-------------------|--------------------|-------------|
| `full_default` | interface, sensors, perception, local, **global**, behavior, logging | nothing | Sim/dev desktop, autonomous Jetson with enough compute, single-machine deployments |
| `lite_default` | interface, sensors, perception, local, behavior | nothing (no global anywhere) | Compute-constrained vehicle flying task-driven missions |
| `lite_offload_global` (`:onboard` + `:offboard`) | interface, sensors, perception, local, behavior | global planning + mapping | VOXL / lite Jetson where global planning is offloaded to a ground station; `desktop_split` profile for debugging the split |

| Variant | Role values | What runs onboard | What runs offboard | When to use |
|--------|-------------|-------------------|--------------------|-------------|
| `onboard_all` | `role:=full` | interface, sensors, perception, local, **global**, behavior | nothing | Sim/dev desktop, autonomous Jetson with enough compute, single-machine deployments |
| `onboard_local_offboard_global` | `role:=onboard` (lite) + `role:=offboard` (GCS) | interface, sensors, perception, local, behavior | global planning + mapping | VOXL / lite Jetson where global planning is offloaded to a ground station; `desktop_split` profile for debugging the split |

The split is significant for multi-robot: with `onboard_local_offboard_global`, **one offboard container is launched per robot** (also via `replicas: ${NUM_ROBOTS}`), all on `ROS_DOMAIN_ID=0`, and each bridges into its own per-robot onboard domain via the domain bridge config in `onboard_local_offboard_global/config/dds_router.yaml`. See [`docs/robot/autonomy_modes.md`](../../../docs/robot/autonomy_modes.md) for the profile matrix.
The split is significant for multi-robot: with `lite_offload_global`, **one offboard container is launched per robot** (also via `replicas: ${NUM_ROBOTS}`), all on `ROS_DOMAIN_ID=0`, and each bridges into its own per-robot onboard domain via the DDS-router config generated from the stack's `bridge.yaml` (`python3 tools/gen_dds_router.py stacks/lite_offload_global/bridge.yaml` — the generated allowlist deliberately drops the legacy split's `set_trajectory_mode` crossing, doctor hard gate #2). See [`docs/robot/autonomy_modes.md`](../../../docs/robot/autonomy_modes.md) for the profile matrix.

## Topic and TF Namespacing

Expand Down Expand Up @@ -449,7 +448,7 @@ Before merging a change that touches anything robot-namespaced:
- [ ] If you added a new module to a layer bringup, you tested it with `NUM_ROBOTS=2` and confirmed both robots' namespaces look identical under `ros2 node list`
- [ ] If you added a sim launch script, it reads `NUM_ROBOTS` and spawns vehicles named `robot_1`, `robot_2`, … with matching `vehicle_id` / `domain_id`
- [ ] If you added a system test that addresses a robot, it loops over `range(1, num_robots + 1)` and uses `domain_id=n` in `ros2_exec(...)`
- [ ] DDS router allowlists in `onboard_all/config/dds_router.yaml` (or the split equivalent) include any new cross-domain topic your module exposes — otherwise it will not appear on the GCS
- [ ] DDS router allowlists in `autonomy_bringup/config/dds_router.yaml` (or the split stack's `bridge.yaml`) include any new cross-domain topic your module exposes — otherwise it will not appear on the GCS
- [ ] Verified end-to-end: `NUM_ROBOTS=3 airstack up`, then `docker exec airstack-robot-desktop-2 bash -c 'ros2 topic list | grep robot_2'` shows the same topics that `airstack-robot-desktop-1` shows under `robot_1`

## Verification Commands
Expand Down Expand Up @@ -486,7 +485,7 @@ docker exec -e ROS_DOMAIN_ID=1 airstack-robot-desktop-1 bash -c \
- [`docs/robot/autonomy_modes.md`](../../../docs/robot/autonomy_modes.md) — profile matrix (`desktop`, `desktop_split`, `voxl`, `l4t`, `offboard`)
- [`robot/docker/robot_name_map/`](../../../robot/docker/robot_name_map/) — mapping YAMLs and `resolve_robot_name.py`
- [`robot/ros_ws/src/autonomy_bringup/launch/robot.launch.xml`](../../../robot/ros_ws/src/autonomy_bringup/launch/robot.launch.xml) — top-level `push_ros_namespace`
- [`robot/ros_ws/src/autonomy_bringup/onboard_all/config/dds_router.yaml`](../../../robot/ros_ws/src/autonomy_bringup/onboard_all/config/dds_router.yaml) — cross-domain allowlist pattern
- [`robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml`](../../../robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml) — cross-domain allowlist pattern
- [`simulation/ms-airsim/config/generate_settings.py`](../../../simulation/ms-airsim/config/generate_settings.py) and [`settings.json.j2`](../../../simulation/ms-airsim/config/settings.json.j2)
- [`simulation/isaac-sim/launch_scripts/example_multi_px4_pegasus_launch_script.py`](../../../simulation/isaac-sim/launch_scripts/example_multi_px4_pegasus_launch_script.py)
- [`tests/conftest.py`](../../../tests/conftest.py) — `airstack_env` fixture and `--num-robots` parametrization
Expand Down
14 changes: 7 additions & 7 deletions .agents/skills/integrate-module-into-layer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ integrate it into a **stack** — the self-contained folder under `stacks/`
whose entry launch file is the single wiring document for a running topology
(RFC #379 §3–4). This replaces the legacy layer-bringup workflow.

> **The layer-bringup workflow is LEGACY.** Editing
> `local_bringup/launch/*.launch.xml` (or any `*_bringup` launch file) to add
> modules is the old monolith pattern. Those files are frozen: the launch
> lint (`tests/meta/test_launch_single_locus.py`) forbids new `<remap>`s
> outside `stacks/*/launch/`, and the grandfather allowlist
> (`tests/meta/launch_lint_allowlist.txt`) only shrinks. Integrate into a
> stack instead.
> **The layer-bringup workflow is GONE.** The legacy layer bringup launch
> files (`local/perception/sensors/global/behavior *.launch.xml`) were
> deleted along with the AUTONOMY_ROLE dispatch — there is nothing left to
> edit there. The launch lint (`tests/meta/test_launch_single_locus.py`)
> forbids `<remap>`s outside `stacks/*/launch/`, and the grandfather
> allowlist (`tests/meta/launch_lint_allowlist.txt`) only shrinks. Integrate
> into a stack.

## The model (read this first)

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/update-documentation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ feat: Add YourModule local planner

- Implement algorithm based on XYZ paper
- Add configuration and launch files
- Integrate into local_bringup
- Integrate into the stack entry file (stacks/full_default)
- Add comprehensive README documentation
- Update mkdocs navigation
```
Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/visualize-in-foxglove/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ running in the GCS container.
Robot container (domain: ROS_DOMAIN_ID)
└─ publishes topics

DDS Router (onboard_all)
DDS Router (shared allowlist)
└─ bridges allowlisted topics to GCS domain

GCS container (domain: 0)
Expand All @@ -35,7 +35,7 @@ in the GCS before it will appear in Foxglove. Missing either step = nothing show

## Step 1 — Bridge the Topic in DDS Router

**File:** `robot/ros_ws/src/autonomy_bringup/onboard_all/config/dds_router.yaml`
**File:** `robot/ros_ws/src/autonomy_bringup/config/dds_router.yaml`

Add an entry to the `allowlist` for every topic you want on the GCS:

Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/write-launch-file/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ A stack folder (`stacks/<name>/`) is the unit of topology — see [docs/developm

```xml
<!-- Local planner: disparity from MAC-VO instead of stereo -->
<include file="$(find-pkg-share local_bringup)/launch/local.launch.xml">
<arg name="local_disparity_in_topic"
<include file="$(find-pkg-share droan_gl)/launch/droan_gl.launch.xml">
<arg name="droan_gl_disparity_topic"
value="/$(env ROBOT_NAME)/perception/macvo/disparity" />
</include>
```
Expand Down Expand Up @@ -189,7 +189,7 @@ If `ros2 node info` shows a node subscribing to `/odometry` instead of `/<robot_

## References

- **Stacks:** [docs/development/stacks.md](../../../docs/development/stacks.md) — anatomy, wrap-vs-flatten status, wiring.md generation, AUTONOMY_ROLE deprecation
- **Stacks:** [docs/development/stacks.md](../../../docs/development/stacks.md) — anatomy, wiring.md generation, the AUTONOMY_ROLE removal/migration table
- **Canonical topic names:** [docs/robot/autonomy/integration_checklist.md](../../../docs/robot/autonomy/integration_checklist.md)
- **Reference stack launch files:** `stacks/full_default/launch/stack.launch.xml`, `stacks/full_droan_cpu/`, `stacks/full_macvo/`
- **Lint:** `tests/meta/test_launch_single_locus.py` + `tests/meta/launch_lint_allowlist.txt`
Expand Down
14 changes: 13 additions & 1 deletion .airstack/modules/module.sh
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,19 @@ function cmd_module_remove {
if [ -L "$MODULE_CHECKOUT_DIR/$name" ]; then
rm "$MODULE_CHECKOUT_DIR/$name"
elif [ -d "$MODULE_CHECKOUT_DIR/$name" ]; then
rm -rf "$MODULE_CHECKOUT_DIR/$name"
# Containers drop root-owned artifacts (__pycache__, build debris)
# into mounted module checkouts; a plain rm then fails and leaves a
# partial dir that breaks the next `vcs import`. Retry via a
# throwaway container when that happens.
if ! rm -rf "$MODULE_CHECKOUT_DIR/$name" 2>/dev/null; then
log_warn "checkout has root-owned files (created in-container) — removing via docker..."
if ! docker run --rm -v "$MODULE_CHECKOUT_DIR:/m" ubuntu:24.04 \
bash -c "rm -rf /m/$name"; then
log_error "could not remove modules/$name — remove it manually, e.g.:"
log_error " docker run --rm -v \"$MODULE_CHECKOUT_DIR:/m\" ubuntu:24.04 rm -rf /m/$name"
return 1
fi
fi
fi

# regenerate the overlay without it (also prunes links + compose entries)
Expand Down
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ PROJECT_NAME="airstack"
# If you've run ./airstack.sh setup, then this will auto-generate from the git commit hash every time a change is made
# to a Dockerfile or docker-compose.yaml file. Otherwise this can also be set explicitly to make a release version.
# auto-generated from git commit hash
VERSION="0.20.0-alpha.6"
VERSION="0.20.0-alpha.7"
# Choose "dev" or "prebuilt". "dev" is for mounted code that must be built live. "prebuilt" is for built ros_ws baked into the image
DOCKER_IMAGE_BUILD_MODE="dev"
# Where to push and pull images from. Can replace with your docker hub username if using docker hub.
Expand Down
42 changes: 40 additions & 2 deletions .github/workflows/deploy_docs_from_develop.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
name: Build/Publish Develop Docs
name: Build/Publish Develop Docs
on:
push:
paths:
- "docs/**"
- "mkdocs.yml"
- "*.md"
- "stacks/**"
- "tools/gen_docs_catalog.py"
- ".github/workflows/deploy_docs_from_develop.yaml"
branches:
- develop
# Module-docs freshness (RFC #379 §9): the catalog is regenerated from the
# live registry on every deploy, so a registry change only reaches the site
# when a deploy runs. Until registry-driven repository_dispatch lands
# ("within a day, exact at releases"), a weekly rebuild plus manual dispatch
# keeps the develop catalog from going stale.
workflow_dispatch:
schedule:
- cron: "17 6 * * 1" # weekly, Mondays 06:17 UTC
permissions:
contents: write
jobs:
Expand All @@ -16,14 +26,42 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
# schedule/workflow_dispatch run on the default branch; this
# workflow always publishes the develop docs.
ref: develop
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: 3.10.6
- name: Install Dependencies
run: |
pip install mkdocs-material mkdocs-same-dir mkdocs-redirects
pip install mkdocs-material mkdocs-same-dir mkdocs-redirects pyyaml
pip install pillow cairosvg mike
# RFC #379 §9: module docs ride the docs deploy. Shallow-clone the
# registry index and each REGISTERED module repo at its registered_ref
# into the gitignored modules/ dir, then regenerate docs/modules/ so
# the published catalog is fresh even when the committed pages lag.
# FAILURE ISOLATION: nothing in this step may fail the deploy — an
# unreachable registry or module repo degrades to the committed pages /
# a stub note on the module's page.
- name: Fetch registry index and registered module repos
run: |
rm -rf .modules-index modules
git clone --depth 1 https://github.com/castacks/airstack-modules-index .modules-index \
|| echo "skipped: registry index unreachable (committed catalog pages will be served)"
if [ -d .modules-index/modules ]; then
mkdir -p modules
python3 tools/gen_docs_catalog.py --index .modules-index --list-refs |
while IFS=$'\t' read -r name repo ref; do
( git init -q "modules/$name" \
&& git -C "modules/$name" remote add origin "$repo" \
&& git -C "modules/$name" fetch -q --depth 1 origin "$ref" \
&& git -C "modules/$name" checkout -q FETCH_HEAD ) \
|| { rm -rf "modules/$name"; echo "skipped: module $name ($repo @ $ref) unreachable — its page keeps the stub note"; }
done
python3 tools/gen_docs_catalog.py --index .modules-index --modules-dir modules \
|| echo "skipped: catalog regeneration failed (committed pages will be served)"
fi
- name: Setup Docs Deploy
run: |
git config --global user.name "Docs Deploy"
Expand Down
31 changes: 29 additions & 2 deletions .github/workflows/deploy_docs_from_main.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
name: Build/Publish Main Docs
name: Build/Publish Main Docs
on:
push:
paths:
- "docs/**"
- "mkdocs.yml"
- "*.md"
- "stacks/**"
- "tools/gen_docs_catalog.py"
- ".github/workflows/deploy_docs_from_main.yaml"
branches:
- main
Expand All @@ -22,8 +24,33 @@ jobs:
python-version: 3.10.6
- name: Install Dependencies
run: |
pip install mkdocs-material mkdocs-same-dir mkdocs-redirects
pip install mkdocs-material mkdocs-same-dir mkdocs-redirects pyyaml
pip install pillow cairosvg mike
# RFC #379 §9: module docs ride the docs deploy. Shallow-clone the
# registry index and each REGISTERED module repo at its registered_ref
# into the gitignored modules/ dir, then regenerate docs/modules/ so
# the published catalog is fresh even when the committed pages lag.
# FAILURE ISOLATION: nothing in this step may fail the deploy — an
# unreachable registry or module repo degrades to the committed pages /
# a stub note on the module's page.
- name: Fetch registry index and registered module repos
run: |
rm -rf .modules-index modules
git clone --depth 1 https://github.com/castacks/airstack-modules-index .modules-index \
|| echo "skipped: registry index unreachable (committed catalog pages will be served)"
if [ -d .modules-index/modules ]; then
mkdir -p modules
python3 tools/gen_docs_catalog.py --index .modules-index --list-refs |
while IFS=$'\t' read -r name repo ref; do
( git init -q "modules/$name" \
&& git -C "modules/$name" remote add origin "$repo" \
&& git -C "modules/$name" fetch -q --depth 1 origin "$ref" \
&& git -C "modules/$name" checkout -q FETCH_HEAD ) \
|| { rm -rf "modules/$name"; echo "skipped: module $name ($repo @ $ref) unreachable — its page keeps the stub note"; }
done
python3 tools/gen_docs_catalog.py --index .modules-index --modules-dir modules \
|| echo "skipped: catalog regeneration failed (committed pages will be served)"
fi
- name: Setup Docs Deploy
run: |
git config --global user.name "Docs Deploy"
Expand Down
Loading
Loading