Files
poimen/tasks/T10.2-reconciler-port.md

4.7 KiB

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 ReconcileCtxGoalId, GoalView (T10.1), the just-terminal run's EpisodeView, WorkflowDef.
  2. Define ReconcileDecisionSpawnNext{workflow_ref, input, dispatch_key}, Retry{step}, Stop{reason}.
  3. Define the trait:
    #[async_trait]
    pub trait Reconciler: Send + Sync {
        fn id(&self) -> ReconcilerId;
        async fn reconcile(&self, cx: &ReconcileCtx) -> Result<ReconcileDecision>;
    }
    
  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 testtests/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 §1, §10, §11.1, §17 · T1.3-run-executor.md · T4.1-verifier-port.md · T10.1-goalid-and-goal-scoped-query.md