Skip to content

Pre-RFC workflow cleanup: intent-based launch, readiness gates, launch-script dedup, truthful logs - #386

Merged
andrewjong merged 6 commits into
developfrom
feature/pre-rfc-workflow-cleanup
Aug 20, 2026
Merged

andrewjong merged 6 commits into
developfrom
feature/pre-rfc-workflow-cleanup

Conversation

@andrewjong

Copy link
Copy Markdown
Member

Motivation

A design session (2026-08-19/20) audited the simulation launch workflow and found heavy friction: six Isaac launch scripts that are 80–90% copy-pasted boilerplate (already drifted — livestream only in the single-drone scripts, ISAAC_SIM_HEADLESS only in the multi scripts, the documented template crashing on a missing import); launching requires memorizing coordinated env-var sets (COMPOSE_PROFILES + URDF_FILE + ISAAC_SIM_SCRIPT_NAME, with NUM_ROBOTS>1 + the single-drone script silently giving N containers and 1 drone); guards that sed .env only and are bypassed by --env-file; no readiness signal (up "succeeds" before builds/sim/PX4); docker logs empty by construction (everything lives in unpiped tmux panes); and ~12 confirmed-wrong statements in the docs.

RFCs #379/#380 define the long-term overhaul. This PR is scoped to everything pre-RFC or tangential: it cleans up the workflow now and shrinks the RFC work later, while staying forward-compatible with #380's config discipline (CLI flags set leaf values only and never define structure; every run dumps its resolved config — the precursor of effective_config.yaml). No RFC-specified design (stacks, fleet files, vehicle configs, module system) is implemented here.

What changed

  • refactor(isaac): launch scripts deduplicated onto pegasus_app.PegasusApp — the six scripts become scenario declarations (1,415 → 536 lines + one documented 443-line base; net −438). Fixes by construction: ISAAC_SIM_HEADLESS/ISAAC_SIM_LIVESTREAM now work in every script, barebones_pegasus_launch.py (the documented template) no longer crashes with NameError, and the single-drone NatNet script's documented NATNET_BODY_NAME/NATNET_TARGET_NAME env overrides actually work. example_multi_drone_scene_import.py keeps its historical (divergent) ZED offset explicitly and annotated.
  • feat(cli): intent flags on airstack up — --sim isaac|airsim, --robots N (also auto-selects the single/multi Isaac script, incl. the natnet pair), --headless, --play/--no-play, --no-autolaunch, --wait, --dry-run. Flags export leaf env values (compose gives shell env precedence), print a resolved-config banner, and dump .airstack/runs/<ts>/effective_config.env.
  • feat(cli): preflight on resolved config — closes the --env-file guard bypass; NUM_ROBOTS>1 + single-drone script is now a named hard error; missing images listed by name with an image-pull hint before compose starts an implicit multi-GB build; missing omni_pass.env / empty Pegasus submodule / Docker < 29 surfaced on the host. AIRSTACK_SKIP_PREFLIGHT=1 downgrades to warnings.
  • feat(cli): airstack ready / up --wait — staged flight-readiness gates mirroring the system-test budgets (containers → sim /clock → per-robot sentinel nodes → MAVROS connected → local_position/odom streaming, the EKF-converged armable signal); per-gate diagnostics name the container/tmux window to inspect; --json for scripts.
  • feat(docker): tmux → docker logs teeing — hooks in the shared .tmux.conf (mounted into robot/gcs/isaac-sim/ms-airsim) pipe every pane to container stdout, so airstack logs finally shows colcon builds, launch output, sim loading, and crashes.
  • docs + skills — ~12 wrong statements fixed (nonexistent ISAAC_SIM_SCENE, wrong defaults tables, paused-by-default sim vs "auto-plays", RViz vs Foxglove, MAVROS port formula, airstack stop/build non-commands); spawning_drones.md now documents PegasusApp authoring (import contract, kwargs, drone-config dict, hooks); docker_usage.md gains a launch-flags/readiness reference; the write-isaac-sim-scene skill was re-taught from scratch (its skeleton API had drifted to non-runnable) and five other skills' stale launch guidance corrected.
  • chore(release): VERSION 0.19.0-alpha.17 → 0.19.0-alpha.18 + CHANGELOG. Image inputs are unchanged (all edits bind-mounted or host-side), so docker-build should registry-retag.

Backward compatible: no env var renamed or removed; flagless airstack up behaves as before (plus banner + preflight); ISAAC_SIM_SCRIPT_NAME still selects scripts.

Validation

Baseline captured on clean develop (9e2e0e39) before any change, then re-run on the branch — full detail in the local feature notebook (notebook/001-pre-rfc-workflow-cleanup/).

Suite Baseline Branch
unit 193 passed 207 passed (+14 new CLI contract tests), 1 skipped
liveliness or sensors (isaacsim, 1 robot) 16 passed 16 passed
takeoff_hover_land (isaacsim, v1.0) 8 passed 8 passed

tests/parse_metrics.py diff: all pass rates 100% → 100%; flight quality equal or better:

Metric (rob#1, v1.0) Baseline Branch
takeoff altitude error −0.151 m −0.164 m
takeoff velocity RMSE 0.227 m/s 0.228 m/s
hover altitude mean error 0.036 m 0.021 m
hover position stddev 0.075 m 0.061 m
landing final altitude 0.052 m −0.001 m
sim realtime factor 1.276 1.301

Flagged deltas judged not regressions: liveliness sim_ready 20 s → 77 s is a baseline outlier (the baseline's own sensors campaign measured 79.6 s for the same gate; branch 73.5 s, −7.6%; 3 of 4 measurements cluster 73–80 s); airstack_up_duration_s +0.2 s absolute is the intended preflight cost; net-io/GPU increases track the branch runs stuttering less (topic min-rates 14–24 Hz → 40+ Hz).

Live end-to-end (new CLI path, new scripts, zero .env edits):

$ airstack up --sim isaac --play --wait
  ✓ robot containers running (0s)
  ✓ sim publishing /clock (77s)
  ✓ robot_1 (domain 1): autonomy nodes up (0s)
  ✓ robot_1: MAVROS connected to PX4 (12s)
  ✓ robot_1: PX4 EKF ready (armable) (2s)
[INFO] Stack is flight-ready (92s).

Also verified: ready --json (8 s on a ready stack, exit 0; exit 1 + actionable message on idle/empty stacks); docker logs now carries 500+ lines of real output per container during bring-up (was ~0); mkdocs build clean; dry-run matrix transcript for every flag/guard combination.

Not covered: ms-airsim was not system-tested locally (no UE4 image on the dev machine; the CLI derivation for it is contract-tested). Worth one comment-triggered run here: /pytest -m liveliness --sim msairsim.

Per-section verdicts

Section Verdict
Launch-script dedup ✅ full system-test parity, −438 lines, 3 latent bugs fixed
CLI intent flags ✅ 14 contract tests + live bring-up
Preflight ✅ guard bypass closed; footguns are named errors
Readiness ✅ 92 s to flight-ready, staged progress, JSON mode
Log teeing ✅ docker logs truthful for all services
Docs & skills ✅ drift fixed + PegasusApp authoring documented

🤖 Generated with Claude Code

andrewjong and others added 6 commits August 20, 2026 04:38
The six launch scripts were 80-90% copy-pasted boilerplate (extension
enabling, wait_for_stage, scene prep, spawn calls, run loop) that had
already drifted: livestream existed only in the *_one_* scripts (so the
isaac-sim-livestream service silently black-screened with multi scripts),
ISAAC_SIM_HEADLESS was honored only by the *_multi_* scripts, and
barebones_pegasus_launch.py (the documented template) crashed with a
NameError (os never imported).

pegasus_app.py now owns the skeleton once: create_simulation_app()
(livestream + headless env handling, uniform across all scripts),
extension enabling, world/env loading, scene prep, drone/sensor spawning
from config dicts, and the run loop. Scripts reduce to scenario
declarations plus hooks (pre_scene_prep/post_scene_prep/post_spawn).

Behavior preserved per script (spawn poses, prim/node names, sensor
offsets, NatNet bodies, GPS origins), with three deliberate fixes:
- ISAAC_SIM_HEADLESS and ISAAC_SIM_LIVESTREAM now work in every script
- barebones template runs again
- NATNET_BODY_NAME/NATNET_TARGET_NAME env overrides now work as the
  one-drone natnet script's docstring already claimed

example_multi_drone_scene_import keeps its historical ZED offset
[0.21, 0, 0.05] (drift vs the canonical [0.2, 0, -0.05] — now visible
and annotated instead of buried).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ack ready'

airstack up learns intent flags that derive the coordinated env-var sets
users previously had to know by heart (they export leaf values only —
compose interpolation gives shell env precedence, so .env is untouched):

  --sim isaac|airsim   swap simulator profile + matching URDF
  --robots N           NUM_ROBOTS + auto-select one/multi Isaac script
                       (also natnet pair; warns on custom scripts)
  --headless           ISAAC_SIM_HEADLESS + MS_AIRSIM_HEADLESS + QT offscreen
  --play/--no-play     PLAY_SIM_ON_START
  --no-autolaunch      AUTOLAUNCH=false
  --wait               chain into 'airstack ready' after compose up
  --dry-run            print + validate the resolved config, start nothing

Every up prints the resolved launch config and dumps it to
.airstack/runs/<ts>/effective_config.env (gitignored; best-effort on
read-only checkouts).

Preflight now validates RESOLVED values (env > --env-file > .env),
fixing the historical guard bypass where 'up --env-file overrides/...'
was checked against .env only. New checks: NUM_ROBOTS>1 with the
single-drone Isaac script (previously a silent 3-containers-1-drone
failure) is a hard error; missing images are listed by name with an
image-pull hint before compose starts a multi-GB implicit build; missing
omni_pass.env / empty Pegasus submodule / docker<29 name-resolution are
surfaced on the host instead of dying invisibly inside tmux.
AIRSTACK_SKIP_PREFLIGHT=1 downgrades errors to warnings.

'airstack ready' (and 'up --wait') answers "can I press Takeoff yet?":
staged gates mirroring the system-test budgets — containers (120s) →
sim /clock (600s) → per-robot sentinel nodes (300s) → PX4 MAVROS
connected + local_position/odom streaming (300s, the EKF-converged
armable signal; connected alone fires ~25s early). --json for scripts;
per-gate failures name the container/tmux window to inspect.

tests/meta/test_launch_intent_contract.py pins the flag derivations,
guard behavior, and exit codes (runs under the unit mark).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every service runs its real workload inside tmux, so 'docker logs' /
'airstack logs' were empty by construction — colcon build failures,
Pegasus import errors, and scene downloads all landed in panes nobody
attaches to. tmux hooks in the shared .tmux.conf (mounted into robot,
gcs, isaac-sim, and ms-airsim containers) now pipe-pane every created
session/window/split to /proc/1/fd/1, making container logs truthful.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corrects statements the audit found wrong, and teaches the new flags:
- getting_started: sim comes up PAUSED by default (PLAY_SIM_ON_START=false
  in .env, docs claimed auto-play), operator UI is Foxglove not RViz
  (DEBUG_RVIZ=false by default), adds 'airstack ready' / --wait and
  --sim/--robots variants
- simulation index + isaac docker.md + key_concepts + docker_usage:
  ISAAC_SIM_SCENE does not exist — scene selection is
  ISAAC_SIM_SCRIPT_NAME (standalone) or ISAAC_SIM_GUI (USD path, non-
  standalone); defaults table now matches .env/compose (AUTOLAUNCH=true,
  PLAY_SIM_ON_START=false, ISAAC_SIM_USE_STANDALONE=true, 100 Hz physics)
- simulation index: NUM_ROBOTS=3 alone does NOT put 3 drones in Isaac —
  documents --robots (auto script switch) and the preflight guard
- docker_usage: the test service is robot-test, not autotest
- gcs user_interface: gcs service is not in the deploy profile (gcs-real is)
- ms-airsim: MAVROS connects on 14540+domain (24540+i is AirSim's own PX4
  channel), camera FOV default is 90 not 110, vehicles are robot_<i> not
  drone<i>
- AGENTS.md: airstack stop/build are not registered commands (down /
  image-build); documents the new up flags and ready
- .env: correct usage comment; PLAY_SIM_ON_START paused-by-default note

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Image inputs are unchanged (all edits are bind-mounted or host-side), so
docker-build should registry-retag rather than rebuild on merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e skills

spawning_drones.md now documents the pegasus_app.PegasusApp base class as
the way to write a launch script: import-order contract, constructor
kwargs, the drone-config dict (incl. prim/node_name/sensor overrides),
hooks (pre_scene_prep/post_scene_prep/post_spawn), and which reference
subclass to study for scene-import and NatNet scenarios.
pegasus_scene_setup.md points at it and drops the false 'PLAY_SIM_ON_START
not supported in standalone mode' claim. docker_usage.md gains a 'Launch
flags and readiness' section (--sim/--robots/--headless/--play/--wait/
--dry-run, effective-config dumps, airstack ready).

The write-isaac-sim-scene skill was re-taught from scratch: it prescribed
copy-pasting a ~240-line skeleton whose API had drifted to non-runnable
(wrong add_zed_stereo_camera_subgraph signature, nonexistent
SIMULATION_ENVIRONMENTS keys, low-level Multirotor API no shipped script
uses). It now teaches scenario declaration on PegasusApp with an explicit
'do not copy-paste' rule. Other skills fixed where the old guidance became
wrong or footgun-inducing: integrate-module-into-layer ('airstack stop' is
not a command), test-in-simulation and configure-multi-robot (bare
NUM_ROBOTS=N up now fails preflight with the single-drone script — use
--robots), use-airstack-cli (new flags + ready in the reference),
optitrack-development (single-drone NatNet body names are env-overridable
now).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andrewjong

Copy link
Copy Markdown
Member Author

/pytest -m liveliness --sim msairsim

@github-actions

Copy link
Copy Markdown
Contributor

Running pytest tests/ -m 'build_packages or liveliness' --sim msairsim — view run. Status will appear as a check on this PR.

Note: build_packages is automatically prepended whenever any marks are specified, to ensure code is built before launch tests run.

@github-actions

Copy link
Copy Markdown
Contributor

Test Metrics — 3a1b954ca39943bfb964240a5972abafa625d982

No metrics report generated.

@github-actions

Copy link
Copy Markdown
Contributor

Test Metrics — e4b499d120ef5157232c6ef6b1109488a94c9641

system

Pass rates

Test Pass Fail Skip Rate
TestColconBuilds.test_colcon_build_gcs 1 0 0 100%
TestColconBuilds.test_colcon_build_ms_airsim 1 0 0 100%
TestColconBuilds.test_colcon_build_robot 1 0 0 100%
TestColconBuilds.test_colcon_test_robot 1 0 0 100%
TestLiveliness.test_compute_usage[msairsim-rob#1] 0 0 1 —
TestLiveliness.test_compute_usage[msairsim-rob#3] 0 0 1 —
TestLiveliness.test_gcs_container_running[msairsim-rob#1] 1 0 0 100%
TestLiveliness.test_gcs_container_running[msairsim-rob#3] 1 0 0 100%
TestLiveliness.test_robot_containers_running[msairsim-rob#1] 1 0 0 100%
TestLiveliness.test_robot_containers_running[msairsim-rob#3] 1 0 0 100%
TestLiveliness.test_sentinel_nodes_present[msairsim-rob#1] 1 0 0 100%
TestLiveliness.test_sentinel_nodes_present[msairsim-rob#3] 1 0 0 100%
TestLiveliness.test_sim_container_running[msairsim-rob#1] 1 0 0 100%
TestLiveliness.test_sim_container_running[msairsim-rob#3] 1 0 0 100%
TestLiveliness.test_sim_ready_time[msairsim-rob#1] 0 1 0 0%
TestLiveliness.test_sim_ready_time[msairsim-rob#3] 0 1 0 0%
TestLiveliness.test_stable[msairsim-rob#1] 0 0 1 —
TestLiveliness.test_stable[msairsim-rob#3] 0 0 1 —
TestLiveliness.test_tmux_panes_have_expected_processes[msairsim-rob#1] 0 1 0 0%
TestLiveliness.test_tmux_panes_have_expected_processes[msairsim-rob#3] 0 1 0 0%

Metrics

Test Metric Value
TestColconBuilds.test_colcon_build_robot duration_s 178.8s
TestColconBuilds.test_colcon_test_robot duration_s 40.07s
TestColconBuilds.test_colcon_build_gcs duration_s 64.75s
TestColconBuilds.test_colcon_build_ms_airsim duration_s 13.76s
TestLiveliness.test_robot_containers_running[msairsim-rob#1] duration_s 3.979s
TestLiveliness.test_sim_container_running[msairsim-rob#1] duration_s 0.02s
TestLiveliness.test_gcs_container_running[msairsim-rob#1] duration_s 0.047s
TestLiveliness.test_sim_ready_time[msairsim-rob#1] duration_s 601.6s
TestLiveliness.test_tmux_panes_have_expected_processes[msairsim-rob#1] duration_s 0.059s
TestLiveliness.test_compute_usage[msairsim-rob#1] duration_s 0s
TestLiveliness.test_sentinel_nodes_present[msairsim-rob#1] duration_s 7.114s
TestLiveliness.test_stable[msairsim-rob#1] duration_s 0s
TestLiveliness.test_robot_containers_running[msairsim-rob#3] duration_s 21.2s
TestLiveliness.test_sim_container_running[msairsim-rob#3] duration_s 0.027s
TestLiveliness.test_gcs_container_running[msairsim-rob#3] duration_s 0.061s
TestLiveliness.test_sim_ready_time[msairsim-rob#3] duration_s 601.8s
TestLiveliness.test_tmux_panes_have_expected_processes[msairsim-rob#3] duration_s 0.066s
TestLiveliness.test_compute_usage[msairsim-rob#3] duration_s 0s
TestLiveliness.test_sentinel_nodes_present[msairsim-rob#3] duration_s 11.28s
TestLiveliness.test_stable[msairsim-rob#3] duration_s 11.93s

system/test_liveliness

Metrics

Test Metric Value
test_robot_containers_running[msairsim-rob#1] airstack_up_duration_s 3.51s
test_robot_containers_running[msairsim-rob#1] airstack_down_duration_s 11.28s
test_sim_ready_time[msairsim-rob#1] sim_ready_duration_s timeouts (1 fail)
test_robot_containers_running[msairsim-rob#3] airstack_up_duration_s 9.35s
test_robot_containers_running[msairsim-rob#3] airstack_down_duration_s 11.93s
test_sim_ready_time[msairsim-rob#3] sim_ready_duration_s timeouts (1 fail)

@andrewjong
andrewjong merged commit 262f126 into develop Aug 20, 2026
3 of 6 checks passed
@andrewjong
andrewjong deleted the feature/pre-rfc-workflow-cleanup branch August 20, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant