feat: a harder trap dataset - the grounding claim now measures +22.2 points - #20
Merged
Conversation
The first dataset stopped measuring anything: 8 of its 12 cases pass 3/3 in both A/B arms, so a frontier model does not fall for a 7-table fan-out trap with or without a semantic model. evals/dataset-hard/ is 11 tables of deliberately bad naming plus a committed glossary (evals/glossary-hard.json, outside the dataset folder because loadFolder would otherwise ingest the .json as a table). Traps were chosen against the engine's MEASURED solvable envelope, not intuition: an FK is only discoverable when it shares a token with '<keyTable> <keyColumn>', so cust_ref and owner were rejected - the engine cannot solve them either, so they would fail in both arms and discriminate nothing. What remains sits in the band where the engine infers a join at 92-100% from value overlap while a name-reading model must guess: opaque FKs, two natural-key joins (sku, region_cd), a stale legacy money column beside the authoritative one, soft-delete and void filters, a decoy load buffer whose ids overlap the real invoice table, and a second has_many that makes fan-out possible. Wrong routes land far from right: revenue is 10944 correct, 10507.50 via the stale column, 11275.50 including void, 49829 including staging, and a naive fan-out join inflates one customer 4x. Discipline notes: - Every expectedSql was run against the CSVs before being committed; a verification test now re-runs all twelve so ground truth cannot silently rot. - The dataset exposed a real engine defect on first derivation: money columns whose values coincided with a price lookup were inferred as foreign keys at 81/68/54%, and since relationship endpoints are excluded from measures, inv.net_amt silently stopped being a measure. The fixture was made realistic (negotiated prices) so the intended traps work, and the defect is recorded as ROADMAP step 6.1 rather than worked around. - eval:engine:hard is 25/25 and now gates CI. Drop --glossary and exactly the five hard-term-* cases fail; a test asserts that, which is the machine-checkable proof the enrichment chain is load-bearing.
…a is hard Same configuration as the first A/B (12 cases, repeat 3, verify on, maxSteps 12), validity checks clean: neither arm hit the turn budget, both baseline controls passed 3/3 in both arms. grounded 29/36 runs (80.6%), 9/12 cases, mean 1.5 tool steps raw-sql 21/36 runs (58.3%), 6/12 cases, mean 4.5 tool steps delta +22.2 points, both metrics agreeing in direction That is the result the first dataset could not produce (+2.8, metrics disagreeing). The control arm's failures collapse to a single cause: it never excludes void invoices, returning 11275.50 / 1297.50 / 2535 / 1128 where the answers are 10944 / 1020 / 2257.50 / 1074. That is a business rule the schema cannot express and only the glossary carries, which is what the semantic layer is for. Two cases go 3/3 vs 0/3. Two results kept in the open rather than tuned away: - Grounding LOST revenue-by-category 0/3 vs 1/3. It summed inv.net_amt after joining down to inv_line, double-counting each invoice across its lines (2520.5 vs 1074). I checked whether this was a bad case: line-level and header totals are both exactly 10944, so the question is unambiguous and this is a real grain error the grounding invited by naming a measure with no grain. Recorded as ROADMAP step 6.2 with a candidate fix. - The fan-out case is 0/3 in BOTH arms: both write the naive double join and inflate one customer 4x. Grounding does not prevent fan-out once the agent leaves query_metric and hand-writes SQL. The ROADMAP's moat paragraph now states the qualified version: the claim holds, but only once the data is hard enough to tell, and on easy schemas the semantic layer buys nothing on accuracy and can even mislead.
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.
Why
The first A/B came back inconclusive (+2.8 points, inside the noise floor, metrics disagreeing on direction) because the dataset had stopped discriminating: 8 of its 12 cases passed 3/3 in both arms. A frontier model does not fall for a 7-table fan-out trap. Stage 3 of 3.
The result
Same configuration as before (12 cases, repeat 3, verify on, maxSteps 12). Validity checks clean - neither arm hit the turn budget, both baseline controls passed 3/3 in both arms:
+22.2 points, and both metrics now agree in direction. Two cases go 3/3 versus 0/3.
The control arm's failures collapse to a single legible cause: it never excludes void invoices, returning 11,275.50 / 1,297.50 / 2,535 / 1,128 where the answers are 10,944 / 1,020 / 2,257.50 / 1,074. That is a business rule the schema cannot express and only a glossary carries - precisely what the semantic layer is for.
Two results kept in the open
revenue-by-category0/3 vs 1/3. It summedinv.net_amtafter joining down toinv_line, double-counting each invoice across its lines (2520.5 vs 1074). I checked whether this was a bad case rather than a real failure: line-level and header totals are both exactly 10,944, so the question is unambiguous and this is a genuine grain error that the grounding invited by naming a measure with no grain. Recorded as ROADMAP step 6.2 with a candidate fix; the case was deliberately not changed.query_metricand hand-writes SQL.The dataset
11 tables of deliberately bad naming plus a committed glossary. Traps were chosen against the engine's measured solvable envelope: an FK is only discoverable when it shares a token with
"<keyTable> <keyColumn>", socust_refandownerwere rejected - the engine cannot solve them either, so they would fail in both arms and discriminate nothing. What remains sits where the engine infers a join at 92-100% from value overlap while a name-reading model must guess.Wrong routes land far from right: revenue is 10,944 correct, 10,507.50 via the stale legacy column, 11,275.50 including void, 49,829 including the decoy load buffer.
It also found a real engine defect before a single agent case ran. On first derivation, money columns whose values coincided with a price lookup were inferred as foreign keys at 81/68/54%, and since relationship endpoints are excluded from measures,
inv.net_amtsilently stopped being a measure at all. The fixture was made realistic (negotiated prices, which real invoices have) so the intended traps work, and the defect is recorded as ROADMAP step 6.1 rather than worked around.Verification
npm run checkexit 0;npm run test:cli140 pass / 0 failnpm run eval:engine18/18;npm run eval:engine:hard25/25, now gating CI--glossaryand exactly the fivehard-term-*cases fail (20/25). A test asserts that, so the enrichment chain cannot silently rotexpectedSqlwas run against the CSVs before being committed, and a test re-runs all twelve so ground truth cannot driftAGENTS.md, nothing was tuned after seeing the numbers🤖 Generated with Claude Code
https://claude.ai/code/session_01CKZ9UVgw2Mhiu6T5YAG7k6