Files
poimen/tasks/T4.3-lazy-blob-access.md
T

86 lines
3.3 KiB
Markdown
Raw Normal View History

2026-08-17 23:05:20 -07:00
# 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<dyn BlobStore>` 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 13 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)