* docs: consolidate governance anchors and clean up test registries * refactor(cli): decompose cli into dedicated modules * test: fix broken test baselines and formatting * docs: add domain boundary READMEs for governance anchors * test: update baseline for determination lane * test: fix capability_pass expectation * test: fix CORE_SHOWCASE_SKIP_BUDGET enforcement * chore: cleanup CLI extraction and unreachable code
7.1 KiB
ADR-0090 — Unified Ingest + Batched Recall (audit Findings 6 + 7)
Status: Proposed
Date: 2026-05-21
Author: Shay
Related: ADR-0054 (vault matrix cache), audit 2026-05-20 Findings 6 & 7, chat/runtime.py:1295-1502
Context
The 2026-05-20 second-opinion audit identified two related issues in ChatRuntime.chat():
Finding 6 — within-turn multi-query recall is not batched. recall() is called once for the gate check (line 1302) and again inside generate()'s walk loop (one call per step). VaultStore.recall_batch (ADR-0054) exists and amortises the matrix scan, but the chat path never uses it for the gate+walk pattern that fires every turn.
Finding 7 — probe-ingest and commit-ingest create two different field states. Line 1301 calls probe_ingest(filtered) to materialize a probe field for the gate check; line 1311 (gate-fired path) or 1379 (main path) calls commit_ingest(filtered) to produce the committed field that the walk actually uses. The two fields differ because:
commit_ingestapplies drive bias and updates session turn state.- The probe field captures the prompt alone; the committed field also reflects the runtime's accumulated dialogue context.
Net effect: the gate observes a slightly different manifold position than the walk subsequently navigates. Honest refusal decisions are made on one field; the user-facing surface is generated from another.
The two findings are entangled. Batching the gate and walk recalls is only meaningful when they happen on the same field — which requires unified ingest. Resolving the ingest coherence gap is the precondition for the batching optimization.
Decision
ADR-0090 ships a flag-gated unified-ingest path following the codebase's standard substantive-change pattern (ADR-0046 forward_graph_constraint, ADR-0062 composed_surface, ADR-0085 gloss_aware_cause, ADR-0088 realizer_grounded_authority). Default-off preserves byte-identity; flipping the flag enables the unified path for operators to validate against their workloads before any default change.
Phase 1 — Unified ingest (this ADR, shippable now)
Add RuntimeConfig.unified_ingest: bool = False.
When unified_ingest=True:
chat()callscommit_ingest(filtered)first, producing the committed field once.- Drive bias is applied to the committed field immediately (matching today's
_apply_drive_biassite on the post-gate path). - The gate check reads
committed.F(notprobe_state.F). Samedefault_gate.check(...)API, same decomposer. - If the gate fires, the stub-response path runs as today with one observable change: the turn has already committed to session state at this point. This is the intentional semantic change — stub-path turns now influence
_context.last_dialogue_bladeand the session vault's history. Operators opt into this by flipping the flag. - If the gate does not fire, the walk proceeds on the same
committed.Fthe gate decided against. The walk and the gate see the same field. No probe_ingest call is made on this path.
When unified_ingest=False (default), today's behavior is preserved bit-for-bit. Null-lift invariant CI-pinned by a test that asserts surface + trace_hash equality across the flag-off and flag-on paths on every cognition eval case where the gate does not fire.
Phase 2 — Batched recall reuse (separate PR after Phase 1 validates)
Once unified ingest is the default-on path:
- The gate's
direct_hits(top-k=3 againstcommitted.F) is passed intogenerate()as a newprebuilt_first_recall: list | None = Noneparameter. - The walk's first step uses the prebuilt hits instead of re-calling
vault.recall()on the same field. - Steps 1+ run the standard per-step recall as today, since each step's query depends on the previous step's propagation.
Phase 2 is not shipped in this PR. The substrate it needs (unified ingest) is the load-bearing first step. Phase 2's win is a single vault-recall call eliminated per turn — small in absolute terms, but the cleaner invariant ("gate and walk see the same hits") is what actually matters for the audit's coherence concern.
Out of scope
recall_batchfor the per-step walk recalls. Each step's query depends on the previous step's field state; they cannot be batched without changing the walk's geometric semantics.- Behavioral change on stub-path turns under
unified_ingest=False. The pre-fix probe-then-stub-without-commit behavior is preserved exactly when the flag is off.
Consequences
- Honest refusal decisions and walk outputs are coherent (Axiom 3 — propagation-over-mutation, Axiom 4 — one forward pass).
- The probe/commit distinction is collapsed when the flag is on.
probe_ingestbecomes dead code on the unified path. It's retained for the flag-off path until the flag-on path is validated and made default; at that pointprobe_ingestitself can be removed via a follow-up cleanup PR. - Stub-path turns now commit when the flag is on. Operators must accept this trade-off (cleaner coherence + session-state continuity on refused turns) when they flip the flag. The alternative (commit, then roll back) introduces field-state rollback complexity that this ADR explicitly rejects.
- Phase 2 substrate is unblocked. The natural follow-up — pass the gate's hits to
generate()to eliminate the duplicate recall — becomes a one-line wiring change once unified ingest is the live path.
Rejected alternatives
- Land Phase 1 + Phase 2 as one PR. Phase 2's
generate()API change touches the walk's hot loop and warrants its own validation lane. Bundled changes risk masking a regression in either half. Rejected. - Roll back the commit on gate-fired turns. Field-state rollback requires snapshotting before commit and restoring after — non-trivial invariants (versor closure, holonomy bookkeeping) need to survive the rollback. The cleaner contract is "stub-path turns commit just like main-path turns; the only difference is the surface." Rejected.
- Default
unified_ingest=Trueimmediately. Stub-path commit-behavior change is observable in any operator workload that distinguishes between "this turn was refused" and "the field was updated." Default-off, flag-flip-after-validation is the codebase's standard pattern (ADR-0046 / ADR-0062 / ADR-0085 / ADR-0088 / ADR-0089). Rejected.
Validation gate
Phase 1 merge must demonstrate:
unified_ingest=False(default) byte-identical to pre-fix on every cognition eval case (CI-pinned bytest_unified_ingest_null_lift).unified_ingest=Trueproduces well-formed responses on a smoke prompt set; gate decisions are made on the committed field; the walk uses the same field the gate decided against.- Stub-path turn-state behavior change documented in
docs/specs/runtime_contracts.mdas a flag-conditional invariant.
Phase 2 merge (separate PR) must demonstrate:
- The walk's first-step recall is bypassed when
prebuilt_first_recallis provided and the field state matches. - Walk surfaces remain byte-identical to the no-prebuilt path on a held-out test set.
- A single recall call per turn is eliminated, measurable in the existing pipeline-profiler benchmark.