# AGENTS.md — sm121 Rust CLI (`edition 2024`) for launching vLLM/SGLang inference containers across a DGX Spark cluster via SSH (`russh`). Docker default, tmux alternative. ## Dev quick-ref | Action | Command | |--------|---------| | Build | `cargo build` | | All gates | `make check` (fmt → clippy → test → ≤500 lines) | | Dry-run | `cargo run -- -d serve --recipe deepseek-v4-flash` | | Tests | `cargo test` (65, all passing) | | l10n | `locales/{en,ru,es,it,fr,de}/main.ftl` — Fluent FTL files | See [`DEVELOPMENT.md`](DEVELOPMENT.md) for architecture, module layout, and dev workflow. ## Commands ``` serve/stop/bench/logs/health/doctor/exec/power/tmux/init-host-keys/config/web/report/runlock/validate ``` `validate` — offline recipe-contract gate (no cluster, no SSH): checks every `recipes//recipe.toml` against the launcher contract — `docker_image_dir` must exist under `docker-images/`, `port` must be the cluster default 30000, no floating `:latest` on engine images (`alpine` test images exempt), models must float (no `@revision`), `required_nodes` within 1..=8. Active recipes: violations are **errors** (exit 1). Archived lanes are grandfathered — their errors downgrade to **warnings** (advisory, always exit 0). Also runs as a `cargo test` over the repo's own `recipes/`, so `make check` fails if a new recipe breaks the contract. Rules live in `src/validate.rs`. `serve` records the launch in the recipe's run ledger (`recipes//.lock`, gitignored) and then **waits for the engine to actually come up** — polls `GET /v1/models` on the head node up to 30 min (`--no-wait` skips); a run that never reaches readiness is closed with its uptime and the serve fails. Follow mode watches liveness (30 s probes; 2 misses = died): a dead engine — or an ended log stream with a dead engine — fails loudly and closes the run. `stop` stops ALL nodes in parallel, every node runs the verifying command (polls up to 60 s that no labeled container remains) and failures are aggregated: one straggler cannot leave the other nodes un-stopped, and the error names the nodes that did not verify. The verified stop closes the run in the ledger. `--no-wait` opts out of the per-node verification. `report` — the table of tested recipes: markdown (default) or an interactive HTML table via `--serve`/`--open`. It shows each recipe's run-ledger state (● running + uptime — live in the HTML, launch count) and supports archive curation: `--archive ` / `--unarchive ` flip the recipe's `archived` flag (comment-preserving), archived recipes leave the output, `--archived` shows them instead. `stop` waits until the containers are actually stopped and removed (polls up 60 s per node) — an immediate `serve` on the same nodes would otherwise race the dying containers' GPU release. `runlock` — offline run-ledger management (the per-recipe `.lock`): `list` shows every recipe with an open (running) entry, `close ` closes the open run without any node verification. `stop` closes the ledger only after verifying every node of the cluster, so a node that died mid-run (or a killed serve) leaves the run open — `sm121 report --serve` cross-checks the ledger against a live fleet probe and renders such rows as orange "stale" (tooltip: `sm121 runlock close `) instead of green "up" when all nodes of the run's cluster are reachable but no running container of the recipe exists; an unreachable node is inconclusive and keeps the green (the fix for a confirmed stale row is `runlock close`). `bench` runs `tool-eval-bench` (from PATH) against a live serve and lands `_