# Controlled RAG write-back

TnsAI 0.14.0 (`TnsAI@4b57a851`, TAN-3009 / PR #151)
ships a **package-private**, default-disabled write-back overlay in
`com.tnsai.intelligence.rag.binding.ControlledWriteBackStore`. It is
in Maven Central `0.14.1`. There is no public constructor, no
`RetrievalEngine` write method, and no `@since` on a published type.

Production `DefaultRetrievalEngineProvider` constructs
`ControlledWriteBackStore.disabled()`. An enabled store exists only
when an **internal** composition root injects one into
`UnifiedRetrievalRuntime`. Agents stay read-only unless that happens.

## What it does

Admission is fail-closed. A candidate is indexed only when all of
these hold:

1. the store is enabled and the writer `agentId` is on the explicit
   allow-list
2. the gate is `USER_CONFIRMED`, `TOOL_VERIFIED`, or `EVAL_GATED`
3. content, identities, and `RetrievalScope` tenant/session strings
   are non-blank and bounded (16 KiB content; 256 characters per
   identity)
4. `confidence` is finite in `0.0..1.0`
5. `expiresAt` is in the future
6. an optional `supersedes` id exists in the **same** scope

`RAW_MODEL_OUTPUT` is always rejected. There is no heuristic promotion
from raw model text to an admitted gate.

Each complete `RetrievalScope` owns a separate in-memory BM25 index.
A query can read only that tenant/session tuple. Expiry and
supersession apply inside the scope before a later query can observe
the entry. Caps: 256 live scopes, 1,024 entries per scope. The
deterministic id is `writeback:` plus a length-prefixed SHA-256 of
scope + source turn + agent + content.

Admitted hits carry metadata:

| Key | Value |
| --- | --- |
| `writeback.gate` | admitted gate name |
| `writeback.sourceTurnId` | source turn |
| `writeback.agentId` | writer |
| `writeback.confidence` | `0.0..1.0` as text |
| `writeback.expiresAt` | ISO-8601 instant |
| `writeback.supersedes` | prior id, or `none` |

## When the overlay joins retrieve

The overlay participates only when **all** of these are true:

- the injected store is enabled
- `RetrievalConfig.sources()` is empty (no named static sources)
- `strategy` is `KEYWORD`
- `queryExpansion` is `NONE`

SEMANTIC, HYBRID, GRAPH, HIERARCHICAL, TEMPORAL, MULTI_QUERY, and
REASONING do not observe the overlay. An explicit `sources=[...]`
declaration is not widened with a reserved mutable source.

Eligible requests **bypass** the shared `RetrievalResultCache`, so
enabled evidence cannot leak into a disabled runtime that shares the
same `RoleRagBinding`. Overlay filters and `minScore` apply before
the overlay `topK`. Overlay and corpus hits merge, rank by descending
BM25, and deduplicate together when the request asks for it.

## Not this page

- A public write-back SPI or `RetrievalEngine.mutate`
- Persistent or distributed storage
- Default-on production wiring
- Automatic ingestion of model answers
- Graph / hierarchy / temporal write-back
- [KeywordRAGStrategy](strategies.md) — the public BM25 constructor
  is a different type and does not admit write-back

## Related

- [Strategies](strategies.md) — public retrieval modes
- [Knowledge Base](knowledge-base.md) — programmatic `KnowledgeBase`
- [Pipeline](pipeline.md) — FILE ingest (read-only corpus)
