Skip to content

mvp v6 phase C: AgentRole::Gateway + BackendClient on AppState - #298

Merged
zzylol merged 3 commits into
mainfrom
mvp/v6-phase-c-role-plumbing
May 6, 2026
Merged

zzylol merged 3 commits into
mainfrom
mvp/v6-phase-c-role-plumbing

Conversation

@zzylol

@zzylol zzylol commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase C of the MVP v6 plan: closes the two stubs Phase B (#297) left
behind. The typed L5 stage_split path emits per-stage runtime configs
for edge / gateway / backend, but only the edge YAML actually pushed —
the other two were info!-logged because (1) AgentRole::Gateway
didn't exist and (2) BackendClient lived only on Replanner.

  • Adds AgentRole::Gateway variant + gateway header parser; existing
    Agent / Backend arms unchanged. from_header is now public.
  • Lifts BackendClient to a shared Option<Arc<BackendClient>> built
    once in main() and cloned into both Replanner (existing YAML
    path, unchanged) and AppState (new Phase C JSON path).
  • Wires StageConfig::Gatewayemit_gateway_yaml(...)
    push_to_role(AgentRole::Gateway, ...).
  • Wires StageConfig::Backendemit_backend_config_json(...)
    new BackendClient::post_streaming_config_json(...) (mirrors the
    existing YAML method, content-type application/json).

Behaviour unchanged when USE_TYPED_STAGE_SPLIT is unset (still
gated). Without CONTROLLER_BACKEND_ENDPOINT, the backend JSON push
silently no-ops (same fire-and-forget contract as Phase B).

3 commits, 3 files, +307/-30 LOC.

Test plan

  • cargo test -p controller — 482 pass / 10 fail; the 10 are the
    Phase A pre-existing failures (analyzer json forward-compat,
    planner workload_cost, intent_algebra lower_promql_cardinality,
    and four WS protobuf-decode tests). Not touched in this PR.
  • 7 new tests pass:
    • opamp::role_from_header_recognises_gateway
    • opamp::gateway_role_round_trips_through_connection
    • opamp::push_to_role_gateway_routes_only_to_gateway_role
    • api_tests::app_state_backend_client_none_by_default
    • api_tests::app_state_backend_client_some_when_constructed_with_url
    • backend_client::json_post_round_trips_body
    • backend_client::json_post_non_2xx_is_error

Phase D / E hand-off

  • AppState.backend_client: Option<Arc<BackendClient>> is the new
    shared knob — read it via st.backend_client.as_ref().
  • BackendClient::post_streaming_config_json(json: String) is the new
    method (alongside the existing push_streaming_config(yaml)).
  • The on_connect callback in main.rs still hardcodes
    first_for_role("agent"); gateway-role collectors today don't pull a
    registry assignment on connect — they receive YAML through the
    stage_split push only. Phase D / E can extend this if needed.

🤖 Generated with Claude Code

zzylol and others added 3 commits May 6, 2026 17:00
Phase B left gateway YAML pushes as info!-logged because no AgentRole
existed for the mid-tier collector. Phase C extends the OpAMP role
vocabulary with `Gateway` so the typed L5 stage_split path can route
the gateway YAML directly via push_to_role, identical to how it
already routes Agent and Backend.

The from_header parser is now public (used by tests) and recognises
"gateway" case-insensitively; unknown values still fall back to Agent
so legacy / mis-configured collectors keep working unchanged.

Tests: gateway round-trips through serde and through a real WebSocket
connect path (verified via connected_agents_with_roles).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase B's typed L5 stage_split path can only info!-log the backend
JSON it emits because the BackendClient that knows how to POST to
ASAPQuery-backend's /api/v1/streaming-config lives only on the
Replanner. Phase C lifts the client out of Replanner-local state into
a shared `Option<Arc<BackendClient>>` built once in main(), passed by
clone to both the Replanner (existing path: post a YAML on every
replan) and AppState (Phase C path: post the typed L5 backend JSON
from handle_plan, wired in the next commit).

Picked the Arc-shared-reference approach over moving ownership: the
diff is smaller (no Replanner API churn — `with_backend_client` is
unchanged) and lets either side push without needing back-references.

Tests: AppState's backend_client is None by default (preserves the
no-endpoint silently-skip contract) and Some when constructed with a
URL — the production path that reads CONTROLLER_BACKEND_ENDPOINT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase B's typed L5 stage_split path produces three per-stage configs
but only the edge YAML reached collectors. The Gateway and Backend
arms of the StageConfig match in handle_plan were info!-logged
because the role / client wiring didn't exist yet. Phase C closes
both gaps:

1. StageConfig::Gateway → emit_gateway_yaml + push_to_role(Gateway).
   Mirrors the existing edge push exactly, now that AgentRole::Gateway
   is in the OpAMP role vocabulary (commit 1).

2. StageConfig::Backend → emit_backend_config_json + new
   BackendClient::post_streaming_config_json. The typed L5 emitter
   produces a serde_json::Value (vs the YAML the existing replanner
   path pushes), so the new method POSTs application/json with the
   same 2xx-or-error contract as push_streaming_config. Skips silently
   when CONTROLLER_BACKEND_ENDPOINT is unset.

Existing behaviour is unchanged when USE_TYPED_STAGE_SPLIT is unset —
the typed branch still gates on planner::stage_split::typed_stage_split_enabled().

Tests:
- backend_client: json_post_round_trips_body + json_post_non_2xx_is_error
- opamp: push_to_role_gateway_routes_only_to_gateway_role
  (verifies routing — does not decode the protobuf, sidestepping a
   pre-existing decode-tag-zero issue in sibling tests)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit 274e98e into main May 6, 2026
@zzylol
zzylol deleted the mvp/v6-phase-c-role-plumbing branch May 9, 2026 18:00
zzylol added a commit that referenced this pull request May 19, 2026
… ASAPQuery-backend #299)

Static bootstrap counterpart to ASAPQuery-backend PR #299. Without
this static fix, the asap-otel agent's BOOTSTRAP config (used before
the controller's OpAMP push lands) lacks cumulativetodelta and sends
cumulative-temporality Counter values to the backend's SumAccumulator
— triggering the ~300× per-window quadratic blowup that bug #298
documented. After OpAMP push (typed-stage-split emit from #299), the
running config has it; this static yaml just keeps the pre-OpAMP
boot window correct too.

`match_type: strict` keeps the processor a no-op for gauges
(http_requests_total_latency_ms etc. — quantile workloads unaffected).
b0/b1 baseline configs are intentionally untouched: VictoriaMetrics
expects cumulative (Prometheus convention).

Closes the ASAPCollector half of #298.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 19, 2026
…ativetodelta + seed) (#398)

* fix(deploy): cumulativetodelta upstream of agent routing (paired with ASAPQuery-backend #299)

Static bootstrap counterpart to ASAPQuery-backend PR #299. Without
this static fix, the asap-otel agent's BOOTSTRAP config (used before
the controller's OpAMP push lands) lacks cumulativetodelta and sends
cumulative-temporality Counter values to the backend's SumAccumulator
— triggering the ~300× per-window quadratic blowup that bug #298
documented. After OpAMP push (typed-stage-split emit from #299), the
running config has it; this static yaml just keeps the pre-OpAMP
boot window correct too.

`match_type: strict` keeps the processor a no-op for gauges
(http_requests_total_latency_ms etc. — quantile workloads unaffected).
b0/b1 baseline configs are intentionally untouched: VictoriaMetrics
expects cumulative (Prometheus convention).

Closes the ASAPCollector half of #298.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(demo): accuracy-validation harness — EXPORTER_SEED + baseline PRW config + per-series quantile

Bundle of supporting changes for apples-to-apples accuracy validation
of asap-tier vs baseline (b0/b1 VictoriaMetrics). Lands alongside the
quantile-shape + cumulativetodelta engine fixes (ASAPQuery-backend
PR #297 + #299, ASAPCollector cumulativetodelta commit above).

Changes:

1. **fake-exporter/main.go** — EXPORTER_SEED env var seeds per-series
   PRNG (math/rand) deterministically so all 3 arms in a sequential
   run emit identical latency value sequences. Without it, cross-arm
   sampling noise masked DDSketch ε behavior in the accuracy report
   (b0 vs b1 differed by 0.04-0.4% just from random draws, not engine
   error). Default seed of 42 in run_demo.sh; back-compat when unset
   (auto-random, original behavior). Per-series PRNG uses
   `seed ^ hash(EXPORTER_PRODUCER_ID) ^ (seriesIdx+1)*prime` so
   distinct producers + series get distinct sequences.

2. **b0/b1 PRW exporter config — add_metric_suffixes: false** —
   VictoriaMetrics' OTLP→PRW path appends `_milliseconds` to metric
   names with `WithUnit("ms")` annotation (Prometheus naming convention).
   Asap tier preserves the original name. Without this fix, the
   accuracy comparison probe queries `http_requests_total_latency_ms`
   land on different metric names per tier — apples-to-oranges.

3. **mvp-workload.yaml** (singlenode + multinode) — dropped
   `grouping_labels: [zone]` from quantile-metric entries
   (http_requests_total_latency_ms, request_size_bytes). Per-series
   DDSketch / KLL sketches preserve PromQL's per-series semantics so
   `quantile_over_time(...)` returns comparable per-series rows in
   both asap and baseline. Counter-metric entries retain
   grouping_labels: [zone] (Sum aggregation is mergeable; no semantic
   asymmetry there). The KLL override on the latency entry is also
   tracked here for the DDSketch-vs-KLL accuracy comparison documented
   in mvp_smoke_test_findings.md.

4. **run_demo.sh** — pass EXPORTER_SEED=42 (default) to all producers.

Accuracy results post all fixes (multinode all-arms):
- p50 quantile: 0.4-0.6% rel-err vs baseline ✅
- p99 quantile: 11-12% rel-err (DDSketch ε + temporal-window variance)
- max by (zone) (quantile_over_time(...)): 8-9% rel-err (DDSketch)
- sum-by-zone/rate/topk: post-#299 returns delta-window semantics
  (not directly comparable to baseline cumulative as raw numbers, but
  bug-correct; ratio drops from ~300× to ~1× when normalized).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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