diff --git a/mcp.md b/mcp.md index 224f856..82891e9 100644 --- a/mcp.md +++ b/mcp.md @@ -208,7 +208,7 @@ server.registerTool('dash-subagent', { task: z.string().describe('Specific instructions and goal for the subagent.'), skills: z.array(z.string()).nullable().optional().describe('Array of SKILL names or slugs to inject.'), conversation_id: z.number().int().positive().nullable().optional().describe('Existing subagent conversation ID to follow up on, or null to create a new one.'), - max_iterations: z.number().int().min(1).max(30).nullable().optional().describe('Safety bound on tool loops (default 15, max 30).'), + max_iterations: z.number().int().min(10).max(50).nullable().optional().describe('Safety bound on tool loops (min 10, default 20, max 50).'), model: z.string().nullable().optional().describe('Optional OpenRouter model override.'), note: z.string().nullable().optional(), }, diff --git a/src/api/routes/mcp.js b/src/api/routes/mcp.js index 7a3383d..9f5884b 100644 --- a/src/api/routes/mcp.js +++ b/src/api/routes/mcp.js @@ -189,7 +189,7 @@ function createDashbackMcpServer({ user }) { task: z.string().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.task || 'Specific instructions and goal for the subagent.'), skills: z.array(z.string()).nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.skills || 'Array of SKILL names or slugs to inject into the subagent prompt.'), conversation_id: z.number().int().positive().nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.conversation_id || 'Existing subagent conversation ID to follow up on, or null to create a new one.'), - max_iterations: z.number().int().min(1).max(30).nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.max_iterations || 'Safety bound on tool loops (default 15, max 30).'), + max_iterations: z.number().int().min(10).max(50).nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.max_iterations || 'Safety bound on tool loops (min 10, default 20, max 50).'), model: z.string().nullable().optional().describe('Optional OpenRouter model override. Defaults to company default model.'), note: z.string().nullable().optional().describe(TOOL_PARAMETER_DESCRIPTIONS.subagent?.note || 'Status message shown during execution.'), }, diff --git a/src/tools/prompts.js b/src/tools/prompts.js index ae8cce0..77ba75b 100644 --- a/src/tools/prompts.js +++ b/src/tools/prompts.js @@ -259,7 +259,7 @@ export const TOOL_PARAMETER_DESCRIPTIONS = { task: 'Specific instructions and goal for the subagent.', skills: 'Array of SKILLs (e.g. ["api-article.SKILL", "label.SKILL"]) to inject directly into the subagent system prompt.', conversation_id: 'Existing subagent conversation ID to follow up on, or null to create a new one.', - max_iterations: 'Safety bound on tool loops (default 15, max 30).', + max_iterations: 'Safety bound on tool loops (min 10, default 20, max 50).', }, draft_delivery_manifest: { diff --git a/src/tools/subagent.js b/src/tools/subagent.js index 59c3637..de6e70b 100644 --- a/src/tools/subagent.js +++ b/src/tools/subagent.js @@ -40,19 +40,21 @@ import { createDraftDeliveryManifestTool } from './draft-delivery-manifest.js'; * stays in the subagent's conversation. */ -// #region Constants - -const DEFAULT_MAX_ITERATIONS = 15; -const MAX_ITERATIONS_LIMIT = 30; +const MIN_ITERATIONS_LIMIT = 10; +const DEFAULT_MAX_ITERATIONS = 20; +const MAX_ITERATIONS_LIMIT = 50; /** Subagents never spawn subagents: the parent runs at depth 0, its child at depth 1. */ const MAX_SUBAGENT_DEPTH = 1; const SUBAGENT_PROVIDER = 'openrouter'; -const SUBAGENT_PROMPT_NOTICE = [ - 'SUBAGENT MODE:', - '- You are a subagent working for a primary agent, not talking to the end user. You cannot ask the user questions.', - '- Finish with a concise, self-contained final answer: what was done, key findings, and any file paths or IDs the primary agent needs.', -].join('\n'); +function buildSubagentPromptNotice(maxIterations) { + return [ + 'SUBAGENT MODE:', + '- You are a subagent working for a primary agent, not talking to the end user. You cannot ask the user questions.', + `- You have a strict limit of ${maxIterations} iterations for this task. Budget your tool calls efficiently to finish and return your final answer within this limit.`, + '- Finish with a concise, self-contained final answer: what was done, key findings, and any file paths or IDs the primary agent needs.', + ].join('\n'); +} // #endregion @@ -242,7 +244,7 @@ export function createSubagentTool({ googleAuthId, companyId, conversationId: pa if (!Number.isInteger(iterationLimit) || iterationLimit < 1) { throw new ToolError(`"max_iterations" must be a positive integer (received ${maxIterations}).`); } - const boundedIterations = Math.min(iterationLimit, MAX_ITERATIONS_LIMIT); + const boundedIterations = Math.max(MIN_ITERATIONS_LIMIT, Math.min(iterationLimit, MAX_ITERATIONS_LIMIT)); const vfsRoot = await prepareVfs({ googleAuthId }); const injectedSkills = buildInjectedSkillsBlock(vfsRoot, skills); @@ -291,7 +293,7 @@ export function createSubagentTool({ googleAuthId, companyId, conversationId: pa const fullHistory = await db.listMessages(googleAuthId, subagentConversationId); const systemPrompt = [ await buildSystemPrompt(vfsRoot, googleAuthId), - SUBAGENT_PROMPT_NOTICE, + buildSubagentPromptNotice(boundedIterations), injectedSkills, ].filter(Boolean).join('\n\n');