Appearance
Configuration (SMITHERS_DEFAULTS)
Smithers' configuration is the single object SMITHERS_DEFAULTS in projects/matrix-3/packages/smithers/src/config/defaults.ts. The header comment is unambiguous:
Smithers Configuration — Single Source of Truth. Every configurable value in Smithers lives here. Actors read from this config (passed via props from
matrix.json) and NEVER hardcode values in source. Zero config = identical behavior to pre-productization.
mergeConfig (defaults.ts:157-179) deep-merges manifest props with defaults. Anything not specified in the manifest falls back to the default.
Top-level shape
ts
export interface SmithersConfig {
nats: SmithersNatsConfig;
git: SmithersGitConfig;
container: SmithersContainerConfig;
providers: SmithersProviderConfig;
timers: SmithersTimerConfig;
limits: SmithersLimitConfig;
storage: SmithersStorageConfig;
}nats
ts
nats: {
port: 4222,
wsPort: 4223,
dockerHost: 'host.docker.internal', // hostname containers use to reach the host's NATS
}dockerHost is platform-dependent. On Linux without Docker Desktop you usually need to override this to the host's actual interface address.
git
ts
git: {
defaultBranch: 'main',
branchPrefix: 'smithers/issue-',
remoteName: 'origin',
repoUrlFormat: 'https://github.com/${repo}.git',
}The supervisor uses branchPrefix + issueNumber when checking out a working branch for the agent. The ${repo} placeholder is replaced from the _repo config (set by smithers.configure).
container
ts
container: {
enabled: true,
runtime: 'docker', // 'docker' | 'podman' | 'none'
image: 'smithers-agent:latest',
workspacePath: '/workspace',
}Setting runtime: 'none' disables containers. mergeConfig honours this:
ts
// defaults.ts:172-174
if (result.container.runtime === 'none') {
result.container.enabled = false;
}So runtime: 'none' and enabled: false are equivalent.
providers
ts
providers: {
claude: { model: 'claude-sonnet-4-20250514', envVar: 'ANTHROPIC_API_KEY' },
codex: { model: 'gpt-5.3-codex', envVar: 'MATRIX_LLM_MODEL' },
}These are the per-provider defaults Smithers presents when prompting the inference catalog. Real provider selection happens in system.inference / inferenceCatalog.createInferenceConfig — these defaults are just hints used by Smithers' own UI surface and the rejection-restart logic.
timers
ts
timers: {
autoDispatchIntervalMs: 60_000, // periodic auto-dispatch scan
agentCleanupIntervalMs: 5 * 60_000,
agentCleanupCutoffMs: 30 * 60_000, // forget completed agents older than this
localPollIntervalMs: 2_000, // local CodingAgent runtime poll
containerPollIntervalMs: 3_000,
containerStartupDelayMs: 5_000,
restartMaxWaitMs: 20 * 60_000,
heartbeatIntervalMs: 10_000,
heartbeatTimeoutMs: 30_000,
pruneIntervalMs: 15_000, // ContainerManager prune
mechanicalCheckTimeoutMs: 120_000,
shellCommandTimeoutMs: 120_000,
inferenceResolutionTimeoutMs: 1_500, // race in CodingAgent._resolvePiAiConfig
issueCacheTtlMs: 60_000, // GitHubAdapter cache
retryDelayMs: 2_000,
}pruneIntervalMs controls how aggressively ContainerManager removes offline containers. inferenceResolutionTimeoutMs is the race window for resolving inference credentials — bumping it helps slow catalog responses.
limits
ts
limits: {
maxTurns: 200, // CodingAgent tool-loop turn cap
maxConcurrentIssues: 3,
maxRoundsPerProvider: 3, // before sequential fallback to next provider
outputTruncationBytes: 16_000,
agentOutputCapBytes: 20_000, // captured for evidence extraction
shellOutputCapBytes: 100_000,
checkOutputTruncationChars:1_000,
chatHistoryLimit: 500,
historyLimit: 200, // _history outcome cap
workstreamRequestLimit: 200,
}maxTurns: 200 is generous — most convergence rounds finish in <50 turns. It's a safety cap, not a target. agentOutputCapBytes: 20000 is what's preserved for evidence extraction — the full transcript may be much longer; this is the working-set window.
storage
ts
storage: {
configDir: '.smithers',
ratchetFile: 'ratchet.json',
configFile: 'smithers-config.json',
sessionDir: '.matrix/sessions',
}All paths are relative to the repo working directory (the cwd Smithers operates in). .smithers/ holds Smithers-specific persistent state (config + ratchet). .matrix/sessions/ is shared with system.agents.
Resolution order
- The manifest's
components[].propsforSmithersSupervisor(any field set there overrides the default). - The defaults above.
There is no environment-variable override for the config object itself. Provider credentials come from environment via system.inference / system.factotum; the config object describes structure, not secrets.
mergeConfig
ts
// defaults.ts:157-179
export function mergeConfig(partial: Record<string, unknown>): SmithersConfig {
const result = structuredClone(SMITHERS_DEFAULTS);
if (!partial || typeof partial !== 'object') return result;
for (const section of Object.keys(SMITHERS_DEFAULTS) as Array<keyof SmithersConfig>) {
const override = (partial as any)[section];
if (override && typeof override === 'object' && !Array.isArray(override)) {
const target = result[section] as unknown as Record<string, unknown>;
for (const [key, value] of Object.entries(override)) {
if (key in target && value !== undefined && value !== null) {
target[key] = value;
}
}
}
}
if (result.container.runtime === 'none') {
result.container.enabled = false;
}
return result;
}It merges shallowly within each section (one level deep), only honouring keys that already exist on the default — that means typos in the manifest don't silently corrupt config; they're ignored.
What is NOT in this config
- LLM API keys. Always via
system.factotum/system.inference. - Smithers' authority root or the gateway port. Those come from the Host environment, not Smithers.
- The static convergence phase list. Hard-coded.
- The shell tool surface. Hard-coded — exactly one Unix shell tool, by design.
See also
Source:
projects/matrix-3/packages/smithers/src/config/defaults.ts(the entire file, 180 lines).