Files
poimen/tasks/T5.1-taskid-at-spawn.md
T

91 lines
3.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# T5.1 — `TaskId` at spawn
| Field | Value |
|---|---|
| Phase | P5 — Grading |
| Size | S — under 1 day |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | T5.5 onward. Cannot be retrofitted |
## Goal
Every run carries a `TaskId` from spawn. Required, no default, no derivation from
`RunId`.
## Facts (inlined — no spec read needed)
- `TaskId` is the **comparison-group key**: a content hash of the task input
**before any workflow touches it**.
- Hashing the prompt does not work — the workflow changes the prompt by
construction, which is the entire point of a variant.
- **`TaskId` cannot be backfilled.** A run recorded without one is permanently
ungroupable and useless to the learning loop. Hence: required at spawn, no
`Default`, no `From<RunId>` (T0.1).
- **Land this during P1**, even though nothing consumes it until P5. It is a
small task deep in P5 and it is the one item on the critical path that a later
phase cannot repair.
- What defines `TaskId` for a given user is open — ticket id, input fixture, or a
hash of the pre-workflow goal. The framework provides the type and a default
hasher and lets it be overridden. Settle it before any run is recorded.
## Steps
1. Add `task: TaskId` as a **required positional field** of the spawn parameters.
Not `Option`, not a builder method that can be omitted.
2. Provide a default hasher over the pre-workflow task input, and a hook to
override it. Document that the choice is permanent for existing data.
3. Record `TaskId` on the run's spawn event so it lands in the log, not only in
materialized state.
4. Expose it on `EpisodeView` for the grading path.
5. Add the `trybuild` compile-fail case: a spawn call omitting `TaskId`.
## Acceptance
- **Spawning without a `TaskId` fails to compile.**
## Verify
**Harness:** `trybuild`, plus a full run to prove the id reaches storage.
**Integration test**`tests/it_taskid_required.rs`:
1. `trybuild` compile-fail: a spawn call omitting `TaskId`. Assert the expected
stderr **names the missing field**, not merely "does not compile".
2. Run a real run; read the **log** (not materialized state) and assert the spawn
event carries the `TaskId`.
3. Assert `EpisodeView` exposes it, so the grading path can group on it.
4. Determinism: hash the same task input twice, in two processes; assert equal
`TaskId`.
5. Independence: assert two runs of the **same** task input share a `TaskId`, and
that two different inputs do not. Both directions.
6. Assert the `TaskId` is **not** derived from the prompt: change the workflow
version (which changes the prompt) and assert the `TaskId` is unchanged for
the same input.
7. `trybuild`: `TaskId::default()` and `TaskId::from(run_id)` both fail.
**Command:** `cargo test -p kernel taskid && cargo test -p kernel --test compile_fail`
**False pass:**
- Step 2 read from materialized state. If the id lives only in state it cannot be
re-derived after a cold fold, and it cannot be backfilled — read the log.
- Step 6 omitted: hashing the prompt passes steps 15 completely and makes every
variant its own group, which is the exact failure that renders the learning
loop inert.
- A builder with a runtime `expect("task_id required")`. It passes a test that
checks for a panic and still allows runs to be recorded without one in any code
path the test did not cover.
## Traps
- A builder with `.task_id(...)` optional and a runtime check. Runs recorded
before someone notices are unrecoverable.
- Deriving it from the prompt or from `RunId`. Both compile; both make every run
its own group of one.
---
Background (not required to do this task):
[rust-agentic-sys.md](../../../rust-agentic-sys.md) §3, §11.5, §18 ·
[rust-agentic-task.md](../../../rust-agentic-task.md)