Files
poimen/tasks/T10.1-goalid-and-goal-scoped-query.md
T

83 lines
3.1 KiB
Markdown
Raw Normal View History

2026-08-18 20:04:13 -07:00
# T10.1 — `GoalId` and goal-scoped query
| Field | Value |
|---|---|
| Phase | P10 — Orchestration |
| Size | S — under 1 day |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | T10.2, T10.3, T10.4, T10.8 |
## Goal
New identity `GoalId(Ulid)` grouping runs that share an objective, optional at
spawn. Extend `T1.7`'s query surface to read across `RunId`s sharing one
`GoalId`.
## Facts (inlined — no spec read needed)
- `GoalId` is tenant-scoped like `RunId`, **not** `TaskId`. `TaskId` is already
the grading comparison-group key (§11.5) — reusing it corrupts
group-epoch/quorum semantics the learning loop depends on.
- Attaching `goal_id` at spawn is **optional**. A run with no `GoalId` behaves
exactly as today — this is additive, not a required field.
- `GoalView` is a new, separate query. It does not replace per-run
`EpisodeView`; it lists member `RunId`s and references their existing
metadata, no duplication.
- Same parity rule as T1.7: `GoalView` from materialized state must equal
`GoalView` from a cold re-fold (T0.8's property applied here too).
## Steps
1. Declare `GoalId(Ulid)` in the `ids` crate, alongside `RunId`.
2. Add optional `goal_id: Option<GoalId>` to run-spawn input and the run
record.
3. Index runs by `(TenantId, GoalId)` in materialized state.
4. Define `GoalView { goal: GoalId, runs: Vec<RunSummary> }`, `RunSummary`
referencing existing run metadata — do not duplicate `EpisodeView` fields.
5. Implement the query against materialized state and against cold re-fold.
6. Test: two runs sharing a `GoalId`, one plain run with none; assert
`GoalView` returns exactly the two, in spawn order.
## Acceptance
- Two runs sharing a `GoalId` are both returned by `GoalView`; a run with no
`GoalId` never appears in any `GoalView`.
- `GoalView` from materialized state equals `GoalView` from cold re-fold.
## Verify
**Harness:** two runs sharing a `GoalId`, one run with no `GoalId`, one
branch/rewind case reused from `T1.7`'s harness.
**Integration test**`tests/it_goal_view_equivalence.rs`:
1. Spawn run A and run B both with `goal_id=G`; spawn run C with none.
2. Query `GoalView(G)` from materialized state → `view_a`.
3. Drop state tables; query again via cold re-fold → `view_b`.
4. Assert `serialize(view_a) == serialize(view_b)`.
5. Assert `view_a` contains exactly `{A, B}`, never `C`.
6. Assert order is spawn order, stable.
**Command:** `cargo test -p query goal_view_equivalence`
**False pass:**
- Testing only one run per goal — a grouping bug is invisible with a single
member.
- Skipping the "run without `goal_id`" exclusion check. A filter that
defaults to "all runs" passes silently without it.
## Traps
- Deriving `GoalId` from `TaskId` "since they're both groupings." Different
lifecycle, different owner — grading vs orchestration.
- Making `goal_id` required at spawn. Breaks every existing plain-workflow
spawn call.
---
Background (not required to do this task):
[rust-agentic-sys.md](../rust-agentic-sys.md) §11.5 ·
[T1.7-episode-query-surface.md](T1.7-episode-query-surface.md) ·
[T0.8-fold-and-re-derive.md](T0.8-fold-and-re-derive.md)