eval(E0): wire workloads + controller-backend link + plan_transition fix - #261
Merged
Merged
Conversation
Lands four fixes in one bundle so the E0 single-cell smoke produces
non-empty answers and exercises the P1-P9 path end-to-end:
1. plan_transition.py: rename `_stop` -> `_stop_event` so it doesn't
collide with Python 3.12's private Thread._stop() method, which
Thread._wait_for_tstate_lock invokes during join() and raised
`TypeError: 'Event' object is not callable`.
2. deploy/configs/workloads.yaml (new): a 4-entry registry matching
the queries-e2e.json families (latency quantile, count_unique by
zone, sum, topk) over the metrics fake-exporter actually emits
(`http_requests_total` + `http_requests_total_latency_ms`).
Without this the controller logged "workloads file not found;
using empty registry" and never published a plan.
3. controller exposes `asap_active_plan_id{metric, plan_id="<hash>"}`
per-plan in `metrics_exposer.rs`, so plan_transition.py and
promql_replay.py's PlanIdTracker have something to poll. The
plan_id is a stable hash of the plan's sketch_type / mode /
delta_transmission / window / backend merge / valid_until, so a
re-plan flips the label and the tracker observes the transition.
4. e2e-overlay.yml: mounts workloads.yaml into the controller at
/etc/asap/workloads.yaml, sets `CONTROLLER_WORKLOADS` to point at
it, and sets `CONTROLLER_BACKEND_ENDPOINT=http://backend:9091/api/v1/streaming-config`
so the replanner pushes StreamingConfig YAML to the backend on
every plan change.
Also fixed a knock-on bug in `e2e_plots.py::fig_transition` that
crashed on cells with null t_plan_ready / t_first_hit / t_steady
(matplotlib bar geometry can't handle None edges) — now skips with
a stderr line and continues to fig_latency_cdf so
`query_latency_cdf.png` lands.
Documented the resulting E0 protocol in `deploy/README.md` so the
single-cell smoke is reproducible without referring back to this
commit.
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
Lands four fixes in one bundle so the E0 single-cell smoke (P5–P9 against a live N=1 b3-delta stack) produces non-empty answers and exercises the full P1–P9 wiring end-to-end. This is the upstream gate for the 60-cell sweep (E3) and the headline Pareto figure (E5).
Fixes
deploy/scripts/plan_transition.py— renameself._stop→self._stop_event. Python 3.12'sthreading.Threadhas a private_stop()method thatThread._wait_for_tstate_lockcalls duringjoin(). Shadowing it with anEventinstance raisedTypeError: 'Event' object is not callableon every join.deploy/configs/workloads.yaml(new) — declarative registry with 4 entries matchingqueries-e2e.json's query families against the metricsdeploy/fake-exporteractually emits (http_requests_total,http_requests_total_latency_ms). Without this the controller loggedworkloads file not found; using empty registryand never published a plan, sobefore_plan/after_planwere always null intransition.jsonl.controller/src/metrics_exposer.rs— exposesasap_active_plan_id{metric, plan_id="<hash>"}per-plan at scrape time, hashed off the plan's content (sketch_type / mode / delta_transmission / window / backend merge / valid_until).plan_transition.py'sPlanIdTrackeralready polls for this exact metric name; with this metric present it picks upbefore_planand detects re-plans by hash flip.deploy/docker-compose/e2e-overlay.yml— mounts the workloads YAML into the controller at/etc/asap/workloads.yaml, setsCONTROLLER_WORKLOADSto point at it, and setsCONTROLLER_BACKEND_ENDPOINT=http://backend:9091/api/v1/streaming-configso the replanner pushesStreamingConfigYAML to the backend on every plan change. The default-path lookupworkloads.yamlresolves to/workloads.yamlin the debian-slim runtime image (no WORKDIR), so the env-override is required.Knock-on fix
deploy/scripts/e2e_plots.py::fig_transitioncrashed withTypeError: unsupported operand type(s) for +: 'int' and 'NoneType'when a cell'st_plan_ready/t_first_hit/t_steadywere null (matplotlib bar geometry can't position aNoneedge). Single-cell smokes against an idle controller hit this every time. Now skips that figure cleanly when timing data is missing and continues tofig_latency_cdfsoquery_latency_cdf.pnglands.Smoke results (cell
cell-smoke-e0, b3-delta, N=1, card=1000, freq=10 Hz, 60 s soak)http_erroronsum(http_requests_total)from cold-fallback torn-line bug; quantile / topk / count_unique succeed but return[]because backend's warm-tier inference patterns don't match these queries (seebackend-inference.yaml: onlyquantile_over_time(...[1m])patterns are wired). Real blocker — see notes below.transition.jsonlnon-nullbefore_plan/after_planbefore_plan=pe2f92ae12434e5c9,after_plan=pe2f92ae12434e5c9. Same hash since the transition query doesn't trigger a re-plan in the current controller (no workload-driven re-plan path for capability misses) — but the observability is now wired and a real plan flip would be visible.sample.jsonlpopulated 1 Hz CPU/mem/netcold-truth/hour-bucketed JSONLhttp_requests_total+http_requests_total_latency_msundercold-truth/<metric>/2026/05/05/18/part-fake-1.jsonl.accuracy.csvrows for all 5 sketch types with non-NaN errorerror/recallcolumns empty because the backend returned[]for warm-tier (criterion 1). Per the task's scope note, full 5-sketch coverage requiresrun_e2e_sweep.sh(E3); b3-delta only exercises DDSketch + HLL.plots/query_latency_cdf.png+ ideallypareto_acc_vs_thru.pngbandwidth_vs_n.png).transition_timeline.pngskipped cleanly with the e2e_plots.py fix above.Smoke artifacts (per-run state, not committed):
/tmp/cell-smoke-e0/{replay.jsonl,transition.jsonl,sample.jsonl,cold-truth/,accuracy.csv,plots/}. Reproduce with the protocol now indeploy/README.md"E0: single-cell smoke".Real blockers surfaced (criterion 1)
histogram_quantile(0.99, sum by (le) (...)),topk(...),count(count by (zone) (...)), andsum(http_requests_total)all fall throughsimple_engine's pattern matcher. Closing this means either expandingbackend-inference.yamlpatterns (out-of-scope wiring change) or — better — fixing the engine's pattern matcher to handlesum by (le)and bare aggregations. Tracked in PROGRESS follow-up KLL Aggregator Implementation #4.sum(http_requests_total)because that query routes to cold and dies on a partial last-line write from the producer.Replannerto wake up on capability_miss POSTs from the backend (PROGRESS follow-up: seereplan.rs::handle_violation). For E0 we observebefore_plan == after_plan, which is technically PASS for (2) but the actual transition path is untested.Stack state at end
UP at compose project
docker-compose(default), with overlays:base.yml + agents-N1.yml + baseline-b3-delta.yml + e2e-overlay.yml,AGENT_CONFIG=sketchcol-agent-b3-delta.yaml,EXPORTER_CARDINALITY=1000,EXPORTER_FREQ_HZ=10.(The b3-delta overlay's default
100k × 100 Hzsaturates the OTLP exporter's 64 MiB max-message-size on the first DDSketch full-state window. Pinned to 1k × 10 Hz for E0 — documented in the README protocol.)Constraints honored
deploy/scripts/measure-baseline.py,deploy/scripts/run_e2e_sweep.sh,processor/*sketchprocessor/,deploy/configs/prometheus.yml(E1's domain).opentelemetry-go-patch/sdk/metric/,deploy/fake-exporter/(E2's domain).asap-precompute-rs/.Test plan
python3 -c "import deploy.scripts.plan_transition"succeeds (verified via worktree-relative import).cargo build --release --bin controllerclean (37 s, 0 errors).controllerbinary exposesasap_active_plan_idat/metricsafter startup withCONTROLLER_WORKLOADSset.replay.jsonlnon-empty with non-nullplan_idon every row.transition.jsonlnon-nullbefore_planandafter_plan.cold-truth/populated.accuracy.csvhas rows for quantile / topk / count_unique / sum.plots/query_latency_cdf.pnglands.docker ps).🤖 Generated with Claude Code