merge: upstream/dev into fix/toolcall-format

Resolved conflict in dynamic-agent-prompt-builder.ts by keeping both
buildToolCallFormatSection() and buildNonClaudePlannerSection() functions.
This commit is contained in:
acamq
2026-03-02 07:48:46 -07:00
172 changed files with 5662 additions and 507 deletions
+11 -11
View File
@@ -1,6 +1,6 @@
# src/agents/ — 11 Agent Definitions
**Generated:** 2026-02-24
**Generated:** 2026-03-02
## OVERVIEW
@@ -10,16 +10,16 @@ Agent factories following `createXXXAgent(model) → AgentConfig` pattern. Each
| Agent | Model | Temp | Mode | Fallback Chain | Purpose |
|-------|-------|------|------|----------------|---------|
| **Sisyphus** | claude-opus-4-6 | 0.1 | primary | kimi-k2.5 → glm-4.7 → gemini-3-pro | Main orchestrator, plans + delegates |
| **Hephaestus** | gpt-5.3-codex | 0.1 | primary | NONE (required) | Autonomous deep worker |
| **Oracle** | gpt-5.2 | 0.1 | subagent | claude-opus-4-6 → gemini-3-pro | Read-only consultation |
| **Librarian** | glm-4.7 | 0.1 | subagent | big-pickle → claude-sonnet-4-6 | External docs/code search |
| **Explore** | grok-code-fast-1 | 0.1 | subagent | claude-haiku-4-5 → gpt-5-nano | Contextual grep |
| **Multimodal-Looker** | gemini-3-flash | 0.1 | subagent | gpt-5.2 → glm-4.6v → ... (6 deep) | PDF/image analysis |
| **Metis** | claude-opus-4-6 | **0.3** | subagent | kimi-k2.5 → gpt-5.2 → gemini-3-pro | Pre-planning consultant |
| **Momus** | gpt-5.2 | 0.1 | subagent | claude-opus-4-6 → gemini-3-pro | Plan reviewer |
| **Atlas** | claude-sonnet-4-6 | 0.1 | primary | kimi-k2.5 → gpt-5.2 → gemini-3-pro | Todo-list orchestrator |
| **Prometheus** | claude-opus-4-6 | 0.1 | — | kimi-k2.5 → gpt-5.2 → gemini-3-pro | Strategic planner (internal) |
| **Sisyphus** | claude-opus-4-6 | 0.1 | all | kimi-k2.5 → glm-5 → big-pickle | Main orchestrator, plans + delegates |
| **Hephaestus** | gpt-5.3-codex | 0.1 | all | gpt-5.2 (copilot) | Autonomous deep worker |
| **Oracle** | gpt-5.2 | 0.1 | subagent | gemini-3.1-pro → claude-opus-4-6 | Read-only consultation |
| **Librarian** | kimi-k2.5 | 0.1 | subagent | gemini-3-flash → gpt-5.2 → glm-4.6v | External docs/code search |
| **Explore** | grok-code-fast-1 | 0.1 | subagent | minimax-m2.5 → claude-haiku-4-5 → gpt-5-nano | Contextual grep |
| **Multimodal-Looker** | gemini-3-flash | 0.1 | subagent | minimax-m2.5 → big-pickle | PDF/image analysis |
| **Metis** | claude-opus-4-6 | **0.3** | subagent | gpt-5.2 → kimi-k2.5 → gemini-3.1-pro | Pre-planning consultant |
| **Momus** | gpt-5.2 | 0.1 | subagent | claude-opus-4-6 → gemini-3.1-pro | Plan reviewer |
| **Atlas** | kimi-k2.5 | 0.1 | primary | claude-sonnet-4-6 → gpt-5.2 | Todo-list orchestrator |
| **Prometheus** | claude-opus-4-6 | 0.1 | — | kimi-k2.5 → gpt-5.2 → gemini-3.1-pro | Strategic planner (internal) |
| **Sisyphus-Junior** | claude-sonnet-4-6 | 0.1 | all | user-configurable | Category-spawned executor |
## TOOL RESTRICTIONS
@@ -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("")
})
})
+30 -14
View File
@@ -277,12 +277,11 @@ Briefly announce "Consulting Oracle for [reason]" before invocation.
### Oracle Background Task Policy:
**You MUST collect Oracle results before your final answer. No exceptions.**
**Collect Oracle results before your final answer. No exceptions.**
- Oracle may take several minutes. This is normal and expected.
- When Oracle is running and you finish your own exploration/analysis, your next action is \`background_output(task_id="...")\` on Oracle — NOT delivering a final answer.
- Oracle catches blind spots you cannot see — its value is HIGHEST when you think you don't need it.
- **NEVER** cancel Oracle. **NEVER** use \`background_cancel(all=true)\` when Oracle is running. Cancel disposable tasks (explore, librarian) individually by taskId instead.
- Oracle takes minutes. When done with your own work: **end your response** — wait for the \`<system-reminder>\`.
- Do NOT poll \`background_output\` on a running Oracle. The notification will come.
- Never cancel Oracle.
</Oracle_Usage>`
}
@@ -292,8 +291,8 @@ export function buildHardBlocksSection(): string {
"- Commit without explicit request — **Never**",
"- Speculate about unread code — **Never**",
"- Leave code in broken state after failures — **Never**",
"- `background_cancel(all=true)` when Oracle is running — **Never.** Cancel tasks individually by taskId.",
"- Delivering final answer before collecting Oracle result — **Never.** Always `background_output` Oracle first.",
"- `background_cancel(all=true)` — **Never.** Always cancel individually by taskId.",
"- Delivering final answer before collecting Oracle result — **Never.**",
]
return `## Hard Blocks (NEVER violate)
@@ -308,8 +307,8 @@ export function buildAntiPatternsSection(): string {
"- **Testing**: Deleting failing tests to \"pass\"",
"- **Search**: Firing agents for single-line typos or obvious syntax errors",
"- **Debugging**: Shotgun debugging, random changes",
"- **Background Tasks**: `background_cancel(all=true)` — always cancel individually by taskId",
"- **Oracle**: Skipping Oracle results when Oracle was launched — ALWAYS collect via `background_output`",
"- **Background Tasks**: Polling `background_output` on running tasks — end response and wait for notification",
"- **Oracle**: Delivering answer without collecting Oracle results",
]
return `## Anti-Patterns (BLOCKING violations)
@@ -334,6 +333,22 @@ When you need to call a tool:
Your tool calls are processed automatically. Just invoke the tool - do not format the call yourself.`
}
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')
@@ -342,12 +357,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(
+130
View File
@@ -39,6 +39,136 @@ Then ACTUALLY CALL those tools using the JSON tool schema. Produce the tool_use
</TOOL_CALL_MANDATE>`;
}
export function buildGeminiToolGuide(): string {
return `<GEMINI_TOOL_GUIDE>
## Tool Usage Guide — WHEN and HOW to Call Each Tool
You have access to tools via function calling. This guide defines WHEN to call each one.
**Violating these patterns = failed response.**
### Reading & Search (ALWAYS parallelizable — call multiple simultaneously)
| Tool | When to Call | Parallel? |
|---|---|---|
| \`Read\` | Before making ANY claim about file contents. Before editing any file. | Yes — read multiple files at once |
| \`Grep\` | Finding patterns, imports, usages across codebase. BEFORE claiming "X is used in Y". | ✅ Yes — run multiple greps at once |
| \`Glob\` | Finding files by name/extension pattern. BEFORE claiming "file X exists". | ✅ Yes — run multiple globs at once |
| \`AstGrepSearch\` | Finding code patterns with AST awareness (structural matches). | ✅ Yes |
### Code Intelligence (parallelizable on different files)
| Tool | When to Call | Parallel? |
|---|---|---|
| \`LspDiagnostics\` | **AFTER EVERY edit.** BEFORE claiming task is done. MANDATORY. | ✅ Yes — different files |
| \`LspGotoDefinition\` | Finding where a symbol is defined. | ✅ Yes |
| \`LspFindReferences\` | Finding all usages of a symbol across workspace. | ✅ Yes |
| \`LspSymbols\` | Getting file outline or searching workspace symbols. | ✅ Yes |
### Editing (SEQUENTIAL — must Read first)
| Tool | When to Call | Parallel? |
|---|---|---|
| \`Edit\` | Modifying existing files. MUST Read file first to get LINE#ID anchors. | ❌ After Read |
| \`Write\` | Creating NEW files only. Or full file overwrite. | ❌ Sequential |
### Execution & Delegation
| Tool | When to Call | Parallel? |
|---|---|---|
| \`Bash\` | Running tests, builds, git commands. | ❌ Usually sequential |
| \`Task\` | ANY non-trivial implementation. Research via explore/librarian. | ✅ Fire multiple in background |
### Correct Sequences (MANDATORY — follow these exactly):
1. **Answer about code**: Read → (analyze) → Answer
2. **Edit code**: Read → Edit → LspDiagnostics → Report
3. **Find something**: Grep/Glob (parallel) → Read results → Report
4. **Implement feature**: Task(delegate) → Verify results → Report
5. **Debug**: Read error → Read file → Grep related → Fix → LspDiagnostics
### PARALLEL RULES:
- **Independent reads/searches**: ALWAYS call simultaneously in ONE response
- **Dependent operations**: Call sequentially (Edit AFTER Read, LspDiagnostics AFTER Edit)
- **Background agents**: ALWAYS \`run_in_background=true\`, continue working
</GEMINI_TOOL_GUIDE>`;
}
export function buildGeminiToolCallExamples(): string {
return `<GEMINI_TOOL_CALL_EXAMPLES>
## Correct Tool Calling Patterns — Follow These Examples
### Example 1: User asks about code → Read FIRST, then answer
**User**: "How does the auth middleware work?"
**CORRECT**:
\`\`\`
→ Call Read(filePath="/src/middleware/auth.ts")
→ Call Read(filePath="/src/config/auth.ts") // parallel with above
→ (After reading) Answer based on ACTUAL file contents
\`\`\`
**WRONG**:
\`\`\`
→ "The auth middleware likely validates JWT tokens by..." ← HALLUCINATION. You didn't read the file.
\`\`\`
### Example 2: User asks to edit code → Read, Edit, Verify
**User**: "Fix the type error in user.ts"
**CORRECT**:
\`\`\`
→ Call Read(filePath="/src/models/user.ts")
→ Call LspDiagnostics(filePath="/src/models/user.ts") // parallel with Read
→ (After reading) Call Edit with LINE#ID anchors
→ Call LspDiagnostics(filePath="/src/models/user.ts") // verify fix
→ Report: "Fixed. Diagnostics clean."
\`\`\`
**WRONG**:
\`\`\`
→ Call Edit without reading first ← No LINE#ID anchors = WILL FAIL
→ Skip LspDiagnostics after edit ← UNVERIFIED
\`\`\`
### Example 3: User asks to find something → Search in parallel
**User**: "Where is the database connection configured?"
**CORRECT**:
\`\`\`
→ Call Grep(pattern="database|connection|pool", path="/src") // fires simultaneously
→ Call Glob(pattern="**/*database*") // fires simultaneously
→ Call Glob(pattern="**/*db*") // fires simultaneously
→ (After results) Read the most relevant files
→ Report findings with file paths
\`\`\`
### Example 4: User asks to implement a feature → DELEGATE
**User**: "Add a new /health endpoint to the API"
**CORRECT**:
\`\`\`
→ Call Task(category="quick", load_skills=["typescript-programmer"], prompt="...")
→ (After agent completes) Read changed files to verify
→ Call LspDiagnostics on changed files
→ Report
\`\`\`
**WRONG**:
\`\`\`
→ Write the code yourself ← YOU ARE AN ORCHESTRATOR, NOT AN IMPLEMENTER
\`\`\`
### Example 5: Investigation ≠ Implementation
**User**: "Look into why the tests are failing"
**CORRECT**:
\`\`\`
→ Call Bash(command="npm test") // see actual failures
→ Call Read on failing test files
→ Call Read on source files under test
→ Report: "Tests fail because X. Root cause: Y. Proposed fix: Z."
→ STOP — wait for user to say "fix it"
\`\`\`
**WRONG**:
\`\`\`
→ Start editing source files immediately ← "look into" ≠ "fix"
\`\`\`
</GEMINI_TOOL_CALL_EXAMPLES>`;
}
export function buildGeminiDelegationOverride(): string {
return `<GEMINI_DELEGATION_OVERRIDE>
## DELEGATION IS MANDATORY — YOU ARE NOT AN IMPLEMENTER
+28 -10
View File
@@ -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])"
@@ -329,7 +333,7 @@ task(subagent_type="explore", run_in_background=true, load_skills=[], descriptio
// Reference Grep (external)
task(subagent_type="librarian", run_in_background=true, load_skills=[], description="Find JWT security docs", prompt="I'm implementing JWT auth and need current security best practices to choose token storage (httpOnly cookies vs localStorage) and set expiration policy. Find: OWASP auth guidelines, recommended token lifetimes, refresh token rotation strategies, common JWT vulnerabilities. Skip 'what is JWT' tutorials — production security guidance only.")
task(subagent_type="librarian", run_in_background=true, load_skills=[], description="Find Express auth patterns", prompt="I'm building Express auth middleware and need production-quality patterns to structure my middleware chain. Find how established Express apps (1000+ stars) handle: middleware ordering, token refresh, role-based access control, auth error propagation. Skip basic tutorials — I need battle-tested patterns with proper error handling.")
// Continue working immediately. Collect with background_output when needed.
// Continue working immediately. System notifies on completion — collect with background_output then.
// WRONG: Sequential or blocking
result = task(..., run_in_background=false) // Never wait synchronously for explore/librarian
@@ -337,10 +341,10 @@ result = task(..., run_in_background=false) // Never wait synchronously for exp
### Background Result Collection:
1. Launch parallel agents \u2192 receive task_ids
2. Continue immediate work (explore, librarian results)
3. When results needed: \`background_output(task_id="...")\`
4. **If Oracle is running**: STOP all other output. Follow Oracle Completion Protocol in <Oracle_Usage>.
5. Cleanup: Cancel disposable tasks (explore, librarian) individually via \`background_cancel(taskId="...")\`. Never use \`background_cancel(all=true)\`.
2. Continue immediate work
3. System sends \`<system-reminder>\` on each task completion — then call \`background_output(task_id="...")\`
4. Need results not yet ready? **End your response.** The notification will trigger your next turn.
5. Cleanup: Cancel disposable tasks individually via \`background_cancel(taskId="...")\`
### Search Stop Conditions
@@ -364,6 +368,8 @@ STOP searching when:
${categorySkillsGuide}
${nonClaudePlannerSection}
${deepParallelSection}
${delegationTable}
@@ -477,9 +483,8 @@ If verification fails:
3. Report: "Done. Note: found N pre-existing lint errors unrelated to my changes."
### Before Delivering Final Answer:
- **If Oracle is running**: STOP. Follow Oracle Completion Protocol in <Oracle_Usage>. Do NOT deliver any answer.
- Cancel disposable background tasks (explore, librarian) individually via \`background_cancel(taskId="...")\`.
- **Never use \`background_cancel(all=true)\`.**
- If Oracle is running: **end your response** and wait for the completion notification first.
- Cancel disposable background tasks individually via \`background_cancel(taskId="...")\`.
</Behavior_Instructions>
${oracleSection}
@@ -565,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(
"</intent_verbalization>",
`</intent_verbalization>\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(
"</tool_usage_rules>",
`</tool_usage_rules>\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 <Constraints> ensures they're in a high-attention zone.
prompt = prompt.replace(
"<Constraints>",
`${buildGeminiDelegationOverride()}\n\n${buildGeminiVerificationOverride()}\n\n<Constraints>`
);
}
const permission = {
+20 -6
View File
@@ -242,14 +242,28 @@ describe("createBuiltinAgents with model overrides", () => {
test("createBuiltinAgents excludes disabled skills from availableSkills", async () => {
// #given
const disabledSkills = new Set(["playwright"])
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set([
"anthropic/claude-opus-4-6",
"opencode/kimi-k2.5-free",
"zai-coding-plan/glm-5",
"opencode/big-pickle",
])
)
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined, undefined, disabledSkills)
try {
// #when
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined, undefined, disabledSkills)
// #then
expect(agents.sisyphus.prompt).not.toContain("playwright")
expect(agents.sisyphus.prompt).toContain("frontend-ui-ux")
expect(agents.sisyphus.prompt).toContain("git-master")
// #then
expect(agents.sisyphus.prompt).not.toContain("playwright")
expect(agents.sisyphus.prompt).toContain("frontend-ui-ux")
expect(agents.sisyphus.prompt).toContain("git-master")
} finally {
cacheSpy.mockRestore()
fetchSpy.mockRestore()
}
})
test("includes custom agents in orchestrator prompts when provided via config", async () => {