diff --git a/docs/tools/subagents.md b/docs/tools/subagents.md index f2554e1be..7d0545c18 100644 --- a/docs/tools/subagents.md +++ b/docs/tools/subagents.md @@ -30,6 +30,9 @@ Cost note: each sub-agent has its **own** context and token usage. For heavy or tasks, set a cheaper model for sub-agents and keep your main agent on a higher-quality model. You can configure this via `agents.defaults.subagents.model` or per-agent overrides. +Thinking level: sub-agents inherit the caller's thinking level by default. Override globally +via `agents.defaults.subagents.thinking` or per-spawn via the `thinking` tool param. + ## Tool Use `sessions_spawn`: @@ -102,7 +105,9 @@ Override via config: agents: { defaults: { subagents: { - maxConcurrent: 1 + maxConcurrent: 1, + model: "anthropic/claude-haiku-4", + thinking: "low" } } }, diff --git a/src/agents/clawdbot-tools.subagents.sessions-spawn-applies-model-child-session.test.ts b/src/agents/clawdbot-tools.subagents.sessions-spawn-applies-model-child-session.test.ts index 1f27a6530..d4a734c70 100644 --- a/src/agents/clawdbot-tools.subagents.sessions-spawn-applies-model-child-session.test.ts +++ b/src/agents/clawdbot-tools.subagents.sessions-spawn-applies-model-child-session.test.ts @@ -195,4 +195,79 @@ describe("moltbot-tools: subagents", () => { model: "minimax/MiniMax-M2.1", }); }); + + it("sessions_spawn applies default subagent thinking from defaults config", async () => { + resetSubagentRegistryForTests(); + callGatewayMock.mockReset(); + configOverride = { + session: { mainKey: "main", scope: "per-sender" }, + agents: { defaults: { subagents: { thinking: "medium" } } }, + }; + const calls: Array<{ method?: string; params?: unknown }> = []; + + callGatewayMock.mockImplementation(async (opts: unknown) => { + const request = opts as { method?: string; params?: unknown }; + calls.push(request); + if (request.method === "agent") { + return { runId: "run-default-thinking", status: "accepted" }; + } + return {}; + }); + + const tool = createMoltbotTools({ + agentSessionKey: "agent:main:main", + agentChannel: "discord", + }).find((candidate) => candidate.name === "sessions_spawn"); + if (!tool) throw new Error("missing sessions_spawn tool"); + + const result = await tool.execute("call-default-thinking", { + task: "do thing", + }); + expect(result.details).toMatchObject({ + status: "accepted", + }); + + const agentCall = calls.find((call) => call.method === "agent"); + expect(agentCall?.params).toMatchObject({ + thinking: "medium", + }); + }); + + it("sessions_spawn tool param thinking overrides config default", async () => { + resetSubagentRegistryForTests(); + callGatewayMock.mockReset(); + configOverride = { + session: { mainKey: "main", scope: "per-sender" }, + agents: { defaults: { subagents: { thinking: "low" } } }, + }; + const calls: Array<{ method?: string; params?: unknown }> = []; + + callGatewayMock.mockImplementation(async (opts: unknown) => { + const request = opts as { method?: string; params?: unknown }; + calls.push(request); + if (request.method === "agent") { + return { runId: "run-override-thinking", status: "accepted" }; + } + return {}; + }); + + const tool = createMoltbotTools({ + agentSessionKey: "agent:main:main", + agentChannel: "discord", + }).find((candidate) => candidate.name === "sessions_spawn"); + if (!tool) throw new Error("missing sessions_spawn tool"); + + const result = await tool.execute("call-override-thinking", { + task: "do thing", + thinking: "high", + }); + expect(result.details).toMatchObject({ + status: "accepted", + }); + + const agentCall = calls.find((call) => call.method === "agent"); + expect(agentCall?.params).toMatchObject({ + thinking: "high", + }); + }); }); diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index e5e1391d1..19a9ee85b 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -176,6 +176,10 @@ export function createSessionsSpawnTool(opts?: { }); } thinkingOverride = normalized; + } else { + // Fall back to config defaults if no explicit override + thinkingOverride = + targetAgentConfig?.subagents?.thinking ?? cfg.agents?.defaults?.subagents?.thinking; } if (resolvedModel) { try { diff --git a/src/config/types.agent-defaults.ts b/src/config/types.agent-defaults.ts index 9c6ce0211..ea224869a 100644 --- a/src/config/types.agent-defaults.ts +++ b/src/config/types.agent-defaults.ts @@ -204,6 +204,8 @@ export type AgentDefaultsConfig = { archiveAfterMinutes?: number; /** Default model selection for spawned sub-agents (string or {primary,fallbacks}). */ model?: string | { primary?: string; fallbacks?: string[] }; + /** Default thinking level for spawned sub-agents. */ + thinking?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; }; /** Optional sandbox settings for non-main sessions. */ sandbox?: { diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index bb1d45bf0..bb7896fe7 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -433,6 +433,8 @@ export type ToolsConfig = { subagents?: { /** Default model selection for spawned sub-agents (string or {primary,fallbacks}). */ model?: string | { primary?: string; fallbacks?: string[] }; + /** Default thinking level for spawned sub-agents. */ + thinking?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; tools?: { allow?: string[]; deny?: string[]; diff --git a/src/config/zod-schema.agent-defaults.ts b/src/config/zod-schema.agent-defaults.ts index a849078ed..6f13c7784 100644 --- a/src/config/zod-schema.agent-defaults.ts +++ b/src/config/zod-schema.agent-defaults.ts @@ -150,6 +150,16 @@ export const AgentDefaultsSchema = z .strict(), ]) .optional(), + thinking: z + .union([ + z.literal("off"), + z.literal("minimal"), + z.literal("low"), + z.literal("medium"), + z.literal("high"), + z.literal("xhigh"), + ]) + .optional(), }) .strict() .optional(),