Files
poimen/tasks/T1.6-prompt-and-output-blob-capture.md
T

83 lines
3.3 KiB
Markdown

# T1.6 — Prompt and output blob capture
| Field | Value |
|---|---|
| Phase | P1 — Walking skeleton |
| Size | S — under 1 day |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | — |
## Goal
Write the prompt to the blob store when it is built, and the output when the
model responds. Record both as `BlobRef` on the attempt.
## Facts (inlined — no spec read needed)
- Two capture points: `prompt-built` and model response. **The prior
implementation had neither** — every downstream consumer worked from
identifiers alone and could not answer "what did the agent actually see".
- Prompts and outputs are large, so they go **by reference**; context partitions
are identifiers and go inline (T1.5). That split is what makes lazy blob access
(T4.3) possible.
- Blobs are content-addressed within a tenant, never across (T0.7).
- A `BlobRef` whose body later returns `None` is normal after reduction (T8.4) —
the log carries a `Reduced { original, summary }` event saying what replaced it.
## Steps
1. At prompt assembly, serialize the final prompt, `BlobStore::put`, keep the
returned `BlobRef`.
2. On model response, `put` the raw output, keep its `BlobRef`.
3. Attach both refs to the attempt record; commit them in the same
`EventLog::commit` as the attempt transition, so a crash cannot leave a
dangling ref in state with no log record.
4. Surface as `AttemptView.prompt: Option<BlobRef>` and
`AttemptView.output: Option<BlobRef>`.
5. Test across a process restart: complete a run, drop the process, reopen the
store, fetch the prompt by ref, assert the text.
## Acceptance
- Prompt text retrievable from the blob store by ref **after a process restart**.
## Verify
**Harness:** embedded store on a temp path that survives process exit; the test
runs the agent in a **child process**, then reopens the store in the parent.
**Integration test**`tests/it_blob_capture_restart.rs`:
1. Child process: run one model step to completion, print the store path, exit.
2. Parent: reopen the store, read the attempt, take `prompt` and `output` refs.
3. `BlobStore::get` both; assert the prompt bytes equal the **assembled** prompt
(with template variables substituted), not the template.
4. Re-hash both bodies; assert each matches its ref.
5. Crash case: arm a fault hook between the blob `put` and the commit; assert on
reopen there is no attempt record pointing at a body-less ref, and no orphan
body that no record points at.
**Command:** `cargo test -p executor blob_capture -- --test-threads=1`
**False pass:**
- Reading the blob back in the same process from a warm cache. The restart is the
point — an in-memory blob map passes everything else.
- Asserting the prompt is non-empty rather than comparing to the expected
assembled text. Capturing the template instead of the rendered prompt passes a
non-empty check.
## Traps
- Putting the blob outside the commit transaction and recording the ref inside
it, or the reverse. Either way a crash leaves a ref with no body or a body no
record points at.
- Capturing the prompt template instead of the assembled prompt. The template is
in the workflow definition; what the model saw is not.
---
Background (not required to do this task):
[rust-agentic-sys.md](../../../rust-agentic-sys.md) §8.6, §10.1 ·
[rust-agentic-task.md](../../../rust-agentic-task.md)