How-To Guide

Using the Knowledge Base

A practical guide to the case-corpus knowledge base: how staff feed resolved escalations into it, how Tier 2 uses it automatically, and how to query or extend it. For the design rationale and data-model decisions, see the V1.1 Knowledge Base PRD — this document is the "how," that one is the "why."

Status Live as of Phase 3 shipped Related V1.1 Knowledge Base PRD, Issue #177

1. Overview

The knowledge base is a small, structured corpus of resolved escalations. Every time a staff engineer resolves an escalation and opts it in, a row goes into kb_entries: the bugcheck/exception code, faulting module, failure bucket, a short root-cause tag, and the engineer's own resolution note — never a transcript. From then on, every Tier 2 run automatically checks that corpus for structurally similar prior cases and, if it finds any, includes them as extra context for the model.

There is no separate "turn the KB on" step and nothing to configure to start using it — it's live in both directions today. The only thing that changes its usefulness over time is corpus size: right after launch, few (if any) escalations have been opted in yet, so most Tier 2 runs will see no prior cases. That's expected — see §7.

2. For Staff: Opting a Resolution In

When you resolve an escalation in the staff queue, the resolve form has a checkbox: "Feed this resolution into the knowledge base." Checking it is the only action required — there's no separate review or approval step.

FieldWhat it's for
Root cause tagA short, free-text label (e.g. driver-bug, hardware-fault, corrupt-heap). This is what shows up alongside your resolution note in a future Tier 2's grounding context and in the queue UI — keep it short and consistent with how you'd tag similar cases, since there's no fixed taxonomy to pick from.
ResolutionYour normal resolution write-up. This exact text becomes the KB entry's resolution_note — write it as something useful to a future engineer (or model) seeing a similar case cold, not just as a closing note for this one customer.
Feed into knowledge base (checkbox)Opts this resolution's structured signals and your note into the shared corpus. It does not share the customer's dump, transcript, or any other case detail — see §3.
When to opt in Opt in whenever the root cause is genuinely understood and the resolution note would help someone (or Tier 2) recognize a similar case later. Skip it for one-off, customer-specific fixes that wouldn't generalize (e.g. "reinstalled the customer's own custom driver from their private branch") — those are true but not useful as grounding for an unrelated future case.

Once resolved, your root cause tag shows up next to the resolution in the escalation detail view and in the copy-to-clipboard markdown export — it's a real column now, not text buried inside the resolution.

3. What Gets Stored — and What Never Does

kb_entries columnSource
bugcheck_code / exception_codeParsed by Tier 1 (transcript_signals.parse_signals), recovered from the stored finding at resolve time — never re-parsed from a transcript
faulting_moduleTier 2's conclusion when Tier 2 ran, else Tier 1's — normalized (lowercased, extension stripped)
failure_bucket_idTier 1's !analyze -v bucket ID
dump_typeuser_mode or kernel_mode, from the dump itself
root_cause_tag, resolution_noteExactly what the engineer typed on the resolve form
source_escalation_idTraceability back to the escalation — nullable, and SET NULL rather than cascaded if that escalation is ever pruned
Never stored in kb_entries The raw debugger transcript, or any reference/pointer to it. This is true regardless of the opt-in checkbox — there's no "also share the transcript" tier. See the PRD §5 for the reasoning: dumps can contain credentials and session tokens in the clear, and a hand-written resolution note carries essentially none of that risk while a transcript carries all of it.

4. How Retrieval Works

Every Tier 2 run calls find_similar_cases() (triage/kb_lookup.py) automatically — nothing to trigger, nothing to configure per job. The match logic, in order:

  1. Look for entries with the same dump type, the same bugcheck/exception code, and the same normalized faulting module as this job.
  2. If nothing matches on code+module, fall back to entries sharing the same failure bucket ID alone.
  3. If neither matches, or the lookup itself fails for any reason (a DB hiccup, say), Tier 2 proceeds exactly as it would with an empty corpus — no error, no retry, no delay.

Matches (up to 5, most recent first) are rendered as plain-text grounding — module, root cause tag, and resolution note — and handed to the model alongside the transcript, explicitly framed as context, not authority: the model is told to verify against this job's own evidence rather than assume the same root cause applies. This is deliberate: a superficially similar bugcheck/module pair can still have an unrelated cause, and the prompt framing exists so the model doesn't anchor on a misleading prior case.

No embeddings, no semantic search Matching is a plain structured filter on columns that are already indexed and already computed by Tier 1/2 — deliberately, not a corner cut. See §7 for when that might change.

5. For Engineers: Code Map & Querying Directly

FileWhat's there
app/models/kb_entry.pyThe KBEntry ORM model / kb_entries table definition
app/routers/escalations_staff.pyresolve_escalation — the write path; builds a KBEntry from the job's stored Tier 1/2 findings on kb_opt_in
app/triage/kb_lookup.pyfind_similar_cases() (the query) and format_kb_context() (the prompt rendering) — the read path
app/orchestrator/tier2_runner.pyCalls both of the above and passes the result through structured_report's context parameter
alembic/versions/0013_kb_entries.pyThe schema migration, if you need the exact column types/indexes

Querying the corpus directly

For ad-hoc exploration (e.g. sanity-checking corpus size or coverage before relying on it for anything), query kb_entries like any other table. A couple of starting points:

# How many opted-in cases exist so far, by dump type
SELECT dump_type, count(*) FROM kb_entries GROUP BY dump_type;

# Every case tagged for a given root cause
SELECT root_cause_tag, faulting_module, resolution_note
FROM kb_entries
WHERE root_cause_tag = 'driver-bug'
ORDER BY created_at DESC;

From Python, use find_similar_cases() directly rather than hand-rolling the query — it already encodes the code+module / failure-bucket fallback logic described in §4:

from app.triage.kb_lookup import find_similar_cases, format_kb_context

matches = await find_similar_cases(
    session,
    dump_type=dump_type,
    bugcheck_code=parsed.bugcheck_code,
    exception_code=parsed.exception_code,
    faulting_module=normalize_module(parsed.faulting_module),
    failure_bucket_id=parsed.failure_bucket_id,
)
print(format_kb_context(matches))

6. Retention & Lifecycle

kb_entries rows are not touched by the dump/transcript retention sweep — there's no foreign key from a dump or transcript into the KB, only a nullable, SET NULL-on-delete pointer back to the source escalation. A KB entry outlives the dump it came from by design: once an engineer opts a resolution in, it stays part of the corpus regardless of what later happens to the original dump, transcript, or even the escalation record itself.

7. Current Limitations

  • Small corpus. The KB only contains what's been opted in since Phase 2 shipped — early on, expect most Tier 2 runs to find no prior cases. This is expected, not a bug; see the PRD's rollout phasing.
  • No embeddings/semantic matching. Two cases with the same underlying cause but a different bugcheck code or module naming won't match. Revisit only if real usage shows structured matching falling short — not before there's data to justify it.
  • No browse UI. The corpus is consumed by Tier 2's prompt construction only; querying it directly (§5) is currently the only way for a human to look at it.
  • No correction/retraction mechanism. If a resolution later turns out to be wrong, there's no built-in way to fix or remove its KB entry yet — flagged as a follow-up in the PRD once the corpus is large enough for this to matter.