Appearance
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:
| Queue | Meaning |
|---|---|
ready | Constraints gathered; not blocked by other issues |
active | A coding agent is working |
blocked | Has at least one open blocker |
escalated | Manual 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:
- Gather constraints. Calls
graph.constraints-for(issueNumber)onConstraintGraph. The result is aConstraintProjectionthat 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).
- Constraints from each depender issue (via
- 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). - Resolve worker. Tries
system.runtimesfor an SDK worker (codex,claude-code); if found, uses Path B (SdkWorkerRef). Else falls back to a localCodingAgent(Path A). - Open session. For Path B, calls
system.agents.session.openwith the system + user prompt; for Path A, instantiatesCodingAgentand sendscode.run. - Subscribe to activity. Subscribes to the session's
$activitysubjects. Frames flow back to the workbench via the bus. - Persist
ActiveIssueInfo._activeIssues.set(issueNumber, info)recordsrequestId,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
| Op | What it does |
|---|---|
smithers.steer | Inject a new prompt into the current session without restarting. onSmithersSteer calls onSmithersStart again with the issue's existing context (SupervisorActor.ts:2471-2494). |
smithers.stop | Stop 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:
- Compares against the gathered
ConstraintProjection— every constraint must have an entry. - Runs
runMechanicalChecksfor any constraint with acheckMethod(compile, test, lint, custom command). - Captures evidence via
extractEvidenceFromAgentOutputagainst the capturedagentOutput. - 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):
| Phase | Meaning |
|---|---|
done | Sign-off accepted, all reviews approved, ratchet updated, branch ready for human PR review. |
error | Unrecoverable error; session stopped, recorded in _history. |
escalated | Escalation 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(onSmithersSignOffand rejection feedback).