* refactor(openai): remove tool-call rescue paths * fix(openai): preserve truncated tool responses * style: format stacked OpenAI checks
54 KiB
Testing mesh-llm
Local inspection
0. Inspect local GPUs
mesh-llm gpus
mesh-llm gpus --json | jq .
mesh-llm gpus detect --json | jq .
- Prints local runtime-selectable GPU entries with stable IDs, backend devices, VRAM, unified-memory status, and cached bandwidth when a fingerprint is available
- In the shipped Skippy-enabled binary, platform tools alone are not enough: if the embedded backend does not enumerate a GPU, the node should report CPU-only rather than advertising probe-visible GPU capacity
--jsonemits machine-readable inventory and benchmark payloads suitable for automation
0a. Startup config smoke
Create ~/.mesh-llm/config.toml:
version = 1
[gpu]
assignment = "auto"
[[models]]
model = "Qwen2.5-3B"
[[models]]
model = "/absolute/path/to/qwen2.5-vl.gguf"
mmproj = "/absolute/path/to/mmproj.gguf"
ctx_size = 8192
Then start:
mesh-llm serve
- Both configured startup models should be considered for launch
- If
[[models]]is empty,mesh-llm serveshould print a⚠️warning, show help, and exit cleanly - Explicit
--modelor--ggufshould ignore configured[[models]] - Explicit
--ctx-sizeshould override configuredctx_size mesh-llm benchmark tuneis the measured local model-serving tuning companion for these startup configs. It only accepts already-downloaded targets, rejects remote-only or not-downloaded refs without fetching them, and runs isolated throughput trials. For speculative decoding changes, run a small sweep that includes the disabled baseline plusmtp,mtp-ngram, or draft candidates as applicable, then inspect trial logs/telemetry for native MTP or draft acceptance statistics in addition to decode tok/s.
0b. Pinned startup smoke
First inspect the valid local IDs:
mesh-llm gpus
mesh-llm gpus --json | jq .
Then create ~/.mesh-llm/config.toml with a real pinnable stable ID from that output (for example pci:*, uuid:*, or metal:*, not fallback IDs like index:* or backend-device names):
version = 1
[gpu]
assignment = "pinned"
[[models]]
model = "Qwen2.5-3B"
gpu_id = "pci:0000:65:00.0"
Start the node:
mesh-llm serve
- Startup should succeed only when
gpu_idmatches a valid local pinnable stable ID frommesh-llm gpus - If the pinned ID is missing, ambiguous, unsupported, or stale, startup should fail closed before local launch
- Explicit
mesh-llm serve --model ...should still bypass configured[[models]]and therefore bypass config-owned pinned IDs - Do not use GPU indexes,
index:*, or backend-device names likeCUDA0/HIP0/MTL0asgpu_id
0c. Requirement-aware mesh smoke
Create an unrestricted mesh:
mesh-llm serve --model Qwen3-8B-Q4_K_M
Create a release-attestation-required public mesh:
mesh-llm serve --model Qwen3-8B-Q4_K_M --publish \
--require-release-attestation \
--release-signer-key ed25519:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
--owner-key ~/.mesh-llm/owner-keystore.json \
--owner-required \
--trust-policy require-owned \
--node-label lab-a
The release-attestation flags are creation-time mesh requirements. The owner and trust-policy flags exercise local owner-identity behavior and must not change the mesh requirements hash.
Or with config-backed creation requirements:
[mesh_requirements]
require_release_attestation = true
release_signer_keys = ["ed25519:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"]
Join via signed bootstrap token:
mesh-llm serve --join <signed-bootstrap-token>
-
mesh-llm runtime bootstrap --port 3131should show the local owner-control bootstrap policy used by the node. -
curl -s http://localhost:3131/api/runtime/control-bootstrap | jq .should report the same local bootstrap policy in JSON form. -
Admission failures for nodes that miss the certified-build gate should be deterministic. In human-facing prose this is "certified build required"; the machine reason codes surfaced by logs, status, and evidence are
certified_binary_required,build_proof_invalid, andrelease_signer_untrusted. -
Legacy unrestricted meshes still accept the older unsigned invite-token path, while requirement-aware meshes require signed bootstrap tokens.
-
For release smoke, always inspect the packaged
mesh-llmexecutable withcargo run -p xtask -- release-attestation inspect --binary /tmp/test-bundle/mesh-llm --public-key-file /tmp/mesh-release-key.pub, never the rawtarget/release/mesh-llmpath. Packaged release archives can reportvalid, unstamped local or dev builds reportmissing, and a binary mutated after download reportsinvalid, but default startup still allows it because this is provenance and admission hardening, not runtime integrity proof. Bareinspect --binary ...is only sufficient for unstamped binaries that should classify asmissing; stamped release binaries require--public-key-fileand otherwise reportinvalidwith an explicit error. -
Smoke and release evidence should come from the packaged archive contents, not raw release output, because the release pipeline now sources publishable binaries from extracted packaged archives.
0d. Terminal dashboard smoke
The pretty dashboard uses raw mode and the alternate screen when both stdin and stderr are interactive TTYs and TERM supports a real terminal. It should leave native terminal text selection available and fall back to line-oriented pretty output when stdin is not a TTY, stderr is not a TTY, or TERM is empty / dumb.
Run these manual checks after changes to runtime/interactive.rs or cli/output/mod.rs:
| Shell | Setup | Expected result |
|---|---|---|
| Plain terminal | mesh-llm serve --model Qwen2.5-3B |
Dashboard renders, resizes cleanly, Tab/Shift-Tab focus panels, Enter or z opens the focused panel full screen, Esc or z returns to the multi-panel view, terminal text selection works, h/i/q work, and exit restores the prompt. |
| Piped stdin | `true | mesh-llm serve --model Qwen2.5-3B` |
| Unsupported terminal | TERM=dumb mesh-llm serve --model Qwen2.5-3B |
Dashboard is disabled and pretty output uses fallback lines. |
| tmux, mouse off | tmux new 'mesh-llm serve --model Qwen2.5-3B' |
Dashboard renders and exits cleanly; keyboard navigation works. |
| tmux, mouse on | Inside tmux: set -g mouse on, then run mesh-llm |
Dashboard renders and exits cleanly; terminal/tmux text selection remains usable. |
| GNU screen default | screen mesh-llm serve --model Qwen2.5-3B |
If the alternate screen is unavailable, fallback behavior or clean restoration is acceptable. |
| GNU screen altscreen | In ~/.screenrc: altscreen on, then run mesh-llm |
Dashboard enters/leaves the alternate screen cleanly. |
For terminal restoration QA:
- Resize during startup, after embedded runtime readiness, and while the dashboard has focus on different panels.
- Open the mesh events/log panel full screen and verify long log lines wrap within the panel.
- Detach and reattach tmux/screen while the dashboard is active.
- Select visible dashboard text with the terminal mouse selection gesture and verify it can be copied.
- Press
qandCtrl+C; the cursor should be visible and the shell prompt should not remain in raw mode. - A
SIGKILL(kill -9) cannot run in-process cleanup. If a terminal is left corrupted after a hard kill, recover withresetor by closing the terminal pane.
0d. Agent tool-call reliability
Run the direct tool-call probe when changing OpenAI chat-completions routing,
agent integrations, MoA reducer behavior, or anything that may affect
tools / tool_calls / tool-result continuation:
scripts/qa-agent-tool-call-reliability.py \
--base-url http://127.0.0.1:9337/v1 \
--models auto,mesh \
--attempts 3 \
--output target/agent-tool-call-reliability/results.jsonl
tool_callandstream_tool_callrequire real OpenAI-style function calls, not prose that says a tool would be usedtool_resultandstream_tool_resultverify that the assistant can continue after a matching tool result and include the deterministic fixture value--print-planis side-effect-free and emits machine-readable JSON- use
--skip-streamingonly when intentionally narrowing a local diagnosis to non-streaming chat-completions
0e. Nightly stability harness
Use the repeatable stability harness when you want black-box evidence that a live mesh endpoint stays usable across repeated chat, streaming, tool-call, and optional agent-client checks:
scripts/qa-nightly-stability.py \
--base-url http://127.0.0.1:9337/v1 \
--models auto,mesh \
--attempts 5 \
--agent-smokes opencode,pi,goose \
--output-dir target/nightly-stability/local
- the harness attaches to an existing OpenAI-compatible
/v1endpoint; it does not start nodes, load models, publish to the public mesh, or change routing policy - direct OpenAI surface probes write
results.jsonlwith/v1/models, non-streaming chat, streaming chat, HTTP status, elapsed time, and TTFT where applicable - the merged tool-call probe remains the canonical tool-call validator; this harness invokes it and records its command/log path instead of reimplementing tool-call parsing
- optional OpenCode, Pi, and Goose smokes reuse the existing CI agent fixtures;
missing optional CLIs are recorded as
PREREQrather than a stability failure - every run writes
manifest.json,commands.jsonl,results.jsonl,summary.json,summary.md, andlogs/ --print-planis side-effect-free and shows the exact checks/artifacts that would run
The scheduled GitHub workflow is opt-in through
MESH_NIGHTLY_STABILITY_ENABLED=1 and a configured
MESH_NIGHTLY_STABILITY_BASE_URL (or the existing agent endpoint variables).
The scheduled/manual wrapper calls the reusable nightly-stability-run.yml
workflow so maintainers can reuse the same harness execution from other
workflows or lab jobs. The job summary includes the timing snapshot from
summary.md, so day-over-day drift can be checked without opening JSONL
artifacts. The reusable workflow is fixed to GitHub-hosted Ubuntu; it never
accepts caller-provided runner labels because it checks out the caller's
repository content. It is intentionally evidence-producing and non-required:
failed nightlies should guide stabilization work, not block unrelated pull
requests.
0f. KV/tool-loop stability certification
Run the KV/tool-loop certification probe when changing Skippy KV slot cleanup,
prefix-cache lookup, agent tool-loop behavior, or any runtime path related to
issues where repeated tool calls eventually hit llama_decode failed or low
same-prefix cache reuse.
scripts/qa-kv-tool-loop-stability.py \
--base-url http://127.0.0.1:9337/v1 \
--models Qwen/Qwen2.5-3B-Instruct-GGUF:q4_k_m \
--attempts 5 \
--pressure-turns 8 \
--timeout 180 \
--min-cached-tokens 2048 \
--suffix-prefill-limit 256 \
--native-log ~/.mesh-llm/runtime/<pid>/logs/skippy-native.log \
--output-dir target/kv-tool-loop-stability/local
- the probe attaches to an existing
/v1/chat/completionsendpoint; it does not start nodes, load models, join a mesh, or change routing policy - each attempt runs a growing non-streaming tool-result conversation with a long stable system prefix, repeated pressure turns, a second forced tool call, and final recall of both deterministic tool facts
same_prefix_cachewarms the same long prefix with one tail and measures a different tail;exact_prefix_cacheverifies the identical-body cache path still works--min-cached-tokens 2048matches the known Qwen 3B reproduction shape where healthy same-prefix reuse is near the shared prefix, not the 256-token floor--native-logscans Skippy native logs forfailed to find a memory slot,llama_decode failed, and proactive eviction errors appended after the certification starts, so stale failures in long-lived logs do not fail a clean run- every run writes
manifest.json,results.jsonl,summary.json,summary.md, and sanitized transcript JSONL undertranscripts/; the transcript directory is reset at run start to keep repeatedlatestruns auditable --print-planis side-effect-free and emits the exact models, thresholds, runtime options, checks, and evidence files that would be produced
This certification is deliberately a lab/release-confidence check, not a required PR gate. Use it to prove KV/cache stability on a real direct-model endpoint after local unit tests and before relying on agent workloads such as Goose, Pi, or OpenCode for broad smoke coverage.
Single-model permutations
1. Solo (single node)
mesh-llm serve --model Qwen2.5-3B --console 3131
- API on
:9337, console on:3131 - Console:
host=true, peers=0 - Embedded runtime reports one local serving route for the model
1a. Headless mode (API-only, no embedded UI)
mesh-llm serve --model Qwen2.5-3B --headless --console 3131
- API on
:9337, management API on:3131 GET /api/statusreturns 200 with normal JSONGET /returns 404 (web console routes are disabled)GET /dashboard,GET /chat, and/assets/*also return 404- The management API (
/api/*) remains fully accessible
This mode is intended for headless server deployments where the embedded web UI is not needed.
2. Two GPU nodes, model fits on host
# node A (more VRAM, becomes host)
mesh-llm serve --model Qwen2.5-32B --bind-port 7842
# node B (joins)
mesh-llm serve --model Qwen2.5-32B --join <TOKEN>
- Both nodes run solo (no split) — each is its own host
- API works from both nodes on
:9337
3. Two GPU nodes, forced split
# node A (coordinator)
mesh-llm serve \
--model meshllm/Qwen3-8B-Q4_K_M-layers \
--split \
--max-vram 5 \
--bind-port 7842 \
--port 9447 \
--console 3232
# node B (worker)
mesh-llm serve \
--model meshllm/Qwen3-8B-Q4_K_M-layers \
--split \
--max-vram 5 \
--join <TOKEN> \
--bind-port 7843 \
--port 9447 \
--console 3232
--splitforces staged execution even when the model fits on the host- Use a published layer package (
*-layers) so each node downloads only the shared artifacts and layer files assigned to its stage. - Embedded runtime assigns participating stage routes
- Stage placement is proportional to available VRAM (e.g.
0.67,0.33) - If
--max-vramis too low, planning should fail before startup with the required memory estimate. In lab testing,3GB was too low for this Qwen3 8B package at the default context, while5GB per node reached readiness. - Wait for
/api/statuson the coordinator to shownode_state="serving",llama_ready=true, a ready runtime stage, and one peer. GET /v1/modelsshould list the full layer-package model id.- A short
/v1/chat/completionsrequest should return an OpenAI-shaped response from the layer-package model.
CI coverage:
two_node_split_smokerunsscripts/ci-two-node-split-smoke.shagainst the Linux inference binary and a tiny GGUF. It starts two serving nodes, waits for a topology with stages on two distinct nodes, checks/v1/models, and sends a short/v1/chat/completionsrequest through stage 0.Other nearby CI coverage:
scripts/ci-two-node-client-serving-smoke.sh— two nodes, but only testsclient->serverouting. The model is held entirely on one node.scripts/skippy-ci-smoke.sh— exercises 3-stage layer splits viaskippy-correctness chain, but all stages run on127.0.0.1in a single runner. It validates the staged-runtime ABI, not mesh-llm node-to-node split serving over QUIC.Use real flags only:
--split,--max-vram,--join,--bind-port,--port,--console. There is no--layer-rangeflag — layer placement is normally decided by the runtime from advertised VRAM. Maintainer runs that require exact placement can supply a complete topology manifest with--split-topology-lock <path>; seedocs/SKIPPY_SPLITS.md.
Multi-interface Docker/Linux bind-IP validation
For host-network Docker or multi-NIC Linux hosts, validate the selected host-to-host interface explicitly:
# seed on the routable management IP
mesh-llm serve --model Qwen2.5-32B --split --bind-ip 10.1.2.3 --bind-port 7842
# worker joins the printed token
mesh-llm serve --model Qwen2.5-32B --split --join <TOKEN>
- Decode the invite token and confirm Docker/CNI bridge addresses such as
172.17.0.1or172.23.0.1are absent when--bind-ipis set. - The seed sees the worker in
/api/status; it must not stay atWaiting for peers.... /v1/modelsbecomes non-empty after split startup and inference works.--listen-allis not a substitute for this test; it only affects local HTTP API/console listeners.
Split-package preflight diagnostics
Before starting a package-backed split run, preflight the local package directory and then certify the immutable published ref:
skippy-model-package preflight ./model-package --stages 2 --verify-sha256
mesh-llm models certify hf://namespace/repo@revision --package-only --report-out target/skippy-preflight/cert.json
Clean packages should pass with a machine-readable report. Deliberately broken
packages or refs should fail before the model is advertised through /v1/models
and should name the blocked manifest field, missing artifact, size/SHA mismatch,
sidecar, or stage part.
4. Two GPU nodes, model too big for one
When the model exceeds host VRAM, split happens automatically without --split.
5. Lite client (no GPU)
mesh-llm client --join <TOKEN> --port 9555
- Uses ephemeral key (unique identity, works on same machine as GPU node)
/v1/modelslists all served models from gossip- API tunneled to correct host per model via QUIC
- VRAM total excludes client
Multi-model permutations
6. Two nodes, different models
# node A: seeds mesh with two models, serves 3B
mesh-llm serve --model Qwen2.5-3B --model GLM-4.7-Flash --console 3131
# node B: joins, auto-assigned to GLM (needed, on disk)
mesh-llm serve --join <TOKEN>
/v1/modelson either node lists both models- Requesting GLM from node A routes via QUIC to node B
- Requesting 3B from node B routes via QUIC to node A
- Both run solo (no tensor split)
- Console shows both models warm with node counts
Compatibility result:
- Verified on 2026-04-02 with the current
codex/model-identity-designbranch on node 1 and the latest GitHub releasev0.54.0on node 2. - Node 1 served
Llama-3.2-1B-Instruct-Q4_K_M; node 2 servedQwen3-4B-Q4_K_M. /api/modelsand/v1/modelsagreed on the same warm model list from both nodes.- Chat from node 1 to node 2's model succeeded, and chat from node 2 to node 1's model succeeded.
7. Auto-assignment
# seeder declares two models
mesh-llm serve --model Qwen2.5-3B --model GLM-4.7-Flash
# joiner with no --model
mesh-llm serve --join <TOKEN>
- Joiner scans the Hugging Face cache and picks an unserved model already on disk
- Log: "Selected to serve GLM-4.7-Flash (needed by mesh, already on disk)"
8. Lite client with multi-model
# GPU nodes running as above
mesh-llm client --join <TOKEN> --port 9555
- Client sees all models via gossip (ephemeral key = unique identity)
/v1/modelslists all served models- Routes to correct host per model
- Streaming works through cross-model QUIC tunnel
9. Unload a model
mesh-llm unload GLM-4.7-Flash-Q4_K_M
- Node serving that model exits cleanly
- Other nodes unaffected
- Model goes cold in console
9a. Local runtime load/unload and local status view
# Running node
mesh-llm serve --model Qwen2.5-0.5B-Instruct-Q4_K_M --console 3131
# Operator surface
mesh-llm load Llama-3.2-1B-Instruct-Q4_K_M
mesh-llm status
mesh-llm unload Llama-3.2-1B-Instruct-Q4_K_M
# REST surface
curl localhost:3131/api/runtime
curl localhost:3131/api/runtime/processes
curl -X POST localhost:3131/api/runtime/models \
-H 'Content-Type: application/json' \
-d '{"model":"Llama-3.2-1B-Instruct-Q4_K_M"}'
curl -X DELETE localhost:3131/api/runtime/models/Llama-3.2-1B-Instruct-Q4_K_M
mesh-llm statusshows the local models currently backed by running inference processes, including PID when presentGET /api/runtimeandGET /api/runtime/processesagree with the CLI output- Loading a small local model adds it to
/v1/modelswithout restarting the node - Unloading any local model removes it cleanly without terminating the mesh-llm process
10. Console model picker
- Dropdown appears when >1 warm model
- Switching models highlights the serving node in topology view
- Chat routes to selected model via API proxy
11. Console live-state and wakeable capacity
cd crates/mesh-llm-ui/
npm run test:run
npm run typecheck
just build
- Live badges show only
Client,Standby,Loading, andServing - Wakeable capacity renders in a separate section from topology peers and live nodes
- Wakeable entries do not appear in the topology peer list
- Validation uses
npm run test:run,npm run typecheck, andjust build just buildmust leavetarget/debug/mesh-llmwith exactly one adjacenttarget/debug/native-runtimes/<runtime-id>/tree. Validate it with./target/debug/mesh-llm --log-format json runtime list; CI also starts the composed client noninteractively, observes either the JSONClient readymessage or the structuredpassive_mode/status=ready/role=clientevent, and requires bounded graceful SIGTERM shutdown on Unix or CTRL_BREAK_EVENT on Windows. Interactive Ctrl-C/SIGINT behavior is tested separately because noninteractive background shell children may inherit SIGINT as ignored.
Mesh Identity
16. Mesh ID generation (originator)
# With --mesh-name (deterministic ID)
mesh-llm serve --model Qwen2.5-3B --mesh-name "test-mesh"
- Log:
📌 Mesh ID: <hex> ~/.mesh-llm/last-meshcontains the same hex- Restart with same
--mesh-name→ same mesh ID (deterministic) - Different
--mesh-name→ different mesh ID
17. Mesh ID propagation (joiner)
# Originator
mesh-llm serve --model Qwen2.5-3B --mesh-name "test-mesh"
# Joiner
mesh-llm serve --model Qwen2.5-3B --join <TOKEN>
- Joiner log:
📌 Mesh ID: <same hex as originator> - Joiner's
~/.mesh-llm/last-meshmatches originator's mesh ID - Mesh ID arrives via gossip (worker nodes) or routing table (passive clients)
18. Sticky mesh preference
- Join a mesh →
~/.mesh-llm/last-meshsaved - On next
--auto,score_mesh()adds +500 for meshes with matchingmesh_id - If that mesh is dead (not on Nostr), scoring proceeds normally without bonus
Bootstrap Proxy
19. Instant API during GPU bootstrap
# Originator (already running)
mesh-llm serve --model Qwen2.5-3B --port 8090
# Joiner
mesh-llm serve --model Qwen2.5-3B --join <TOKEN> --port 8091
- Joiner log:
⚡ API ready (bootstrap): http://localhost:8091 - BEFORE the joiner finishes its local embedded runtime startup:
curl localhost:8091/v1/models→ lists mesh modelscurl localhost:8091/v1/chat/completions→ inference via tunnel to originator
- Log:
⚡ Bootstrap proxy handing off to full API proxy - After handoff, API continues working (now served locally or via election)
20. Bootstrap proxy not started for originator
mesh-llm serve --model Qwen2.5-3B
- No
⚡ API ready (bootstrap)message (only joiners get bootstrap proxy) - API port opens only after election resolves
No-Arg Behavior & Management API
21. No-arg help
mesh-llm
- Prints the same usage/help text as
mesh-llm --help - No ports are bound
curl localhost:3131/api/statusfails to connect
22. Join and observe via console
mesh-llm client --auto
# In browser: http://localhost:3131 → observe status/discovery
# Or join explicitly from the CLI:
mesh-llm client --join <token>
- CLI join triggers full flow: connect → gossip → assign model → download → serve
- Console updates: status, peers, model name all reflect new state
- Inference port starts working after model loads
23. Management API while serving
mesh-llm serve --auto
# After serving:
curl localhost:3131/api/status # JSON: node, peers, routing, mesh_id, mesh_name
curl localhost:3131/api/events # SSE stream
curl 'localhost:3131/api/search?q=qwen&catalog=true&artifact=gguf&limit=5' # JSON search results
curl -X POST localhost:3131/api/model-interests \
-H 'Content-Type: application/json' \
-d '{"model_ref":"Qwen3-Coder-Next-Q4_K_M","source":"ui"}'
curl localhost:3131/api/model-interests
curl localhost:3131/api/model-targets
curl localhost:3131/api/discover # Nostr meshes (current mesh marked by mesh_id)
/api/statusincludesmesh_idandmesh_name- SSE events push every 2s and on topology changes
/api/searchreturns 200 JSON with canonical model refs for matching results/api/model-interestsstores and returns local explicit-interest entries keyed by canonical model refs/api/model-targetsreturns ranked targets with explicit-interest counts, request counts, serving-node counts,wantedfor targets not currently served, and derivedcapacity_advicewithout changing ranking or routing behavior- If
[runtime] reconcile_model_targets = trueis enabled, unserved local explicit interests that are already present on disk and fit the current node may be runtime-loaded automatically. Ifreconcile_model_target_demand_upgrades = trueis also enabled, an already-serving host may replace a lower-demand local model with a locally present, higher-demand unserved target whose active demand is still withinmodel_target_demand_upgrade_max_age_secs. Leave these unset for read-only advisory checks. - Discover results can be matched to current mesh by
mesh_id - In
--mesh-discovery-mode mdns,/api/discovermust show LAN advertisements without raw invite tokens; token fingerprints are expected, while proof challenges and/api/discovery/lan-detailsshould appear only when the publishing node's management API is LAN-reachable, such as with--listen-all. - POST
/api/discovery/lan-detailsmust reject missing or wrong-token proof and must return local detail without echoing the raw invite token.
24. HTTP proxy single-request connection contract
- Send a routed inference request, then pipeline or reuse the same TCP connection for a second request.
- Verify only the first request reaches the selected upstream.
- Verify the proxy closes the routed connection after the first response.
- Verify the upstream-observed request includes
Connection: close.
25. OpenAI guardrail corpus smoke
Enable the server-side guardrail mode first. Either start the runtime in that mode or update the running process without restarting it:
mesh-llm serve --model MiniMax-M2.5-Q4_K_M --mesh-guardrails metrics
# or, for an already-running node:
mesh-llm runtime guardrails --mode metrics --port 3131
curl -s localhost:3131/api/status | jq '.runtime.openai_guardrails'
python3 scripts/run-openai-guardrail-corpus.py \
--base-url http://127.0.0.1:9337/v1 \
--model MiniMax-M2.5-Q4_K_M \
--guardrail-mode metrics \
--trials 20 \
--out .sisyphus/evidence/openai-guardrail-corpus.json
- Phase 0 stays off by default.
metricsandenforceare opt-in. --guardrail-modeonly records request intent and sends the matchingmesh_guardrailsrequest override. It does not reconfigure the server; use--mesh-guardrails,mesh-llm runtime guardrails, or the management API for server-side activation.- If the runtime is unavailable, the script falls back to deterministic fake-backend mode and still writes the expected JSON artifact.
- The corpus covers streaming pass-through, native tool-call validation,
structured
_mesh_respondoutput, strict structured output, and the unsupported real tools plus strict structured combination. - The command is a reliability check, not a hard constrained decoding promise.
If a Python sidecar baseline is available, you may optionally run a smoke comparison on the same corpus to compare behavior before and after this adaptation. Treat it as a local comparison aid only; it is not a mandatory implementation gate.
Resilience
11. Dead peer cleanup
- Kill a node with
kill -9 - Cleanup happens in ~41s via the reconnect-gossip-probe mechanism:
- QUIC detects connection drop (~5-30s depending on idle timeout and relay state)
- Reconnect attempt with 10s gossip probe timeout
- Gossip probe fails →
remove_peercalled immediately
- Heartbeat also detects dead peers (60s interval, 2 consecutive failures) as a fallback
- On-use detection: tunnel failure → immediate death broadcast via stream 0x06
- Dead model goes cold, peer removed from list, death broadcast to mesh
- Dead peer won't be re-added by gossip (dead_peers set)
- Console updates automatically
12. Node rejoin
- Kill a node, restart it with
--join <token> - Rejoin loop (60s) reconnects to bootstrap if connection drops
- Inbound reconnection clears dead_peers entry
- Model goes warm again, cross-model routing resumes
13. Gossip stability
- Regossip after becoming host should NOT cause restart loops
- Log should show "still host, no restart needed" on re-check
- The embedded runtime starts exactly once per election (not 5-9 times)
- Heartbeat gossip doesn't re-discover dead peers (discover_peers=false)
Control-Plane Protocol (Protobuf v1)
The control plane uses QUIC ALPN mesh-llm/1 with the meshllm.node.v1 protobuf schema. Scoped control-plane streams use 4-byte LE framing followed by protobuf bytes. Skippy control/artifact streams are advertised through gossip subprotocol features and run through mesh STREAM_SUBPROTOCOL (0x0d); activation transport stays on skippy-stage/2.
| Stream | Type | Format |
|---|---|---|
| 0x01 | GOSSIP | protobuf GossipFrame |
| 0x03 | TUNNEL_MAP | protobuf TunnelMap |
| 0x05 | ROUTE_REQUEST | protobuf RouteTableRequest / RouteTable |
| 0x06 | PEER_DOWN | protobuf PeerDown |
| 0x07 | PEER_LEAVING | protobuf PeerLeaving |
| 0x0b | CONFIG_SUBSCRIBE | reserved legacy mesh-plane config stream ID; do not reuse |
| 0x0c | CONFIG_PUSH | reserved legacy mesh-plane config stream ID; do not reuse |
| 0x0d | STREAM_SUBPROTOCOL | protobuf MeshSubprotocolOpen, then subprotocol-owned bytes |
Config and inventory mutation must use mesh-llm-control/1; mesh-llm/1 no longer dispatches request/response handlers for the reserved 0x0b/0x0c config stream IDs. Raw TCP relay streams (0x02 RPC, 0x04 HTTP) are unchanged.
Verifying protobuf gossip in logs
After two nodes connect, look for log lines indicating gossip was exchanged:
DEBUG mesh: gossip received from <peer_id>
DEBUG mesh: admitted peer <peer_id>
Absence of JSON-related log lines for streams 0x01/0x03/0x05/0x06/0x07 confirms the protobuf path is active.
Verifying Skippy peer artifact transfer
For a layer-package split where the coordinator already has the HF package cached and a worker does not:
- Current/current mesh: the worker may use mesh
STREAM_SUBPROTOCOL(0x0d) to openskippy-stage/2, then Skippy artifact-transfer stream 0x03, to fetch only its assigned package files before the normal HF fallback path. - Current/released mixed mesh: a released coordinator without advertised
skippy-stage/2artifact-transfer,stage-generation-4, anddirect-prediction-returnsupport must not be selected for a generation-4 split topology; the worker must fall back to local/HF package resolution. - Default public-mesh safety: with
MESH_LLM_ARTIFACT_TRANSFERunset, the node must advertise noartifact-transferfeature, reject inbound artifact transfer requests, and continue through local/HF fallback resolution. - Trusted-owner opt-in: with
MESH_LLM_ARTIFACT_TRANSFER=trusted, artifact transfer is eligible only for same-owner or explicitly trusted-owner peers. - Explicit lab opt-in: with
MESH_LLM_ARTIFACT_TRANSFER=open, the node may advertise and serve artifact transfer to any peer that is authorized by the active split topology. - Privacy check: gossip/status output must not include local package inventory, cache roots, or artifact file lists; only subprotocol feature support is advertised.
- Integrity check: corrupt or same-sized cached artifacts must be refetched or rejected by SHA-256 verification before stage load.
13. Mixed-version owner-control coexistence
Owner-control QA needs to prove three things at the same time:
- Public-mesh join and routed inference still work across released/current binaries.
- Explicit endpoint bootstrap and
mesh-llm-control/1work for current nodes while released peers continue to coexist on the public mesh plane for join, gossip, routing, and inference. Config and inventory mutation stay on owner-control only. - A current same-owner requester can run the public
runtime scan-refreshcommand against exactly one current remote target and receive the sorted, metadata-bearing result. Released targets are not expected to return the new private response field.
Use the dedicated harness for the full mixed-version pass:
scripts/qa-control-plane-mixed-version.sh \
--released-binary /absolute/path/to/released/mesh-llm \
--current-binary /absolute/path/to/current/mesh-llm \
--evidence-dir /absolute/path/to/evidence
For a loopback-only smoke on one machine:
scripts/qa-control-plane-mixed-version.sh \
--released-binary /absolute/path/to/released/mesh-llm \
--current-binary /absolute/path/to/current/mesh-llm \
--evidence-dir /absolute/path/to/evidence \
--local-only
For the owner-control bootstrap lane only:
scripts/qa-control-plane-mixed-version.sh \
--released-binary /absolute/path/to/released/mesh-llm \
--current-binary /absolute/path/to/current/mesh-llm \
--evidence-dir /absolute/path/to/evidence \
--local-only \
--config-only
The harness writes a timestamped run directory containing logs, status payloads, owner-control bootstrap evidence, owner-control get-config and scan-refresh evidence, and a markdown/json summary.
It also writes manifest.json, commands.jsonl, results.jsonl, versions/*.txt, and grouped status/, models/, chat/, and control/ payloads so a reviewer can audit which binaries, commands, and local endpoints produced the evidence.
Use --print-plan when CI or review automation needs to validate the planned checks without starting mesh processes or writing evidence; planned check names match the name values emitted to results.jsonl.
Expected checks:
- Public mode: both binaries must bring up
/api/status, list at least one model from/v1/models, and complete a routed chat request against the public mesh. - Loopback mode: the harness runs both mixed-version directions (
current -> releasedandreleased -> current) on a private local mesh, waits for peers to appear on both nodes, then runsmesh-llm runtime bootstrap --jsonplusmesh-llm runtime get-config --jsonagainst the current-version node's explicit endpoint. When two current same-owner nodes are available, it also recordsruntime scan-refresh --jsonagainst the explicitly bootstrapped remote target. - Config-only mode: the harness skips public probes, runs the same loopback coexistence checks, then executes the compatibility tests that prove new clients require explicit endpoints, use
mesh-llm-control/1, reject legacy frames on the owner-control ALPN, and accept a legacy snapshot-only refresh response without claiming rich inventory support. - The current-version bootstrap payload must keep
requires_explicit_remote_endpoint=trueand expose an endpoint token when owner-control is enabled. - If the bootstrap payload reports
enabled=false, the harness records aPREREQresult showing that a signed same-owner keystore is required before runtime owner-control requests can be proven on that machine.
Manual spot checks if the harness fails:
mesh-llm runtime bootstrap --port 3131 --json
mesh-llm runtime get-config --port 3131 --endpoint '<control-endpoint>' --json
mesh-llm runtime scan-refresh --port 3131 --endpoint '<control-endpoint>' --json
curl -s localhost:3131/api/runtime/control-bootstrap | jq .
curl -s -X POST localhost:3131/api/runtime/control/get-config \
-H 'Content-Type: application/json' \
-d '{"endpoint":"<control-endpoint>"}' | jq .
curl -s -X POST localhost:3131/api/runtime/control/scan-refresh \
-H 'Content-Type: application/json' \
-d '{"endpoint":"<control-endpoint>"}' | jq .
For a manual current/current scan-refresh check:
- Start two current nodes with owner-control enabled and signed by the same
owner. Obtain the target's endpoint from the target node's loopback
runtime bootstrap --jsonoutput and transfer it to the requester out of band. Do not derive it from public status or a peer ID. - From the requester, run
runtime scan-refresh --endpoint <token> --json. Asserttarget_node_idis the target,dispositionisexecutedorcoalesced, inventory entries are strictly sorted bycanonical_model_ref, sizes are present, and any compact metadata remains attached to the matching entry. - Start two concurrent scans. Assert one may report
executedand the joined caller reportscoalesced, both payloads contain the same entries, and the target performs only one loader scan. - Retry from a requester signed by a different owner and with a request bound
to the wrong target ID. Assert structured
unauthorizedandtarget_node_mismatchfailures respectively, with no inventory mutation. - Exercise the hidden
runtime refresh-inventoryorPOST /api/runtime/control/refresh-inventorycompatibility alias and assert its snapshot-only response shape is unchanged. Separately decode a frozen snapshot-only wire response with the new client and assert success withinventory = null/ no disposition. - Force a scan loader error and assert all joined callers fail while the last good runtime inventory and model advertisement remain unchanged.
- Inspect logs,
/api/status, peer state, and public gossip evidence. Endpoint tokens and raw inventory command results must not appear there; only the existing successful available-model projection may change.
Transport-focused evidence must also cover rejection of outbound and inbound frames over 8 MiB, 2-second open/handshake/write deadlines, 5-second unary and watch-accept deadlines, the 30-second scan deadline, non-zero request IDs after wraparound, and the 32-worker per-connection ceiling. An accepted watch must remain open rather than inherit a unary timeout.
Failure interpretation:
control_endpoint_required: you did not use the explicit bootstrap endpoint.control_unsupported: the target does not negotiatemesh-llm-control/1; this should not silently downgrade.control_unavailable: listener, token, network path, or local owner-key loading failed.unauthorized: same-owner authentication failed.revision_conflict: the apply CAS revision is stale; re-read before retrying.legacy_json_unsupported: a legacy mesh-plane frame was sent to the owner-control ALPN.
Config-only result interpretation:
config-missing-endpoint-required: executable proof that config bootstrap without an explicit owner-control endpoint is rejected when cargo-backed protocol tests run.config-new-client-owner-control: executable proof that new clients prefermesh-llm-control/1when cargo-backed protocol tests run.config-control-rejects-legacy-frames: executable proof that owner-control does not silently accept legacy frames on the wrong ALPN when cargo-backed protocol tests run.config-current-scan-refresh: a same-owner current requester used an explicit current target endpoint and received a sorted private inventory response.config-current-scan-refresh-wrong-owner: a requester with a different owner was rejected without changing target inventory state.config-owner-control-client-hardening: the new client accepted a frozen snapshot-only response and passed its timeout, framing, and request lifecycle tests; public released/current coexistence is recorded independently.PREREQ config-cargo-tests: cargo-backed protocol tests were skipped or cargo was unavailable.PREREQ config-runtime-bootstrap: runtime owner-control requests could not be proven because the local node did not expose a signed owner-control endpoint.
Reserved-ID note: mesh-plane stream IDs 0x0b and 0x0c are kept reserved, but current nodes should not advertise or rely on legacy config subscribe/push behavior there.
Daemon lifecycle certification
The daemon lifecycle harness certifies the runtime model lifecycle rollout: zero-model serve, all modes, best-effort/fail-fast, runtime load/unload/ensure/drain, activity overrides/admission, and privacy — without downloading models or publishing a mesh.
Running the harness
# Local daemon lifecycle certification
scripts/qa-runtime-daemon-lifecycle.sh \
--current-binary ./target/debug/mesh-llm \
--evidence-dir .sisyphus/evidence/task-15-local
The harness uses temporary homes ($TMP_ROOT/mesh-runtime-daemon-lifecycle.*) and ports (base 19740+) with scoped cleanup. Each test scenario isolates a fresh $HOME via MESH_LLM_EPHEMERAL_KEY=1 so multiple nodes coexist on one machine.
Planned checks
Each check produces a PASS/FAIL/PREREQ record in results.jsonl:
| Check | Description |
|---|---|
prereq.current-binary |
Binary is executable and reports a version string |
prereq.owner-identity |
Temporary owner keystore can be created for authenticated lifecycle checks |
zero_model_serve_ready |
Start the daemon with --headless and explicit API, console, and bind ports but no model; verify management API alive, /v1/models returns success (empty list acceptable), and the process stays alive |
runtime_mode_serve |
Default serve mode starts and responds on management API within timeout |
runtime_mode_on_demand |
On-demand mode starts idle without a model; if unsupported, records PREREQ |
best_effort_startup |
Start with nonexistent model under explicit best-effort policy — daemon stays alive rather than crashing |
fail_fast_startup |
Start with nonexistent model under explicit fail-fast policy — process exits promptly |
runtime_load_model |
Authenticated owner-control POST /api/runtime/control/load-model returns an accepted intent |
runtime_unload_model |
Authenticated owner-control POST /api/runtime/control/unload-model returns an accepted intent |
runtime_ensure_model |
Authenticated owner-control POST /api/runtime/control/ensure-model returns an accepted intent |
runtime_drain_model |
Authenticated owner-control POST /api/runtime/control/drain-model returns an accepted intent |
activity_override |
PUT /api/runtime/activity/override with bare JSON string "active", then DELETE, round-trips successfully |
privacy_no_raw_data |
Assert /api/runtime/activity exposes exactly effective_state, override_mode, and detector_category, each limited to its documented coarse enum |
clean_process_teardown |
After stopping, verify no leaked mesh-llm processes via tracked instance metadata or fallback pkill |
The lifecycle POST checks first read /api/runtime/control-bootstrap, then send
Content-Type: application/json requests to /api/runtime/control/{load,unload,ensure,drain}-model
with body {"endpoint":"<control-endpoint>","model":"qa.invalid/model@main:missing.gguf"}.
The harness expects HTTP 200, accepted=true, the same model, no instance_id,
and accepted_state of present, absent, present, and draining, respectively.
Print plan (side-effect-free)
scripts/qa-runtime-daemon-lifecycle.sh \
--current-binary ./target/debug/mesh-llm \
--print-plan
Produces compact JSON without starting any processes:
{
"base_port": 19740,
"checks": ["prereq.current-binary", "zero_model_serve_ready", ...],
"current_binary": "./target/debug/mesh-llm",
"evidence_dir": ".sisyphus/evidence",
"max_wait_seconds": 60,
"script": "qa-runtime-daemon-lifecycle.sh"
}
Evidence directory structure
Each run creates a timestamped directory:
.sisyphus/evidence/runtime-daemon-lifecycle-20260724T123456Z-1234/
manifest.json Run inputs and mode flags
commands.jsonl Commands executed by the harness and their logs
results.jsonl Machine-readable PASS/FAIL/PREREQ records
summary.md Human-readable final summary
summary.json Machine-readable final summary (overall: pass|fail|prereq)
versions/current.txt Captured binary version string
logs/*.log Per-test process output
status/*.json Collected /api/status payloads
control/*.json Lifecycle command responses
Prerequisites and failure modes
PREREQ is never reported as PASS. Checks that reach execution record missing
runtime prerequisites as PREREQ. An invalid --current-binary is rejected
earlier as a usage error (exit 2), before an evidence directory is created:
# Missing binary — exits 2 with an executable-path usage error and no evidence files
scripts/qa-runtime-daemon-lifecycle.sh \
--current-binary /nonexistent/path/mesh-llm \
--evidence-dir .sisyphus/evidence/prereq-test
test "$?" -eq 2
After any run, pgrep -f 'mesh-runtime-daemon-lifecycle' must find no leaked processes. The cleanup trap kills all harness-owned process trees and verifies zero survivors.
Mixed-version lifecycle certification
The mixed-version harness is extended to probe owner lifecycle commands
(load-model, unload-model, ensure-model, drain-model) and typed
unsupported behavior for new commands on released hosts, while preserving
all existing scan-refresh/public mesh checks.
Running the harness
# Mixed-version with lifecycle commands (local-only)
scripts/qa-control-plane-mixed-version.sh \
--released-binary ./target/qa/released/mesh-llm \
--current-binary ./target/debug/mesh-llm \
--local-only \
--evidence-dir .sisyphus/evidence/task-15-mixed-local
# Public mesh certification (preserves existing probes)
scripts/qa-control-plane-mixed-version.sh \
--released-binary ./target/qa/released/mesh-llm \
--current-binary ./target/debug/mesh-llm \
--require-public \
--evidence-dir .sisyphus/evidence/task-15-mixed-public
# Inspect planned checks (side-effect-free)
scripts/qa-control-plane-mixed-version.sh \
--released-binary ./target/qa/released/mesh-llm \
--current-binary ./target/debug/mesh-llm \
--local-only \
--print-plan | python3 -c "import json,sys; d=json.load(sys.stdin); [print(c) for c in d['checks'] if 'lifecycle' in c]"
New lifecycle probes (local mode only)
When --local-only is set, the harness adds five new checks after existing
owner-control bootstrap and scan-refresh tests:
| Check | Description | Expected result |
|---|---|---|
lifecycle-load-model |
Probe current-host owner lifecycle load command | PASS only when the authenticated command returns HTTP 200 with accepted=true |
lifecycle-unload-model |
Probe current-host owner lifecycle unload command | PASS only when the authenticated command returns HTTP 200 with accepted=true |
lifecycle-ensure-model |
Probe current-host owner lifecycle ensure command | PASS only when the authenticated command returns HTTP 200 with accepted=true |
lifecycle-drain-model |
Probe current-host owner lifecycle drain command | PASS only when the authenticated command returns HTTP 200 with accepted=true |
lifecycle-legacy-unsupported |
Send a lifecycle command through the current controller to the released target | PASS only for a structured response that explicitly reports unsupported/unknown command; a generic 404 is a failure |
run_local_direction and the config-current-controller setup assign the
available console ports to CURRENT_HOST_CONSOLE and RELEASED_HOST_CONSOLE;
the lifecycle probes do not scan ports. When CURRENT_HOST_CONSOLE is empty,
all five checks record PREREQ. When only RELEASED_HOST_CONSOLE is empty, the
four current-host lifecycle command probes still run and only
lifecycle-legacy-unsupported records PREREQ.
Existing behavior preserved
--require-public continues to produce PASS for existing public mesh probes (status, models, chat) without lifecycle command extensions — the --local-only flag gates the new probes so they do not affect public-mesh certification runs.
Expected outcomes
The local-only run produces PASS for owner-control/scan compatibility and typed unsupported for new lifecycle commands on released hosts. The --require-public run produces PASS for existing public mesh probes. Both mixed-version summary.json files must contain zero FAIL records when prerequisites are satisfied (PREREQ is acceptable for optional checks).
Failure mode: prerequisite verification
Run each harness with a nonexistent required binary or documented missing prerequisite:
# Missing released binary — exits nonzero with PREREQ in summary.json
scripts/qa-control-plane-mixed-version.sh \
--released-binary /nonexistent/mesh-llm \
--current-binary ./target/debug/mesh-llm \
--local-only \
--evidence-dir .sisyphus/evidence/prereq-test
# Verify summary shows prereq status, not pass
cat .sisyphus/evidence/control-plane-mixed-version-*/*/summary.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'overall={d[\"overall\"]}, counts={d[\"counts\"]}')"
# No leaked processes after failure
pgrep -f 'mesh-control-plane-mixed-version|task-15-' || echo "no leaks"
Single-machine testing with ephemeral keys
Set MESH_LLM_EPHEMERAL_KEY=1 to give a second process a unique identity on the same machine.
Without this, both processes share ~/.mesh-llm/key and appear as the same node.
14. Forced split on one machine
# Terminal 1: host with --split
mesh-llm serve --model Qwen2.5-3B --port 9337 --console 3131 --split
# Terminal 2: worker with ephemeral key
MESH_LLM_EPHEMERAL_KEY=1 mesh-llm serve --model Qwen2.5-3B --join <TOKEN> --port 9338 --console 3145 --split --max-vram 1
- Host starts solo, then re-elects with split when worker joins
- Worker receives a stage assignment and proxies API requests to the host
- Stage placement is proportional to VRAM (e.g.
0.98,0.02) - Kill worker → host detects via heartbeat (~60s), reverts to solo mode
Split startup + worker-loss recovery certification
For large-model/manual release QA, use the opt-in certification harness after building a binary and preparing a model or layer-package ref:
scripts/certify-split-startup-recovery.sh \
target/release/mesh-llm \
/absolute/path/to/model.gguf
The harness starts one seed and two workers with isolated homes, ephemeral mesh keys, private join-token bootstrapping, fixed local ports, and per-process runtime roots. It then proves:
- a multi-node split topology becomes routable via
/api/runtime/stagesand/v1/models; - an active downstream worker is terminated;
- the coordinator reaches the requested recovery outcome without using the killed worker.
By default the expected outcome is replacement:
MESH_SPLIT_CERT_EXPECT=replacement \
scripts/certify-split-startup-recovery.sh target/release/mesh-llm /models/qwen.gguf
Supported expectations are:
replacement— a new ready split topology appears without the killed node;withdraw— the split route is explicitly withdrawn after the worker loss;local-fallback— serving remains available through a single local route;any— accept the first stable explicit recovery state for exploratory QA.
Useful knobs:
MESH_SPLIT_CERT_WORKERS=2
MESH_SPLIT_CERT_SEED_MAX_VRAM=10
MESH_SPLIT_CERT_WORKER_MAX_VRAM=9,10
MESH_SPLIT_CERT_CTX_SIZE=2048
MESH_SPLIT_CERT_DISCOVERY_MODE=mdns
MESH_SPLIT_CERT_RUN_INFERENCE=1
MESH_SPLIT_CERT_KEEP_LOGS=1
MESH_SPLIT_CERT_WORK_DIR=target/split-recovery-cert/manual
MESH_SPLIT_CERT_PROCESS_ROOT=/tmp/mesh-split-cert-manual
Expected output includes machine-readable PASS/FAIL lines and a
result.jsonl evidence file in the work directory. The per-process homes and
runtime roots default to a short temp directory to avoid Unix socket path length
limits on macOS. The script never publishes a mesh and does not run by default
in CI.
15. Passive client on one machine
# Terminal 1: host
mesh-llm serve --model Qwen2.5-3B --port 9337
# Terminal 2: client (the client surface uses an ephemeral key automatically)
mesh-llm client --join <TOKEN> --port 9338
- Client connects without gossip (no peer list entry on host)
/v1/modelsreturns models from routing table- Inference routes through QUIC tunnel to host
- Host does NOT see client in its peer list (zero per-client state)
16. Release host/runtime/product boundary
For release, installer, SDK, or packaging changes, validate the three layers without relying on a pre-existing user runtime:
just release-host-build
just release-runtime-build metal # choose the platform backend
product_out="$(mktemp -d)"
just release-bundle "v$(./target/release/mesh-llm --version | awk '{print $NF}')" "$product_out"
Required evidence:
scripts/verify-host-dependencies.py target/release/mesh-llmreports no rejected backend imports.- Extracting the product archive yields one host, one runtime tree,
product-manifest.json, andhost-imports.json; all recorded digests match. - With isolated
HOME, XDG cache, andMESH_LLM_NATIVE_RUNTIME_CACHE_DIR, the extracted host passes--version,--help, andruntime list. The adjacent runtime is listed and the isolated cache remains empty. - A product with an incompatible MeshLLM version or Skippy ABI is rejected before loading.
client --auto --log-format json --no-consolestarts without a native runtime or GPU driver, emits a real ready event, and the noninteractive service probe stops cleanly on SIGTERM (Unix) or CTRL_BREAK_EVENT (Windows).
Run the command-surface smoke on every product platform without device passthrough. Separately qualify backend loading and a minimal operation on suitable CUDA, ROCm, Vulkan, or Metal hardware. Preserve unique temp paths and ports and verify process/listener cleanup.
Deploy to remote node
just bundle
# scp, then on remote:
codesign -s - ~/mesh-bundle/mesh-llm
xattr -cr ~/mesh-bundle/mesh-llm
Must codesign + xattr after every scp or macOS kills the binary (exit 137).
Cleanup
mesh-llm stop || pkill -f mesh-llm
Prefer mesh-llm stop for tracked local instances. If the runtime is wedged,
kill any remaining mesh-llm process and then verify no stale process is still
bound to the test ports.