Recall X-ray

See why a memory appeared.

Debugging an agent-memory recall used to mean stitching three separate outputs together: the tier-explain block, the audit log, and the MMR diversification trace. Remnic now emits a single RecallXraySnapshot that fuses all three. The same snapshot is available from the CLI, HTTP, and MCP. All three flow through one shared renderer, so the surfaces never drift.

Start here. Opt in with xrayCapture: true on any recall call, or run remnic xray. The snapshot includes the served-by tier, a filter trace with considered/admitted counts per gate, the score decomposition (vector + bm25 + importance + tier prior + MMR penalty) per result, the graph path (when applicable), and the audit entry id.

What the X-ray answers

Which tier served this?

Each result is tagged with servedBy: direct-answer, hybrid, graph, recent-scan, procedural, or review-context. No more guessing whether QMD or the fallback path materialized a given memory.

What dropped candidates?

The filter trace records every gate the recall walked: namespace, trust zone, confidence, result limit, with the candidate pool it saw and the number it admitted. considered - admitted tells you exactly where the dropped candidates went.

Why this score?

Every result carries a score decomposition: final, vector, bm25, importance, mmrPenalty, tierPrior. You can see the MMR penalty applied for diversity, the importance prior applied by the tier, and the raw ranking signals.

Which audit entry?

Each result is cross-linked to its recall-audit entry id, so you can jump from the X-ray back to the durable audit ledger for forensic inspection or regression bisect.

Three surfaces, one renderer

CLI, HTTP, and MCP all call the same recall-xray-renderer.ts module. The surfaces cannot drift. A fix to the renderer fixes all three simultaneously.

REST routes ship under the /engram/v1/ compatibility prefix during the rename window; the canonical MCP tools use the remnic.* namespace. See the rename notes.

CLI

remnic xray "what editor do I use" \
  --format markdown \
  --namespace workspace-a \
  --out ./xray.md

Flags: --format (text|markdown|json), --budget, --namespace, --out. Invalid flag values throw listed-options errors rather than silently defaulting.

HTTP

GET /engram/v1/recall/xray?q=…
Authorization: Bearer <token>

Returns the snapshot as JSON. Namespace scope is enforced against the authenticated principal before the recall fires; a mismatch returns {snapshotFound: false} so cross-tenant data cannot leak.

MCP

tools/call
  name: "remnic.recall_xray"
  arguments: { query, namespace?, budget? }

Exposed as the canonical remnic.recall_xray tool. Returns identical JSON to the HTTP surface.

Renderer

renderXray(snapshot, "markdown")
renderXray(snapshot, "text")
renderXray(snapshot, "json")

Pure function in packages/remnic-core/src/recall-xray-renderer.ts. Golden-file tests guard the output shape on every commit.

Opt-in, zero overhead by default

Capture is gated on xrayCapture: true. Without the flag, recall is byte-for-byte identical to the non-X-ray path. There is no additional retrieval cost when a snapshot is captured. The data is composed from values the orchestrator has already computed. Capture failures are caught and logged; they never propagate into the primary recall path.

What's in the snapshot

{
  "schemaVersion": "1",
  "snapshotFound": true,
  "query": "what editor do I use",
  "snapshotId": "…uuid…",
  "capturedAt": 1700000000000,
  "sessionKey": "sess-42",
  "namespace": "workspace-a",
  "traceId": "trace-xyz",
  "tierExplain": { "tier": "direct-answer", … },
  "filters": [
    { "name": "namespace", "considered": 120, "admitted": 30 },
    { "name": "recall-result-limit",
      "considered": 30, "admitted": 5, "reason": "cap=5" }
  ],
  "results": [
    {
      "memoryId": "mem-1",
      "path": "facts/tools/editor.md",
      "servedBy": "direct-answer",
      "scoreDecomposition": {
        "final": 0.87, "vector": 0.81, "bm25": 0.42,
        "importance": 0.9, "mmrPenalty": 0.05, "tierPrior": 0.2
      },
      "admittedBy": ["namespace", "trustZone", "importance"],
      "graphPath": ["mem-root", "mem-1"],
      "auditEntryId": "audit-2026-07-16-abc"
    }
  ],
  "budget": { "chars": 4096, "used": 1234 }
}

The schemaVersion field is version-gated so downstream consumers can pin their parsers. The renderer handles missing / out-of-range fields (including non-finite capturedAt) gracefully rather than crashing.

What shipped

  • Shipped Schema + in-process capture (xrayCapture: true), PR #578.
  • Shipped Shared renderer (renderXray) with text / markdown / JSON formats and golden-file tests.
  • Shipped remnic xray CLI command.
  • Shipped GET /engram/v1/recall/xray HTTP route (bearer auth + namespace scope).
  • Shipped remnic.recall_xray MCP tool.
  • Shipped Legacy /recall/explain delegates to the X-ray renderer in markdown mode for backwards-compatible output.