run_agent_across_list
Execute AI coding agents in parallel batches to analyze, refactor, or generate code across multiple files by substituting list items into prompts.
Instructions
Spawns an AI coding agent for each item in a previously created list. Agents run in batches of 10 parallel processes with automatic permission skipping enabled.
WHEN TO USE:
Performing complex code analysis, refactoring, or generation across multiple files
Tasks that require AI reasoning rather than simple shell commands
When you need to delegate work to multiple AI agents working in parallel
AVAILABLE AGENTS:
claude: Claude Code CLI (uses --dangerously-skip-permissions for autonomous operation)
gemini: Google Gemini CLI (uses --yolo for auto-accept)
codex: OpenAI Codex CLI (uses --dangerously-bypass-approvals-and-sandbox for autonomous operation)
HOW IT WORKS:
Each item in the list is substituted into the prompt where {{item}} appears
Agents run in batches of 10 at a time to avoid overwhelming the system
Each agent has a 5-minute timeout
Output streams directly to files as the agents work
This tool waits for all agents to complete before returning
AFTER COMPLETION:
Read the stdout files to check the results from each agent
Check stderr files if you encounter errors
Files are named based on the item (e.g., "myfile.ts.stdout.txt")
VARIABLE SUBSTITUTION:
Use {{item}} in your prompt - it will be replaced with each list item
Example: "Review {{item}} for bugs" becomes "Review src/file.ts for bugs" for item "src/file.ts"
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| list_id | Yes | The list ID returned by create_list. This identifies which list of items to iterate over. | |
| agent | Yes | Which AI agent to use: 'claude', 'gemini', 'codex'. All agents run with permission-skipping flags for autonomous operation. | |
| prompt | Yes | The prompt to send to each agent. Use {{item}} as a placeholder - it will be replaced with the current item value. Example: 'Review {{item}} and suggest improvements' or 'Add error handling to {{item}}' |
Implementation Reference
- src/index.ts:662-777 (handler)The core handler function for 'run_agent_across_list'. Retrieves the list by ID, creates per-item prompts by substituting {{item}}, builds agent-specific CLI commands with permission bypass flags, runs agents in parallel batches (default 10) with 5min timeout each, streams stdout/stderr to timestamped files, and returns a list of output file paths.async ({ list_id, agent, prompt }) => { const items = lists.get(list_id); if (!items) { return { content: [ { type: "text", text: `Error: No list found with ID "${list_id}". Please call create_list first to create a list of items, then use the returned ID with this tool.`, }, ], isError: true, }; } // Create output directory const runId = randomUUID(); const runDir = join(outputDir, runId); await mkdir(runDir, { recursive: true }); const results: Array<{ item: string; files: OutputFiles }> = []; const tasks: Array<{ command: string; stdoutFile: string; stderrFile: string; timeout: number; }> = []; // Build the agent command with skip permission flags and streaming output // Additional args can be passed via PAR5_AGENT_ARGS (all agents) or PAR5_CLAUDE_ARGS, PAR5_GEMINI_ARGS, PAR5_CODEX_ARGS (per-agent) const getAgentCommand = ( agentName: string, expandedPrompt: string, ): string => { const escapedPrompt = expandedPrompt.replace(/'/g, "'\\''"); const agentArgs = process.env.PAR5_AGENT_ARGS || ""; switch (agentName) { case "claude": { // Claude Code CLI with --dangerously-skip-permissions and streaming output const claudeArgs = process.env.PAR5_CLAUDE_ARGS || ""; return `claude --dangerously-skip-permissions --output-format stream-json --verbose ${agentArgs} ${claudeArgs} -p '${escapedPrompt}'`; } case "gemini": { // Gemini CLI with yolo mode and streaming JSON output const geminiArgs = process.env.PAR5_GEMINI_ARGS || ""; return `gemini --yolo --output-format stream-json ${agentArgs} ${geminiArgs} '${escapedPrompt}'`; } case "codex": { // Codex CLI exec subcommand with full-auto flag and JSON streaming output const codexArgs = process.env.PAR5_CODEX_ARGS || ""; return `codex exec --dangerously-bypass-approvals-and-sandbox ${agentArgs} ${codexArgs} '${escapedPrompt}'`; } default: throw new Error(`Unknown agent: ${agentName}`); } }; for (let i = 0; i < items.length; i++) { const item = items[i]; // Replace {{item}} with the actual item value const expandedPrompt = prompt.replace(/\{\{item\}\}/g, item); const safeFilename = toSafeFilename(item); const stdoutFile = join(runDir, `${safeFilename}.stdout.txt`); const stderrFile = join(runDir, `${safeFilename}.stderr.txt`); tasks.push({ command: getAgentCommand(agent, expandedPrompt), stdoutFile, stderrFile, timeout: 300000, // 5 minute timeout per item }); results.push({ item, files: { stdout: stdoutFile, stderr: stderrFile }, }); } // Run agents in batches of 10 await runInBatches(tasks); // Build prose response const fileList = results .map( (r) => `- ${r.item}: stdout at "${r.files.stdout}", stderr at "${r.files.stderr}"`, ) .join("\n"); const agentNames: Record<string, string> = { claude: "Claude Code", gemini: "Google Gemini", codex: "OpenAI Codex", }; const numBatches = Math.ceil(items.length / BATCH_SIZE); return { content: [ { type: "text", text: `Completed ${results.length} ${agentNames[agent]} agents in ${numBatches} batch(es) of up to ${BATCH_SIZE} parallel agents each. Output has been streamed to files. OUTPUT FILES: ${fileList} NEXT STEPS: 1. Read the stdout files to check the results from each agent 2. If there are errors, check the corresponding stderr files for details All agents have completed (with a 5-minute timeout per agent) and output files are ready to read.`, }, ], }; },
- src/index.ts:644-660 (schema)Zod input schema validation for the tool: requires list_id (string), agent (enum of enabled agents: claude/gemini/codex based on env vars), prompt (string with {{item}} placeholder).inputSchema: { list_id: z .string() .describe( "The list ID returned by create_list. This identifies which list of items to iterate over.", ), agent: z .enum(ENABLED_AGENTS as unknown as [string, ...string[]]) .describe( `Which AI agent to use: ${ENABLED_AGENTS.map((a) => `'${a}'`).join(", ")}. All agents run with permission-skipping flags for autonomous operation.`, ), prompt: z .string() .describe( "The prompt to send to each agent. Use {{item}} as a placeholder - it will be replaced with the current item value. Example: 'Review {{item}} and suggest improvements' or 'Add error handling to {{item}}'", ), },
- src/index.ts:616-778 (registration)Registration of the tool with McpServer.registerTool, including description, inputSchema, and handler. Conditionally registered inside if (ENABLED_AGENTS.length > 0) block (602-779). Agents enabled unless PAR5_DISABLE_CLUDE/PAR5_DISABLE_GEMINI/PAR5_DISABLE_CODEX env vars are set.server.registerTool( "run_agent_across_list", { description: `Spawns an AI coding agent for each item in a previously created list. Agents run in batches of ${BATCH_SIZE} parallel processes with automatic permission skipping enabled. WHEN TO USE: - Performing complex code analysis, refactoring, or generation across multiple files - Tasks that require AI reasoning rather than simple shell commands - When you need to delegate work to multiple AI agents working in parallel AVAILABLE AGENTS: ${availableAgentsDoc} HOW IT WORKS: 1. Each item in the list is substituted into the prompt where {{item}} appears 2. Agents run in batches of ${BATCH_SIZE} at a time to avoid overwhelming the system 3. Each agent has a 5-minute timeout 4. Output streams directly to files as the agents work 5. This tool waits for all agents to complete before returning AFTER COMPLETION: - Read the stdout files to check the results from each agent - Check stderr files if you encounter errors - Files are named based on the item (e.g., "myfile.ts.stdout.txt") VARIABLE SUBSTITUTION: - Use {{item}} in your prompt - it will be replaced with each list item - Example: "Review {{item}} for bugs" becomes "Review src/file.ts for bugs" for item "src/file.ts"`, inputSchema: { list_id: z .string() .describe( "The list ID returned by create_list. This identifies which list of items to iterate over.", ), agent: z .enum(ENABLED_AGENTS as unknown as [string, ...string[]]) .describe( `Which AI agent to use: ${ENABLED_AGENTS.map((a) => `'${a}'`).join(", ")}. All agents run with permission-skipping flags for autonomous operation.`, ), prompt: z .string() .describe( "The prompt to send to each agent. Use {{item}} as a placeholder - it will be replaced with the current item value. Example: 'Review {{item}} and suggest improvements' or 'Add error handling to {{item}}'", ), }, }, async ({ list_id, agent, prompt }) => { const items = lists.get(list_id); if (!items) { return { content: [ { type: "text", text: `Error: No list found with ID "${list_id}". Please call create_list first to create a list of items, then use the returned ID with this tool.`, }, ], isError: true, }; } // Create output directory const runId = randomUUID(); const runDir = join(outputDir, runId); await mkdir(runDir, { recursive: true }); const results: Array<{ item: string; files: OutputFiles }> = []; const tasks: Array<{ command: string; stdoutFile: string; stderrFile: string; timeout: number; }> = []; // Build the agent command with skip permission flags and streaming output // Additional args can be passed via PAR5_AGENT_ARGS (all agents) or PAR5_CLAUDE_ARGS, PAR5_GEMINI_ARGS, PAR5_CODEX_ARGS (per-agent) const getAgentCommand = ( agentName: string, expandedPrompt: string, ): string => { const escapedPrompt = expandedPrompt.replace(/'/g, "'\\''"); const agentArgs = process.env.PAR5_AGENT_ARGS || ""; switch (agentName) { case "claude": { // Claude Code CLI with --dangerously-skip-permissions and streaming output const claudeArgs = process.env.PAR5_CLAUDE_ARGS || ""; return `claude --dangerously-skip-permissions --output-format stream-json --verbose ${agentArgs} ${claudeArgs} -p '${escapedPrompt}'`; } case "gemini": { // Gemini CLI with yolo mode and streaming JSON output const geminiArgs = process.env.PAR5_GEMINI_ARGS || ""; return `gemini --yolo --output-format stream-json ${agentArgs} ${geminiArgs} '${escapedPrompt}'`; } case "codex": { // Codex CLI exec subcommand with full-auto flag and JSON streaming output const codexArgs = process.env.PAR5_CODEX_ARGS || ""; return `codex exec --dangerously-bypass-approvals-and-sandbox ${agentArgs} ${codexArgs} '${escapedPrompt}'`; } default: throw new Error(`Unknown agent: ${agentName}`); } }; for (let i = 0; i < items.length; i++) { const item = items[i]; // Replace {{item}} with the actual item value const expandedPrompt = prompt.replace(/\{\{item\}\}/g, item); const safeFilename = toSafeFilename(item); const stdoutFile = join(runDir, `${safeFilename}.stdout.txt`); const stderrFile = join(runDir, `${safeFilename}.stderr.txt`); tasks.push({ command: getAgentCommand(agent, expandedPrompt), stdoutFile, stderrFile, timeout: 300000, // 5 minute timeout per item }); results.push({ item, files: { stdout: stdoutFile, stderr: stderrFile }, }); } // Run agents in batches of 10 await runInBatches(tasks); // Build prose response const fileList = results .map( (r) => `- ${r.item}: stdout at "${r.files.stdout}", stderr at "${r.files.stderr}"`, ) .join("\n"); const agentNames: Record<string, string> = { claude: "Claude Code", gemini: "Google Gemini", codex: "OpenAI Codex", }; const numBatches = Math.ceil(items.length / BATCH_SIZE); return { content: [ { type: "text", text: `Completed ${results.length} ${agentNames[agent]} agents in ${numBatches} batch(es) of up to ${BATCH_SIZE} parallel agents each. Output has been streamed to files. OUTPUT FILES: ${fileList} NEXT STEPS: 1. Read the stdout files to check the results from each agent 2. If there are errors, check the corresponding stderr files for details All agents have completed (with a 5-minute timeout per agent) and output files are ready to read.`, }, ], }; }, );
- src/index.ts:114-132 (helper)Helper function to execute command tasks in configurable batches (default BATCH_SIZE=10) using Promise.all for parallelism, calling runCommandToFiles for each.async function runInBatches( tasks: Array<{ command: string; stdoutFile: string; stderrFile: string; timeout?: number; }>, ): Promise<void> { for (let i = 0; i < tasks.length; i += BATCH_SIZE) { const batch = tasks.slice(i, i + BATCH_SIZE); await Promise.all( batch.map((task) => runCommandToFiles(task.command, task.stdoutFile, task.stderrFile, { timeout: task.timeout, }), ), ); } }