14
77 Comments

My AI agent could "see" the fact — it just never wrote it down

I've been building AnchorStrategy, a multi-agent pipeline that takes meeting notes and turns them into a strategy report: it reads the minutes, has several AI agents split up research on competitors, customers, and cross-industry analogies, then a strategist agent pulls it into a SWOT and a set of strategic options.

I'd been validating it against Japanese business-school case studies (past exams from a Japanese small-business consultant certification), which worked, but left me unable to tell whether a weakness I was seeing was specific to Japanese-language input or a real flaw in the pipeline itself. So I built an English case to re-run the same validation and find out.

Building a clean case

Copyright was the first snag. Harvard Business School cases are cheap to buy for personal use (under $10), but publishing AI-generated output based on one needs separate licensing. I ended up using data from NIST's Baldrige National Quality Award — a US quality-award program. NIST's own 2-page profile of a winning company is clean to use but way too thin for a case study. The company's own ~50-page award application has plenty of detail, but it's the company's copyrighted work.

So I pulled only the facts out of that application — numbers, org structure, the business issues — and wrote an entirely new case narrative from scratch, using none of the original wording, headings, or product names. Facts aren't copyrightable; only the specific expression is, which is the same principle newsrooms and textbook writers rely on. The result: a fictional metal-nameplate and label manufacturer, "Company A," Dallas, Texas — family-owned since the 1940s, won a quality award in the late '90s.

The setup

Generation and grading run on different vendors (Gemini for generation, Claude for grading) so the grader isn't just validating its own house style. The grader never sees my prompt or intent — only the question, the official scoring rationale, a checklist of required elements, and the full generated report. It's judging one thing only: does the report touch the facts it needs to, not whether the strategy itself is any good.

The first result looked fine

Five runs, same case, same settings: average 78.0/100, range 73.5–86.5. That's roughly the same run-to-run variance I'd seen on the Japanese cases — nothing that looked alarming.

Until I broke it down by question. Question 1 (a 3C analysis plus a call to identify a structural weakness, worth 40 points) averaged around 30 — not bad on the surface. But that question has four separate scoring elements (E1–E4), and one of them — "references a competitor-related fact" (Q1-E2, worth 10) — scored zero in 4 of 5 runs. Everything else scored near-full marks, so the total buried a failure that was happening almost every time.

I nearly missed it myself. If I'd only tracked the total, this would have filed under "decent."

What was disappearing

The case narrative states the company's competitive strengths plainly:

Company A is a founding member of its industry's national trade association... To the company's knowledge, no other chemical-etching business operates in Texas; the nearest direct competitor is in a neighboring state. Unlike most of its competitors, Company A carries essentially no debt, maintains its own on-site metal inventory... It formally benchmarks seven of its leading competitors every year through a mystery-shopper program.

(No in-state competitor / debt-free, in-house inventory and tooling / benchmarks 7 competitors annually.)

But in the first run's output, the section that should have covered this read:

[HYPOTHESIS VALIDATION] The President & CEO's suspicion that reliance on spreadsheets is a major barrier to efficiency is supported by the operational bottleneck created by manual data entry between production, sales, and accounting...

No trace of the competitive picture — just a validation of the CEO's offhand comment about spreadsheets. In other runs the report went further and stated outright, "No individual competitor data is available in the input," when the data was very much there.

Tracing it back

I walked the pipeline's intermediate state, node by node, to find where this dropped out.

At the very first node — the one that summarizes the minutes (minutes_ingest) — this competitive information was already missing from the extracted key facts. It never even reached the dedicated competitor-analysis node (market_competitor); it was gone before that.

Digging further: the competitor-analysis node's prompt sorted incoming information into two buckets — "industry averages" and "named-competitor data." But "a fact about our own relative competitive strength, stated without naming a specific competitor" (no in-state competitor, debt-free, in-house capabilities) fit neither bucket. It fell through the gap in the classification scheme and was dropped before extraction even happened.

The fix

I widened what the minutes-summarization node extracts — from "numbers and named entities" to include causal statements and third-party business-structure context generally. I added a new category to the competitor-analysis node: "relative competitive position, without naming a specific competitor" — constrained to only fire when the input explicitly states it, so it doesn't start inventing unsupported claims.

Midway through, I found a side effect I hadn't planned for: a downstream node that formats the final report had the category list hardcoded, so adding a new category upstream didn't actually surface it in the output. Fixed that too.

Re-ran the same case 3 times after the fix. Competitor analysis scored full marks all 3 times.

Company A is the only chemical-etching business in Texas; the nearest direct competitor is in a neighboring state.

That line held, every time. Average total jumped to 89.3.

What's still broken

Not a clean sweep. One more fact — "benchmarks 7 competitors annually via a mystery-shopper program" — stayed missing in all 3 post-fix runs. It didn't affect the score, since that question only required any one of three facts, but it doesn't fit the three categories I just expanded (numbers/named-entities, causal statements, third-party structure), so there's likely a fourth pattern I haven't classified yet. One data point isn't enough to fix it properly, so I logged it and left it for next time.

Patching the classification scheme every time a gap surfaces is inherently reactive — I went from 3 categories to 4 this round, and I'd bet a 5th pattern turns up eventually.

Separately: the guardrail that blocks fabricated content (citation fidelity) tripped in 3 of 5 runs on this sample — a higher rate than I've seen on the Japanese cases. Not sure yet if that's an English-specific weak spot or just noise from a small sample.

The actual takeaway

The bug wasn't the interesting part. What stuck with me was that I almost didn't catch it, because I was reading the average. Sum several scoring elements together and one of them can fail systematically, every single run, and still get buried under everything else scoring well. The agent looks fine on the surface metric while consistently missing one specific thing — which is exactly the pattern AnchorStrategy is supposed to catch in a human's business analysis. Finding it in my own grading pipeline first was not lost on me.

Try it: I'm running an early-access program. Registration takes just an email address — apply here, and I'll follow up with access.

Request access

Full score matrices, the complete generated reports (before/after the fix), and the grading rationale for every checklist item are public here, if you want to check my work:

Validation archive

on September 22, 2026
  1. 1

    Interesting problem.

  2. 1

    Great write-up, and the fact that you published the full validation archive makes it much easier to trust the numbers.

    On the "3 categories became 4, a 5th will show up" problem: what if the extraction step had a catch-all bucket for facts that don't fit any category, instead of dropping them? Downstream nodes could ignore it by default, but you'd at least see what's falling through. Then each run tells you which new category is needed, instead of you finding it through a missing score.

    The mystery-shopper fact also looks like a slightly different type than the others to me. The facts you fixed describe the company's position (no in-state competitor, no debt). This one describes something the company does about competitors, more like a practice or process. Might be the 4th pattern you mentioned.

    Looking forward to the results on a second case!

  3. 1

    Nice, this makes a lot of sense. What's been the most surprising part of it so far?

    1. 1

      Probably the rubric element that turned out to be satisfied by the wrong fact. We went looking for whether a specific competitor-benchmarking detail showed up in the report, expected a clean pass/fail, and instead found the grading criterion was being satisfied by an entirely different fact that happened to also qualify. So the score said "pass," the criterion was technically met, and the actual thing we cared about had never shown up at all. It wasn't hiding a failure so much as finding a legitimate-looking substitute to hide behind — which is a sneakier failure mode than I expected going in.

      Second most surprising: that fact still doesn't show up even after two separate rounds of fixing the extraction pipeline. Each fix closed the specific gap we found, but the fact kept dying somewhere else. That's the part that convinced me point-patches weren't going to cut it and pushed us toward the bigger rework that's happening now.

  4. 1

    This is a great example of why aggregate scores are dangerous in agent pipelines. I’d make each required fact an explicit handoff contract—source span, category, and confidence—and fail the run when a required element is missing, instead of letting a strong average mask it. Keeping the missing benchmark fact as a regression fixture should help prevent the next prompt patch from becoming whack-a-mole.

    1. 1

      Two of your three points are already shipped, one is exactly what we're working on right now — which is a good signal that this is the right shape of fix, not just our own pet theory.

      "Fail the run when a required element is missing instead of letting a strong average mask it" — done. We shipped per-element pass/fail gating a few days ago specifically because we found a run where the total looked fine but a specific element had failed on every single attempt, hidden entirely by the average.

      "Keep the missing fact as a regression fixture" — also done, same batch of work. We registered the exact fact from our root-cause writeup (a specific competitor-benchmarking practice that kept getting dropped) as a permanent held-out check. It still fails today, on purpose — the fixture's job is to stay red until the underlying extraction is actually fixed, not to get quietly patched away.

      "Source span, category, and confidence as an explicit handoff contract" — this is the piece we don't have yet, and it's next. Right now our fact-check is closer to a boolean (keyword present or not) than a structured contract with confidence. We're mid-way through pulling the grounding-check logic into its own module, and your phrasing of "handoff contract" is a cleaner way to describe where that's headed than what we had in our own notes. The confidence dimension specifically isn't something we'd built into the design yet — worth stealing.

  5. 1

    The element-level breakdown is the part everyone skips. We hit the same shape with tool calls: answer quality looked fine while the agent quietly ignored half of its tools, because the score measured the report rather than which tool produced it. Only showed up once we logged the actual call on each turn.

    1. 1

      Same shape, different layer. Our version: a rubric element would show as "satisfied" in the aggregate score, but when we dug into why, it turned out to be satisfied by a completely different fact than the one we thought we were testing for. The score measured "did a qualifying fact show up somewhere" rather than "did the specific fact we cared about show up" — so a real gap sat underneath a passing grade for who knows how long, exactly the way your tool-call example sat underneath a "looks fine" answer.

      Your framing of the fix is basically identical to where we landed too: you had to log the actual call per turn instead of trusting the final answer; we had to start tracking claims against the specific source span that grounds them instead of trusting the final rubric score. Both are the same move — stop scoring the artifact at the end and start requiring the intermediate step to be provably real. We're mid-way through pulling that grounding check into its own module right now, partly because "trust the aggregate score" is exactly the failure mode that bit us here.

      Curious how you're logging the tool calls — full request/response per call, or just a call-happened/call-didn't-happen boolean? We went with the richer version (keep the actual source span, not just a pass/fail flag) because a flag alone doesn't tell you which claim it validated, and that's usually the thing you actually need when you go looking for what broke.

  6. 1

    Version each fixture entry with the case that produced it — minimal repro input plus the fact it should have surfaced — so six months from now the file explains itself instead of becoming a wall of booleans nobody dares delete.

    For the per-run analog of session state: a facts ledger per input, where coverage = every ledger row either appears in the strategist's final report or is explicitly listed in an exclusions appendix. "Silently dropped" becomes a diffable failure instead of a vibe.

    1. 1

      Fair hit on the fixture format — right now the entry just points back at the full source document, not a minimal isolated repro. Six months from now that's exactly the "wall of booleans nobody dares delete" problem you're describing, because nobody will want to re-derive why that line exists. Pulling the minimal input + expected fact into the fixture itself, versioned together, is a straightforward fix and we'll do it that way going forward.

      The ledger idea is the sharper version of something we'd already sketched (tracking facts as retrieved/cited/omitted instead of a binary), but your framing closes a gap ours didn't: we were going to track three states internally, you're requiring every row to resolve to either "in the report" or "in an exclusions appendix," with nothing allowed to just not show up. That's the part that actually makes it diffable — a three-state internal tracker can still fail silently if nobody looks at it, but a mandatory exclusions appendix means an unaccounted-for fact is a visibly missing artifact, not a log line someone has to go dig for.

      Worth being honest about where this leaves us: what we have today is closer to "spot-check known facts we went looking for" (the holdout check from this thread) than "exhaustive ledger over everything extracted." Getting to your version means the ingest side has to retain a fact inventory in the first place, not just classify into fixed buckets — which is the same rework already on our plate for generalizing this past two cases. So it's less "great idea, filed away" and more "this is now the acceptance criteria for that rework."

  7. 1

    The "saw it but never wrote it down" failure is the one that keeps me up at night with multi-agent pipelines too. We run two-session agent workflows daily, and the only reliable check we've found is adversarial: after the pipeline finishes, a FRESH session gets the artifacts only — no shared context — and has to answer questions whose answers exist somewhere upstream. If it can't answer, the fact evaporated: the write never happened, no matter what the intermediate agent "saw".

    Curious which layer your fact died in — a tool result the research agent consumed but never quoted, or an intermediate summary the strategist's context truncated? The fixes differ: the first wants "cite it or lose it" rules baked into each artifact's definition of done; the second wants structured handoff files instead of prose summaries between stages.

    1. 1

      Ours died even earlier than both of your candidates: at ingest, before any research or strategist agent got a chance to consume or summarize it. The extraction schema pulled numbers and named entities from the source text, but a competitive-positioning fact that wasn't tied to a specific number or name never made it into structured form at all — so there was nothing downstream to truncate or fail to quote. It wasn't lost in translation between stages, it was never translated in the first place.

      What's uncomfortable is that fixing that (widening the extraction schema, then fixing a hardcoded category list one node downstream) didn't close the gap entirely. We have a second fact — a specific benchmarking practice — that still doesn't survive to the final report even after both fixes, and we haven't pinned down exactly which layer eats it this time. That's actually forced our hand toward your "structured handoff" direction rather than "cite it or lose it" alone: patching node-by-node every time we find a dead fact doesn't scale, and each fix so far has only caught the specific failure mode we went looking for, not the general one. We're now building toward per-node contracts (a defined set of facts/source-spans each node is required to pass forward, checked automatically) plus stable fact IDs so a missing one fails loud instead of just quietly not showing up in the output. Basically your two fixes aren't an either/or for us — the source-span requirement is the "cite it or lose it" part, and it only works because it's enforced through a structured contract instead of a prose summary.

  8. 1

    That "saw it but never wrote it down" gap is the #1 reliability failure I hit running an AI-agent-operated service desk. Two things actually fixed it:

    1. Schema, not habit: every customer-facing artifact must carry a provenance block — source URL, timestamp, the exact quoted claim — written at generation time. The artifact is invalid without it. Habits don't survive context loss; constraints do.

    2. A state file the agent re-reads at every session start: what shipped, what's pending, what failed and why. Boring, cheap, and it's the difference between an agent that's occasionally brilliant and one that's reliably employable.

    1. 1

      Both land, but differently for what I'm building. The provenance-block-as-schema idea is basically where this whole thread has been pushing me — treating "no source span, no valid output" as a hard constraint at generation time rather than something to catch downstream. Framing it as "the artifact is invalid without it" is a sharper way to say it than what I'd been calling a coverage check.

      The session-start state file is a different shape of problem for what I have, though — this pipeline runs one report per input, not a long-lived agent with sessions to pick back up. But there's an analog: I've been accumulating known failure patterns as I find them (this thread talked me into turning that into a proper regression fixture), and that's functionally the same thing — a small file the system gets checked against every run so past failures aren't silently repeated. Different trigger (per-run vs. per-session), same underlying idea: don't rely on memory or habit, make the constraint something the system re-reads.

  9. 2

    Good point. Did you test that with users before committing to it?

  10. 1

    Interesting problem. I think I would structure it a little differently.
    First, I would have one coordinating AI read all of the source material. Then I would ask it to determine what kinds of specialist AIs are needed, how many are needed, and how the work should be divided among them.
    After the specialists finish their work, I would feed all of their results back to the coordinating AI and have it integrate them.
    In other words, rather than deciding the specialist categories in advance, I would let the source material determine which specialists are needed.

    1. 1

      Interesting reframe — if I'm reading it right, this sidesteps the ingest problem entirely rather than fixing it, because there's no separate lossy summarization step deciding what's "worth" passing to a specialist. The coordinator sees the raw material and decides both what specialists are needed and what each one should look at.

      Tradeoff I'd want to think through: right now I can test a fixed node in isolation with a fixture and know exactly what it's supposed to do with a given input. If specialist allocation itself becomes a model decision made per-case, debugging "why did this pipeline drop a fact" gets harder, because the failure could now also be "the coordinator decided a competitor-analysis specialist wasn't needed here," which is a different and less visible failure mode than the one in this post.

      Does your version have the coordinator working off the full raw minutes, or a pre-processed version? That distinction seems like it determines whether this actually fixes the class of bug I found, or just moves it up a layer.

      1. 1

        Ideally, I would put an interpreter step before the coordinator.

        I would give the original meeting transcript to the interpreter first. But its job would not be to summarize the material or decide what is worth passing on.

        Its job would be to resolve ambiguity with the human. If something is unclear, missing, or seems easy to misinterpret, it should not fill in the blank by itself. It should stop and ask the human. The human and the interpreter can go back and forth until those possible misunderstandings have been clarified.

        Then the coordinator receives a version that is easier for the AI to understand and less likely to be misinterpreted.

        Before the coordinator starts, I would also give it a clear purpose, boundaries, and assumptions. It can then decide which specialists are needed and what each specialist needs to see.

        If information is still dropped after that, I would also look at whether there was a hole in the purpose, boundaries, or assumptions given to the coordinator, rather than treating it only as a routing failure.

        So yes, there is preprocessing in my approach, but the goal is not to reduce the information. The goal is to reduce misunderstanding before the coordinator begins making decisions.

        This isn't only theoretical for me. I've used this kind of approach while building WordPress plugins with AI, and I now have a working demo machine using them on my site.

  11. 1

    The line "I was reading the average" is the real finding here, more than the bug itself. A lot of eval setups aggregate scores exactly this way and never catch a systematic single-element failure hiding underneath a decent total this is a good argument for always breaking down by scoring element, not just tracking the composite number.

    Also appreciate the honesty about the "benchmarks 7 competitors" fact still not being caught after the fix. Easy to present a clean before/after and quietly skip the part that's still broken.

    How are you deciding when a new category is genuinely a new pattern versus just overfitting to this one case? With 4 categories after one round of patching, that reactive-classification concern you raised seems like the real long term risk here.

    1. 1

      Honestly, right now I don't have a good way to tell the difference — that's exactly the gap you're pointing at, and it's the same one a few other comments in this thread converged on independently. The answer I'm landing on isn't "get better at judging when a category is real," it's to stop doing category-based inclusion at that node entirely — default to keeping everything from the minutes and define a small denylist of what's safe to drop, instead of trying to positively enumerate what matters. If I'm not classifying to decide what survives, the "is this genuinely a new pattern" question mostly goes away, because nothing has to earn its way into a bucket to avoid being dropped.

  12. 1

    Average scores can hide the failure that matters, and the same is true of AI visibility. I would keep a per-question ledger for each evaluation run, recording which facts were retrieved, cited, or omitted, then compare the same prompts after every change rather than trusting one blended score.

    1. 1

      The three-state version — retrieved / cited / omitted — is more granular than what I've been tracking, which has basically been binary (did the fact make it into the report or not). That distinction would've told me something extra here: whether the competitor fact was pulled out at minutes_ingest and then dropped later, versus never retrieved at all. Turns out it was the latter (dropped at the very first step), but I only know that because I happened to trace it manually — a ledger with that granularity would've told me immediately, and it would also flag the "retrieved but not cited" case, which right now I have zero visibility into.

  13. 1

    The bucket gap is the sneaky part - a strength stated without naming a competitor fits neither "industry averages" nor "named-competitor data", and a single total score hides exactly that kind of failure. I run a similar generator/grader split and only caught a comparable drop after logging per-criterion scores instead of just the total. Did you fix it with a third bucket, or by widening what minutes_ingest extracts?

    1. 1

      Both, actually — widened what minutes_ingest extracts (from numbers/named-entities to causal statements and third-party structure generally), and added a new category at the competitor-analysis node specifically for "relative position without naming a competitor." Widening ingest alone wouldn't have helped, since the downstream node still had nowhere to put the fact once it arrived. Same experience on the per-criterion logging, for what it's worth — the total genuinely looked fine until I broke it down.

  14. 1

    I'd treat the extractor's output as a coverage contract, not just a summary: emit each fact with a stable ID, source quote, and category, then fail the next node if a required ID is missing. That would make the "competitor" drop visible in the intermediate state and give you a regression fixture without patching every downstream category.

    1. 1

      The stable-ID piece is the detail I was missing from "assert facts survive" — without an ID, "did this fact survive" is fuzzy and hard to check automatically, since paraphrasing makes exact-text matching brittle. Tying a fact to an ID at extraction and failing the next node on a missing required ID turns this from "periodically eyeball whether it's still there" into an actual pipeline gate. And getting the regression fixture as a byproduct of that, instead of having to build it as a separate project, is a real bonus — makes it much more likely I actually do this instead of just agreeing it's a good idea and not getting to it.

  15. 1

    What stood out to me was that the final score looked healthy while the pipeline had already lost an important fact several steps earlier.

    That makes intermediate-state testing really important for multi-step AI workflows. A final-output test can tell you that something is wrong, but it may not tell you where the information disappeared.

    I’d be tempted to keep a small set of “must survive” facts and check them after each major node. Then when a prompt or schema changes, you can immediately see which stage stopped carrying the information forward.

    The aggregate score is still useful, but the intermediate checks seem like they could turn debugging from “find the missing fact” into “find the exact stage that dropped it.”

    1. 1

      That last line is a good way to put what actually happened — finding the missing fact was an afternoon of manually walking the intermediate state output, and it would've been minutes with must-survive checks at each node instead. The aggregate score told me something was off; it took a lot of manual tracing to find where. This is roughly the fourth version of "add intermediate checks" this thread has converged on, so it's clearly the first thing to build, not a nice-to-have I get to later.

  16. 1

    Exactly. The issue isn’t always that the AI can’t see the information—it’s that the information never gets captured, structured, and stored in a way the system can use later. Great reminder that observation and memory are two very different things.

    1. 1

      That distinction is really the whole crux of it — the model technically "saw" the fact in the sense that it was right there in the input, but seeing something and doing something durable with it turned out to be two different capabilities, and I'd been implicitly assuming the first implied the second. Wouldn't have put it that cleanly myself.

  17. 1

    The post-fix lift is compelling, especially because you kept the same case and isolated the dropped category. I’d separate two checks going forward: (1) a coverage invariant at each node so required facts retain source spans, and (2) a small untouched holdout set to distinguish generalization from patching known failures. For the benchmark fact that still disappears, could an “unclassified but retained” lane be safer than adding another bucket?

    1. 1

      Yes, and that's a good, concrete way to apply what this whole thread converged on — instead of asking "what's the 5th category," the benchmarks-7-competitors fact becomes the first real test of the retained-but-unclassified approach. If it survives in an unclassified lane instead of getting silently dropped again, that's a much better signal than "I added a category and it worked on the one case I built the category from." Actually going to use that specific fact as the first entry in the holdout set, since I already know the current pipeline fails it.

  18. 1

    The end-to-end fixture is the piece I'd prioritize. Keep one case with required facts and assert at each node that the facts and source spans survive, then run it on every prompt or schema change. I would also hold out a few unseen cases so a fix does not just memorize the failures you already found.

    1. 1

      Prioritizing the end-to-end fixture over the per-node assertions makes sense as the first piece — it's the one that would've caught the original bug outright, no manual intermediate-state walking required. And the held-out set is the part I was actually missing: everything I've tested so far, including the "3 clean runs post-fix," was against cases where I already knew the failure mode existed. A fix that only works because it memorized the specific gap isn't really a fix. Adding a couple of genuinely unseen cases before I call anything resolved.

      (Also — good username to be giving me pipeline architecture advice under.)

  19. 1

    The category-by-category patching is going to keep finding new gaps forever — there's no reason the space of "kinds of facts that matter" is finite, and you've already found a 4th pattern that doesn't fit the first 3. Might be worth inverting the whole approach: instead of classifying facts into buckets and keeping only what's classified, default to keeping everything from the minutes and define a much smaller "safe to drop" list (boilerplate, exact restatement, obviously irrelevant procedural notes). An unclassified fact then survives by default instead of silently disappearing, which matches what you actually want from something whose whole job is not losing information.

    1. 1

      This is the third comment in this thread converging on the same fix, which is a pretty strong signal I should stop resisting it. Framing it as a "safe to drop" denylist instead of "keep everything with no filter at all" is the piece that makes it concrete — a short list of boilerplate/restatement/procedural noise is something I can actually write and audit, whereas "classify what matters" turned out to be an open-ended list I kept discovering the hard way, one production gap at a time. Inverting that node from allowlist to denylist is the actual change, not just "add more categories."

  20. 1

    Solid point. Validating user demand before getting too deep into the architecture saves so much time.

    1. 1

      Fair, and it's a real tension with where this thread has been pushing me — a lot of good architectural suggestions here (retention over classification, per-node contracts, promotion rules) that I want to implement, but none of them matter much if nobody outside my own testing ever uses the output. Probably makes more sense to get a couple of real users on the current version before I rebuild the ingest node, not after.

  21. 1

    Good point. Did you test that with users before committing to it?

    1. 1

      Not with real users, no — everything so far has been tested against exam rubrics and scoring criteria, not against someone actually using it to do their job. That's the gap I'm aware of and haven't closed yet. The early-access program is partly meant to fix that: get it in front of a few people who'd actually use this kind of report, see where it breaks in practice rather than on paper.

  22. 1

    The classification-gap diagnosis is right, but the 3→4 categories trajectory is a signal about the architecture, not just the taxonomy. Every classification scheme is a lossy filter — whatever doesn't fit a bucket gets dropped silently. For the ingest node specifically, retention beats classification: keep every fact with its source line, tag nothing, and let the nodes that actually know the question do the filtering downstream. Ingest doesn't yet know which facts will matter; the grader does. And on almost missing it: per-element scores saved you because the total is a summary designed to hide exactly this class of bug — per-element failure rates deserve to be the default view, not a drill-down.

    1. 1

      This reframes it correctly — 3→4 wasn't "found two more patterns," it's evidence that anything I bucket at ingest will leak, no matter how many buckets I add. "Retention beats classification" at that specific node is the fix I hadn't considered: don't ask ingest to judge relevance at all, just keep every fact with its source line and let the downstream node that actually knows the question do the filtering. That also makes the uncategorized-bucket idea from earlier in this thread unnecessary at that stage — nothing to bucket if nothing gets dropped there in the first place.

      One thing I'm not sure about: does full retention at ingest just push the same lossy-filter problem one node downstream, onto whichever node does the question-aware filtering? Or is that fine specifically because a node that knows the question is a fundamentally better place to filter than one that doesn't? Leaning toward the latter, but want to think it through before I rip out the ingest schema.

      And agreed on per-element being the default view — burying it as a drill-down is basically saying "trust the summary unless you go looking," which is the exact behavior that let this slip past me in the first place.

  23. 1

    Good point. Did you test that with users before committing to it?

  24. 1

    Aggregate scores can hide the one failure that matters. I’d pair the overall score with minimum gates for critical criteria, so a systematic zero can’t be averaged away. That’s a principle we use in Agiloop’s assessments: the score summarizes, but the evidence and individual findings drive the decision.

    1. 1

      That's the same direction I'm already moving in — minimum gates per criterion, not just an overall number. What made this one persuasive is that the summarized score genuinely read as "seems fine," and it took slicing by question instead of by the total to find the one gate failing almost every run. Curious how Agiloop decides which criteria get a hard gate versus which stay folded into the summary score — is that a fixed list per assessment type, or tuned per engagement?

  25. 1

    That gap between perceiving something and actually persisting it is such a subtle failure mode. Did you trace it back to a memory/context issue or something in how the agent decides what's worth recording?

    1. 1

      Not memory/context — nothing was truncated or fell out of a window. It's squarely the second one: how the agent decides what's worth recording in the first place. The minutes-summarization step had an extraction schema tuned for numbers and named entities, and this fact (no in-state competitor, stated without naming one) didn't match either. It wasn't dropped after being noticed — it was never flagged as recording-worthy to begin with. Same story one step downstream, too: the competitor-analysis node's own classification only had two buckets (industry averages / named-competitor data), and a fact about relative position without naming a competitor fit neither. So less a memory failure, more a taxonomy failure, twice in a row.

  26. 1

    This is one of the best build-in-public debugging write-ups I've read here, and the meta-lesson is the real contribution: aggregate scores bury systematic failures. Three pushes: 1. The reactive category patching is the actual unsolved problem. Instead of widening the extraction schema each time a gap surfaces, invert it: extract everything candidate-relevant into an unstructured pool first, then classify — with an explicit "uncategorized" bucket that gets reviewed, not silently dropped. Silent drops are the failure mode; an uncategorized queue is at least visible. 2. Watch your grader. The citation-fidelity guardrail tripping 3 of 5 runs on the English case vs rarely on Japanese is worth investigating — but note the confound: you wrote the English case. Blind the grader to pre/post-fix and case authorship. 3. n=5 → n=3 is thin for the claims. "Full marks all 3 times" is encouraging, not conclusive — with that variance, three clean runs could be luck. Report the range alongside the mean every time. Publishing the full score matrices and grading rationale is exactly right — that's what turns a blog post into something the community cites.

    1. 1

      This is a great comment, going through each point:

      The extract-everything-into-a-pool-then-classify-with-an-uncategorized-bucket approach is better than what I've been doing. My current method is additive — widen the schema each time a gap surfaces — which means anything that doesn't fit yet gets silently dropped, and I only find out by manually walking the intermediate state after something already looks wrong. An uncategorized queue makes the gap visible immediately instead of requiring me to go looking for it. That's a real fix for the "there's probably a 5th pattern" problem, not just a patch. Reworking extraction around this instead of hand-adding a 4th and 5th category.
      Fair catch, and sharper than the doubt I already had. I flagged the English-vs-Japanese trip rate as "not sure if it's language-specific or noise," but hadn't isolated that I wrote the English case myself — that's a real confound, separate from sample size. The grader doesn't see my prompt or intent, but I honestly can't say right now whether it's blind to case authorship or pre/post-fix status the way it should be. Need to check that before I put any weight on the 3-of-5 number.
      Agreed on n=3. "Full marks all 3 times" reads more conclusive than it is — should be reporting the range next to the mean by default, and probably shouldn't have called it "fixed" off three runs.

      And appreciate the note on the matrices — that's the whole point of publishing them, glad it's landing as something to actually check rather than just a claim to take on faith.

      1. 1

        Glad the uncategorized-bucket idea landed. One thing I'd add on the review cadence: give the bucket a promotion rule, not just a review — e.g. if the same item type shows up uncategorized 3+ times in a week, it automatically becomes a category candidate. Otherwise the queue becomes a junk drawer you stop opening.

        On the grader confound: the cleanest fix is having someone else write fresh cases, but even without that you can blind the grader to pre/post-fix by shuffling case IDs and running both versions through the same grading pass. That at least separates 'the fix works' from 'I know which runs are the fix.'

        And for the thin-n problem: pre-register the pass criteria before running. 'Three consecutive full marks, declared in advance' turns three clean runs from 'maybe luck' into an actual test. The discipline that caught the bug is worth applying to the fix.

        1. 1

          All three land.

          Good point on the promotion rule — a review-only queue is exactly the kind of thing I'd stop opening after week three. A concrete threshold (3+ occurrences in a week → category candidate) turns it into something that surfaces itself instead of something I have to remember to check.
          Shuffling case IDs and running both versions through the same grading pass is the actionable version of "blind the grader" — I don't have a second person to write fresh cases, but shuffling IDs is something I can just do. Going to rerun the existing before/after set that way before I trust the comparison at all.
          Fair, and it stings a little because it's obviously right — I declared "fixed" after seeing the result, not before. Pre-registering the pass bar (e.g., "3 consecutive full marks on this specific check" stated before running, not after) is the same discipline that found the bug in the first place, and I wasn't applying it symmetrically to the fix. Adopting that for the next round.

  27. 1

    Interesting approach. What was the hardest part to get right?

    1. 1

      Tracing the drop, honestly — not fixing it, finding it. The fact wasn't just misclassified somewhere visible, it vanished before it ever reached the node whose job was to handle it. Had to walk the intermediate state output node by node until I found the first point where it was already gone, which turned out to be the very first summarization step. If I'd only looked at the node "responsible" for competitor analysis, I'd have never found it, because by the time execution got there the information simply didn't exist anymore.

  28. 1

    The useful design distinction here is between a score and a coverage contract. For each case, keep a small set of must-survive facts (with source spans) and fail the run if any disappear before the final report. That turns “the agent saw it but dropped it” into a testable handoff failure, rather than another downstream prompt patch. I’d version that contract separately from the prompts.

    1. 1

      Tracing the drop, honestly — not fixing it, finding it. The fact wasn't just misclassified somewhere visible, it vanished before it ever reached the node whose job was to handle it. Had to walk the intermediate state output node by node until I found the first point where it was already gone, which turned out to be the very first summarization step. If I'd only looked at the node "responsible" for competitor analysis, I'd have never found it, because by the time execution got there the information simply didn't exist anymore.

  29. 1

    Really relatable. How much time do you put into this each week?

    1. 1

      This is a side project around a full-time day job, so realistically it's evenings and weekends — not a fixed number, more like whatever's left after work on a given week. Some weeks that's a few focused hours on one bug (like the one in this post), other weeks it's mostly writing up what I found rather than building. Not sustainable at startup-hours pace, but enough to keep the validation loop moving.

  30. 1

    Such a good catch. Aggregate scores can hide a repeatable failure for a long time. I would add tiny invariants at every node so a required fact disappearing fails loudly. The hardcoded downstream list is painfully real too.

    1. 1

      Yeah, "fail loudly" is the key phrase — right now a disappearing fact fails silently, it just quietly lowers an average three questions later. Invariants at every node instead of one end check is the fix. And glad it's not just me — the hardcoded downstream list is the kind of bug that makes you feel dumb for an afternoon and then relieved when you realize it's a common shape of mistake, not a personal one.

  31. 1

    Averages can hide exactly the failure you care about. I like treating each intermediate transform as a contract: list required facts, assert they survive every node, and keep one end-to-end fixture. Score coverage and fidelity separately from final usefulness. That makes regressions visible without patching prompts ad hoc.

    1. 1

      Per-node contracts is a sharper version of what I've been doing by hand — tracing intermediate state after I already suspect something's off, instead of asserting it automatically at each step. That's exactly why the minutes_ingest drop sat unnoticed as long as it did: nothing flagged it, I only found it because I went looking.

      And splitting coverage / fidelity / final usefulness into separate scores instead of one blended number is a real correction. I've been treating "did the required fact survive" and "was it represented correctly" as basically the same signal, and they're clearly not — the mystery-shopper fact failed on coverage, but a fact that survives and gets subtly distorted downstream is a different failure mode entirely, and right now I'd have no way to tell those apart. Building the fixture set with that split in mind from the start, not bolting it on after.

  32. 1

    The "averages hide systematic failure" discovery is the most useful thing in here. Sum several scoring elements and one can fail every single run while the total still looks acceptable. That pattern is everywhere in multi-agent pipelines.

    I hit something similar building Genie. Intent classification was scoring fine on aggregate accuracy but failing completely on one edge case category that made up 12% of real production requests. The category wasn't in the eval breakdown so it never flagged. I only caught it when I sliced the failures by type instead of reading the aggregate pass rate.

    The hardcoded category list in the downstream formatter is the part that always catches people off guard. Fix the upstream extraction, re-run, wonder why the output didn't change. The dependency is invisible until you trace the intermediate state manually.

    On the reactive patching problem: have you considered writing a coverage test that explicitly asserts each fact type against a canonical case? That way a new classification gap surfaces as a failing test rather than a production scoring drop.

    1. 1

      The Genie example is a really clean parallel — 12% of real traffic silently failing because the category wasn't even in the eval breakdown is the same shape of bug, just found from the production side instead of the eval side. Good reminder that this isn't a "my pipeline" problem, it's a "aggregate metrics" problem.

      And yes, hit that exact hardcoded-formatter trap — fixed the upstream extraction, reran, output didn't change, spent longer than I'd like to admit assuming I'd made an error before tracing it down to a downstream node silently dropping the new category. Invisible until you manually walk the intermediate state, agreed.

      On the coverage test — that's exactly where I'm heading next: a canonical case per fact type, asserted explicitly, so a new gap shows up as a red test instead of a quietly-lower average three weeks later. Don't have it built yet, but this thread is basically talking me into prioritizing it over the next validation round instead of after it.

  33. 1

    Nice work shipping it. What has been the biggest challenge since launch?

    1. 1

      Honestly, "since launch" undersells where I'm at — it's still invite-only, no public launch yet. So the biggest challenge so far isn't a launch-day fire, it's the gap this thread already poked at: getting from "passes my own rubric" to "actually useful for someone who isn't me." Everything's been validated against exam scoring criteria, not against a real person using it for their own work, and I don't yet know how those two diverge.

  34. 1

    Thanks for sharing the numbers, that makes it much easier to follow.

    1. 1

      Thanks for reading it — glad the numbers made it easier to follow rather than just more noise to wade through.

  35. 1

    The real finding here is not about agents, it is about averages. A 78 total hiding one element that fails 4 runs out of 5 is the same trap as a healthy blended CAC hiding one channel that never converts, and you only catch it in the line items. I would stop scoring on totals and make each element a pass/fail gate, so a run with 90 points and a zero anywhere still counts as a failed run.

    1. 1

      You're right, and this is actually the direction I'm already moving toward — tracking pass/fail per required element instead of a summed score, so a 90 with one hard zero doesn't get averaged into "looks fine." The CAC analogy is a good one; that's exactly the kind of blending that let this sit unnoticed for longer than it should have. Going to make the gate explicit rather than something I have to remember to check manually each time.

  36. 1

    Good point. Did you test that with users before committing to it?

  37. 1

    Good point. Did you test that with users before committing to it?

    1. 1

      Not with real users, no — everything so far has been tested against exam rubrics and scoring criteria, not against someone actually using it to do their job. That's the gap I'm aware of and haven't closed yet. The early-access program is partly meant to fix that: get it in front of a few people who'd actually use this kind of report, see where it breaks in practice rather than on paper.

  38. 1

    The post-fix score is encouraging, but how will you distinguish genuine generalization to unseen cases from improving performance on failure patterns you've already identified?

    1. 1

      Good question — honestly the part I'm least confident about. The 3 post-fix runs were on the same case I used to find the bug in the first place, so this doesn't rule out just overfitting to that one pattern. What would actually convince me is running the same fix against a completely different case (different industry, different competitive structure) and checking the miss rate holds up there too — haven't done that yet, it's next on the list, along with building a proper held-out test set instead of eyeballing individual runs. Will post an update once I have real numbers instead of "seems better."

  39. 1

    This is useful. How are you finding your first users so far?

    1. 1

      Thanks, glad it was useful! Honestly still very early on the user side — no public launch yet, just reaching out directly to a handful of people who actually write this kind of report for a living (mostly independent consultants) and asking them to try it. Wanted the failure modes ironed out first before pushing on distribution. This post is partly that too — seeing if the story holds up with people outside my own bubble before I put more effort into finding users.