Skip to content

Spec: Memory Planner — Two-Phase Evidence Selection (F7 math)

File: .ai/specs/core-engine/2026-07-02-memory-planner.md Status: Approved (F7 math review complete 2026-07-04 — conditions incorporated in v0.2; see Changelog) Owners: Planner lane Related: PR-050; confidence-decay spec (S(n) is the evidence weight); ingestion-provenance spec (spans are the unit); generation-orchestration spec (consumer)


1. Overview

Defines Phase A (candidate recall) and Phase B (budgeted selection) of evidence planning, the token accounting model, truth/cache separation, and the determinism guarantees the trace system depends on.

2. Problem Statement

Generation quality is bounded by evidence selection: recall too narrow and answers miss facts; packing without budget discipline blows context windows and cost caps; nondeterministic selection makes traces unreproducible; and mixing verified source spans with derived summaries without labels poisons claim verification downstream.

3. Proposed Solution

Phase A — recall: union of (structured query results per request filters) ∪ (semanticSearch top-k over the request's temporal window) ∪ (1–2 hop expansion from both), each node carrying S(n) from the fusion model; explode nodes to their provenance spans; evidence pool = spans with weight w(sp) = S(node) × kind_prior(sp.kind) (paragraph 1.0, heading 0.6, cell 0.8, code 0.9 — defaults). Phase B — selection: greedy pack by density w(sp)/max(1, tokens(sp)) — the denominator is floored so a zero-estimate span cannot have infinite density and dominate packing (review condition); spans that no longer fit the remaining budget are skipped and scanning continues; spans with tokens(sp) > B are unpackable and recorded in stats. Subject to: total ≤ budget B; per-source cap (≤ max_frac 0.4 of B from one source_id — diversity). The cap is hard and never relaxes: when it strands budget (pool dominated by one source), the stranded amount is reported as stats.budget_stranded_by_cap rather than silently trading the diversity guarantee away (review decision — a conditional cap makes plans sensitive to pool composition in surprising ways; revisit with eval-harness evidence). Dedup near-identical spans — same-source only (byte offsets are only comparable within one source's canonical text; cross-source paraphrase dedup is MMR territory, deferred per §9): overlap = |byte-range intersection| / min(len_a, len_b), > dedup_overlap (0.8) keeps the higher-weight one (containment ⇒ overlap 1.0 ⇒ always deduped). Ties break deterministically by (weight desc, source_id, spanStart) compared via f64::total_cmp; the plan's canonical serialization encodes weights as raw IEEE-754 bit patterns, never decimal strings, and the weight product is computed in one fixed association order — both required for the cross-platform plan_hash guarantee in §12 (review conditions). Truth/cache separation: spans are truth; any derived artifact (cached summaries, Phase-4 materializations) enters the pool flagged cache with a weight discount (×0.5) and is never offered to claim verification as supporting evidence.

4. Architecture

/v1/generate request → planner.plan(request, budget) → EvidencePlan {selected spans, weights, stats, plan_hash} → prompt assembly (generation-orchestration spec) → verification (claim-verification spec) consumes the same plan.

5. Data Models

EvidencePlan { spans: [(ref, weight, tokens, class: truth|cache)], budget, used, recall_stats, plan_hash: blake3(canonical serialization) }. plan_hash is the reproducibility anchor persisted in traces.

6. API Contracts

plan(scope, request: {query parts, window, budget_tokens, model}) -> EvidencePlan. Token counts via the target model's tokenizer when the LlmProvider exposes one; fallback heuristic is script-aware (review condition): estimate = ascii_bytes/3.6 + non_ascii_chars × 1.0 — a flat bytes/3.6 underestimates CJK by roughly 3× (≈3 UTF-8 bytes per character at ≈1 token per character) and would silently break the used ≤ B property; counting each non-ASCII character as one token slightly overestimates accented European text, which is the safe direction. Sets token_estimate: true in stats and the budget then applies a 0.9 safety factor. The budget-adherence property test must include a CJK fixture. Empty pool → typed InsufficientEvidence (generation may proceed only with an explicit allow_ungrounded flag, default off).

7. UI/UX

Trace viewer shows the plan: selected vs rejected-by-budget spans with weights — the operator answer to "why didn't it cite X".

8. Configuration

planner.default_budget (6000 tokens), planner.max_frac_per_source (0.4), planner.kind_priors, planner.cache_discount (0.5), planner.dedup_overlap (0.8). Validation (review condition): kind_priors ∈ (0,1], cache_discount ∈ (0,1), max_frac_per_source ∈ (0,1], dedup_overlap ∈ (0.5,1] — rejected at config load, not clamped. kind_priors are the single source of truth shared with the embedding span-kind priors (cross-spec design recorded in the build doc) — the two must not drift.

9. Alternatives Considered

Optimal knapsack (rejected: NP-hard exactly, and marginal over greedy-by-density at this problem shape; determinism and speed win). LLM-as-selector (rejected for the budget stage: cost, nondeterminism, and it re-ranks better as a Phase-4 refinement atop a deterministic base). MMR diversity (deferred: per-source cap captures the dominant redundancy mode; embedding-MMR is a tunable refinement once eval harness can measure it). Sentence-level packing (rejected v1: span ledger is the provenance unit; sub-span slicing breaks the verification coordinate system).

10. Implementation Approach

PR-050 commits as planned: this spec (F7 sign-off) → recall orchestration → greedy selection + dedup + caps → truth/cache flags → budget-adherence property tests + determinism + recall regression fixtures.

11. Migration Path

Greenfield. Weight-formula changes version the plan model (plan_model: v1 in traces), same doctrine as score_model.

12. Success Metrics

Budget adherence: used ≤ B always (property test incl. estimate mode with safety factor). Determinism: identical inputs → identical plan_hash across runs/platforms. Diversity: per-source cap never exceeded. Recall fixtures: labeled must-include spans selected on the reference corpus ≥ target (baseline set with eval harness, PR-057).

13. Open Questions

  • OQ-1: Resolved at F7 review (2026-07-04) — budget auto-scales: B = min(configured, floor(model_ctx × 0.5)). The 0.5 is confirmed as a deliberately coarse ceiling (not a target): it must leave room for system prompt, instructions, and output at any context size, and a smarter subtraction of measured prompt overhead is a Phase-4 refinement once generation telemetry exists. Config can only lower it.

14. Changelog

  • 2026-07-04 — v0.3: as-built record (PR-050). Implemented as core-engine/src/planner/ (recall orchestration in mod.rs, pure Phase-B in select.rs, token accounting in tokens.rs, plan model + canonical hash in plan.rs); config [planner] validated at load; Ingestor::canonical_slice/span_row added as the slice API (§7 consumers). As-built clarifications, none weakening a review condition: (1) dedup keys on (source_id, source_version_tx) — strictly finer than "same-source", since byte offsets are only comparable within one version's canonical text; (2) containment-always-dedups implemented as overlap == 1.0 alongside the strict > dedup_overlap rule (threshold 1.0 would otherwise exempt containment); (3) budget_stranded_by_cap = final unused budget when ≥ 1 span was cap-blocked at a moment it fit the then-remaining budget; (4) S(n) per recall leg: structured primaries 1.0 (or the executor's vector scores), semantic roots fuse(sim, 1.0, α), expansion fuse(sim, decay, α) when the node has a similarity else its path decay; (5) spans whose ledger row is missing take kind "body" (prior 1.0, counted in stats.recall.kind_missing); spans whose canonical text cannot be sliced are skipped and counted; (6) the plan carries each span's text so prompt assembly and verification operate on the plan alone. Budget-adherence property test (incl. the mandated CJK fixture), determinism-under-permutation, cap/stranding, and recall-regression fixtures live in tests/planner_plan.rs + module tests.
  • 2026-07-04 — v0.2 countersigned by J. Porter (jp@densops.com) — approval final.
  • 2026-07-04 — v0.2: F7 math review complete — Approved with conditions, all incorporated (reviewer: Claude, acting F7 reviewer by J. Porter's delegation). Conditions: (1) density denominator floored at 1 token (no infinite-density spans); (2) token-estimate heuristic made script-aware (flat bytes/3.6 breaks budget adherence on CJK) + CJK property-test fixture required; (3) dedup pinned to same-source, intersection/min-length definition; (4) tie-breaks via f64::total_cmp and plan_hash serialization uses raw f64 bit patterns (cross-platform determinism); (5) per-source cap confirmed hard — stranded budget reported, never silently relaxed; (6) config ratios validated at load; kind_priors single-sourced with embedding span-kind priors. OQ-1 resolved (B = min(configured, floor(model_ctx × 0.5))).
  • 2026-07-02 — v0.1: initial draft. Awaiting F7 sign-off.