Skip to content

Issues, agent sessions, and sign-off

A Smithers "session" is the runtime of a single convergence attempt for a single issue. Multiple issues can have active sessions concurrently (up to maxConcurrentIssues, default 3). This page walks the lifecycle from start to finish.

Picking an issue

The Topology Rail (left edge of the workbench) lists issues in queues:

QueueMeaning
readyConstraints gathered; not blocked by other issues
activeA coding agent is working
blockedHas at least one open blocker
escalatedManual review required (escalation policy fired)

Selecting an issue sets WorkspaceActor.selectedIssue and refreshes the inspector with graph.issue-inspector. Selection persists in WorkspaceState.

Dispatching: smithers.start

bash
matrix invoke smithers smithers.start '{"issueNumber": 73}'

SmithersSupervisor.onSmithersStart (SmithersSupervisor.ts:714+) runs the dispatch sequence:

  1. Gather constraints. Calls graph.constraints-for(issueNumber) on ConstraintGraph. The result is a ConstraintProjection that aggregates:
    • Constraints from each depender issue (via parseBlockedBy + body parsing).
    • SYSTEM_CONSTRAINTS (always-on architectural invariants).
    • getBootstrapConstraints() (build/test gates).
    • Approved chat constraints for that issue (smithers.constraint-approved).
    • Architecture-doc-watcher constraints (ArchitectureDocWatcher).
  2. Build the prompt. buildCodingAgentPrompt (src/prompts/constraint-prompt.ts) materialises the projection into the agent system + user message. The prompt is the same text that drives the inspector Constraints tab and the sign-off checklist (Projection Principle).
  3. Resolve worker. Tries system.runtimes for an SDK worker (codex, claude-code); if found, uses Path B (SdkWorkerRef). Else falls back to a local CodingAgent (Path A).
  4. Open session. For Path B, calls system.agents.session.open with the system + user prompt; for Path A, instantiates CodingAgent and sends code.run.
  5. Subscribe to activity. Subscribes to the session's $activity subjects. Frames flow back to the workbench via the bus.
  6. Persist ActiveIssueInfo. _activeIssues.set(issueNumber, info) records requestId, traceId, branch, agentSessionId, workerRef, runtimeKind, round, agentMount, cwd, dockerContainerId, llmConfigIndex, maxRoundsPerProvider, roundsOnCurrentProvider, maxTurns, agentOutput, eventUnsub.

Watching the live agent stream

The Agent Chat panel (<smithers-agent-chat>) renders ActivityFrames from $activity. Frame kinds (defined in @open-matrix/core):

  • text — model output token chunk.
  • tool-call — the agent invoked a tool (Smithers' single shell tool, or one provided by the SDK worker).
  • tool-result — the tool's reply.
  • state-change — runtime/status transition.
  • lifecycle — session opened/closed.

Smithers also captures agentOutput server-side (ActiveIssueInfo.agentOutput) so that evidence extraction at sign-off time has the full transcript.

Steering and stopping

OpWhat it does
smithers.steerInject a new prompt into the current session without restarting. onSmithersSteer calls onSmithersStart again with the issue's existing context (SupervisorActor.ts:2471-2494).
smithers.stopStop the agent. onSmithersStop (SmithersSupervisor.ts:2259+) tears down the session, sends code.stop if Path A, or stops the SDK worker session for Path B. Returns { stopped: number[] } listing the issue numbers stopped.

Both are exposed in the Control Palette panel.

Sign-off — the forcing function

When the agent emits code.done, the supervisor transitions to sign-off phase. smithers.sign-off is then forced: the agent must argue, per constraint, why the constraint is satisfied — pointing at code, test runs, doc citations, etc. Mechanically (SmithersSupervisor.ts:2294+):

ts
async onSmithersSignOff(payload: { issueNumber, signOffReport: SignOffReport })

Every SignOffEntry in the report names a constraint id and an argument string + evidence. The supervisor:

  1. Compares against the gathered ConstraintProjection — every constraint must have an entry.
  2. Runs runMechanicalChecks for any constraint with a checkMethod (compile, test, lint, custom command).
  3. Captures evidence via extractEvidenceFromAgentOutput against the captured agentOutput.
  4. If any mechanical check fails or any constraint lacks an argument, the supervisor either restarts the agent with rejection feedback or escalates.

A "successful sign-off" produces MechanicalResult[] and an aggregated argument set; the supervisor then transitions to the review phase.

What "done" means

A session ends in one of three terminal states (ConvergencePhase):

PhaseMeaning
doneSign-off accepted, all reviews approved, ratchet updated, branch ready for human PR review.
errorUnrecoverable error; session stopped, recorded in _history.
escalatedEscalation policy fired (too many rejections, too many provider fallbacks, manual override needed).

The completed entry lands in _history: CompletedIssueOutcome[] (SmithersSupervisor.ts:202-213):

ts
interface CompletedIssueOutcome {
  issueNumber: number;
  phase: 'done' | 'error';
  summary: string;
  startedAt: string | null;
  completedAt: string;
  toolCallCount: number;
  turns: number;
  branch: string | null;
  requestId: string | null;
  traceId: string | null;
}

Read it via smithers.history. The Inspector's session tab shows the same.

Concurrent sessions and provider fallback

maxConcurrentIssues (default 3) caps simultaneous sessions. If you have multiple LLM providers configured, _llmConfigs lists them in order; roundsOnCurrentProvider and maxRoundsPerProvider (default 3) drive sequential fallback — after enough failed rounds, the supervisor moves the issue to the next provider and starts a new round.

Provider affinity is tracked by ProviderAffinityTracker so that a provider that succeeds for an issue gets preference next time.

See also

Source: projects/matrix-3/packages/smithers/src/actors/SmithersSupervisor.ts:155-230 (ActiveIssueInfo), projects/matrix-3/packages/smithers/src/actors/SmithersSupervisor.ts:2294-2469 (onSmithersSignOff and rejection feedback).