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.
60 lines
1.8 KiB
TypeScript
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;
|
|
}
|