Skip to content

Convergence loop and review barrier

This page describes what happens between the agent saying code.done and the issue actually entering the done phase. The "harness" of Smithers in the user-facing sense is this loop — not the test/proof harness in tests/.

The phases

gathering → working → sign-off → reviewing → barrier → continuing → done | error | escalated

These are concrete ConvergencePhase values from src/types/convergence.ts. Each phase is a real supervisor state, not a UI label.

PhaseActive because
gatheringConstraintGraph.graph.constraints-for(N) is collecting the projection.
workingThe agent (Path A CodingAgent or Path B SDK worker) is running.
sign-offThe agent emitted code.done; the supervisor is now forcing the agent to argue every constraint.
reviewingSign-off passed; reviewer fan-out is in flight.
barrierReviews are gathered; supervisor is waiting for the slowest reviewer or a quorum.
continuingReviews fed rejection feedback back to the agent; a new round is starting.
doneApproved; ratchet updated; session closed.
errorUnrecoverable failure recorded in _history.
escalatedEscalation policy fired; manual intervention queued.

The round protocol

A "round" is one pass of working → sign-off → reviewing → barrier → (continuing | done). Rounds count up in ActiveIssueInfo.round (SmithersSupervisor.ts:178).

mermaid
stateDiagram-v2
    [*] --> gathering
    gathering --> working: prompt built
    working --> sign-off: agent emits code.done
    sign-off --> working: argument incomplete (forced restart)
    sign-off --> reviewing: argument complete
    reviewing --> barrier: all reviewers dispatched
    barrier --> done: every constraint approved → ratchet
    barrier --> continuing: any rejection
    continuing --> working: rejection feedback injected
    continuing --> escalated: too many rejections
    working --> error: unrecoverable

maxRoundsPerProvider (default 3, SmithersConfig.limits.maxRoundsPerProvider) caps how many rounds happen on one provider before sequential fallback to the next.

Parallel review fan-out

When the supervisor enters reviewing, it iterates over the gathered constraints and dispatches each to a ReviewerAgent in parallel:

ts
// SmithersSupervisor.ts:1590-1593 (excerpt)
const result = await reviewer.onReviewerEvaluate({ ... });

ReviewerAgent evaluates one constraint against the agent's evidence (file diff, tool-call transcript, mechanical-check output). It returns a ReviewResult:

ts
type ReviewResult = {
  constraintId: string;
  approved: boolean;
  argument: string;
  evidence: ReviewEvidence[];
  rejectionReason?: string;
}

Importantly, every reviewer sees the same evidence. There is no "first reviewer wins" or partial review — they all evaluate against the same agentOutput snapshot.

The barrier

barrier is the gather phase. The supervisor waits until every dispatched reviewer either returns or times out:

  • All approved: true — proceed to ratchet.
  • Any approved: false — collect every rejection and build feedback for the agent.
  • Reviewer timeout — counted as approved: false with rejectionReason: 'timeout'.

The barrier is what makes review not piecemeal (per DESIGN.md §1) — agents cannot ship after one reviewer approves; everyone votes against the same evidence.

Rejection feedback and restart

If the barrier rejects, _buildRejectionFeedback (SmithersSupervisor.ts:1886+) constructs a new prompt segment listing what failed and why. The supervisor then calls onSmithersStart again with round + 1, the same agentMount, the same systemPrompt, and the new feedback appended to userMessage.

This is restart-on-rejection — the agent gets a fresh session with the same identity and the rejection context. It does NOT have access to its previous transcript automatically; the rejection feedback is the only carryover (this prevents agents from rationalising past attempts).

Ratchet

When all reviews approve, the supervisor writes a RatchetEntry per constraint via RatchetStore:

ts
// projects/matrix-3/packages/smithers/src/actors/RatchetStore.ts
export interface RatchetEntry {
  constraintId: string;
  testConstraintId: string;     // toRatchetTestConstraintId(constraintId, ...)
  satisfiedBy: { issueNumber, branch, evidence };
  ratchetedAt: string;
}

The ratchet is append-only. smithers.ratchet returns the count and entries; the workbench renders this in the Governance route. A future issue cannot regress a ratcheted constraint — ConstraintGraph.graph.constraints-for will surface previously-ratcheted constraints as invariants the agent must continue to satisfy.

Escalation policy

EscalationPolicy (src/actors/EscalationPolicy.ts) decides when to give up vs continue. Inputs:

  • Round count.
  • Number of consecutive rejections.
  • Provider fallback exhaustion.
  • Time-since-start.

Decision values include continue, fallback-provider, escalate-human, mark-error. The supervisor consults the policy on each barrier rejection (SmithersSupervisor.ts:1445-1450).

When escalate-human fires, the supervisor:

  1. Sets phase to escalated.
  2. Emits smithers.escalated (declared in matrix.json emits).
  3. Writes the convergence log entry event: 'escalated'.
  4. Stops the agent, leaves the session record in place for human review.

Escalated issues appear in the Topology Rail's escalated queue.

Workstream-request inbox

Smithers itself can be the recipient of workstream-requests from other actors:

bash
matrix invoke smithers smithers.request-service '{
  "targetWorkstream": "smithers",
  "summary": "Re-run convergence on issue 73 with relaxed constraint X",
  "linkedIssueNumber": 73,
  "priority": "high"
}'

onSmithersRequestService (SmithersSupervisor.ts:2833+) appends to _workstreamRequests. The supervisor periodically services pending requests via _servicePendingWorkstreamRequests. Requests can be dispatched (smithers.request-dispatch), acknowledged, messaged, or resolved — full inbox semantics.

See also

Source: projects/matrix-3/packages/smithers/src/actors/SmithersSupervisor.ts:1410-1510 (barrier), :1886-1900 (rejection feedback), :2294-2469 (sign-off), :1445-1450 (escalation policy invocation).