Skip to content

controller: port handle_bootstrap_agent_config to typed-stage-split path - #329

Merged
zzylol merged 1 commit into
mainfrom
mvp/port-bootstrap-to-typed-stage-split
May 7, 2026
Merged

zzylol merged 1 commit into
mainfrom
mvp/port-bootstrap-to-typed-stage-split

Conversation

@zzylol

@zzylol zzylol commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Ports handle_bootstrap_agent_config (the static /api/v1/collector-config/agent URL each fresh agent fetches at boot) off the legacy generate_agent_config path and onto the typed L5 emit pipeline that handle_plan already uses — gated on USE_TYPED_STAGE_SPLIT=1.
  • Wires the X-Agent-Runtime header per Phase ε.1.5 (Sketchcollector / Sketchotap / Sketchtelegraf, defaults to Sketchcollector); also honours an optional X-Agent-ID header so a previously-pinned agent gets the same workload its replanner mapping records — bootstrap and the first OpAMP push converge.
  • Backwards-compat: when the gate is off OR the typed path can't resolve a workload (empty registry, unsupported topology), falls back to the legacy generate_agent_config emit. Bootstrap never returns 500 just because the typed path hit a gap.

Why this matters

Without this fix, a fresh agent connecting at startup misses Phase 3.2.5's gorillas3 archive emit + warm-passthrough routing processor and the Phase ε.1.5 per-runtime dispatch — those only arrive once handle_plan is later invoked. Phase 3.3 (#94) worked around this by having the demo driver POST /api/v1/plan after stack-up; the driver POSTs stay as a safety belt (they exercise the typed-backend JSON push that the bootstrap GET doesn't fire), but the typed pipeline now runs at fetch time too.

Test plan

  • cargo test --release -p controller — 558 passing, 10 pre-existing failures unchanged
  • New tests cover all four behaviours:
    • bootstrap_legacy_path_when_env_unset — env unset → legacy ddsketch: block
    • bootstrap_typed_path_when_env_set — env set + registered workload → typed ddsketchprocessor: (proves Phase 3.2.5's emit_edge_yaml is on the path)
    • bootstrap_typed_path_sketchotap_runtime_dispatchX-Agent-Runtime: sketchotapotel_dataflow/v1 DAG YAML
    • bootstrap_typed_path_sketchtelegraf_runtime_dispatch → Telegraf TOML
    • bootstrap_typed_path_falls_back_to_legacy_when_no_workload — typed gate on + empty registry → legacy fallback (no 500)

Files changed

  • controller/src/main.rs — handler rewrite + new emit_bootstrap_typed helper + 5 unit tests + test-router wiring
  • docs/control-plane-design.md — new "Bootstrap and plan-push converge on the same emit pipeline" subsection
  • docs/system-overview.md — new "Bootstrap fetch surface" entry under §6 Push surfaces
  • deploy/scripts/run_mvp_demo.sh — comment now describes the driver POST as a safety belt rather than a load-bearing step

🤖 Generated with Claude Code

Phase 3.3 (#94) worked around the bootstrap-vs-replan-push divergence
by adding a driver-side POST to /api/v1/plan after stack-up. This is
the deep fix: handle_bootstrap_agent_config now mirrors handle_plan's
typed L5 pipeline under USE_TYPED_STAGE_SPLIT=1 — bind_workload_typed
→ split_typed_three_stage → emit_for_runtime, dispatched by the
X-Agent-Runtime header (defaults to Sketchcollector for legacy
agents). Pinned-plan lookup via X-Agent-ID + the replanner's
agent_to_metric mapping avoids drift between bootstrap and the first
OpAMP push.

A fresh agent fetching its initial config now sees the Phase 3.2.5
gorillas3 archive emit + warm-passthrough routing processor and the
Phase ε.1.5 per-runtime dispatch without requiring the demo driver's
explicit POST. The driver POSTs stay as a safety belt (they still
exercise the typed-backend JSON push that the bootstrap GET doesn't
fire).

Backwards-compat: when USE_TYPED_STAGE_SPLIT is unset or the typed
path can't resolve a workload (empty registry, unsupported topology)
the handler falls back to the legacy generate_agent_config emit.
Bootstrap never returns a 500 just because the typed path hit a gap.

Tests:
- bootstrap_legacy_path_when_env_unset — env unset → legacy ddsketch:
- bootstrap_typed_path_when_env_set — env set + workload → typed
  ddsketchprocessor:
- bootstrap_typed_path_sketchotap_runtime_dispatch — X-Agent-Runtime:
  sketchotap → otel_dataflow/v1 DAG YAML
- bootstrap_typed_path_sketchtelegraf_runtime_dispatch →
  Telegraf TOML
- bootstrap_typed_path_falls_back_to_legacy_when_no_workload — typed
  gate on but empty registry → legacy fallback (no 500)

cargo test --release: 558 passed, 10 pre-existing failures unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit e80a522 into main May 7, 2026
zzylol added a commit that referenced this pull request May 8, 2026
…rt + archive-tier accuracy ground truth (#332)

This is the Option-B-as-sole-path validation re-run. With PR #329
(controller bootstrap GET → typed-stage-split) merged, the driver's
post-stack-up `POST /api/v1/plan` workaround (PR #328) is redundant
for the bootstrap path. Default it OFF so we can verify the bootstrap
GET actually carries the load; operators can keep the safety belt by
setting `ENABLE_OPTION_A_DRIVER_POST=1`.

Three coupled fixes also land here so the demo's verdict matrix
returns to all-green:

1. Backend container was crashing immediately on startup because
   `deploy/docker-compose/base.yml` passed `--ingest-port=9090` to a
   binary (`precompute_engine`, the deployed Dockerfile.backend
   target) that has no such flag — clap exited with `unexpected
   argument` and every subsequent Connection refused / failed POST
   from the controller cascaded from there. Drop the invalid flag and
   the redundant `19090:9090` host publish (Prometheus remote-write
   was deleted in ASAPQuery-backend PR #100; OTLP-only ingest goes
   over 4317 intra-docker).

2. `accuracy_reduce.py` is ported off the deleted
   `/var/asap/cold/raw/` JSONL tee onto the archive-engine-via-header
   path: `--cell-dir` re-issues each replay PromQL with
   `X-ASAP-Engine: thanos_archive` (Step 2.3 / PR #97) for ground
   truth. The driver's accuracy phase is rewritten to drop the
   `docker cp cold-truth/` step that was failing silently and
   skipping the entire reduce. The legacy JSONL path is kept behind
   `--use-jsonl` / `--use-jsonl-truth`.

3. STATUS detection in `capture_emitted_configs()` recognises the
   bootstrap-time typed marker
   (`[USE_TYPED_STAGE_SPLIT] emitted bootstrap config from typed path`)
   so a successful Option-B bootstrap renders as `live-bootstrap`
   even when the Option-A POST is gated off and `pushing typed`
   never fires.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 8, 2026
… pipeline (#334)

`Replanner::push_config_to_agent` (and the agent-config push in
`replan_metric`) called the legacy `generate_agent_config` emitter,
which produces a single-pipeline DDSketch YAML with no `gorillas3`
archive write, no OTTL `routing` processor, and no
`metrics/warm_passthrough` pipeline. Agents that connected via the
OpAMP `on_connect` callback (the post-#333 reconnection path) got
that legacy YAML — so freshness-probe routing reached fresh-connect
agents (criterion ⑥ on issue #46) but NOT plan-pinned reconnecting
agents.

Mirror `main::emit_bootstrap_typed`'s flow on the OpAMP push path:
`bind_workload_typed` → `split_typed_three_stage` → pick the Edge
stage → extend with freshness-probe + workload-registry archive
metrics → `emit_for_runtime`. Gate it behind the existing
`USE_TYPED_STAGE_SPLIT` env var so the legacy single-pipeline
fallback still runs when the gate is off, matching `handle_plan` /
`handle_bootstrap_agent_config`. On any typed-emit error the push
falls back to the legacy emitter so it never silently drops.

The bootstrap-scope demo plumbing (freshness probes +
workload-registry archive metrics) is lifted out of
`emit_bootstrap_typed` into a shared `config::extend_edge_with_demo_plumbing`
helper; both bootstrap and replan now apply the SAME extension. The
runtime defaults to `AgentRuntime::AsapOtel` since OpAMP `on_connect`
doesn't surface the agent's runtime header today (matches the
bootstrap default for legacy clients).

Adds two unit tests in `replan::tests`:
- `typed_replan_emit_includes_freshness_probe_routing` —
  asserts the typed path emits `gorillas3`, `routing`,
  `metrics/warm_passthrough`, and references `http_freshness_probe_warm`.
- `legacy_path_omits_typed_processors_when_gate_off` — pins the
  gate semantics so a regression that always-on'd the typed path
  would surface here.

Together with PR #329 (bootstrap GET path) this finishes the
OpAMP-on-connect side of the typed emit so reconnecting agents now
receive the same routed YAML as fresh-connect agents.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 8, 2026
Activates the typed-stage-split path for every compose invocation that
includes base.yml, so the demo (and any ad-hoc `docker compose up
controller` against base alone) routes through the typed pipeline:
bootstrap GET (#329), OpAMP-on-connect replan (#334), and per-metric
5-sketch family routing (#339 + #340 + the in-flight stitching PR).

The mvp-multi-stage.yml overlay already sets the same default; this
just promotes the gate to the base layer so it is on without the
overlay.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the mvp/port-bootstrap-to-typed-stage-split branch May 9, 2026 18:00
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