Originally created by: Akarsh-Hegde
This branch lands the classifier experimentation harness (KAN-139) — what started as a golden-dataset PR has grown into the full eval loop the team will use to evaluate every prompt edit, model swap, and config change going forward. Three eval runs are already in the bank, with their failures clustered into actionable prompt-rule candidates.
It also bundles a separate stream of OpenObserve daemon stability fixes that landed on this branch.
services/tests/evals/render_seeds.py — bridges hand-authored golden_seed/dev_<persona>_sessions.json (with structured ground_truth + design_notes) into the deepeval Golden shape that test_mlx_classifier.py consumes. Recent-context block uses only scoreable prior sessions, matching build_dataset.py:_fetch_recent so sub-scoreable timeline filler can't leak into the prompt.services/tests/evals/smoke_run.py — one-shot runner that emits OTel spans to OpenObserve under service.name=meridian-eval. One eval.run root + one eval.classify per Golden, with per-Golden force_flush so spans land live. Deepeval's @observe hijacks the global TracerProvider for Confident AI, so this OpenObserve-targeted runner is a deliberate separate process.services/tests/evals/test_mlx_classifier.py — reads dataset path from EVAL_DATASET_PATH env var, defaulting to .dataset.json. Same pytest suite now gates against either the legacy real-data export or the synthetic seed-rendered Goldens.services/tests/evals/golden_seed/dev_a_sessions.json — Dev A persona expanded from 35 → 50 hand-authored sessions (40 scoreable + 10 sub-scoreable timeline filler). Coverage spans 7 difficulty tiers: easy, medium, hard, hard-decoy, overhead, untracked, context-only. Each scoreable Golden targets a documented failure mode (design_notes field) — chat-mention false positive, branch-name vs file-path disagreement, topical-adjacency, decoy resistance, etc.services/tests/evals/golden_seed/candidates_meridian.json — 5 real KAN tickets + 2 synthetic decoys (already on main).services/agents/_prompts.py — SESSION_TEXT_CAP is now env-overridable (SESSION_TEXT_CAP=0 disables truncation entirely). Default stays at 2500 chars for production safety. Used during the cap-experiment run to test whether truncation was the bottleneck (it wasn't — see Findings below).CLAUDE.md — documents the new env var.FEEDBACK.json)services/skills/activity/task-classifier/FEEDBACK.json — accumulates per-run failures across eval runs as structured observations clustered into reusable failure_classes. Three top-level slices:runs[] — one entry per eval execution (trace_id, model, prompt_version, dataset, metrics, optional experiment block for A/B runs)observations[] — one entry per failing Golden per run, with classifier's full reasoning text and a failure_class_id foreign keyfailure_classes[] — synthesized actionable backlog with proposed_prompt_rule ready to copy into SKILL.md, plus status: open | resolved_in:<version> for tracking which prompts close which classes_meta.failure_class_schema — declared contract for required vs optional fields with defaults documented; aggregator scripts can do .get(key, default) safely..claude/skills/eval-feedback/)jq (lean path — avoids loading the whole file into context as it grows), clusters failures, appends via a Python script (load → mutate → write), validates, and produces a ranked summary of open failure_classes for the next SKILL.md revision.task #1 TODO documents the fix (add /info endpoint to services/agents/server.py + emit model_id as a span attribute on eval.run).ZO_MEMORY_CACHE_MAX_SIZE=2GB + ZO_DATAFUSION_POOL_SIZE=4GB — OO was auto-sizing to ~22.5 GB on a 36 GB machine.run.sh: RUST_LOG and memory caps configurable without editing the plist; default log level warn (override: echo info > ~/.openobserve/.log_level).ai.openobserve agent: was crash-looping 17,495 times (~23 GB of log waste).TESTING.md §9 — full eval-pipeline section: input/output schema, run flow, failure-mode taxonomy, OpenObserve queries, when-to-re-run checklist.services/tests/evals/README.md — file inventory, run cookbook (smoke run / pytest / Confident AI flows), Golden schema, OpenObserve trace schema, OpenObserve-vs-Confident-AI decision matrix, known issues.CLAUDE.md — registers 5 new env vars (MERIDIAN_OTLP_ENDPOINT, MERIDIAN_OO_AUTH, MLX_SERVER_URL, EVAL_DATASET_PATH, SESSION_TEXT_CAP); "Add a Golden" common task; eval commands in the quick command reference..gitignore — ignore .deepeval/ cache, .synthetic-dataset-*.json (regenerable artifact).mlx-community/Qwen3.5-9B-OptiQ-4bit for all 3)| run_id | trace_id | Goldens | both | task_key | session_type | notes |
|---|---|---|---|---|---|---|
phi4-4bit-dev_a-baseline-20260528 |
945c2f50… |
26 | 80.8% | 80.8% | 84.6% | First end-to-end run. Baseline against 26-Golden subset. |
smoke_20260528T162251 |
dfc053b2… |
40 | 65.0% | 67.5% | 72.5% | Dataset expanded to 40 Goldens (same prompt). Surfaced 5 new failure_classes including the first false-negative class. |
smoke_20260528T180202 |
3ae5eb31… |
40 | 60.0% | 65.0% | 67.5% | Cap-experiment (SESSION_TEXT_CAP=0). Both-accuracy DROPPED vs control; finding documented below. |
Model label correction (see commit
925b1e89): the first two runs were originally logged withmodel: mlx-community/phi-4-4bitbased on conversation context. Verified against~/.meridian/logs/mlx-server.logand the live launchd MLX process: the model has been Qwen3.5-9B-OptiQ-4bit since 2026-05-25.phi-4is cached on disk but was never loaded by the running daemon. FEEDBACK.jsonmodelfields are corrected;run_idstrings and the Confident AI run identifier (phi4-4bit-dev_a-baseline) carry the old phi-4 string as immutable history. The drift source is tracked in task [#1] (add/infoendpoint).
optimism-bias (27 occurrences across 3 runs)Cross-cutting parent of 6 specific classes. Every wrong answer in the false-positive direction was a task claim with a real ticket key at confidence ≥0.85. The classifier wants to find a ticket and given any thread to pull on, will pull. Proposed prompt rule: "Default to untracked when evidence is adjacent (mentions, related reading, topical overlap). Default to task when evidence is direct (active editing, terminal execution, debugging, or analysis whose output IS the ticket's deliverable). Cap confidence at 0.6 when only adjacent signals are present."
SESSION_TEXT_CAP is a covariate, not the bottleneckRemoving truncation (cap=0 vs cap=2500) made overall accuracy worse (65.0% → 60.0% both). But the per-tier movement was bidirectional:
hard +15pp (71% → 86%) — more context helped disambiguationoverhead −15pp, untracked −33pp, medium −12pp — more adjacent signals = more optimism-biasConclusion: prompt-design fix is the right next move; keep cap default at 2500.
failure_classes ranked| rank | class | occurrences | runs |
|---|---|---|---|
| 1 | optimism-bias | 27 | 3 |
| 2 | chat-mention-as-work | 10 | 3 |
| 3 | reading-as-doing | 6 | 3 |
| 4 | branch-name-locks-classification | 4 | 2 |
| 5 (tied) | meta-file-edit-as-ticket-work, filepath-outweighs-branch, overhead-untracked-boundary, dataset-analysis-missed-as-ticket-work | 3 each | 2–3 |
| 9 | pr-review-as-ticket-work | 2 | 2 |
| 10 | dogfooding-as-ticket-work | 1 | 1 |
GET /info endpoint to services/agents/server.py returning {model_id, backend, max_tokens}; smoke_run.py queries at startup and sets model_id as a span attribute on eval.run; eval-feedback skill reads from that span instead of source-code defaults. Closes the model-label drift source.SKILL.md v2.1 with the anti-optimism rule. Should close the top 5 failure_classes simultaneously (~50 of 62 total occurrences) based on the projected coverage. Re-run eval to verify, mark resolved classes resolved_in:2.1.0 in FEEDBACK.json.services/.venv/bin/python services/tests/evals/render_seeds.py produces .synthetic-dataset-a_meridian.json with 40 Goldens (50 sessions → 40 scoreable + 10 sub-scoreable)smoke_run.py against MLX server on port 7823 lands 1 root + N child spans in OpenObserve (verified with per-Golden force_flush ensures live span delivery)deepeval test run --identifier qwen35-9b-optiq-4bit-dev_a-baseline posts test-run + per-Golden traces to Confident AI_meta.failure_class_schema is the authoritative contract — verified no class uses an undeclared fieldcargo fmt --check + cargo clippy -- -D warnings clean on every commit (pre-commit hooks)🤖 Generated with Claude Code
Originally posted by: Akarsh-Hegde
PR description updated — what's new since the last version
Since the original PR body ("eval pipeline + OpenObserve stability"), 7 more commits landed that materially change what this PR ships. Highlights:
FEEDBACK.jsonnow exists as the structured backlog (3 runs logged, 35 observations clustered into 10 failure_classes) — the single most useful artifact for whoever picks up SKILL.md v2.1eval-feedbackClaude Code skill automates the FEEDBACK.json maintenance loop end-to-end, with lean-read via jq and a Python load→mutate→write pattern that scales as the file grows_meta.failure_class_schemadeclares the required + optional field contract (9 required, 5 optional with documented defaults)phi-4-4bittoQwen3.5-9B-OptiQ-4bitafter verification against the live launchd process and mlx-server.log.run_idstrings preserved as immutable history;modelfields fixed; correction note + drift source documented in FEEDBACK.json_metaSESSION_TEXT_CAPmade env-overridable inservices/agents/_prompts.py— default stays 2500; tested with cap=0 (no truncation) and result is logged in FEEDBACK.json with experiment metadataTwo follow-ups are tracked: task [#1] (
/infoendpoint to fix model-label drift authoritatively) and task [#12] (cut SKILL.md v2.1 with the anti-optimism rule).Related
Tickets:
#1Tickets:
#12Ticket changed by: adityaharishch
Originally posted by: adityaharishch
🎉 This PR is included in version 1.0.0 🎉
The release is available on:
v1.0.0Your semantic-release bot 📦🚀