Files
nanoclaw/container/agent-runner/src/config.ts
Petki Tamás ad5d4d2664 feat(container-config): add per-group model + effort overrides
Allow individual agent groups to opt into different models or effort levels
without changing host-wide defaults. Useful when one group is high-stakes
(opus, high effort) but most are routine (sonnet/haiku, low effort).

container.json gains two optional fields:
  - model: alias ("sonnet" | "opus" | "haiku") or full model ID
  - effort: "low" | "medium" | "high" | "xhigh" | "max"

Both omitted = SDK default (current behavior). The host plumbs them as
NANOCLAW_MODEL / NANOCLAW_EFFORT env vars at container spawn time; the
agent-runner reads them in providers/index.ts and threads through to the
provider via ProviderOptions. The Claude provider passes them straight to
sdkQuery options.

`effort` is currently typed as `any` because the @anthropic-ai/claude-
agent-sdk type doesn't surface it yet — passing it through still works at
runtime via the SDK's loose option handling. Drop the cast once the SDK
adds an `effort` field to its options type.
2026-05-09 21:04:08 +03:00

60 lines
1.8 KiB
TypeScript

/**
* Runner config — reads /workspace/agent/container.json at startup.
*
* This file is mounted read-only inside the container. The host writes it;
* the runner only reads. All NanoClaw-specific configuration lives here
* instead of environment variables.
*/
import fs from 'fs';
const CONFIG_PATH = '/workspace/agent/container.json';
export interface RunnerConfig {
provider: string;
assistantName: string;
groupName: string;
agentGroupId: string;
maxMessagesPerPrompt: number;
mcpServers: Record<string, { command: string; args: string[]; env: Record<string, string> }>;
model?: string;
effort?: string;
}
const DEFAULT_MAX_MESSAGES = 10;
let _config: RunnerConfig | null = null;
/**
* Load config from container.json. Called once at startup.
* Falls back to sensible defaults for any missing field.
*/
export function loadConfig(): RunnerConfig {
if (_config) return _config;
let raw: Record<string, unknown> = {};
try {
raw = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
} catch {
console.error(`[config] Failed to read ${CONFIG_PATH}, using defaults`);
}
_config = {
provider: (raw.provider as string) || 'claude',
assistantName: (raw.assistantName as string) || '',
groupName: (raw.groupName as string) || '',
agentGroupId: (raw.agentGroupId as string) || '',
maxMessagesPerPrompt: (raw.maxMessagesPerPrompt as number) || DEFAULT_MAX_MESSAGES,
mcpServers: (raw.mcpServers as RunnerConfig['mcpServers']) || {},
model: (raw.model as string) || undefined,
effort: (raw.effort as string) || undefined,
};
return _config;
}
/** Get the loaded config. Throws if loadConfig() hasn't been called. */
export function getConfig(): RunnerConfig {
if (!_config) throw new Error('Config not loaded — call loadConfig() first');
return _config;
}