mvp v6 phase C: AgentRole::Gateway + BackendClient on AppState - #298
Merged
Merged
Conversation
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>
This was referenced May 6, 2026
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>
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
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::Gatewaydidn't exist and (2)
BackendClientlived only onReplanner.AgentRole::Gatewayvariant +gatewayheader parser; existingAgent/Backendarms unchanged.from_headeris now public.BackendClientto a sharedOption<Arc<BackendClient>>builtonce in
main()and cloned into bothReplanner(existing YAMLpath, unchanged) and
AppState(new Phase C JSON path).StageConfig::Gateway→emit_gateway_yaml(...)→push_to_role(AgentRole::Gateway, ...).StageConfig::Backend→emit_backend_config_json(...)→new
BackendClient::post_streaming_config_json(...)(mirrors theexisting YAML method, content-type
application/json).Behaviour unchanged when
USE_TYPED_STAGE_SPLITis unset (stillgated). Without
CONTROLLER_BACKEND_ENDPOINT, the backend JSON pushsilently 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 thePhase 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.
opamp::role_from_header_recognises_gatewayopamp::gateway_role_round_trips_through_connectionopamp::push_to_role_gateway_routes_only_to_gateway_roleapi_tests::app_state_backend_client_none_by_defaultapi_tests::app_state_backend_client_some_when_constructed_with_urlbackend_client::json_post_round_trips_bodybackend_client::json_post_non_2xx_is_errorPhase D / E hand-off
AppState.backend_client: Option<Arc<BackendClient>>is the newshared knob — read it via
st.backend_client.as_ref().BackendClient::post_streaming_config_json(json: String)is the newmethod (alongside the existing
push_streaming_config(yaml)).main.rsstill hardcodesfirst_for_role("agent"); gateway-role collectors today don't pull aregistry assignment on connect — they receive YAML through the
stage_split push only. Phase D / E can extend this if needed.
🤖 Generated with Claude Code