Case-Corpus Knowledge Base for Tier 2
V1's Dump Triage PRD (§4.3/§4.4) scopes a knowledge base as a V1.1 feature: every resolved, opted-in escalation seeds a corpus that a later Tier 2 run can retrieve prior similar cases from. V1 ships only the write side; no case corpus exists to retrieve from at launch. This document specifies that V1.1 build — schema, write path, and structured-match retrieval — now that the write-side stopgaps in the code are ready to be replaced with the real thing.
1. Overview & Goals
The V1 PRD's §4.4 already commits to the shape of this feature: "Engineer resolution is captured back with structured metadata (bugcheck, module, root cause tag) to seed the V1.1 knowledge base — full customer transcripts are not copied into the shared corpus without explicit customer opt-in." §4.3's "V1 cut" box makes the sequencing explicit: V1 writes, V1.1 reads. This PRD is that read side, plus the write-side cleanup V1 deliberately deferred.
- Give every staff-resolved, opted-in escalation a durable, structured home — independent of the escalation queue's own lifecycle and independent of dump/transcript retention.
- Let Tier 2 ground its analysis in prior similar cases the same way it already grounds in Microsoft's own bugcheck docs (
triage/bugcheck_reference.py): best-effort, cached, never load-bearing. - Close out the two stopgaps V1 deliberately left in place —
root_cause_tagliving as a text prefix onresolutioninstead of a real column, and the fact that nothing readskb_opt_inyet — without redesigning anything V1 already committed to in its PRD.
2. Non-Goals
- Embeddings/vector search. The corpus is small and newly-seeded; exact/structured matching on already-deterministic signals (bugcheck code, module, failure bucket) is cheap, explainable, and sufficient at this scale. Revisit only once corpus size and match-quality data justify the added pipeline.
- Raw customer transcripts in the shared corpus. Not a phased rollout item — never in scope, at any corpus size, under this design. See §5.
- A staff-facing "browse the KB" UI. Retrieval is consumed by Tier 2's prompt construction, not by a human browsing interface. A browse view is a plausible future nicety, not a launch requirement.
- A second, separate customer-facing consent flow. §5 explains why the existing
kb_opt_incheckbox is sufficient given the transcript-exclusion decision, rather than requiring a new customer-facing opt-in surface.
3. Current State (V1)
V1 already ships the scaffolding this build completes:
escalations.kb_opt_inandescalations.resolution_fed_back_to_kbexist (models/escalation.py) and are set together inresolve_escalation— V1 only ever writes the flag, nothing reads it yet.- The staff resolve form already has a "Feed this resolution into the future knowledge base (V1.1)" checkbox (
StaffEscalationQueuePage.tsx). root_cause_taghas no real column.escalations_staff.py'sresolve_escalationstuffs it intoresolutionas a[root_cause_tag=...]text prefix, with a comment flagging this as a stopgap for exactly this PRD's build.transcript_signals.parse_signalsalready extracts bugcheck code, exception code, faulting module, and failure bucket deterministically from every job's transcript — the KB doesn't need to re-derive any of this; it only needs to persist what Tier 1/2 already computed.triage/bugcheck_reference.pyestablishes the pattern this build's retrieval layer follows: best-effort, per-process cached, fails safe, never blocks or fails a job.
No case corpus exists yet at meaningful volume — this PRD's write path is what starts accumulating one; its retrieval path should not go live until it has.
4. Data Model
New dedicated table rather than columns on escalations — the corpus needs to survive independent of the escalation queue's own lifecycle (an operational, short-lived staff record) and independent of the dump/transcript retention sweep (§8). Coupling the two would mean either the KB corpus decays with routine retention, or the escalation record has to be kept alive for the KB's sake — both wrong.
| Table / Column | Type | Notes |
|---|---|---|
kb_entries.id | UUID, PK | |
kb_entries.bugcheck_code | int, nullable | From ParsedTriage, kernel-mode jobs |
kb_entries.exception_code | int, nullable | From ParsedTriage, user-mode jobs |
kb_entries.faulting_module | text, nullable | Via normalize_module — same normalization already used for the Tier 1/2 agreement check |
kb_entries.failure_bucket_id | text, nullable | !analyze -v's bucket ID where recognized |
kb_entries.dump_type | enum | user_mode / kernel_mode, same enum as dumps.dump_type |
kb_entries.root_cause_tag | text, nullable | Engineer-authored short tag |
kb_entries.resolution_note | text | Engineer's resolution text. Never a transcript reference — see §5 |
kb_entries.source_escalation_id | UUID, FK → escalations.id, nullable | ON DELETE SET NULL — traceability without coupling the KB row's survival to the escalation row's |
kb_entries.created_at | timestamptz | |
escalations.root_cause_tag | text, nullable | New real column, replacing the [root_cause_tag=...] prefix hack in resolution |
Indexes on bugcheck_code, faulting_module, and failure_bucket_id — the three columns §7's retrieval query filters on.
5. Consent & Privacy
kb_entries, regardless of kb_opt_in. A KB row holds only structured signals already extracted deterministically (bugcheck/exception code, module, failure bucket) plus the engineer's own root_cause_tag and free-text resolution note.
This is the stricter of two readings the V1 PRD's §4.4 sentence supports — "structured metadata... to seed the knowledge base" as the baseline behavior, with the transcript-opt-in clause read as a description of what V1 doesn't do, not as a future opt-in tier to build. It's the safer default for a shared corpus: dumps can contain credentials and session tokens in the clear (V1 PRD §8), and a resolution note an engineer writes by hand carries essentially none of that risk, while a raw transcript carries all of it even after redaction. It also avoids designing a second, transcript-specific consent gate on top of the existing kb_opt_in checkbox — one flag, one meaning: "this case's structured shape and resolution may inform future triage," never "this transcript may be read by staff working an unrelated customer's case."
If a future need for full-transcript retrieval emerges, that is a separate, explicitly-scoped feature with its own customer-facing consent surface — not an extension of this design.
6. Write Path
Changes to resolve_escalation (escalations_staff.py):
- Write
body.root_cause_tagto the newescalations.root_cause_tagcolumn. Drop the[root_cause_tag=...]prefix onresolutionentirely — the resolution field goes back to being just the engineer's resolution text. - When
body.kb_opt_inis true: load the job's tier reports, pull the already-parsed signals (bugcheck/exception code, faulting module, failure bucket — computed once byparse_signals, not re-parsed here), and insert onekb_entriesrow alongside the existing escalation update, in the same transaction. escalation.resolution_fed_back_to_kbkeeps its current meaning as the audit-visible flag that a KB row was (or wasn't) written for this escalation.
No change to the staff UI's existing checkbox or its copy — it already reads "Feed this resolution into the future knowledge base (V1.1)," which is accurate as-is.
7. Retrieval Design
New triage/kb_lookup.py, deliberately mirroring bugcheck_reference.py's shape: a lookup failure (or simply no match) means Tier 2 runs without prior-case grounding, same as it does today — never a reason to fail or delay a job.
- After
parse_signalsruns intier2_runner.run_tier2(tier2_runner.py), querykb_entriesfiltered on the new job'sbugcheck_code/exception_codeplusfaulting_modulewhere available, falling back tofailure_bucket_idalone if the code+module pair has no match. Most-recent-first, capped at a small number of entries (e.g. 3–5) — enough context, not enough to dominate the prompt. - Inject matches into
TIER2_SYSTEM_PROMPT's context as a distinct, clearly-labeled section ("prior similar cases"), each entry showing bugcheck/module/root-cause-tag/resolution-note — never framed as authoritative, since a superficially similar bugcheck/module pair can still have an unrelated root cause. Tier 2's existing instruction to cite specific transcript evidence for its own conclusion stays unchanged; prior cases are grounding context, not a substitute for evidence from this job's own transcript. - No embeddings, no semantic ranking — a structured filter match on columns that are already indexed and already computed. See §2 for why this is the right starting point rather than a corner cut.
8. Retention Interaction
kb_entries has no foreign key to dumps or to any transcript storage — only a nullable, SET NULL-on-delete reference back to the source escalations row. The retention sweep (retention_sweep.py) already only walks dump/transcript-linked state; a KB entry should be structurally unreachable from it. This needs an explicit test (§9), not just an argument from schema shape — the existing note at test_retention_sweep.py:73 ("metadata survives for internal aggregate/KB use") already anticipates this and should be extended to cover the new table directly.
9. Testing Plan
- Write-on-resolve: a
kb_opt_in=trueresolution produces exactly one correctly-populatedkb_entriesrow; akb_opt_in=falseresolution produces none;root_cause_taglands in its own column on both the escalation and (when opted in) the KB row, with no prefix hack anywhere. - Retrieval matching: exact bugcheck+module match, failure-bucket-only fallback match, and the no-match case (empty result, Tier 2 proceeds normally) — following the fixture-driven pattern already started for
bugcheck_reference.py(test_bugcheck_reference.py,fixtures/transcripts/kernel_wrong_symbols_nmi.json). - Retention survival: a swept dump/transcript's associated KB entry (via its now-
NULLedsource_escalation_id) is still present and queryable after the sweep runs.
10. Rollout Plan
-
Phase 1 P0
Schema
Migration adding
kb_entriesandescalations.root_cause_tag. No behavior change on its own. -
Phase 2 P0
Write path
resolve_escalationwrites the realroot_cause_tagcolumn and, on opt-in, akb_entriesrow. This is what starts the corpus accumulating — ship and let it run before Phase 3. -
Phase 3 — gated on real corpus volume P1
Retrieval
triage/kb_lookup.pyplus thetier2_runnerwiring. Do not enable against an empty or near-empty corpus — there's nothing to retrieve, and it's easier to validate match quality once there's real data to match against. -
Not scheduled P2
Embeddings/semantic retrieval, staff-facing KB browse view
Revisit only if structured matching's precision/recall against real usage data falls short, or a concrete staff workflow need for browsing emerges. Neither is a launch requirement (§2).
11. Risks & Open Questions
- Match-quality is unvalidated until there's a real corpus to test retrieval against — Phase 3's gate on corpus volume (§10) is deliberate, but "enough volume" isn't yet a defined threshold.
- A superficially similar bugcheck+module match can still have an unrelated root cause; the prompt framing in §7 mitigates this but doesn't eliminate the risk of Tier 2 anchoring on a misleading prior case. Worth watching for in Tier 2 output review once Phase 3 ships.
- No mechanism yet to correct or retract a bad KB entry (e.g. a resolution later found to be wrong). Out of scope for this build; flag as a follow-up once the corpus is large enough for bad entries to matter.
12. Tracking & Ownership
Implementation is tracked on issue #177, phased per §10 above. This PRD expands on the V1 PRD's §4.3/§4.4/§7, which already committed to the write-then-read sequencing and the metadata shape this document specifies in full.