/library/decisions/jim-agent
ADR-0006 — Research quality: memo cache, completeness check, structured judge, eval rubric
The pipeline could prove a memo was sourced (the deterministic gate) and score it faithful (a single 0–1 judge number), but four quality gaps remained:
mirrored from ~/dev/jim-agent/docs/adr/0006-research-quality-memo-cache-completeness-judge-rubric.md · commit e9a4186 · synced 2026.07.09Accepted
Status: Accepted
Context
The pipeline could prove a memo was sourced (the deterministic gate) and score it faithful (a single 0–1 judge number), but four quality gaps remained:
- Redundant inference. Identical repeat queries (precompute, popular tickers, an agent re-asking) re-ran the whole LLM pipeline even when the underlying data hadn't moved — paying full inference for a byte-identical answer.
- Blind to omission. The gate validates what the memo includes; nothing checked what it left out. A memo could pass while silently dropping the single most important line item.
- A coarse judge. One groundedness float says "0.7" but not which claim is weak, and every run used the same small model regardless of stakes.
- "Better" was underdefined. Gate pass-rate + one faithfulness score don't capture quality; there was no composite to rank single-pass vs. debate vs. a model swap on goodness.
Decision
A fingerprinted memo cache that serves identical answers for free, a deterministic completeness signal, a per-claim judge with a high-stakes model tier, and a weighted rubric that makes "better" measurable offline.
- Memo cache (engine.py
_memo_cachenode +memo_cachetable). Aftergather, the fresh snapshot is fingerprinted (Snapshot.fingerprint()— a hash of (label, unit, value) over all facts). If a cached memo exists for{product}:{identifier}:{mode}with a matching fingerprint, within TTL, and it still passes the deterministic gate against the fresh snapshot, it's served and synthesis/debate/judge are skipped (inference → $0). The gate re-check is the safety net: a memo can only ship if it's still fully sourced, so nothing stale leaks through. Volatile data (a moved price) changes the fingerprint and correctly forces a re-synthesis — the cache hits only when the data is genuinely unchanged. Bypassable per-run (use_memo_cache=False); the eval disables it so A/B variants aren't short-circuited. - Completeness (completeness.py). The gate's mirror image: which snapshot facts did the memo never cite, and which of those are material (core line items + headline ratios)? Deterministic, no model. A signal, not a gate — terse agent-mode legitimately omits, so a material omission lowers the quality score and is surfaced to the caller, but never rejects a run.
- Structured judge (judge.py). The judge now
returns a per-claim checklist (each claim → supported? which citation? why),
not just a scalar — so a low score is explainable. High-stakes runs
(
high_stakes=True) upgrade to a stronger model (JUDGE_HIGH_STAKES_MODEL, Sonnet) for more scrutiny where a wrong call is expensive. Still fail-closed (unparseable → reject) and still skipped without a key. - Eval rubric (rubric.py). A weighted composite over sourcing + completeness + impersonal (all deterministic, no key) plus faithfulness when a key is set. Weights are explicit and in one place, so "what we optimise for" is legible. The eval reports per-dimension means + the composite and uses it as the headline single-pass-vs-debate lift metric.
Consequences
Positive
- Repeated identical queries cost ~$0 inference, with correctness guaranteed by the same gate that guards fresh runs — caching never weakens the core promise.
- Omissions become visible; the product can be judged on what it should have said, not only on what it did.
- A weak judge verdict now names the offending claim, and high-stakes runs get a stronger reviewer without making every run pay for it.
- "Better output" is a number that moves, computable offline for CI.
Negative / trade-offs
- The fingerprint is exact: any moved value invalidates the cache, so equities with live prices (RSI/MACD) rarely hit it — correct, but it means the win concentrates on stable fundamentals / quiet windows, not every query.
MATERIAL_LABELSis a curated set; a fact outside it that's contextually important won't be flagged. It's a floor, not a complete model of materiality.- The per-claim judge spends more output tokens than the scalar one; high-stakes Sonnet costs more — opt-in for that reason.
- The rubric weights are a judgement call (sourcing-dominant); they're tunable in one place precisely because they're opinions, not ground truth.
- New table → existing deployments re-run
jim-initdb(idempotent) before the memo cache persists.
ELI5 / what I learned
Four upgrades to "is the answer good?" First: if someone asks the exact same question and nothing in the data changed, don't pay to think again — hand back the saved answer, but only after re-checking it still lines up with the facts, so a stale note can never sneak out. Second: the old checker only made sure every number written down was real; the new one also notices when an important number was left off the page. Third: instead of the grader scribbling one score, it now writes a checklist — this sentence is backed by that fact, that one isn't — and for the high-stakes papers it brings in a sharper grader. Fourth: we wrote down what "good" actually means as a weighted scorecard, mostly checkable without calling the expensive grader at all. The lesson: cache the thinking, not just the data; check for sins of omission, not only commission; make the grader show its work; and turn "better" into a number before you try to improve it.