Skip to content

feat(asap-tools): rqe query protocol and frontend switch for e2e runs - #890

Merged
milindsrivastava1997 merged 44 commits into
mainfrom
884-featasap-tools-rqe-query-protocol-and-frontend-switch-for-e2e-runs
Oct 15, 2026
Merged

milindsrivastava1997 merged 44 commits into
mainfrom
884-featasap-tools-rqe-query-protocol-and-frontend-switch-for-e2e-runs

Conversation

@milindsrivastava1997

Copy link
Copy Markdown
Contributor

Part of #884 (code; the end-to-end check is still to run) and #886. Stacked on #889 (→ #888 → #887).

  • Querier:
    • New rqe protocol (RqeQueryClient). Configs keep the PromQL/SQL text. A side file maps that text, unrendered, to its RQE tree. The client POSTs {"expr", "time"} to /api/v1/rqe_query and parses the response exactly like the Prometheus client, so calculate_fidelity is unchanged.
    • A text missing from the side file fails with no fallback.
    • Clients get config_query (the unrendered text).
    • Vector parsing and the runtime-info fetch are shared with the Prometheus client.
  • Runner:
    • query_engine.frontend: promql|rqe (default promql) and query_engine.rqe_exprs_file.
    • With rqe, generate_controller_client_configs checks that every query has a tree, copies the side file next to the client configs (rsynced with them), and switches only the sketchdb mode's server to protocol: rqe.
    • The compose template mounts the side file.
  • Config writer: sketch_bench_e2e_configs.py writes <stem>.rqe_exprs.json from e2e.queries[].expr. With --frontend rqe, it drops queries without a tree (rate) from every mode, so the arms run the same queries, and prints a note listing them.

Run:

python sketch_bench_e2e/sketch_bench_e2e_configs.py --workload-table <table> --seed 42 \
  --starting-delay-secs 120 --query-duration-secs 3600 --prebuilt-output-dir <planner out> --frontend rqe
python experiment_run_e2e.py experiment_type=sketch_bench_e2e/<stem> experiment.name=<name> ...

Tests:

  • Querier: 23 unittest.
  • Experiments: 189 pytest.
  • Dry run on the real classic table: the writer produced a 42-entry side file, the generated configs and compose mount were as expected, and the querier sent a tree to a stub server and parsed the reply.

Not in scope: random workloads, which will go through the same writer once S2 (sketch-bench#223) produces their eval tables.

🤖 Generated with Claude Code

milindsrivastava1997 and others added 4 commits October 14, 2026 18:29
Add `asap_types::rqe_expr`: a serde type for RQE expression trees (rqe
leaves, agg, binary, scalar nodes), a `validate` check run at load, and
`leaves` / `output_labels` helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
An e2e query's optional `expr` (an RQE expression tree, validated at load)
now names the plan RQEs it reads. Each tree leaf becomes one more optimizer
item and Raqe, served by its plan pick like a text leaf, and gets an entry
in a new `rqe_configs` section of the inference config: RQE id -> the
aggregations it reads and, for an exact roll-up, the query-time stage that
regroups to the RQE's grouping. `query_configs` are unchanged; a table
without trees converts as before and writes no `rqe_configs`.

A query may have only a tree: its language is that of the metrics its
leaves read (records -> SQL). The engine's `InferenceConfig` loads
`rqe_configs` (empty when absent); nothing reads it yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…uery

`SimpleEngine::handle_rqe_query` evaluates a validated RQE tree at a query
time without parsing PromQL/SQL. A leaf reads the aggregations its
`rqe_configs` entry names, over the lookback ending at the time aligned as
the PromQL instant path aligns it, through the shared context tail and
executor; a roll-up config reads at the aggregation's grouping and its
query-time stages regroup to the leaf's. `agg` nodes regroup with the
query-time aggregation pipeline, `binary` nodes reuse PromQL's vector and
scalar combiners. Output labels are the root's labels, never `__name__`.

The route answers Prometheus instant-vector JSON whatever the adapter's
language and never forwards: invalid body/tree, unknown RQE id or bad time
is a 400 `bad_data`, an execution failure a 500, an empty window an empty
result. Top-k leaves rank globally (a leaf has no bucketing labels).

The SQL context tail moves to `build_execution_context_tail` (now returning
the planning error) and `get_aggregation_id_info` takes aggregation refs, so
both the SQL and RQE builders share them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
The querier gains an `rqe` protocol client. Queries keep their PromQL/SQL
text in the config; a JSON side file (query text -> RQE expression tree)
named by the server's `rqe_exprs_file` gives the tree, which is POSTed with
the evaluation time to `/api/v1/rqe_query`. The response is parsed as the
Prometheus client parses an instant vector, so `QueryResult.result` and
fidelity scoring are unchanged. The lookup uses the config's unrendered
text (clients now also get `config_query`); a query missing from the side
file fails without a request. A relative side file resolves against the
client config's directory, and the compose generator mounts it there.

`experiment_run_e2e` takes `query_engine.frontend: promql|rqe` (default
promql) and `query_engine.rqe_exprs_file`. Under rqe the side file is
checked to cover every query, copied next to the client configs, and the
sketchdb mode's server switches to the rqe protocol; baselines keep
PromQL/SQL.

`sketch_bench_e2e_configs.py` writes `<config stem>.rqe_exprs.json` from
the table's `e2e.queries[].expr`, keyed by the exact PromQL/SQL text in the
config. `--frontend rqe` sets the frontend and leaves out queries without
`expr` (e.g. rate) with a note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
milindsrivastava1997 and others added 6 commits October 14, 2026 20:16
An RQE leaf may name `topk_by` labels: like PromQL `topk by (...)`, a
top-k leaf then ranks its input independently within each value
combination of those labels, while `grouping` stays its output labels.
Absent, the whole input is one bucket. Validation allows `topk_by` only
on `topk_by_value` leaves, non-empty and within `grouping`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…tion' into 882-featplanner-planner-saved-reads-rqe-trees-and-writes-rqe_configs
A top-k leaf's optimizer item now carries its `topk_by` labels as
`topk_by_labels`, as a PromQL `topk by (...)` query's does, so its Raqe
is grouped by the buckets (the plan's top-k RQE grouping) and its heap
is one per bucket, keyed by the remaining labels. Without `topk_by` the
Raqe is a single bucket and can't share the deployment of a
`topk by (...)` query.

Tests run the pilot random workload (seed-862/workload-01-pilot) and its
saved plans: `topk by (region, service)` leaves read the same heaps as
their PromQL queries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…s-rqe_configs' into 883-featquery-engine-rqe-tree-query-route
A top-k leaf now ranks per bucket the way the PromQL path ranks
`topk by (...)`: its aggregation keeps one heap per bucket (the
aggregation's grouping) and the instant plan's top-k limit ranks within
each. The leaf's `topk_by` (none: one bucket) must equal that grouping;
otherwise the leaf fails as an execution error instead of answering a
different ranking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…tasap-tools-rqe-query-protocol-and-frontend-switch-for-e2e-runs

@milindsrivastava1997 milindsrivastava1997 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review: 10 findings.

Findings on lines outside the diff:

asap-tools/experiments/experiment_only_ingest_path.py:172 — Only experiment_run_e2e.py passes rqe_exprs_file into generate_controller_client_configs. This runner (and experiment_run_grafana_demo.py:146) never call rqe_exprs_file_for_frontend, so query_engine.frontend=rqe is silently ignored.

Failure scenario: Running with query_engine.frontend=rqe keeps protocol prometheus and sends PromQL with no error; results are labelled RQE but measure the PromQL frontend (breaks 'fail loud').

@@ -212,11 +243,27 @@ def build_config(
prebuilt_output_dir = os.path.abspath(prebuilt_output_dir)
check_prebuilt_controller_output(prebuilt_output_dir)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With --frontend rqe, queries without an expr (e.g. rate) are dropped from query_groups, but --prebuilt-output-dir is accepted as-is and never checked against the reduced set.

Failure scenario: A planner output built for all 42 queries used with --frontend rqe makes ASAPQuery maintain sketches for queries never sent, skewing the sketchdb arm's memory/CPU vs the baseline with no warning.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1686601: with --frontend rqe, a --prebuilt-output-dir whose inference_config.yaml plans a dropped query is refused, and the error says to plan with asap-planner --planner saved --expr-only. That flag, added in #888, plans only queries with expr.

)
if response.status_code == 200:
return response.json().get("data", {})
except Exception:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch_runtime_info is now shared by the Prometheus and rqe clients, and swallows every exception (except Exception: pass), returning None.

Failure scenario: rqe server unreachable or malformed JSON during query-time alignment → None instead of the real error, surfacing later as a confusing assert/misalignment (code-design-review rule 1).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b74fae7: fetch_runtime_info raises on HTTP and connection errors and on a malformed body, and no longer returns None.

) -> None:
"""Write the config and, next to it, its RQE side file."""
os.makedirs(os.path.dirname(output_path), exist_ok=True)
side_file = os.path.abspath(rqe_exprs_path(output_path))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write_config always writes <stem>.rqe_exprs.json and sets query_engine.rqe_exprs_file to an absolute local path, even with --frontend promql where it's never read. Write the file and key only when frontend == rqe.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 396b534: the side file and query_engine.rqe_exprs_file are written only when frontend == rqe.

self,
query: str,
query_time: Optional[int] = None,
config_query: Optional[str] = None,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new config_query parameter is threaded through every QueryClient.execute_query signature and caller, but only RqeQueryClient uses it, to look up a tree by exact unrendered query text.

Failure scenario: Prometheus/ClickHouse clients take an ignored parameter, and any normalization of the query text (whitespace, template edit) between generator, YAML round-trip and querier gives a runtime 'no RQE expression' failure. Attaching the tree to the query entry in the client config (per-query rqe_expr) or keying by query index avoids both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this as is (decided on #886). Templated SQL renders differently on every run, so the unrendered config text is the only stable key; an index-keyed side file breaks when the config is edited. The writer produces the config and the side file together, and the runner checks that every query has a tree before any service starts. 4e469a6 explains this in the docstrings.

debug_adapter = DebugHTTPAdapter(server.name)
client.session.mount("http://", debug_adapter)
client.session.mount("https://", debug_adapter)
elif protocol == "rqe":

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The protocol name 'rqe' is a bare string literal in ~6 places: here, classes/config.py:18, generate_prometheus_client_compose.py:34, the factory registry, RqeQueryClient.protocol_name, and experiment_utils/config.py RQE_PROTOCOL. Code-design-review rule 2 (one source of truth); a rename in one place silently breaks the compose mount or ServerConfig check.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1cc5f2e: one RQE_PROTOCOL per package. In the querier it lives in rqe_query_client.py; in the experiments it lives in constants.py. These are separate packages and images, so one constant each is the floor.

),
)

# Mount debug adapter for HTTP request logging

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rqe branch copies the DebugHTTPAdapter creation and http/https mount block, now in all three protocol branches. Mount once after the if/elif/else that builds the client.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a762a00: mounted once after the if/elif/else.

so the file keeps that relative place inside the container.
"""
with open(config_file) as f:
servers = yaml.safe_load(f)["servers"]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rqe_exprs_mounts runs for every compose generation and indexes yaml.safe_load(f)['servers'] unchecked.

Failure scenario: A non-rqe client config without servers (or a not-yet-rsynced path) used to produce a compose file; it now fails with an unexplained KeyError/FileNotFoundError.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in beb812f: returns [] when the config has no servers or the file is missing. A missing side file for an rqe server still raises a clear FileNotFoundError, because Docker would otherwise mount an empty directory.

"--frontend rqe does)"
)
with open(os.path.join(output_dir, RQE_EXPRS_FILENAME), "w") as f:
json.dump(exprs, f, indent=1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_copy_rqe_exprs re-serializes the whole side file with json.dump instead of copying it, and copies every entry rather than only the queries that run. shutil.copyfile after validation, or writing only the run's subset, is simpler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 786d0f6: validate, then shutil.copyfile.

error_message=f"RQE query failed: {body}",
raw_response=body,
)
raw_result = body["data"]["result"]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A 200 response is assumed to hold an instant vector: body['data']['result'] goes to normalize_vector with no resultType check, and a broad except turns mismatches into a generic failure.

Failure scenario: A scalar or matrix result makes normalize_vector call .get on a float/list → reported as 'AttributeError: ...' rather than 'unexpected result type'.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c02311c: a resultType other than vector fails with 'unexpected result type X'.

milindsrivastava1997 and others added 12 commits October 14, 2026 22:32
Require every subtree that feeds the result (root, agg operand, one side
of each binary op) to read at least one RQE leaf; allow a repeated leaf id
only for identical leaves; reject duplicate labels in grouping, by and
topk_by; reject an empty id or metric and a zero lookback. Document that
label lists are sets matched by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…tion' into 882-featplanner-planner-saved-reads-rqe-trees-and-writes-rqe_configs
…frontend=rqe

experiment_only_ingest_path.py and experiment_run_grafana_demo.py never
switch the querier to the rqe protocol, so an rqe run there silently
measured the PromQL frontend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…nd rqe drops

With --frontend rqe, queries without an expr are left out of the config,
but a --prebuilt-output-dir planned for them made ASAPQuery maintain
sketches for queries never sent. Refuse it and point to
`asap-planner --planner saved --expr-only`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
It swallowed every exception and returned None, hiding an unreachable
server or a malformed body behind a later confusing failure. No caller
relies on the None.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
Under promql nothing reads <stem>.rqe_exprs.json, so neither the file nor
query_engine.rqe_exprs_file is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…uery text

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
The querier's config, factory, client and main share RQE_PROTOCOL from
rqe_query_client; the experiments' config generation and compose mounts
share constants.RQE_PROTOCOL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
Every protocol branch mounted the same DebugHTTPAdapter; mount it after
the client is built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
A client config without `servers`, or not on disk yet, now gets no rqe
mounts instead of a KeyError/FileNotFoundError; a missing side file of an
rqe server is a clear error rather than an empty directory mount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…serializing it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
A scalar or matrix result reached normalize_vector and surfaced as an
AttributeError; check data.resultType first and report the mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
@milindsrivastava1997

Copy link
Copy Markdown
Contributor Author

Review-body finding (experiment_only_ingest_path.py:172, experiment_run_grafana_demo.py:146): fixed in 2e75152. Both runners now call require_promql_frontend, so query_engine.frontend other than promql fails with an error pointing to experiment_run_e2e.py.

milindsrivastava1997 and others added 3 commits October 14, 2026 22:38
- Compare a query's `rqes` with its tree's leaves as sets.
- Allow one RQE leaf at several repeat intervals: each interval is a
  Raqe, and the interval-independent rqe_configs entry is written once.
  Different leaves under one id are still refused.
- Name a query in errors by its text, else its tree's RQE ids.
- Compute and check a query's RQE ids once.
- Check a tree's leaves read one kind of metric without dedup().
- Describe the e2e-tables fixture instead of citing its PR.
- --expr-only (with --planner saved) plans only the e2e queries that
  have an RQE tree; the others (e.g. rate) get no deployment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…s-rqe_configs' into 883-featquery-engine-rqe-tree-query-route
Before reading a leaf, check it against the aggregation its rqe_configs
entry names: same metric (table.value_column for SQL), a type that
serves its capability with value weighting, a lookback its windows
tile, top-k buckets equal to the heap grouping, and a grouping the
aggregation serves (its own labels, a mergeable subset of its groups,
or what its query-time stages regroup to). A mismatch is the request's
fault: 400 bad_data, not 500.

Also: drop error paths validation already rules out (missing phi/k,
scalar root), make the end-timestamp alignment warning
language-neutral, and stop claiming a config snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…ery-route' into 884-featasap-tools-rqe-query-protocol-and-frontend-switch-for-e2e-runs

@milindsrivastava1997 milindsrivastava1997 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated code review: 4 finding(s), unverified candidates.

so the file keeps that relative place inside the container. A config
that isn't there (yet) or lists no servers has no rqe server to mount for.
"""
if not os.path.isfile(config_file):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rqe_exprs_mounts silently returns [] when the client config file doesn't exist at generation time, so an rqe server's side file is not mounted.

Failure scenario: Compose generated before client configs are rsynced (or wrong path): no rqe_exprs mount, container starts, RqeQueryClient.__init__ raises FileNotFoundError inside it. Since prometheus_client_service runs gen; docker compose up with ;, nothing fails at generation time.

(from automated /code-review; unverified)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 892fad3: a missing client config raises; a config without servers gives no mounts. The service now chains generation and docker compose up with && (25e8e3e), so a failed generation stops the start.

pass
return result

def get_runtime_info(self) -> Optional[Dict[str, Any]]:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_runtime_info changed from returning None on error to raising, and it has no callers; meanwhile main_prometheus_client.get_query_start_times still does its own requests.get to /api/v1/status/runtimeinfo instead of fetch_runtime_info.

Failure scenario: Two implementations of the runtime-info fetch that will drift; a future caller relying on the Optional return type gets an exception instead of None.

(from automated /code-review; unverified)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in da6b110: get_query_start_times uses fetch_runtime_info; the unused get_runtime_info methods are deleted.

return config, notes, rqe_exprs


def check_prebuilt_plans_no_dropped_queries(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_prebuilt_plans_no_dropped_queries inspects only inference_config queries by substring match and ignores streaming_config aggregations.

Failure scenario: Dropped sum(rate(x[5m])) and kept sum(rate(x[5m])) / 2 share a leaf: the check passes but the rate aggregation planned for the dropped query is still deployed, skewing cost. A planned entry without a query key raises KeyError instead of the intended ValueError.

(from automated /code-review; unverified)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4023959: replaced by check_prebuilt_plan_is_expr_only. In rqe mode the plan must have non-empty rqe_configs and no queries, otherwise a ValueError points to --expr-only. At run time, 7d76cc8 checks that every tree leaf id is a key of rqe_configs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(line 517) Query-time alignment (get_query_start_times) waits only on aggregation ids under inference_config queries, never on ids referenced only by rqe_configs.

Failure scenario: Under the rqe frontend, a tree leaf served by an aggregation no text query reads isn't in required_aggregation_ids; alignment starts querying before it has data, so early RQE results are empty and score as misses.

(from automated /code-review; unverified)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 10725d9: alignment also waits on aggregations named in rqe_configs, and works when there are no query configs.

milindsrivastava1997 and others added 12 commits October 15, 2026 00:23
…tion' into 882-featplanner-planner-saved-reads-rqe-trees-and-writes-rqe_configs
A client config not on disk at generation time would otherwise leave its
rqe servers' side files unmounted; a config without `servers` still gets
no mounts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
get_query_start_times read /api/v1/status/runtimeinfo with its own
request; it now goes through fetch_runtime_info, keeping its behaviour of
raising (not retrying) on an unreachable server or a bad response. The
clients' get_runtime_info methods had no callers and are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
- A query with `promql`/`sql` must name its `rqes`: text leaves map to
  plan RQEs by `rqes`, never by the tree's leaf order. Only a tree-only
  query may omit them.
- `--expr-only` no longer plans query text: it writes only the tree
  leaves' deployments and `rqe_configs`, and no `query_configs`.
- `leaf_item` drops the lookback check `RqeExpr::validate` owns, and
  shares one metric lookup (`leaf_metric`) with `is_sql`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
An aggregation read only by an RQE leaf was not waited on, so the first
RQE queries could run before it had data. Alignment now also waits on the
aggregation ids of every inference_config `rqe_configs` entry, and works
for an `--expr-only` plan that has no `queries` section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…s-rqe_configs' into 883-featquery-engine-rqe-tree-query-route
The substring check for planned queries left out of an rqe config missed
aggregations shared with kept queries and raised KeyError on entries
without `query`. Under --frontend rqe a --prebuilt-output-dir must now be
an --expr-only plan: non-empty `rqe_configs` and no per-query configs
under `queries`; otherwise a ValueError says to plan with
`asap-planner --planner saved --expr-only`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
Under query_engine.frontend=rqe the querier sends the side file's trees,
and an --expr-only plan has no per-query configs, so
copy_prebuilt_controller_output refused every query. It now checks that
every leaf id of every tree in the side file has an `rqe_configs` entry,
listing the missing ids otherwise. The promql check is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
The start command chained its steps with `;`, so a failed compose
generation (e.g. a missing rqe side file) still ran `docker compose up`.
The steps are now chained with `&&`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…isabled handling

- A leaf whose lookback reads more closed windows than its `rqe_configs`
  entry retains of the value aggregation is a 400. Windows read are
  `(lookback - window) / slide + 1`, as the planner sizes retention.
- A top-k leaf whose `k` exceeds its CountMinSketchWithHeap's `heapsize`
  is a 400 (`cms_heap_params` is now shared with the accumulator factory).
- `POST /api/v1/rqe_query` answers 500 `internal` when request handling
  is disabled: it has no fallback, like the instant route without one.
- The tree reads one streaming-config snapshot for all its leaves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…ery-route' into 884-featasap-tools-rqe-query-protocol-and-frontend-switch-for-e2e-runs
milindsrivastava1997 and others added 6 commits October 15, 2026 01:25
…on-tree-type-and-validation' into 882-featplanner-planner-saved-reads-rqe-trees-and-writes-rqe_configs

# Conflicts:
#	asap-planner-rs/src/optimizer/milp_output.rs
…ads-rqe-trees-and-writes-rqe_configs' into 883-featquery-engine-rqe-tree-query-route

# Conflicts:
#	asap-query-engine/src/engines/simple_engine/sql.rs
#	asap-query-engine/src/tests/mod.rs
…ery-route' into 884-featasap-tools-rqe-query-protocol-and-frontend-switch-for-e2e-runs
build_config now returns (config, notes, rqe_exprs); main's
random_workload_artifacts.pilot_configs still unpacked two values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gKTCAEz3sJ1Hzde16xxU1
…e-query-protocol-and-frontend-switch-for-e2e-runs
@milindsrivastava1997
milindsrivastava1997 changed the base branch from 883-featquery-engine-rqe-tree-query-route to main October 15, 2026 14:09
@milindsrivastava1997
milindsrivastava1997 merged commit 4db156f into main Oct 15, 2026
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.

feat(asap-tools): rqe query protocol and frontend switch for e2e runs

1 participant