feat(introspection): ADR-099 midstream tap + /ws/introspection + /api/v1/introspection/snapshot#554
Merged
Merged
Conversation
…ion + low-latency tap (Proposed)
ADR-098 rejected midstream as a *replacement* for RuView's existing seams.
ADR-099 is the other half: midstream's `temporal-compare` (DTW) and
`temporal-attractor-studio` (Lyapunov + regime classification) crates as a
*parallel* per-frame introspection tap, alongside the existing window-aggregated
event pipeline.
The 8 decisions:
D1 — Only midstreamer-temporal-compare 0.2 + midstreamer-attractor 0.2;
scheduler / neural-solver / strange-loop are out of scope of this ADR.
D2 — Tap point: post-validate, parallel to WindowBuffer::push in csi.rs.
The existing /ws/sensing path is unchanged.
D3 — New /ws/introspection topic + /api/v1/introspection/snapshot REST endpoint
carrying IntrospectionSnapshot { regime, lyapunov_exponent,
attractor_dim, top_k_similarity }.
D4 — Per-frame updates only, never window-blocked. Soonest-event latency on
the "shape recognized" path collapses from ~533 ms (16-frame @ 30 Hz
window) to ~33 ms (one frame), a ~16× win.
D5 — temporal-neural-solver (LTL) is out of scope (separate MAT audit ADR).
D6 — ESP32 firmware unchanged; deployment is host-side only.
D7 — Signature library is JSON, on-disk, customer-owned; three reference
signatures ship as developer fixtures.
D8 — Promotion bar is empirical: ≥10× p99 latency reduction vs. the existing
/ws/sensing event path, or the feature stays behind a CLI flag.
Indexed in docs/adr/README.md. Phased adoption (P0 spike + benchmark → P1 first
real signature library → P2 dashboard widget → P3 capture workflow → P4 optional
adaptive_classifier hook). Implementation lands as ~150–250 lines + one
integration test in v2/crates/wifi-densepose-sensing-server in follow-up PRs.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds the per-frame introspection state that ADR-099 specifies, plus the two
midstream dependencies. Pure addition — no other code touched.
v2/crates/wifi-densepose-sensing-server/Cargo.toml
+ midstreamer-temporal-compare = "0.2"
+ midstreamer-attractor = "0.2"
v2/crates/wifi-densepose-sensing-server/src/introspection.rs (new, 530 lines)
pub struct IntrospectionState
├─ midstreamer-attractor's AttractorAnalyzer (regime + Lyapunov)
├─ SignatureLibrary (JSON-loaded labelled segments)
├─ VecDeque<f64> sliding amplitude buffer (default 128 points)
└─ update(timestamp_ns, derived_feature) — never window-blocked
+ snapshot() -> IntrospectionSnapshot
{ timestamp_ns, frame_count, regime, lyapunov_exponent,
attractor_dim, attractor_confidence, top_k_similarity }
pub enum Regime { Idle, Periodic, Transient, Chaotic, Unknown }
pub struct Signature { id, label, vectors, dtw, promotion_threshold }
pub struct SimilarityMatch { signature_id, score, above_threshold }
DTW path is currently a host-side stand-in (length-normalised L1 with the
real DTW call deferred to I3/I5 once vec128 embeddings exist — ADR-099 P1).
The attractor path is wired to midstream directly. The analyze() step only
runs every N frames (default 8) to stay under the per-frame ms budget.
8 unit tests (snapshot defaults, frame-count + timestamp advance, empty
library, scoring + ordering invariants, threshold gating, empty-signature
fault-tolerance, regime classification after 200 frames). 199 → 207 lib tests,
0 failures. cargo build clean (only pre-existing warnings).
Co-Authored-By: claude-flow <ruv@ruv.net>
…ion + /api/v1/introspection/snapshot
I3 (per ADR-099). Three changes in main.rs:
1) AppStateInner: + intro: IntrospectionState + intro_tx: broadcast::Sender<String>
(256-slot ring, same shape as the existing tx).
2) ESP32 frame path: after the global frame_history push, before the
per-node mutable borrow of s.node_states, compute the per-frame derived
feature (mean amplitude across subcarriers), call s.intro.update(ts_ns,
feature), and broadcast the snapshot JSON to s.intro_tx. Placement is
deliberate — between the global state's mutable touch and the per-node
&mut so borrow-checking stays linear; ns is borrowed *after* the tap
completes its s.intro / s.intro_tx access.
3) Routes:
ws_introspection_handler → /ws/introspection
api_introspection_snapshot → /api/v1/introspection/snapshot
Same Axum + tokio::sync::broadcast pattern as ws_sensing_handler,
subscribed against s.intro_tx. Wrapped by the bearer-auth middleware
already on /api/v1/* — orchestrator probes and unauthenticated /ws/sensing
reachers continue to land on the existing topic.
Verified:
cargo build -p wifi-densepose-sensing-server --no-default-features ✓
cargo test -p wifi-densepose-sensing-server --no-default-features
lib: 207 passed, 0 failed (199 pre-tap + 8 introspection)
integration suites: 70, 8, 16, 18 passed, 0 failed
cargo clippy: clean on the introspection surface (pre-existing warnings
on -core / -ruvector / -signal unchanged).
Co-Authored-By: claude-flow <ruv@ruv.net>
…seline
I5. Measures the architectural latency floor of the introspection path
vs. the window-aggregated event path, plus the per-frame update cost.
Result on this run:
ADR-099 D8 floor ratio : 3.20× (16 frames / 5 frames)
D8 target ≥10× — NOT YET MET on the host-side
L1 stand-in scoring; I6 closes the gap.
ADR-099 D4 update p50/p99 : 0.001 ms / 0.012 ms (~83× under the 1 ms
budget on a desktop runner; even with thermal
throttling on a Pi 5 we have orders of
magnitude of headroom).
Regime after 200 frames : Idle, lyapunov=-2.32, confidence=1.0
(attractor analyzer is firing as designed).
The D8 gap is structural to the current scoring: signature_score() uses a
length-normalised L1 over the trailing window, which requires roughly the
full signature length of in-shape frames before crossing
promotion_threshold. Closing it is the I6 work — swap in the real
midstreamer-temporal-compare DTW (partial-match scoring) and/or surface
the attractor's regime-change as an *earlier* trigger than full signature
match.
The latency-ratio test asserts a regression bar (≥3.0×) on the L1 baseline,
prints the D8 ratio + whether it's met, and explicitly defers the ≥10×
target to I6 in the docstring. Better empirical reporting than a flag that
silently fails until tuned.
ESP32 sanity (independent of the benchmark): COM7 device alive at csi_collector
cb #84500 (~30 min uptime), len=128/256 HT20/HT40, ch5, RSSI swings -44 to
-79 (= real motion in the room). UDP target still unreachable from this
host per the earlier diagnosis; that's a deployment fix, not a measurement
gate.
Co-Authored-By: claude-flow <ruv@ruv.net>
… honest ADR-099 D8 amendment
Three threads in this commit:
1) Per-frame attractor analysis (default analyze_every_n: 8 → 1).
The I5 benchmark put per-frame update at 0.012 ms p99 — 83× under D4's
1 ms budget. The cost case for the every-8th-frame default doesn't hold;
per-frame analysis is what makes regime_changed a viable early-detection
trigger.
2) New `regime_changed: bool` field in IntrospectionSnapshot — flips on any
frame whose attractor regime classification differs from the previous
frame's. Pairs with top_k_similarity (full-shape match) to give
downstream consumers two latencies with different robustness profiles.
3) Honest amendment of ADR-099 D8 to reflect empirical reality:
- L1 stand-in achieves 3.20× ratio (5-frame shape match vs 16-frame
event-path floor); the 10× aspirational bar is architecturally
unreachable at 1-D scalar feature resolution.
- regime_changed didn't fire in the 10-frame motion window — the
200-frame noise trajectory dominates the Lyapunov classification, and
short perturbations don't shift the regime fast enough on a scalar
feature.
- Path to 10×: ADR-208 Phase 2 (Hailo NPU vec128 embeddings) — multi-dim
partial matches discriminate from noise in 1-2 frames, not 5.
- Side finding: midstream temporal-compare::DTW uses *discrete equality*
cost (designed for LLM tokens), not numeric distance — swapping it in
for f64 amplitude scoring would be strictly worse than the L1 stand-in.
A numeric DTW is a separate concern (hand-roll or new crate).
- Revised D8: ship behind --introspection (off by default) until multi-
dim features land. Per-frame update budget IS met (0.041 ms p99 in this
bench, ~24× under the 1 ms bar) — the feature is cheap enough to
carry dark today.
cargo test -p wifi-densepose-sensing-server --no-default-features:
introspection (lib): 8 passed, 0 failed
introspection_latency (test): 5 passed, 0 failed (incl. new
regime_change_path_latency)
clippy: clean on the introspection surface (pre-existing approx_constant
lints in pose.rs / main.rs unchanged).
Co-Authored-By: claude-flow <ruv@ruv.net>
Lists the new `/ws/introspection` + `/api/v1/introspection/snapshot` endpoints, the empirical baseline (0.041 ms p99 update, 5-frame shape match on 1-D L1 stand-in), and the honest D8 amendment. Co-Authored-By: claude-flow <ruv@ruv.net>
Open
8 tasks
Owner
Author
|
Companion issue tracking the v0.8.0 capability surface, measured baseline, and the remaining work to close ADR-099 D8 (10× ratio): #556. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds midstream as a parallel tap alongside RuView's existing event pipeline — not a replacement, no behaviour change to
/ws/sensingorwifi-densepose-signal. Two new endpoints (off by default,--introspection):GET /ws/introspection— newline-delimited JSON snapshots streamed at CSI frame rate.GET /api/v1/introspection/snapshot— single-shot snapshot, auth-gated whenRUVIEW_API_TOKENis set.Each snapshot carries
frame_count,regime(Idle / Periodic / Transient / Chaotic / Unknown),lyapunov_exponent,attractor_dim,attractor_confidence,regime_changed(boolean — flips on first frame after a regime transition),top_k_similarity[](highest-scoring signature matches).What's in this PR
900b877c694ef12524introspectionmodule skeleton (530 lines, 8 lib tests)4a1f3a1e1main.rs+/ws/introspection+/api/v1/introspection/snapshot59d2d0e54tests/introspection_latency.rs— empirical baseline against ADR-099 D4/D8ca9752764regime_changedsignal + per-frame analyze + honest D8 amendmentce3304222[Unreleased]Empirical baseline (ADR-099 §D4 + §D8)
Measured on this branch via
tests/introspection_latency.rs(200 frames of noise warm-up → 5-frame motion-ramp signature, host-side L1 stand-in scoring on a 1-D mean-amplitude feature):top_k_similarity[0].above_thresholdregime_changed(10-frame motion window)update()p99Honest D8 amendment: the aspirational 10× ratio is architecturally unreachable on 1-D scalar features. Closing the gap requires ADR-208 Phase 2 (Hailo NPU
vec128embeddings) so multi-dim partial matches can discriminate from noise in 1–2 frames rather than 5. Until that lands, the tap ships off by default behind--introspection— D4 (per-frame budget) is met today, D8 (10× ratio) is contingent on ADR-208 P2.Side finding (documented in ADR-099 D8): midstream's
temporal-compare::dtw()uses discrete equality cost (designed for LLM tokens —0ifseq1[i].value == seq2[j].valueelse1). Onf64amplitude values that is strictly worse than the L1 stand-in. The L1 stand-in is what produces the 5-frame number; swapping in midstream DTW is not the path to closing D8. A numeric DTW is a separate concern (hand-roll or new crate).I7 validation
cargo test --workspace --no-default-featurespython archive/v1/data/proof/verify.py8c0680d7…51c6matches expectedcargo test -p wifi-densepose-sensing-server --no-default-features --lib introspectioncargo test -p wifi-densepose-sensing-server --no-default-features --test introspection_latencyWhat's NOT in this PR
v0.6.4-esp32). Firmware versioning is unchanged./ws/sensingorwifi-densepose-signal— ADR-098 already settled that those existing seams are correct; this ADR is the parallel-addition counterpart, not a substitution.Files
docs/adr/ADR-099-midstream-introspection-tap.md— 242 lines, D8 reflects measured reality.v2/crates/wifi-densepose-sensing-server/src/introspection.rs— 578 lines, 8 unit tests.v2/crates/wifi-densepose-sensing-server/src/main.rs— +119 lines (state wiring, WS handler, snapshot endpoint, route registration).v2/crates/wifi-densepose-sensing-server/src/lib.rs—pub mod introspection;v2/crates/wifi-densepose-sensing-server/Cargo.toml—midstreamer-temporal-compare = "0.2",midstreamer-attractor = "0.2".v2/crates/wifi-densepose-sensing-server/tests/introspection_latency.rs— 288 lines, 5 tests.CHANGELOG.md— entry under[Unreleased].Closes / relates to
🤖 Generated with claude-flow