Skip to content

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

  1. The manifest's components[].props for SmithersSupervisor (any field set there overrides the default).
  2. 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).