You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: DECISIONS.md
+51-1Lines changed: 51 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6664,7 +6664,7 @@ This is a coverage contract, not an indexing claim. The current complete path is
6664
6664
- `max_records` remains a caller-owned partial-corpus request. In complete mode, setting it below `total_records` is still an evidence failure.
6665
6665
- Response consumers should inspect `coverage.strategy`, not just `truncated_corpus`, when they need to defend a recall claim.
6666
6666
- Large complete recalls may still be slow. That is now a performance gap, not a correctness guard disguised as a corpus limit.
6667
-
- The next structural step is a durable recall index with high-water-mark verification. It must preserve the [D125](#d125-complete-content-recall-is-coverage-first-not-cap-first) coverage contract.
6667
+
- Durable content-token indexing is now covered by [D126](#d126-content-recall-uses-a-durable-index-behind-complete-evidence-coverage). Future index strategies must preserve the [D125](#d125-complete-content-recall-is-coverage-first-not-cap-first) coverage contract.
6668
6668
6669
6669
**Cross-references.**
6670
6670
@@ -6673,6 +6673,56 @@ This is a coverage contract, not an indexing claim. The current complete path is
## D126: Content recall uses a durable index behind complete-evidence coverage
6677
+
6678
+
**Date:** 2026-06-21
6679
+
6680
+
**Status:** Accepted
6681
+
6682
+
**Extends:** [D062](#d062-local-mirror-sidecar-two-tier-private-local--public-canonical-persistence), [D084](#d084-read-primitive-instrumentation-for-empirical-loop-closure-measurement), [D086](#d086-bm25-corpus-extended-from-annotations-to-per-event_type-record-content), [D123](#d123-critical-path-content-recall-requires-complete-evidence-or-explicit-fallback), and [D125](#d125-complete-content-recall-is-coverage-first-not-cap-first).
6683
+
6684
+
**Context.** [D125](#d125-complete-content-recall-is-coverage-first-not-cap-first) fixed the correctness bug: `require_complete` no longer turns an arbitrary record-count guardrail into an evidence boundary. That still left two operational gaps:
6685
+
6686
+
- Complete recall rebuilt the content-search corpus inside each fresh MCP process, so critical-path recall stayed slower than it needed to be.
6687
+
- Operators could verify source, npm, and tags while a running MCP process still served an older implementation. The response shape needed a runtime contract that made stale process binding obvious.
6688
+
6689
+
**Decision.** Add a durable content-token index for `recall_by_content`, behind the [D125](#d125-complete-content-recall-is-coverage-first-not-cap-first) coverage contract:
6690
+
6691
+
- The sidecar schema is `content-index-v1`.
6692
+
- The sidecar stores the BM25 token corpus plus display metadata needed by `recall_by_content`; it does not store log-inclusion proofs and does not change the local-signature trust boundary.
6693
+
- A sidecar is accepted only when its stored mirror signature and mirror high-water mark match the current local mirror fingerprint.
6694
+
- `require_complete` writes or rewrites the sidecar after a complete loaded-mirror build when the sidecar is missing or stale.
6695
+
- Bounded recall may use a valid durable sidecar when present, but it does not force a full mirror load just to create one.
6696
+
- If the sidecar is disabled, missing, stale, invalid, or unwritable, recall falls back to the loaded-mirror path and reports that state.
6697
+
6698
+
Extend `recall_by_content` responses:
6699
+
6700
+
- `runtime` names the loaded `@atrib/recall` package version, `coverage-v1`, and `content-index-v1`.
6701
+
- `coverage.index` reports the sidecar status: `hit`, `rebuilt`, `memory_only`, `disabled`, or `write_failed`.
6702
+
- `coverage.corpus` remains `local_mirror`; the index is an acceleration and process-restart cache, not a new source of truth.
6703
+
6704
+
**Alternatives considered.**
6705
+
6706
+
- _SQLite FTS in the first durable-index patch._ Deferred. A JSON token sidecar avoids native dependencies in the MCP startup path and lets the coverage contract settle first.
6707
+
- _Persist full inverted BM25 postings._ Deferred. Persisting tokens is enough to avoid reparsing and re-tokenizing every local mirror line across MCP restarts. The in-memory postings can still be rebuilt cheaply from the sidecar.
6708
+
- _Create the durable index on every bounded search._ Rejected. That would make a casual bounded query unexpectedly load the full mirror. Complete-mode recall is the right index-build trigger because it already claims full corpus coverage.
6709
+
- _Trust package version or git state to prove runtime freshness._ Rejected. A running MCP process can stay old after source and npm are correct. The result itself must expose the runtime contract.
6710
+
6711
+
**Consequences.**
6712
+
6713
+
- Complete content recall can reuse a mirror-keyed sidecar across MCP process restarts.
6714
+
- Stale or partial sidecars cannot produce a complete coverage claim because the mirror signature must match.
6715
+
- Agents can detect stale MCP processes by checking for `runtime.content_index_version` and `coverage.index` in `recall_by_content` responses.
6716
+
- The sidecar is local cache material. It inherits the privacy posture of the local mirror and should not be committed.
6717
+
- Embedding retrieval remains future work. It can add semantic relevance over the same mirror-keyed coverage boundary, but it cannot weaken the [D125](#d125-complete-content-recall-is-coverage-first-not-cap-first) complete-evidence semantics.
6718
+
6719
+
**Cross-references.**
6720
+
6721
+
- [`services/atrib-recall/src/index.ts`](services/atrib-recall/src/index.ts), content-index implementation and runtime metadata.
6722
+
- [`services/atrib-recall/test/mcp-protocol.test.ts`](services/atrib-recall/test/mcp-protocol.test.ts), JSON-RPC coverage for rebuild, hit, stale-index invalidation, and disabled mode.
6723
+
- [`services/atrib-recall/README.md`](services/atrib-recall/README.md), operator-facing content-index contract and env vars.
6724
+
- [`skills/atrib/SKILL.md`](skills/atrib/SKILL.md), agent-facing stale-runtime and coverage guidance.
6725
+
6676
6726
# Pending decisions
6677
6727
6678
6728
These will get full ADRs when we act on them. Recorded here so they remain findable and don't silently drop. Per the global Deferred Decision Logging convention, this section uses the forward-looking pattern (forward-looking decisions that will become numbered ADRs when codified).
Copy file name to clipboardExpand all lines: services/atrib-recall/README.md
+14-9Lines changed: 14 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -81,10 +81,12 @@ Every call to this tool (and every sibling tool below) writes a per-call jsonl e
81
81
82
82
-`mcp__atrib-recall__recall_revisions({ record_hash })` - returns the forward revision chain for the target record. Each chain entry carries `record_hash`, `timestamp`, and the [D086](../../DECISIONS.md#d086-bm25-corpus-extended-from-annotations-to-per-event_type-record-content)-normative content fields (`new_position`, `reason`, `importance`) when present, so the agent can read the chain inline without follow-up `recall` calls per revision. The chain follows the first-by-timestamp revision at each step; when more than one revision targets the same record (sibling fan-out, common in multi-agent flows), the other branch heads are listed on that step's entry as `sibling_hashes`, so the agent can recursively call `recall_revisions` on a sibling to traverse a parallel branch instead of having to manually enumerate revisions via `recall_my_attribution_history`.
83
83
84
-
- `mcp__atrib-recall__recall_by_content({ query, k?, max_records?, evidence_mode? })` - BM25 free-form retrieval over the newest `max_records` records' indexable text + annotation summary + topic_tags when present, then reranked by Park et al. weighted-sum scoring (recency + importance + relevance). Default k=10, max 50. Default `max_records` is `ATRIB_RECALL_CONTENT_MAX_RECORDS` or 5000. The default `evidence_mode: "bounded"` keeps casual searches fast by tail-loading that newest-first window instead of loading the whole mirror. The response includes `evidence_mode`, `evidence_status`, `fallback_required`, `total_records`, `searched_records`, `candidate_records`, `truncated_corpus`, and `coverage`; `total_records` is `null` when recall served a partial tail-loaded snapshot instead of a full mirror snapshot. `coverage` carries a version, strategy, local-mirror high-water mark, mirror file count, and searched record count so callers can tell whether a result came from a bounded newest-first window or a complete loaded-mirror scan. Per [D086](../../DECISIONS.md#d086-bm25-corpus-extended-from-annotations-to-per-event_type-record-content) and [D118](../../DECISIONS.md#d118-primary-trace-path-is-a-presentation-rule-over-trace-and-chain), "indexable text" is per-event_type record content from the [D062](../../DECISIONS.md#d062-local-mirror-sidecar-two-tier-private-local--public-canonical-persistence) sidecar (observation: `what + why_noted + intent + rationale + topics`; tool_call: `tool_name + args excerpt + result excerpt`; annotation: `summary + topics`; revision: `prior_position + new_position + reason + topics`; transaction: counterparty + memo + protocol fields; directory_anchor: `tree_root + epoch_id`). Extension URIs fall back to a generic recursive string-walk (depth <= 4, field cap 2KB). OpenInference local sidecars add recall tokens for span kind/name, tool/agent/model, prompt identifiers and templates, inputs/outputs, usage, cost, score, and metadata when those fields are mirrored locally per [D108](../../DECISIONS.md#d108-observability-span-trees-are-intake-local-sidecars-are-cognitive-payload). BM25 contribution is clamped to [0, 1] in the parkScore site so the documented Park-component bound is honored. Layer 2 (sqlite-vec sidecar, separate ship) extends with embedding similarity over the same indexed text.
84
+
- `mcp__atrib-recall__recall_by_content({ query, k?, max_records?, evidence_mode? })` - BM25 free-form retrieval over the newest `max_records` records' indexable text + annotation summary + topic_tags when present, then reranked by Park et al. weighted-sum scoring (recency + importance + relevance). Default k=10, max 50. Default `max_records` is `ATRIB_RECALL_CONTENT_MAX_RECORDS` or 5000. The default `evidence_mode: "bounded"` keeps casual searches fast by tail-loading that newest-first window instead of loading the whole mirror. The response includes `runtime`, `evidence_mode`, `evidence_status`, `fallback_required`, `total_records`, `searched_records`, `candidate_records`, `truncated_corpus`, and `coverage`; `total_records` is `null` when recall served a partial tail-loaded snapshot instead of a full mirror snapshot. `runtime` names the loaded `@atrib/recall` package version plus the coverage and content-index contract versions, so a stale MCP process is detectable from the result. `coverage` carries a version, strategy, local-mirror high-water mark, mirror file count, searched record count, and `coverage.index` status so callers can tell whether a result came from a bounded newest-first window, a complete scan that rebuilt the durable sidecar, a durable-index hit, or an explicit disabled/write-failed fallback. Per [D086](../../DECISIONS.md#d086-bm25-corpus-extended-from-annotations-to-per-event_type-record-content) and [D118](../../DECISIONS.md#d118-primary-trace-path-is-a-presentation-rule-over-trace-and-chain), "indexable text" is per-event_type record content from the [D062](../../DECISIONS.md#d062-local-mirror-sidecar-two-tier-private-local--public-canonical-persistence) sidecar (observation: `what + why_noted + intent + rationale + topics`; tool_call: `tool_name + args excerpt + result excerpt`; annotation: `summary + topics`; revision: `prior_position + new_position + reason + topics`; transaction: counterparty + memo + protocol fields; directory_anchor: `tree_root + epoch_id`). Extension URIs fall back to a generic recursive string-walk (depth <= 4, field cap 2KB). OpenInference local sidecars add recall tokens for span kind/name, tool/agent/model, prompt identifiers and templates, inputs/outputs, usage, cost, score, and metadata when those fields are mirrored locally per [D108](../../DECISIONS.md#d108-observability-span-trees-are-intake-local-sidecars-are-cognitive-payload). BM25 contribution is clamped to [0, 1] in the parkScore site so the documented Park-component bound is honored. A future embedding sidecar can add semantic similarity over the same indexed text.
85
85
86
86
For critical-path audits, use `evidence_mode: "require_complete"`. That mode loads the full mirror and searches every loaded record. If a caller also sets `max_records` below `total_records`, the tool returns no results with `evidence_status: "incomplete"`, `fallback_required: true`, `truncated_corpus: true`, and the `search_cap` / `total_records` mismatch. Do not treat that as an empty search result. The deterministic fallback is to rerun without `max_records` for full loaded-mirror coverage, or to run a caller-owned partition plan and treat each partition as its own explicit coverage claim.
87
87
88
+
Complete-mode recall uses a durable content-token sidecar when it can. The sidecar is keyed to the current local mirror fingerprint and stores the BM25 token corpus plus display metadata for `recall_by_content`. A sidecar is accepted only when its stored mirror signature and high-water mark match the current mirror stats. If the sidecar is absent or stale, `require_complete` rebuilds it from the full local mirror and still returns complete evidence. If the sidecar is disabled with `ATRIB_RECALL_CONTENT_INDEX=0`, or if writing the sidecar fails, recall falls back to the loaded-mirror path and reports that status in `coverage.index`.
89
+
88
90
In wrapped MCP hosts, the recall tool call and its JSON response are signed as a `tool_call` record. That means an incomplete critical-path recall is not a quiet warning in transcript prose; the signed result carries `fallback_required: true`. Agents should emit an observation naming the incomplete recall status and the fallback they chose before continuing.
89
91
90
92
- `mcp__atrib-recall__recall_session_chain({ context_id?, limit?, include_content? })` - returns all records in a context_id, ordered chronologically (oldest-first). The natural traversal of the CHAIN_PRECEDES topology for a single session/trace. Each entry carries `record_hash`, `event_type`, `timestamp`, `display_summary`, `display_producer`, `age`, plus signed causal/tool fields when present (`informed_by`, `tool_name`, `args_hash`, `result_hash`). When `include_content` is true, each entry also includes the [D062](../../DECISIONS.md#d062-local-mirror-sidecar--two-tier-private-local--public-canonical-persistence) local mirror body as `local_content` and the local producer label as `local_producer`. Defaults false to keep the session chain cheap. When `context_id` is omitted, falls back to `resolveEnvContextId` (the same precedence as the other tools: `ATRIB_CONTEXT_ID` env, then a [D083](../../DECISIONS.md#d083-harness-session-id-discovery-extends-d078-for-cognitive-primitive-mcp-servers)-registered harness env like `CLAUDE_CODE_SESSION_ID`).
@@ -97,14 +99,17 @@ In wrapped MCP hosts, the recall tool call and its JSON response are signed as a
97
99
98
100
The Park et al. ranking weights and recency time constant are environment-tunable for per-axis sensitivity studies:
|`ATRIB_RECALL_TAU_DAYS`| 7 | Exponential-decay time constant for recency |
108
+
|`ATRIB_RECALL_NOISE_FLOOR`| 0.6 | Anti-noise threshold for `rank_by=relevance` (see below) |
109
+
|`ATRIB_RECALL_CONTENT_MAX_RECORDS`| 5000 | Newest-first corpus size for bounded `recall_by_content` searches |
110
+
|`ATRIB_RECALL_CONTENT_INDEX`| enabled | Set to `0` to disable the durable content-token sidecar |
111
+
|`ATRIB_RECALL_CONTENT_INDEX_DIR`|`~/.atrib/cache`| Directory for mirror-keyed content index files |
112
+
|`ATRIB_RECALL_CONTENT_INDEX_FILE`| unset | Exact content index file path, mainly for tests |
108
113
109
114
The implementation does not enforce that alpha + beta + gamma sum to 1.0; the operator-facing defaults do. See [D085](../../DECISIONS.md#d085-recall-calibration-defaults-survey-grounded-rationale) for the survey-grounded rationale: `ALPHA=0.3` matches CrewAI's `recency_weight=0.3` (the only normalized-weights peer in a 2026-05-23 OSS survey); `TAU_DAYS=7` produces a ~4.85-day half-life inside the field range and close to Park et al.'s ~5.75-day empirical anchor.
0 commit comments