Update Driver Check#3
Merged
Merged
Conversation
vgodsoe
approved these changes
Feb 20, 2025
This was referenced Mar 9, 2026
itomek
pushed a commit
that referenced
this pull request
Mar 12, 2026
- [x] Adjust driver check to be equal or greater than a specific minimum version (`NPU_DRIVER_VERSION`) - [x] Update content of popup warning messages - [x] Modify messages to allow user to exit installation if needed during driver check
3 tasks
4 tasks
Merged
5 tasks
kovtcharov
added a commit
that referenced
this pull request
Apr 20, 2026
…steps 7-8) Opens a **draft** self-fix PR against the coder integration branch via `gh pr create` (shells out — GitHubToolsMixin is Phase-10 work). Hard rules enforced at the module boundary: * regression_test_path must be non-empty; §7.4 step 5 forbids opening a self-fix PR without a regression test. Raises ValueError. * plan.feedback_id must match the feedback_id argument; a mismatched binding would defeat Pass 7 (feedback-binding) before it even runs. * base branch defaults to 'coder' — never main (§5.6). compose_pr_body() cites feedback_id explicitly and block-quotes the EM's feedback verbatim (§15.8 P7 requirements #3). The review-pass table is templated from an optional ReviewGateResult; when the Phase-4 gate isn't wired yet we emit a "(review gate not available)" stub. notify_em() routes to `gh pr comment` or `gh issue comment` based on the feedback context_url; missing/unrecognised URLs log a WARN and return a deferred marker so the loop driver can retry.
kovtcharov
added a commit
that referenced
this pull request
Apr 20, 2026
…steps 7-8) Opens a **draft** self-fix PR against the coder integration branch via `gh pr create` (shells out — GitHubToolsMixin is Phase-10 work). Hard rules enforced at the module boundary: * regression_test_path must be non-empty; §7.4 step 5 forbids opening a self-fix PR without a regression test. Raises ValueError. * plan.feedback_id must match the feedback_id argument; a mismatched binding would defeat Pass 7 (feedback-binding) before it even runs. * base branch defaults to 'coder' — never main (§5.6). compose_pr_body() cites feedback_id explicitly and block-quotes the EM's feedback verbatim (§15.8 P7 requirements #3). The review-pass table is templated from an optional ReviewGateResult; when the Phase-4 gate isn't wired yet we emit a "(review gate not available)" stub. notify_em() routes to `gh pr comment` or `gh issue comment` based on the feedback context_url; missing/unrecognised URLs log a WARN and return a deferred marker so the loop driver can retry.
kovtcharov
added a commit
that referenced
this pull request
Apr 20, 2026
…steps 7-8) Opens a **draft** self-fix PR against the coder integration branch via `gh pr create` (shells out — GitHubToolsMixin is Phase-10 work). Hard rules enforced at the module boundary: * regression_test_path must be non-empty; §7.4 step 5 forbids opening a self-fix PR without a regression test. Raises ValueError. * plan.feedback_id must match the feedback_id argument; a mismatched binding would defeat Pass 7 (feedback-binding) before it even runs. * base branch defaults to 'coder' — never main (§5.6). compose_pr_body() cites feedback_id explicitly and block-quotes the EM's feedback verbatim (§15.8 P7 requirements #3). The review-pass table is templated from an optional ReviewGateResult; when the Phase-4 gate isn't wired yet we emit a "(review gate not available)" stub. notify_em() routes to `gh pr comment` or `gh issue comment` based on the feedback context_url; missing/unrecognised URLs log a WARN and return a deferred marker so the loop driver can retry.
This was referenced Apr 22, 2026
3 tasks
itomek
pushed a commit
that referenced
this pull request
Apr 29, 2026
…loor, walk __context__ - Delete tunnel-friendly-error.png — debug screenshot that slipped in via upstream commit f0844d0; no references in code/docs (#3 from Apr-29 review). - Restore uv.lock requires-python ">=3.13" to match origin/main (was silently narrowed to ">=3.12" in the same upstream commit). setup.py's python_requires stays >=3.10; the lock now no longer drifts from main (#4 from Apr-29 review). - Restore src/gaia/apps/webui/package-lock.json to origin/main (revert my drive-by 0.17.3 -> 0.17.4 bump). Main itself has the package.json=0.17.4 vs lockfile=0.17.3 drift; the auto-correction triggered the heavy Build Installers workflow on this PR, which then timed out at the workflow's hardcoded 90s state-ready poll while still downloading the ~3 GB Gemma-4-E4B-it-GGUF. Reverting eliminates the unrelated CI noise; the lockfile/package.json drift is its own tech debt. - _classify_chat_exception now walks __context__ as well as __cause__ so implicit exception chains (raise ... inside an except block, no `from`) preserve typed-class metadata like LemonadeContextOverflowError.retryable (#5 from Apr-29 review).
itomek
pushed a commit
that referenced
this pull request
Apr 29, 2026
…loor, walk __context__ - Delete tunnel-friendly-error.png — debug screenshot that slipped in via upstream commit f0844d0; no references in code/docs (#3 from Apr-29 review). - Restore uv.lock requires-python ">=3.13" to match origin/main (was silently narrowed to ">=3.12" in the same upstream commit). setup.py's python_requires stays >=3.10; the lock now no longer drifts from main (#4 from Apr-29 review). - Restore src/gaia/apps/webui/package-lock.json to origin/main (revert my drive-by 0.17.3 -> 0.17.4 bump). Main itself has the package.json=0.17.4 vs lockfile=0.17.3 drift; the auto-correction triggered the heavy Build Installers workflow on this PR, which then timed out at the workflow's hardcoded 90s state-ready poll while still downloading the ~3 GB Gemma-4-E4B-it-GGUF. Reverting eliminates the unrelated CI noise; the lockfile/package.json drift is its own tech debt. - _classify_chat_exception now walks __context__ as well as __cause__ so implicit exception chains (raise ... inside an except block, no `from`) preserve typed-class metadata like LemonadeContextOverflowError.retryable (#5 from Apr-29 review).
This was referenced May 3, 2026
3 tasks
5 tasks
itomek
added a commit
that referenced
this pull request
May 21, 2026
CI block: - cli.py: drop the last `import json as _json` alias (W0404 reimport flagged by pylint without `-E`). The aliased loads call now uses the top-level json module. Bot review #3: - judge_turn.md: collapse "the the OEM MCP service's tools" duplicate and drop the OEM-specific percentage; the rule is vendor-agnostic. - cli.py `_print_reliability_summary`: guard ANSI escapes behind `sys.stdout.isatty()` so piped output (CI, logs, non-ANSI Windows shells) doesn't show literal `^[[32m` sequences. - blender/chat agent.py `_post_process_tool_result` docstrings: note the `super()` requirement for `single_tool_per_turn=True` subclasses. - test_agent_source_invariants.py: scope the "Task completed with" literal walk to `_build_loop_break_summary`'s body so unrelated mentions (test assertions, future docstrings) don't trip the gate.
This was referenced Jun 2, 2026
8 tasks
19 tasks
7 tasks
alexey-tyurin
added a commit
to alexey-tyurin/gaia
that referenced
this pull request
Jun 23, 2026
… (amd#1828) ## Summary Procedural memory now keeps **one** proven recipe per goal even when the distiller renames it between synthesis passes — recipes are matched by meaning, not by an exact name string. ## Why The procedural-memory tier (amd#887) promises "learn a task once, keep one proven recipe, replace it when a better version appears." Supersession is the *replace* half. But `reconcile_and_store` decided "same recipe?" by exact-string match on `name` — and the distiller LLM authors a fresh kebab name each pass (`DISTILL_TEMPERATURE = 0.1`, kebab-validated only, never canonicalized). So a recurring goal that drifts from `summarize-unread-emails` one pass to `summarize-my-unread-emails` the next slipped past the name lookup: **before**, a drifted name produced a *second* ADD and the supersede branch silently never fired — duplicates accumulated, `recall_skill(top_k=2)` could surface two near-identical copies into the planner prompt, and a genuinely better recipe coexisted with the stale one instead of replacing it. **After**, reconcile matches the nearest enabled, non-superseded procedure by `when_to_use` embedding cosine `>= SIMILARITY_TAU` (0.82), so a drifted name still resolves to the same recipe and supersession fires as designed. Go-forward only — cleaning up rows already duplicated by prior drift is out of scope (the feature is new and off-by-default). ## Linked issue Closes amd#1818 ## Changes - **Match key is meaning, not name.** `reconcile_and_store` now finds the prior recipe via a new pure helper `_nearest_enabled_procedure` (cosine `>= similarity_tau` over `when_to_use` vectors), reusing the embedding the call site already computes and passes as `embedding=` — previously persisted but never used for matching. The supersede-by-`success_count` dominance check, the insert-new-then-`superseded_by` lineage, the ADD/UPDATE/NOOP shape, and "never DELETE" are all unchanged; only the match source moved. - **Live SQLite cosine pass, not the FAISS index** (per maintainer steer in [issue amd#1818 comment 4772627749](amd#1818 (comment))). The scan goes over `search_skills(..., with_embedding=True)`, which sees rows written earlier in the *same* `max_clusters_per_pass` loop (`put_skill` commits immediately) — the FAISS index isn't refreshed until after reconcile returns, so reusing it could let a same-pass double-ADD through. This also keeps `reconcile_and_store` a pure function (`store`-only) that unit-tests without a live backend. - **Fail-loud, no silent cap.** The scan is bounded by `_RECONCILE_SCAN_LIMIT = 500`; saturating it logs a `warning` rather than silently dropping rows. Degraded inputs (missing/zero-norm/wrong-dim embeddings) are skipped with intent, not swallowed. - **No import-cycle regression.** Embedding BLOBs are decoded locally with `np.frombuffer` so `skill_synthesis` imports no `memory` symbol, preserving the `memory → procedural_memory → skill_synthesis` one-way direction. ## Test plan - [x] `python -m pytest tests/unit/test_skill_synthesis.py tests/unit/test_memory_mixin.py tests/unit/test_memory_store.py -q` — all green (578 passed locally), including the 4 new pure-fn tests, the e2e two-pass drift test, and the 3 updated pure-fn tests. - [x] `python util/lint.py --all` — black/isort/pylint/flake8 clean (remaining MyPy/Bandit warnings are pre-existing and not in the touched files). - [x] **Regression check (optional but recommended):** revert the one match line in `reconcile_and_store` to the old `search_skills(name=candidate.name, …)` lookup and re-run the four meaning tests — they must FAIL on the old code and PASS on the fix, proving they exercise the changed branch (not vacuous). > No `gaia eval agent` run is included — this is persistence-only (no prompt, tool schema, recall path, model, or error-classification change), and a cold eval runs from empty memory so it cannot reach the multi-pass name-drift branch the fix targets. The deterministic unit + e2e tests above exercise that branch directly. ## Deviations from the approved sketch | Sketch / comment said | Code reality | Resolution | |---|---|---| | "search the procedures **FAISS index** (or a `search_skills` cosine pass)" | `_proc_faiss_index` lives on the mixin and is refreshed only *after* `reconcile_and_store` returns; the function is pure and takes only `store`. | Took the offered **`search_skills` cosine pass** — keeps the function pure/unit-testable and catches same-pass ADDs the index wouldn't. No behavior gap vs the sketch. | | Comment cites the call-site vector at `procedural_memory.py:86` | After the amd#1794 extraction, the vector is computed at [`procedural_memory.py:463`](src/gaia/agents/base/procedural_memory.py#L463) and passed as `embedding=`. | Line-drift only; the vector exists and is reused as the match input. | | (none — net-new) | The existing pure-fn tests `test_noop_when_existing_dominates` and `test_update_supersedes_lower_success_count` seeded rows with **no embedding** and relied on name equality. | Updated to seed matching embeddings (the new match key) — they would otherwise ADD instead of supersede. In-scope test churn. | ## Checklist - [x] I have linked a GitHub issue above (`Closes amd#1818`). - [x] I have described **why** this change is being made, not just what changed. - [x] I have run linting and tests locally (`python util/lint.py --all`, `pytest tests/unit/`). - [x] No user-visible/CLI/API surface changed — internal reconcile logic only, so no docs update is required. --- ## Acceptance-criteria proof — amd#1818 The match key in `reconcile_and_store` moved from exact `name` to `when_to_use` embedding cosine `>= SIMILARITY_TAU` (0.82) via a new live-SQLite cosine pass `_nearest_enabled_procedure` ([skill_synthesis.py:580](src/gaia/agents/base/skill_synthesis.py#L580)); dominance, lineage, and never-DELETE are unchanged. Every AC is proven by a **deterministic** test that exercises the changed branch — and each is shown to **fail on the old name-match code and pass on the fix** (regression proof below), which is what makes the green meaningful. ### AC → proof map | Acceptance criterion | Proving test(s) | Key assertions | |---|---|---| | **AC amd#1** — match by `when_to_use` cosine `>= SIMILARITY_TAU`, not `name`; recurring goal → one enabled row under name drift | `test_matches_by_meaning_supersedes_under_name_drift` ([:542](tests/unit/test_skill_synthesis.py#L542)), `test_distinct_meaning_adds_even_with_same_name` ([:578](tests/unit/test_skill_synthesis.py#L578)) | drifted name + cosine-1.0 vector → **1** enabled row; identical `name` + orthogonal vector (cosine 0 < τ) → **2** rows (ADD). Proves the key is meaning, not name. | | **AC amd#2** — supersession fires under drift: higher-`success_count` candidate supersedes (insert-new + `superseded_by`, never DELETE); dominance unchanged | `test_matches_by_meaning_supersedes_under_name_drift` ([:542](tests/unit/test_skill_synthesis.py#L542)), `test_lower_success_same_meaning_noops` ([:597](tests/unit/test_skill_synthesis.py#L597)), `test_update_supersedes_lower_success_count` ([:513](tests/unit/test_skill_synthesis.py#L513)), `test_noop_when_existing_dominates` ([:494](tests/unit/test_skill_synthesis.py#L494)) | `action=="update"`, `superseded_id==old_id`, old row kept with `superseded_by` set (never deleted); weaker cluster under a drifted name → `noop` (dominance logic intact under the new key). | | **AC amd#3** — same cluster distilled twice with differing names → exactly one surviving enabled, non-superseded row, second supersedes first | `test_name_drift_across_passes_supersedes` ([test_memory_mixin.py:3331](tests/unit/test_memory_mixin.py#L3331)) — two real `_synthesize_skills` passes | pass 1 ADDs `summarize-unread-emails`; pass 2 (drifted `summarize-my-unread-emails`, higher `success_count`) → `stored==1` (UPDATE); `search_skills()` returns **1** row; pass-1 id `superseded_by` the pass-2 id. | ### Comment [#4772627749](amd#1818 (comment)) refinements — both addressed **1. "Live SQLite + cosine pass, not `_proc_faiss_search`"** (avoids a stale-index same-pass double-ADD). `_nearest_enabled_procedure` scans `store.search_skills(enabled_only=True, include_superseded=False, with_embedding=True)` — the live table, never the FAISS index. Pinned by a contract-shape spy: > `test_reconcile_queries_store_by_embedding_not_name` ([:619](tests/unit/test_skill_synthesis.py#L619)) — asserts every `search_skills` call passes `with_embedding=True, enabled_only=True, include_superseded=False` and **`name` is always `None`**, so a silent revert to name-matching (or to the index) is caught. **2. "Assert the surviving row carries the *second* candidate's name/body, not just count==1"** (catches a wrong-direction supersede). Both the pure-fn and e2e AC tests assert name **and** body of the survivor: - pure-fn: `visible[0]["name"] == "summarize-my-unread-emails"` **and** `visible[0]["markdown_body"] == "# New\n1. better step"`. - e2e: `visible[0]["name"] == "summarize-my-unread-emails"` **and** `"digest" in visible[0]["markdown_body"]`, plus `old[0]["superseded_by"] == visible[0]["id"]`. ### Verbatim results **All AC tests pass on the fix:** test_matches_by_meaning_supersedes_under_name_drift PASSED test_distinct_meaning_adds_even_with_same_name PASSED test_lower_success_same_meaning_noops PASSED test_reconcile_queries_store_by_embedding_not_name PASSED test_update_supersedes_lower_success_count PASSED test_noop_when_existing_dominates PASSED test_name_drift_across_passes_supersedes PASSED (e2e, 2 synthesis passes) test_rerun_is_noop_and_never_deletes PASSED (never-DELETE) 8 passed in 1.28s **Regression proof — the tests catch the bug** (temporarily revert the match line to the old `search_skills(name=…)` lookup): === AC tests against OLD exact-name code — MUST FAIL === FAILED test_matches_by_meaning_supersedes_under_name_drift FAILED test_distinct_meaning_adds_even_with_same_name FAILED test_lower_success_same_meaning_noops FAILED test_name_drift_across_passes_supersedes 4 failed in 1.05s === same 4 against the fix — MUST PASS === 4 passed in 0.78s Full suite for the touched modules: **578 passed**; `python util/lint.py --all` → **ALL QUALITY CHECKS PASSED**. Co-authored-by: Alexey Tyurin <>
9 tasks
theonlychant
pushed a commit
to theonlychant/gaia
that referenced
this pull request
Jun 25, 2026
…ion (amd#1844) <!-- PR title: test(tool-loader): pin amd#800 doc-profile data-vs-recall disambiguation Branch: test/800-tool-collision-regression --> ## Summary Before this PR, amd#800's scratchpad/memory tool collision was resolved in *code* (by the amd#688 dynamic tool loader, landed via amd#1449/amd#1450/amd#1451) but **nothing pinned it** — no test asserted that the structured-data tool and memory `recall` can't crowd each other out of the prompt, so the fix could silently regress. This adds a deterministic regression test, a live eval scenario, and an in-repo note that together lock the resolution and let amd#800 close with evidence. No runtime code changes — this is a closeout + regression, not a feature. ## Why amd#800 is a coordination tracker: it exists to *prove* the collision is fixed, not leave it as a known gap. The decisive finding (verified on `main`) is that the literal pair in the title — `scratchpad.query_data` vs `memory.recall` — **cannot occur in the loaded profile**: scratchpad tools are registered only for the ChatAgent `data`/`full` profiles, never `doc`, which is the only profile the loader is wired to. The real doc-profile arbitration is `analyze_data_file` (structured-data, **conditional**) vs `recall` (**CORE, always-on**). That asymmetry *is* the resolution — and until now no test encoded it. ## Linked issue Closes amd#800 ## Changes - **Deterministic regression that pins the resolution** — asserts `recall ∈ CORE` (always present) while `analyze_data_file` loads only when the turn's query clears the semantic threshold, using the real `DOC_CORE_TOOLS`/`DOC_BUNDLES` config and production τ/cap (not hand-picked literals), with a fresh loader per case so it tests the cold/empty-memory new-user state. - **Live eval scenario** exercising the same routing end-to-end on the committed `sales_data_2025.csv` corpus, so the disambiguation is checked against a real model, not just unit logic. - **In-repo closeout note** in the tool-loader plan doc explaining how amd#800 is resolved (design asymmetry), cross-linked to the test and scenario. ## Deviations from the approved sketch (amd#800's body) Flagged per CLAUDE.md — the landed design diverges from the original sketch in several places: | amd#800 sketch said | Reality on `main` | Resolution in this PR | |---|---|---| | Collision is `scratchpad.query_data` vs `memory.recall` | Scratchpad tools aren't in the `doc` profile; loader is `doc`-only | Test the doc analog: `analyze_data_file` (conditional) vs `recall` (CORE) | | The two not both in prompt unless justified | `recall` is **CORE → always present**; the *conditional* side is `analyze_data_file` | Regression asserts the conditional side; `recall` is intentionally always-on | | Prompt drops ~12K → ~3-4K tokens | 12K premise is the wrong cost model; gate is **TTFT / native-token reduction** (~50–60%) | AC #1 reframed onto the token-budget proxy already on `main` | | Decisions logged to memory's SQL `tool_history` | Logged as structured `TOOL_LOADER {json}` INFO lines; `gaia.eval.tool_recall` consumes them | AC amd#4 satisfied via log signal (deliberate: no UI-DB migration) | | Bundle table = core/rag/filesystem/scratchpad/browser/memory/mcp | Landed bundles are finer & doc-scoped; no `scratchpad`/`browser` in `doc` | Landed taxonomy supersedes the sketch | | AC amd#7 pivot example "file browsing → web research" | Browser tools aren't in the `doc` profile | Mid-conversation re-eval proven for **in-profile** pivots | | *(new)* Refresh committed Gemma-4-E4B baseline | Local run timed out `smart_discovery` (hardware artifact); fixture is also independently stale | **Baseline refresh deferred** to a clean run on target hardware; committed fixture left untouched | | *(new)* Eval scenario passes | Records **FAIL 6.58** — but for reasons unrelated to amd#800 (see AC amd#6) | Kept as honest corroboration; the unit test is the binding gate | ## Test plan - [x] `python -m pytest tests/unit/test_tool_loader_disambiguation.py -v` → 4 passed (the AC amd#5 gate) - [x] `python -m pytest tests/unit/test_tool_loader_selection.py tests/unit/test_chat_tool_bundles.py tests/unit/test_chat_dynamic_tools.py -q` → 67 passed (no loader regression; confirms the new test's registry/embedder assumptions match shipped config) - [x] `python -m pytest tests/test_eval.py -k scenarios -q` → 27 passed (validates the new scenario YAML: required fields, sequential turns, existing corpus path) - [x] `python util/lint.py --all` → clean - [x] *(optional, needs a running Lemonade backend + UI server)* `gaia eval agent --category tool_selection --agent-type doc` → the 4 pre-existing scenarios match the amd#1451 Part-3 proof (no selection regression) ## Checklist - [x] I have linked a GitHub issue above (`Closes amd#800`). - [x] I have described **why** this change is being made, not just what changed. - [x] I have run linting and tests locally (`python util/lint.py --all`, `pytest tests/unit/`). - [x] I have updated documentation if user-visible behavior changed (in-repo closeout note; no user-facing behavior changed). --- ## amd#800 Acceptance Criteria — Proof **Verdict:** all 7 ACs satisfied on `main` + this PR. The collision is resolved structurally by the amd#688 dynamic tool loader (landed via amd#1449/amd#1450/amd#1451); this PR pins it. Three ACs are satisfied **with the documented reframing** above. > **Structural finding:** the literal `scratchpad.query_data` vs `memory.recall` pair cannot occur in the `doc` profile (scratchpad isn't registered there). The proofs test the real pair: `analyze_data_file` (conditional) vs `recall` (CORE, always-on). | # | Acceptance criterion | Status | |---|---|---| | 1 | Tool prompt drops ~12K → ~3-4K tokens | ✅ *reframed to TTFT/token reduction* | | 2 | `query_data` & `recall` not both unless justified | ✅ *via doc analog* | | 3 | Core tools always available despite heuristic failure | ✅ | | 4 | Selection decisions logged for eval/tune | ✅ *log signal, not SQL sink* | | 5 | E2E regression test (data-query vs recall) | ✅ **new in this PR** | | 6 | Eval suite passes, no selection regression | ✅ | | 7 | Mid-conversation re-evaluation works | ✅ *in-profile* | ### AC #1 — Tool-prompt token reduction Reframed: the ~12K figure assumed the text path; the real cost (and gate) is the **native tool-schema path / TTFT**. The 38-tool doc profile is capped to **14** (`DEFAULT_MAX_TOOLS`). ``` $ pytest tests/unit/test_tool_loader_token_budget.py -q → 10 passed ``` `test_core_only_is_the_reduction_best_case` pins the always-on CORE floor at **≤45% of the native baseline** (~50–60% reduction). ### AC #2 — `query_data` and `recall` not both unless justified `recall ∈ DOC_CORE_TOOLS`; `analyze_data_file ∈` the conditional `data` bundle (`tool_bundles.py`). The conditional tool loads only when the query clears τ: ``` $ pytest tests/unit/test_tool_loader_disambiguation.py -v test_data_tool_is_conditional_and_recall_is_core PASSED test_structured_data_query_loads_data_tool_with_recall_present PASSED test_recall_query_keeps_recall_and_omits_data_tool PASSED ← recall present, data tool ABSENT when unjustified test_pivot_loads_data_bundle_mid_conversation PASSED ``` ### AC amd#3 — Core tools always available despite heuristic failure CORE is admitted unconditionally and is cap-/eviction-exempt; on embedder failure the loader disables for the session and falls back to the full registry, **logging loudly** (`tool_loader.py`). ``` test_core_always_admitted_even_without_match PASSED test_embedder_failure_session_disables_loudly PASSED ``` ### AC amd#4 — Decisions logged for eval/tune Satisfied with a deliberate deviation: decisions are emitted as structured `TOOL_LOADER {json}` INFO lines (not the SQL `tool_history` table — avoids a UI-DB migration). Consumed by `src/gaia/eval/tool_recall.py` (`_TOOL_LOADER_RE` / `_SESSION_RE` / `_ESCAPE_HATCH_RE`) → per-turn loaded sets + escape-hatch rate for τ-tuning. ### AC amd#5 — End-to-end regression test (data-query vs recall) — new in this PR - Deterministic gate: `tests/unit/test_tool_loader_disambiguation.py` (4 tests; real config, fresh loader per case = cold/empty-memory state; asserts loaded-**set membership**, not "select was called"). - Live scenario: `eval/scenarios/tool_selection/data_vs_recall_disambiguation.yaml` (validates + discovered: `find_scenarios(category='tool_selection')` → 5 scenarios incl. the new one). ### AC amd#6 — Eval suite passes, no selection regression Live serial run on Gemma-4-E4B corroborates the amd#1451 Part-3 success-criteria proof — no regression: | scenario | amd#1451 Part-3 proof | this run | |---|---|---| | `known_path_read` | PASS 9.45 | PASS 9.38 | | `no_tools_needed` | PASS 9.97 | PASS 9.87 | | `multi_step_plan` | FAIL 7.62 | FAIL 8.47 (both FAIL — borderline, pre-existing) | | `smart_discovery` | PASS 9.95 | TIMEOUT* | \* hardware artifact of the local Apple-Silicon box (Metal llama.cpp ~14–19 tok/s), not a behavior change. The new scenario records FAIL 6.58 **for reasons unrelated to amd#800**: the disambiguation works (agent routed both aggregates to `analyze_data_file`, never misused `recall`; Turn 2 returned the exact answer), but Turn 1's correctness failed on `analyze_data_file`'s date-filter handling of the monthly-summary CSV plus an agent hallucination — flagged as a separate follow-up. Committed baseline refresh deferred to a clean run on target hardware. ### AC amd#7 — Mid-conversation re-evaluation works Proven for **in-profile** pivots (the sketch's "file→web" example is out of the doc profile — browser tools aren't registered there): ``` test_pivot_loads_data_bundle_mid_conversation PASSED ← turn 1 omits data tool; turn 2 adds it test_monotonic_growth_no_pruning_on_score_drop PASSED test_lru_evicts_oldest_last_call PASSED test_evicted_tool_can_be_readmitted PASSED ``` Co-authored-by: Alexey Tyurin <>
This was referenced Jun 26, 2026
kovtcharov-amd
pushed a commit
to TravisHaa/gaia
that referenced
this pull request
Jun 29, 2026
amd#1875) The email-triage eval corpus was scored against the wrong vocabulary. The agent emits the schema-2.0 five-bucket taxonomy (`URGENT / NEEDS_RESPONSE / FYI / PROMOTIONAL / PERSONAL`), but the committed `ground_truth.json` still carried the retired 4-way labels (`urgent / actionable / informational / low priority`). Since `category_accuracy` is a case-insensitive exact match and the two vocabularies overlap only on `urgent`, almost every prediction scored wrong — the email scorecard read `category_accuracy = 0.04` purely as a labeling artifact, not real quality. After this change the corpus carries the same five buckets the agent predicts, so a correct triage prediction is scored correct. The corpus generator already maps to the schema-2.0 strings; the fixture was simply never regenerated after that mapping landed. Regenerating is deterministic — the `.mbox` bytes and Gmail-id keys are byte-identical, so existing throughput/perf and FakeGmailBackend hashing baselines stay comparable (only the `category` labels and `_meta.taxonomy` change). Closes amd#1874 ## Test plan - [ ] `PYTHONPATH=...:src:hub/agents/python/email python -m pytest tests/unit/test_synthetic_mbox.py tests/unit/email/ tests/unit/eval/ -x` passes (ran here: 372 passed) - [ ] `python util/lint.py --all` passes - [ ] `python tests/fixtures/email/generate_mbox.py --verify` prints `VERIFY OK` (committed fixtures match deterministic output, mbox hash unchanged) - [ ] **Manual (needs AMD hardware + Lemonade):** run `gaia eval benchmark --mbox-path tests/fixtures/email/synthetic_inbox.mbox --ground-truth tests/fixtures/email/ground_truth.json --limit 25 --output-dir <dir>` and confirm `category_accuracy` moves from ~0.04 to a representative value with predicted categories now drawn from the same vocabulary as the labels. - [ ] **Follow-up (AC amd#3, needs hardware):** regenerate the real-run baselines (`tests/fixtures/email/baseline_accuracy*.json` via `score_baseline.py`) and the email scorecard at the next eligible version — these still hold stale-taxonomy numbers and are out of scope for an offline change. >⚠️ **Needs manual validation** — the automated checks here confirm no Python > regression and that the relabel is deterministic/byte-stable, but can't exercise > the live benchmark. A maintainer should run the `gaia eval benchmark` step above > on AMD hardware (Lemonade running) before relying on the new scorecard number. <details> <summary>🔍 Technical details</summary> **Root cause.** `tests/fixtures/email/generate_mbox.py:70-75` (`_BUCKET_TO_CATEGORY`) maps the generator's internal buckets to the production `triage_heuristics.ALL_CATEGORIES` strings, and line 365 routes every label through it. The committed `ground_truth.json` predates that mapping, so it held the old 4-way labels while the generator (and the agent) had moved to the 5-bucket set. `src/gaia/eval/quality_metrics.py:126` `category_accuracy` does a lower-cased exact match, so the vocab mismatch floored the score. **Changes:** - `tests/fixtures/email/ground_truth.json` — regenerated via the deterministic generator (`SEED=23023`). Verified the `.mbox` sha256 (`a4243f72…`) and the full id set are unchanged; only `category` values and `_meta.taxonomy` differ. New realized counts: URGENT 47, NEEDS_RESPONSE 56, FYI 80, PROMOTIONAL 37 (PERSONAL not yet populated in the synthetic corpus — pre-existing, tracked by amd#1438). - `src/gaia/eval/quality_metrics.py:53` — `NEEDS_ATTENTION_CATEGORIES` `{"urgent", "actionable"}` → `{"urgent", "needs_response"}` (plus the two docstring references). Without this the FP/FN needs-attention axis would silently drop the old `actionable` cohort after the relabel; `needs_response` is the schema-2.0 successor. Compared lower-cased, matching the scorer. - `tests/unit/test_synthetic_mbox.py` — updated `test_category_coverage_and_counts` and `test_meta_block_present` to the 5-bucket strings, and added `test_corpus_vocab_matches_scorer_taxonomy` (AC amd#4) asserting the committed corpus vocabulary and the scorer's attention axis both stay a subset of `ALL_CATEGORIES` — a drift guard so a future taxonomy change can't silently re-break scoring. **Verification run here:** 372 email+eval unit tests pass; `generate_mbox.py --verify` self-checks clean; black + isort clean. The inline-GT unit tests in `test_quality_metrics.py` / `test_benchmark.py` use synthetic labels to exercise the taxonomy-agnostic scorer mechanics and are intentionally left unchanged. </details> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Tomasz Iniewicz <[email protected]>
4 tasks
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.
NPU_DRIVER_VERSION)