# T10.2 — `Reconciler` port | Field | Value | |---|---| | Phase | P10 — Orchestration | | Size | M — 1 to 3 days | | Status | Not started | | Flags | — | | Spec | inlined below | | Blocks | T10.6, T10.8 | ## Goal New port answering "how far toward the goal, what next" from a run's terminal resting state — distinct from `Verifier` (fail-closed pass/fail) and `Judge` (relative-only comparison). ## Facts (inlined — no spec read needed) - `Verifier::verify` is fail-closed pass/fail (rust-agentic-sys.md §10). `Judge::compare` is deliberately relative-only, "cannot return an absolute score" (rust-agentic-sys.md §11.1). "How far toward goal, what next" is a third question — forcing it through either port blurs a firewall built on purpose. `Reconciler` is new, sized like `Verifier` (one method, S/M). - Fires **only** off `Verified{pass|fail}`, `Graded`, `Ungraded` — states `T1.3` already designed to rest in, never resolved inline. - Reconciler failure degrades the record, never the run — same principle as §1: "the framework cannot break the agent it runs." A `Reconciler` error or timeout leaves the run at its terminal state; the goal simply does not advance until retried. - The decision does **not** spawn anything itself. Spawning happens only through the admission path (T10.3) via the inbox relay (T10.7) — never a direct call from the `Reconciler` or its caller. - One new `#[non_exhaustive]` `WorkEvent` variant for provenance: `SpawnedFromReconciliation{goal_id, parent_run, decision_ref}`. Added, not replacing any existing variant. ## Steps 1. Define `ReconcileCtx` — `GoalId`, `GoalView` (T10.1), the just-terminal run's `EpisodeView`, `WorkflowDef`. 2. Define `ReconcileDecision` — `SpawnNext{workflow_ref, input, dispatch_key}`, `Retry{step}`, `Stop{reason}`. 3. Define the trait: ```rust #[async_trait] pub trait Reconciler: Send + Sync { fn id(&self) -> ReconcilerId; async fn reconcile(&self, cx: &ReconcileCtx) -> Result; } ``` 4. Wire the trigger: on a run reaching `Verified`/`Graded`/`Ungraded` with a `GoalId` set, call the `Reconciler` bound to that goal. 5. On `Reconciler` error or timeout: log, leave the run at its terminal state, do not auto-retry — matches `Verifier`'s explicit-only-retry discipline. 6. Add `WorkEvent::SpawnedFromReconciliation`, written on the **next** spawned run's first log record, referencing `parent_run` and the decision. 7. Test with a stub `Reconciler` against all three trigger states. ## Acceptance - A run reaching `Verified{pass}` with a `GoalId` triggers exactly one reconcile call. - The decision is durably recorded and traceable from the next spawned run back to the parent run and goal. - A `Reconciler` error leaves the run at its terminal state and does not crash the executor. ## Verify **Harness:** stub `Reconciler` returning each decision variant in turn; one goal with a 2-run chain. **Integration test** — `tests/it_reconciler_trigger.rs`: 1. Run A (goal G) reaches `Verified{pass}`; assert `reconcile()` called exactly once with the correct `ReconcileCtx`. 2. Stub returns `SpawnNext`; assert the decision is persisted, referencing run A. 3. Assert the next spawned run's first log record carries `SpawnedFromReconciliation{goal_id: G, parent_run: A, decision_ref}`. 4. Repeat for `Graded` and `Ungraded` trigger states. 5. Stub `Reconciler` panics or times out; assert run A stays at its terminal state, no crash, no infinite retry. 6. Run with no `GoalId` reaches `Verified{pass}`; assert `reconcile()` is never called. **Command:** `cargo test -p kernel reconciler_trigger` **False pass:** - Asserting reconcile "ran" via a boolean flag instead of asserting the `WorkEvent` provenance record exists — passes even if a crash lost the decision before persistence. - Testing only `Verified{pass}`, skipping `Graded`/`Ungraded` — those are the states most likely miswired, being newer additions to the FSM. ## Traps - `Reconciler` spawning the next run directly. Violates "never bypass the admission path" — see T10.3/T10.7. - Overloading `Judge::compare` to also answer "what next." Collapses a boundary `Judge`'s signature deliberately enforces (rust-agentic-sys.md §11.1, "deliberately cannot return an absolute score"). - Auto-retrying a failed `Reconciler` call with no cap — a broken impl then spins forever against every terminal run in the tenant. --- Background (not required to do this task): [rust-agentic-sys.md](../rust-agentic-sys.md) §1, §10, §11.1, §17 · [T1.3-run-executor.md](T1.3-run-executor.md) · [T4.1-verifier-port.md](T4.1-verifier-port.md) · [T10.1-goalid-and-goal-scoped-query.md](T10.1-goalid-and-goal-scoped-query.md)