# T4.3 — Lazy blob access | Field | Value | |---|---| | Phase | P4 — Verification | | Size | S — under 1 day | | Status | Not started | | Flags | — | | Spec | inlined below | | Blocks | — | ## Goal A verifier that needs no text performs no blob reads. ## Facts (inlined — no spec read needed) - Context partitions hold **identifiers** — packed, available-but-not-packed, dropped — and are small enough to inline. Prompts and outputs are large and go **by reference**. - Laziness matters concretely: a verifier that shells out and checks an exit code needs none of this. Making every verifier carry prompt text penalizes the common case and blows broker payload limits. - `VerifierCtx.blobs: Arc` is the mechanism — the verifier calls `get` if and only if it wants a body. - `get` may return `None` after reduction (T8.4); that is a normal answer, and a verifier must not treat it as an error. ## Steps 1. Confirm `EpisodeView` carries `BlobRef` only. No `prompt_text` field, no eagerly-resolved cache. 2. Pass the store handle through `VerifierCtx.blobs`. 3. Instrument the store with a call counter behind a test-only wrapper — counting is what turns this from an intention into an assertion. 4. Write the exit-code verifier used as the acceptance case: it reads the attempt state and nothing else. 5. Write a second, text-reading verifier and assert its call count is exactly the number of refs it asked for — no over-fetch, no prefetch-all. ## Acceptance - An exit-code verifier records **zero** `BlobStore::get` calls. ## Verify **Harness:** a counting `BlobStore` decorator wrapping the real store, injected through `VerifierCtx.blobs`. **Integration test** — `tests/it_lazy_blobs.rs`: 1. Run a real run with an exit-code verifier that reads only attempt state. 2. Assert the counter is **exactly 0**. 3. Run a text-reading verifier that resolves 2 refs. Assert the counter is **exactly 2** — not more (no prefetch) and not fewer (no silent cache hit masking a missing read). 4. Assert `EpisodeView` has no field holding blob **bytes** — a compile-level check if the type is closed, otherwise a serialization size assertion on a run with a 1 MB prompt: the view must stay small. 5. Reduction interaction: delete a blob body, then run the text verifier; assert `get` returns `Ok(None)` and the verifier handles it without erroring. 6. Assert the counter is per-run, not global, so parallel tests do not mask each other. **Command:** `cargo test -p verify lazy_blobs` **False pass:** - Counting at the store level while a caching layer sits above it. The count then measures cache misses, not verifier behaviour. Wrap the handle the verifier actually receives. - Step 4 omitted: a `prompt_text` convenience field makes every verifier pay, and steps 1–3 still pass if the resolution happens at view-build time rather than through `cx.blobs`. The size assertion catches that. ## Traps - A "convenience" `EpisodeView::prompt_text()` that resolves on access. Every verifier then pays, invisibly. - Prefetching all refs when the view is built, which is the same regression one layer earlier. --- Background (not required to do this task): [rust-agentic-sys.md](../../../rust-agentic-sys.md) §10.1 · [rust-agentic-task.md](../../../rust-agentic-task.md)