# 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` 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` 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)