05 Close the loop
Turn one reviewed production failure into golden data, a calibrated business-policy evaluator, and protection for future traffic.
Starting point
Module 04 ended with a completed human
annotation for the authoritative Module 03 chat_turn. Keep its corrected SQL and
provenance worksheet available:
source=production-feedback
source_trace_id=<authoritative Chat trace ID>
failure_category=stale-business-policy
source_policy_version=policy-v1
annotation_id=<completed task ID when available>The original production trace has sql-execution-success=true and
user-thumbs=false. The thumbs-down found a trace worth reviewing; the completed
annotation supplied the diagnosis and corrected ground truth.
The continuous evaluation and improvement loop
This module closes one turn of the loop:
- user feedback exposes a blind spot in the current online evaluator;
- a human investigates and approves a correction;
- that reviewed incident expands the golden dataset;
- baseline and candidate releases run over the same expanded dataset;
- a general evaluator is calibrated offline before it is enabled online; and
- future traffic keeps collecting both evaluator scores and user feedback.
The last step matters: deploying a better evaluator does not end user feedback. An evaluator can only measure dimensions represented in its policy catalog and prompt. A future 👎 can reveal another missing policy, ambiguous request, or failure mode and start the same loop again.
Goal
Pass five evidence gates: promote, baseline, candidate, calibrate, then enable and replay. Use the Module 02 winner for both experiments so policy version is the only intended treatment change.
Run every command below from ClickHouse_Demos/workshops/agent_arena:
cd ClickHouse_Demos/workshops/agent_arena
source .env
export WINNER_MODEL="${WINNER_MODEL:-qwen3.7-flash}"
export WINNER_PROMPT="${WINNER_PROMPT:-P2_fewshot}"
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"The defaults are the verified workshop winner. If your room selected another
config_id, set all three values to that model/prompt instead and keep them unchanged
through every gate.
Evidence gate 1 — Promote the reviewed incident
Create reviewed.json in the lab root with the following three records. Replace the
two placeholder values everywhere before running promotion. If Langfuse does not
expose an annotation task ID, remove annotation_id from all three records instead
of leaving a placeholder; that field is optional, while the other production
provenance fields are required.
[
{
"id": "prod-active-001",
"question": "How many active customers do we have?",
"golden_sql": "SELECT uniqExact(customer_id) FROM v_orders WHERE order_ts >= now() - INTERVAL 30 DAY AND status NOT IN ('cancelled', 'returned')",
"tier": 2,
"ordered": false,
"source": "production-feedback",
"source_trace_id": "<paste the Module 03 Chat trace ID>",
"failure_category": "stale-business-policy",
"source_policy_version": "policy-v1",
"annotation_id": "<paste the completed task ID>"
},
{
"id": "prod-active-002",
"question": "What is our active customer count right now?",
"golden_sql": "SELECT uniqExact(customer_id) FROM v_orders WHERE order_ts >= now() - INTERVAL 30 DAY AND status NOT IN ('cancelled', 'returned')",
"tier": 2,
"ordered": false,
"source": "production-feedback",
"source_trace_id": "<paste the Module 03 Chat trace ID>",
"failure_category": "stale-business-policy",
"source_policy_version": "policy-v1",
"annotation_id": "<paste the completed task ID>"
},
{
"id": "prod-active-003",
"question": "How many customers qualify as active under our business definition?",
"golden_sql": "SELECT uniqExact(customer_id) FROM v_orders WHERE order_ts >= now() - INTERVAL 30 DAY AND status NOT IN ('cancelled', 'returned')",
"tier": 2,
"ordered": false,
"source": "production-feedback",
"source_trace_id": "<paste the Module 03 Chat trace ID>",
"failure_category": "stale-business-policy",
"source_policy_version": "policy-v1",
"annotation_id": "<paste the completed task ID>"
}
]Only prod-active-001 is the exact question from the user-feedback trace.
prod-active-002 and prod-active-003 are reviewer-authored paraphrases derived
from that same investigated incident. They use the same source trace and completed
annotation for auditability; they are not two additional production feedback traces.
The three inputs intentionally all invoke the governed active-customer metric, so the
baseline cannot appear healthy by testing unrelated counts.
Promote the reviewed batch:
source .env
.venv/bin/python -m scripts.promote_to_golden reviewed.jsonExpect three prepared prod-active-* lines followed by:
promoted 3 question(s) into the 'arena-golden' datasetOpen Langfuse → Datasets → arena-golden and inspect each new item's metadata.
Verify source=production-feedback, the same real source_trace_id,
failure_category=stale-business-policy, and source_policy_version=policy-v1.
The repo source corpus has 20 YAML questions. q019 and q020 are few-shot prompt
holdouts, so a clean project starts with 18 arena-golden Experiment items. This
three-item promotion makes that clean dataset 21 items. A reused project may contain
additional approved items; record that provenance instead of deleting them to force
a count, and require the baseline and candidate to use identical item IDs.
reviewed.json is ignored mutable operator state and is the primary workshop path.
The tracked --synthetic-fixture is only a reproducible rehearsal fallback. It does
not represent human annotation and cannot satisfy this module's evidence gate. The
two modes are mutually exclusive; never run the synthetic fallback after genuine
promotion.
Promotion validates the complete batch, read-only SQL, and required provenance before querying ClickHouse or writing dataset items. It then reads existing dataset metadata and refuses a colliding ID with different production provenance. If that authenticated preflight cannot establish provenance safely, it stops without a write. Repeating a genuine promotion is safe only when the colliding production provenance is identical.
Evidence gate 2 — Run the policy-v1 baseline
First provision the catalog-driven judge for experiments. This creates its online observation rule in a disabled state:
source .env
.venv/bin/python -m scripts.provision_online_evaluators \
--business-policy-experimentsExpect experiment rule enabled=True; online rule enabled=False. Confirm the online
rule is still disabled in Langfuse before continuing.
Give this workshop attempt a unique suffix, then run the selected model and prompt over the expanded dataset with the stale policy:
export LOOP_RUN_SUFFIX="${LOOP_RUN_SUFFIX:-$(date +%Y%m%d-%H%M%S)}"
export BASELINE_RUN_ID="online-loop-baseline-${LOOP_RUN_SUFFIX}"
export CANDIDATE_RUN_ID="online-loop-candidate-${LOOP_RUN_SUFFIX}"
.venv/bin/python -m eval.harness --run-id "$BASELINE_RUN_ID" \
--policy-version policy-v1 --models "$WINNER_MODEL" --prompts "$WINNER_PROMPT" \
--wait-for-score business-policy-adherenceThe harness appends --policy-v1 to the release ID. It waits for three exact
Experiment score names on every trace: correctness, agent-arena-llm-judge, and
business-policy-adherence. Do not proceed if the run times out or any score is
missing.
In Langfuse Experiments, record the baseline's dataset item count and aggregate correctness. Expect 21 items in a clean project after promotion. Reused projects can have more approved items, and provider responses can vary, so the release gate is the paired comparison below rather than a hard-coded aggregate score.
Evidence gate 3 — Run the policy-v2 candidate
Without changing the dataset, model, prompt, or run suffix, run the candidate:
.venv/bin/python -m eval.harness --run-id "$CANDIDATE_RUN_ID" \
--policy-version policy-v2 --models "$WINNER_MODEL" --prompts "$WINNER_PROMPT" \
--wait-for-score business-policy-adherenceRecord the candidate's actual aggregate, then compare the two runs in Langfuse and require:
- identical dataset item IDs and item counts;
- all three
prod-active-*items move fromcorrectness=0underpolicy-v1tocorrectness=1underpolicy-v2; - every item that predates
prod-active-*is compared per item, with nocorrectness=1tocorrectness=0regression; and - aggregate candidate correctness is not lower than baseline correctness.
Stop if a pre-existing item regresses. A candidate that fixes the incident by breaking known behavior has not passed the release gate.
Evidence gate 4 — Calibrate one general policy judge
business-policy-adherence is not an “active-customer evaluator.” It receives the
question, generated SQL, and the complete policy-v2 metric catalog. It determines
which governed metric applies and returns PASS, FAIL, or NOT_APPLICABLE. The
same design can check active customers, revenue, conversion, and gross margin without
creating one evaluator per question phrasing.
Before enabling it for production observations, inspect these Experiment items:
| Calibration probe | Run/item | Required business-policy-adherence |
|---|---|---|
| stale active-customer SQL | baseline prod-active-001 | FAIL |
| corrected active-customer SQL | candidate prod-active-001 | PASS |
| revenue policy | candidate q005 | PASS |
| view-to-purchase conversion policy | candidate q018 | PASS |
| plain customer count | candidate q001 | NOT_APPLICABLE |
Repeat the active-customer check for prod-active-002 and prod-active-003. Read
the judge reasoning as well as the category: it should name the applicable catalog
policy and evaluate the generated SQL against it. A plain count must remain
NOT_APPLICABLE, demonstrating that the judge does not force every count question
into the active-customer policy.
Keep the online rule disabled if any category is wrong, any required score is missing, the structured output is malformed, or the correctness comparison regressed. Offline experiment calibration comes first because it lets you inspect false passes and false failures against known examples before the evaluator affects production monitoring.
Evidence gate 5 — Enable and replay on policy-v2
Only after all calibration gates pass, enable the observation rule:
source .env
.venv/bin/python -m scripts.provision_online_evaluators \
--enable-business-policy-onlineExpect the exact rule name agent-arena-business-policy-online with enabled=True.
The command fails closed when it cannot find a dataset-scoped Experiment score named
business-policy-adherence; your manual calibration checks above remain the quality
gate.
Stop the policy-v1 server. In the first terminal, start the candidate and leave it
running:
source .env
AGENT_ARENA_POLICY_VERSION=policy-v2 \
.venv/bin/uvicorn serving.api:app --port 8100In the second terminal, define a helper that accepts a question and returns its trace
ID only after confirming a successful policy-v2 response:
source .env
export WINNER_CONFIG_ID="${WINNER_CONFIG_ID:-qwen3.7-flash__P2_fewshot}"
ask_trace() {
local question="$1"
local body
body=$(.venv/bin/python -c \
'import json,sys; print(json.dumps({"question": sys.argv[1], "config_id": sys.argv[2]}))' \
"$question" "$WINNER_CONFIG_ID")
curl -fsS http://localhost:8100/ask \
-H 'content-type: application/json' -d "$body" | \
.venv/bin/python -c \
'import json,sys; data=json.load(sys.stdin); assert data["policy_version"] == "policy-v2" and data["outcome"] == "ok"; print(data["trace_id"])'
}Ask the active-customer and revenue questions once. Online observation scores use
the rule name agent-arena-business-policy-online, not the Experiment score name:
ACTIVE_TRACE=$(ask_trace "How many active customers do we have?")
.venv/bin/python -m scripts.verify_online_scores "$ACTIVE_TRACE" \
sql-execution-success=true agent-arena-business-policy-online=PASS
REVENUE_TRACE=$(ask_trace "What was revenue in the last 30 days?")
.venv/bin/python -m scripts.verify_online_scores "$REVENUE_TRACE" \
sql-execution-success=true agent-arena-business-policy-online=PASSThe conversion question has a verified stochastic boundary. Ask it once and preserve
that trace. If the serving outcome is non-ok, or its exact required scores are
missing or fail, retry the same question and config at most once. This block keeps
both attempts visible:
ask_conversion() {
local body
body=$(.venv/bin/python -c \
'import json,sys; print(json.dumps({"question": sys.argv[1], "config_id": sys.argv[2]}))' \
"What is our view-to-purchase conversion rate for the last 7 days?" \
"$WINNER_CONFIG_ID")
curl -fsS http://localhost:8100/ask \
-H 'content-type: application/json' -d "$body" | \
.venv/bin/python -c \
'import json,sys; data=json.load(sys.stdin); assert data["policy_version"] == "policy-v2"; print("\t".join((data["trace_id"], data["outcome"])))'
}
IFS=$'\t' read -r CONVERSION_TRACE_1 CONVERSION_OUTCOME_1 <<< \
"$(ask_conversion)"
if .venv/bin/python -m scripts.verify_online_scores "$CONVERSION_TRACE_1" \
sql-execution-success=true agent-arena-business-policy-online=PASS; then
CONVERSION_SCORES_1=pass
else
CONVERSION_SCORES_1=fail
fi
if [ "$CONVERSION_OUTCOME_1" = ok ] && [ "$CONVERSION_SCORES_1" = pass ]; then
CONVERSION_RESULT_1=pass
else
CONVERSION_RESULT_1=fail
fi
printf 'conversion_attempt=1 trace_id=%s outcome=%s exact_scores=%s result=%s\n' \
"$CONVERSION_TRACE_1" "$CONVERSION_OUTCOME_1" \
"$CONVERSION_SCORES_1" "$CONVERSION_RESULT_1"
CONVERSION_TRACE_2=not-run
CONVERSION_OUTCOME_2=not-run
CONVERSION_SCORES_2=not-run
CONVERSION_RESULT_2=not-run
if [ "$CONVERSION_RESULT_1" != pass ]; then
IFS=$'\t' read -r CONVERSION_TRACE_2 CONVERSION_OUTCOME_2 <<< \
"$(ask_conversion)"
if .venv/bin/python -m scripts.verify_online_scores "$CONVERSION_TRACE_2" \
sql-execution-success=true agent-arena-business-policy-online=PASS; then
CONVERSION_SCORES_2=pass
else
CONVERSION_SCORES_2=fail
fi
if [ "$CONVERSION_OUTCOME_2" = ok ] && [ "$CONVERSION_SCORES_2" = pass ]; then
CONVERSION_RESULT_2=pass
else
CONVERSION_RESULT_2=fail
fi
fi
printf 'conversion_attempt=2 trace_id=%s outcome=%s exact_scores=%s result=%s\n' \
"$CONVERSION_TRACE_2" "$CONVERSION_OUTCOME_2" \
"$CONVERSION_SCORES_2" "$CONVERSION_RESULT_2"
if [ "$CONVERSION_RESULT_1" != pass ] && \
[ "$CONVERSION_RESULT_2" != pass ]; then
printf '%s\n' \
'STOP: conversion failed twice; preserve both traces and investigate.' >&2
false
fiDo not retry until green. If both attempts fail, retain both traces, keep the result visible, and route the new evidence through human annotation, golden-data improvement, and the same paired calibration loop.
Only after conversion passes, ask the plain-count question once:
PRODUCT_TRACE=$(ask_trace "How many products are there?")
.venv/bin/python -m scripts.verify_online_scores "$PRODUCT_TRACE" \
sql-execution-success=true agent-arena-business-policy-online=NOT_APPLICABLEThe online evaluator runs asynchronously. The verifier polls for up to 180 seconds by default; a score that is still pending is not the same as a failing score.
Keep the loop running
Leave 👍/👎 enabled after rollout. Monitor disagreements such as
agent-arena-business-policy-online=PASS beside user-thumbs=false: they are
high-value candidates for the next annotation queue. Human review decides whether to
correct policy, prompts, data, or the evaluator. Approved cases return to
arena-golden, then the next candidate repeats the same baseline → candidate →
calibration → guarded enablement sequence.
The workshop rules sample 100% of eligible traces so every learner sees evidence. That is a teaching setting, not a production default. Real sampling should reflect traffic, evaluator cost, latency, risk, and the incident coverage you need.
Completion evidence
- The production root trace still shows
sql-execution-success=trueand Booleanuser-thumbs=false. - The
production-investigation-<session>human-annotation task is completed with a verified correction andapproved-for-golden=true. - All three golden items exist with genuine
production-feedbackprovenance; you can distinguish the one user question from the two reviewer-authored paraphrases. - Baseline and candidate used the same expanded dataset, model, and prompt; the candidate fixed all three promoted items and introduced no existing correctness regression.
- Experiment calibration produced
FAIL,PASS, andNOT_APPLICABLEwith exact score namebusiness-policy-adherence. - The observation rule was disabled during calibration, then enabled only after the gates passed.
- Active-customer, revenue, and conversion traces have
agent-arena-business-policy-online=PASS; the plain product count hasagent-arena-business-policy-online=NOT_APPLICABLE. - You can explain why online evaluation and user feedback continue to improve each other after deployment.