From 13d689cb3acd495f5c57b6b1b11a16f2b87fd3c7 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 28 Feb 2026 12:13:10 +0900 Subject: [PATCH] feat(agents): add Plan Agent dependency and strengthen Deep Parallel Delegation for non-Claude models Non-Claude models skip planning and under-parallelize. Two new sections injected only when model is not Claude: - Plan Agent Dependency: multi-step tasks MUST consult Plan Agent first, use session_id for follow-ups, ask aggressively when ambiguous - Deep Parallel Delegation (rewrite): explicit '4 units = 4 agents' pattern, each with clear GOAL + success criteria, all run_in_background --- .../dynamic-agent-prompt-builder.test.ts | 84 +++++++++++++++++++ src/agents/dynamic-agent-prompt-builder.ts | 27 ++++-- src/agents/sisyphus.ts | 23 ++++- 3 files changed, 127 insertions(+), 7 deletions(-) diff --git a/src/agents/dynamic-agent-prompt-builder.test.ts b/src/agents/dynamic-agent-prompt-builder.test.ts index f105542b7..8572e72eb 100644 --- a/src/agents/dynamic-agent-prompt-builder.test.ts +++ b/src/agents/dynamic-agent-prompt-builder.test.ts @@ -4,6 +4,8 @@ import { describe, it, expect } from "bun:test" import { buildCategorySkillsDelegationGuide, buildUltraworkSection, + buildDeepParallelSection, + buildNonClaudePlannerSection, type AvailableSkill, type AvailableCategory, type AvailableAgent, @@ -172,4 +174,86 @@ describe("buildUltraworkSection", () => { }) }) +describe("buildDeepParallelSection", () => { + const deepCategory: AvailableCategory = { name: "deep", description: "Autonomous problem-solving" } + const otherCategory: AvailableCategory = { name: "quick", description: "Trivial tasks" } + + it("#given non-Claude model with deep category #when building #then returns parallel delegation section", () => { + //#given + const model = "google/gemini-3-pro" + const categories = [deepCategory, otherCategory] + + //#when + const result = buildDeepParallelSection(model, categories) + + //#then + expect(result).toContain("Deep Parallel Delegation") + expect(result).toContain("EVERY independent unit") + expect(result).toContain("run_in_background=true") + expect(result).toContain("4 independent units") + }) + + it("#given Claude model #when building #then returns empty", () => { + //#given + const model = "anthropic/claude-opus-4-6" + const categories = [deepCategory] + + //#when + const result = buildDeepParallelSection(model, categories) + + //#then + expect(result).toBe("") + }) + + it("#given non-Claude model without deep category #when building #then returns empty", () => { + //#given + const model = "openai/gpt-5.2" + const categories = [otherCategory] + + //#when + const result = buildDeepParallelSection(model, categories) + + //#then + expect(result).toBe("") + }) +}) + +describe("buildNonClaudePlannerSection", () => { + it("#given non-Claude model #when building #then returns plan agent section", () => { + //#given + const model = "google/gemini-3-pro" + + //#when + const result = buildNonClaudePlannerSection(model) + + //#then + expect(result).toContain("Plan Agent") + expect(result).toContain("session_id") + expect(result).toContain("Multi-step") + }) + + it("#given Claude model #when building #then returns empty", () => { + //#given + const model = "anthropic/claude-sonnet-4-6" + + //#when + const result = buildNonClaudePlannerSection(model) + + //#then + expect(result).toBe("") + }) + + it("#given GPT model #when building #then returns plan agent section", () => { + //#given + const model = "openai/gpt-5.2" + + //#when + const result = buildNonClaudePlannerSection(model) + + //#then + expect(result).toContain("Plan Agent") + expect(result).not.toBe("") + }) +}) + diff --git a/src/agents/dynamic-agent-prompt-builder.ts b/src/agents/dynamic-agent-prompt-builder.ts index 79a6a17f5..f6e8cbc65 100644 --- a/src/agents/dynamic-agent-prompt-builder.ts +++ b/src/agents/dynamic-agent-prompt-builder.ts @@ -316,6 +316,22 @@ export function buildAntiPatternsSection(): string { ${patterns.join("\n")}` } +export function buildNonClaudePlannerSection(model: string): string { + const isNonClaude = !model.toLowerCase().includes('claude') + if (!isNonClaude) return "" + + return `### Plan Agent Dependency (Non-Claude) + +Multi-step task? **ALWAYS consult Plan Agent first.** Do NOT start implementation without a plan. + +- Single-file fix or trivial change → proceed directly +- Anything else (2+ steps, unclear scope, architecture) → \`task(subagent_type="plan", ...)\` FIRST +- Use \`session_id\` to resume the same Plan Agent — ask follow-up questions aggressively +- If ANY part of the task is ambiguous, ask Plan Agent before guessing + +Plan Agent returns a structured work breakdown with parallel execution opportunities. Follow it.` +} + export function buildDeepParallelSection(model: string, categories: AvailableCategory[]): string { const isNonClaude = !model.toLowerCase().includes('claude') const hasDeepCategory = categories.some(c => c.name === 'deep') @@ -324,12 +340,13 @@ export function buildDeepParallelSection(model: string, categories: AvailableCat return `### Deep Parallel Delegation -For implementation tasks, actively decompose and delegate to \`deep\` category agents in parallel. +Delegate EVERY independent unit to a \`deep\` agent in parallel (\`run_in_background=true\`). +If a task decomposes into 4 independent units, spawn 4 agents simultaneously — not 1 at a time. -1. Break the implementation into independent work units -2. Maximize parallel deep agents — spawn one per independent unit (\`run_in_background=true\`) -3. Give each agent a GOAL, not step-by-step instructions — deep agents explore and solve autonomously -4. Collect results, integrate, verify coherence` +1. Decompose the implementation into independent work units +2. Assign one \`deep\` agent per unit — all via \`run_in_background=true\` +3. Give each agent a clear GOAL with success criteria, not step-by-step instructions +4. Collect all results, integrate, verify coherence across units` } export function buildUltraworkSection( diff --git a/src/agents/sisyphus.ts b/src/agents/sisyphus.ts index 950df6b1c..042cec1a1 100644 --- a/src/agents/sisyphus.ts +++ b/src/agents/sisyphus.ts @@ -6,6 +6,8 @@ import { buildGeminiDelegationOverride, buildGeminiVerificationOverride, buildGeminiIntentGateEnforcement, + buildGeminiToolGuide, + buildGeminiToolCallExamples, } from "./sisyphus-gemini-overlays"; const MODE: AgentMode = "all"; @@ -32,6 +34,7 @@ import { buildHardBlocksSection, buildAntiPatternsSection, buildDeepParallelSection, + buildNonClaudePlannerSection, categorizeTools, } from "./dynamic-agent-prompt-builder"; @@ -170,6 +173,7 @@ function buildDynamicSisyphusPrompt( const hardBlocks = buildHardBlocksSection(); const antiPatterns = buildAntiPatternsSection(); const deepParallelSection = buildDeepParallelSection(model, availableCategories); + const nonClaudePlannerSection = buildNonClaudePlannerSection(model); const taskManagementSection = buildTaskManagementSection(useTaskSystem); const todoHookNote = useTaskSystem ? "YOUR TASK CREATION WOULD BE TRACKED BY HOOK([SYSTEM REMINDER - TASK CONTINUATION])" @@ -364,6 +368,8 @@ STOP searching when: ${categorySkillsGuide} +${nonClaudePlannerSection} + ${deepParallelSection} ${delegationTable} @@ -564,12 +570,25 @@ export function createSisyphusAgent( : buildDynamicSisyphusPrompt(model, [], tools, skills, categories, useTaskSystem); if (isGeminiModel(model)) { + // 1. Intent gate + tool mandate — early in prompt (after intent verbalization) prompt = prompt.replace( "", `\n\n${buildGeminiIntentGateEnforcement()}\n\n${buildGeminiToolMandate()}` ); - prompt += "\n" + buildGeminiDelegationOverride(); - prompt += "\n" + buildGeminiVerificationOverride(); + + // 2. Tool guide + examples — after tool_usage_rules (where tools are discussed) + prompt = prompt.replace( + "", + `\n\n${buildGeminiToolGuide()}\n\n${buildGeminiToolCallExamples()}` + ); + + // 3. Delegation + verification overrides — before Constraints (NOT at prompt end) + // Gemini suffers from lost-in-the-middle: content at prompt end gets weaker attention. + // Placing these before ensures they're in a high-attention zone. + prompt = prompt.replace( + "", + `${buildGeminiDelegationOverride()}\n\n${buildGeminiVerificationOverride()}\n\n` + ); } const permission = {