83 lines
3.1 KiB
Markdown
83 lines
3.1 KiB
Markdown
# 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)
|