Appearance
Actor topology (Supervisor, Graph, Workspace, Container)
This page is the canonical map of which actor owns what state, who holds whose reference, and which ops cross which boundaries. It is the page to read before modifying any actor in src/actors/.
The tree
smithers (SmithersSupervisor)
├── smithers.graph (ConstraintGraph)
├── smithers.github (GitHubAdapter)
├── smithers.containers(ContainerManager)
├── smithers.workspace (WorkspaceActor)
├── smithers.archdocs (ArchitectureDocWatcher) — when present
└── (ephemeral, per-issue)
├── IssueActor — one per active issue
├── ReviewerAgent — one per dispatched review
└── CodingAgent — Path A only; Path B uses system.agents insteadSmithersSupervisor is the only persistent root. Everything else either lives until disposal alongside it, or is created+disposed per issue/review.
SmithersSupervisor
| Concern | What it owns |
|---|---|
_repo | Configured GitHub repo (persisted in .smithers/smithers-config.json) |
_maxConcurrentIssues | Concurrency cap (default 3) |
_llmConfigs | Resolved provider list from inferenceCatalog |
_activeIssues: Map<number, ActiveIssueInfo> | Currently-running sessions |
_history: CompletedIssueOutcome[] | Terminal outcomes |
_browsers: Map<string, ConnectedBrowser> | Subscribed browser sinks for activity-frame routing |
_workstreamRequests | Inbox of cross-actor requests |
_chatConstraints | Approved chat-derived constraints per issue |
_ratchet | Reference to RatchetStore |
_archWatcher, _escalationPolicy, _providerAffinity | Optional integrations, lazy-initialised |
_autoDispatchTimer, _autoDispatchEnabled | Background dispatch loop |
onBootstrap (SmithersSupervisor.ts:556-617) wires children, loads persisted config, resolves LLM credentials, and arms timers.
ConstraintGraph
The projection engine (ConstraintGraph.ts:60-97). Stateless beyond the cached issue list:
ts
private _issues: GitHubIssueWithConstraints[] = [];
private _githubMount = '';
private _version = 0;
private _ratchetStore: RatchetStore | null = null;
private _lastFetchError: string | null = null;Accepts (CONSTRAINT_GRAPH_ACCEPTS, ConstraintGraph.ts:41-58):
$introspect, $prompt,
graph.rebuild, graph.constraints-for, graph.neighborhood,
graph.topology, graph.status, graph.summary, graph.dependers,
graph.readiness, graph.dependency-chain, graph.critical-path,
graph.blocker-report, graph.topology-rail, graph.issue-inspector,
graph.conflictsEach graph.*-for op computes a typed projection on demand from the cached issues. The cache is refreshed by graph.rebuild which calls GitHubAdapter.github.list-issues.
Projection Principle reminder. Every browser pane, every agent prompt, every sign-off checklist, and every review dispatch is derived from one of these projections. The browser computes nothing; it renders typed projections. (
DESIGN.md §1.1.)
GitHubAdapter
Single-purpose: wrap the gh CLI. Accepts github.list-issues, github.get-issue, github.refresh, github.set-repo. Caches with TTL (_ttlMs = 60000, _lastFetch).
Two-pass enrichment in _fetchIssues:
- Build a prefix map
'TS-8' → issueNumberfrom titles. - Walk every issue body, parse
**Blocked by:**lines andBlocked by #N/Depends on #N, resolve symbolic refs via the prefix map.
This is what makes the dependency graph richer than just GitHub's "linked issues" relation.
WorkspaceActor
Session-scoped UI state owner (WorkspaceActor.ts:71-104). Multiple browser tabs, agents, and CLI sessions share the same workspace via $stateChanged events.
The DOM is NOT the control plane — this actor is. (
DESIGN.md §4.)
Accepts (WorkspaceActor.ts:80-99):
$introspect, $prompt,
workspace.focus, workspace.select-issue, workspace.navigate,
workspace.show-tab, workspace.set-overlay, workspace.graph-mode,
workspace.set-graph-mode, workspace.set-filter, workspace.set-queue,
workspace.toggle-pin, workspace.state, workspace.select-edge,
workspace.fork, workspace.attach, workspace.treeOwns WorkspaceState (the full UI projection) and a private _version. Every mutation increments the version and emits $stateChanged. workspace.tree returns a filesystem snapshot of the working dir for the diff tab — see buildWorkspaceTreeSnapshot (WorkspaceActor.ts:18-61) which walks src/, tests/, types/ (entry limit 200, no node_modules/.git/dist).
ContainerManager
ContainerManager.ts:81-120. Manages Docker containers running Matrix CodingAgents. Subscribes to mx.discovery.heartbeat for auto-discovery. Prunes offline containers every 15s.
Accepts: container.spawn, container.stop, container.list, container.remove, container.images.
State:
ts
private _containers = new Map<string, ContainerRecord>();
private _heartbeatUnsub: (() => void) | null = null;
private _pruneTimer: ReturnType<typeof setInterval> | null = null;Each ContainerRecord carries the docker container ID, the NATS root, status, last heartbeat, image, label, and WorkerRecord[].
IssueActor
Per-issue. Owns the runtime fingerprint of one active session — branch name, current round number, sign-off report so far, accumulated activity log. The supervisor's _activeIssues.set(N, info) carries an agentMount field that points at this actor.
ReviewerAgent
Per-review. Single-shot: created when the supervisor enters reviewing, lives just long enough to evaluate one constraint, returns its ReviewResult, and disposes. The barrier collects every reviewer's result before the supervisor proceeds.
ArchitectureDocWatcher
Optional. When present, watches a configured set of architecture-doc files. On change, parses constraints out of them and surfaces them via smithers.arch-constraints. The supervisor folds these into every issue's projection — so an architecture-doc edit propagates into the next agent prompt automatically.
Cross-actor message flow (sign-off example)
mermaid
sequenceDiagram
participant Browser as SmithersApp
participant Sup as SmithersSupervisor
participant Graph as ConstraintGraph
participant Issue as IssueActor
participant Reviewer as ReviewerAgent
Browser->>Sup: smithers.start { issueNumber: 73 }
Sup->>Graph: graph.constraints-for(73)
Graph-->>Sup: ConstraintProjection
Sup->>Issue: addChild(IssueActor, { issueNumber: 73 })
Sup->>Sup: dispatch agent (Path A or B)
note over Sup: agent runs, emits code.event, eventually code.done
Sup->>Sup: enter sign-off phase
Sup->>Sup: validate sign-off report against projection
Sup->>Reviewer: addChild(ReviewerAgent, { constraintId, evidence }) × N
Reviewer-->>Sup: ReviewResult × N
Sup->>Sup: barrier — all back?
alt all approved
Sup->>RatchetStore: write entries
Sup-->>Browser: smithers.converged
else any rejected
Sup->>Sup: build feedback, restart agent
endCommunication patterns
- Cross-actor invocation:
RequestReply.execute(this._context, mount, op, payload, { timeoutMs }). Used everywhere the supervisor needs a typed reply. - Activity routing: Activity frames flow on
$activitysubjects with addressing built bycreateSmithersAddress/encodeSmithersAddress(src/addressing.ts). Browsers subscribe byaddressKey. - State broadcast:
$stateChangedevents flow from actors that own state (Workspace, Graph). Subscribers re-render rather than re-fetching.
See also
- Coding agent and inference integration
- Smithers package layout
- Actor ops (SmithersSupervisor and friends)
Source:
projects/matrix-3/packages/smithers/src/actors/SmithersSupervisor.ts:373-412(state declarations),:556-617(bootstrap),projects/matrix-3/packages/smithers/src/actors/ConstraintGraph.ts:41-87,projects/matrix-3/packages/smithers/src/actors/WorkspaceActor.ts:71-111.