fix: make LLM actually use spawn/subagent in orchestration mode
The LLM never called spawn even with --orchestration enabled due to five compounding issues: 1. Identity said "helpful assistant" instead of "conductor" — reframed conditionally when orchestration is active 2. Plan executing instruction said "work through steps" — replaced with "delegate steps via spawn" in orchestration mode 3. SubagentTool (blocking) was referenced in guidance but never registered — now wired up alongside SpawnTool 4. No iteration reminder to use spawn — added orchestration nudge that fires on iteration 1 and every 3rd iteration during plan execution 5. Guidance was abstract with no examples — added preset table and concrete spawn/subagent call examples 6. Orchestration section in MEMORY.md was dropped from plan context — now extracted via existing extractSection() All changes gated behind orchestrationEnabled / Subagents.Enabled. Non-orchestration mode is completely unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ff0a6362f5
commit
f96f535c78
3 changed files with 88 additions and 20 deletions
|
|
@ -19,28 +19,48 @@ import (
|
||||||
|
|
||||||
const orchestrationGuidance = `## Orchestration
|
const orchestrationGuidance = `## Orchestration
|
||||||
|
|
||||||
You are the conductor, not the performer. Prefer delegation over doing everything inline.
|
You are the conductor, not the performer. **Your primary job is to delegate, not to implement.**
|
||||||
|
|
||||||
Use **spawn** (non-blocking) when:
|
### spawn (non-blocking) — default choice
|
||||||
- Tasks can run in parallel or in the background
|
- Tasks that can run in parallel or in the background
|
||||||
- Multiple independent tasks can run simultaneously — spawn each one
|
- Multiple independent tasks — spawn each one simultaneously
|
||||||
- You don't need the result to decide the next step
|
- Long-running operations (implementation, test suites, analysis)
|
||||||
- The operation is long-running (builds, fetches, analysis, file processing)
|
- Any work that involves more than 2-3 tool calls
|
||||||
|
|
||||||
Use **subagent** (blocking) when:
|
### subagent (blocking) — when you need the answer now
|
||||||
- You need the result before you can continue
|
- You need the result before deciding the next step
|
||||||
- Correctness of the next step depends on the outcome
|
- Correctness of your next action depends on the outcome
|
||||||
|
|
||||||
Do inline only when:
|
### Inline — only for trivial operations
|
||||||
- It's a single fast tool call (read a file, quick search)
|
- A single fast tool call (read one file, quick search)
|
||||||
- Delegation overhead clearly outweighs the benefit
|
- Overhead of delegation clearly outweighs the benefit
|
||||||
|
|
||||||
Default bias: if a task involves more than 2-3 tool calls or can run independently, delegate it.
|
### Presets
|
||||||
When you spawn, immediately plan what comes next — blocking means you've stopped thinking.
|
| preset | role | can write | can exec |
|
||||||
Fork aggressively: explore multiple directions simultaneously.
|
|--------|------|-----------|----------|
|
||||||
|
| scout | explore, investigate | no | no |
|
||||||
|
| analyst | analyze, run tests | no | go test/vet, git |
|
||||||
|
| coder | implement + verify | yes (sandbox) | test/lint/fmt |
|
||||||
|
| worker | build + install | yes (sandbox) | build/package mgr |
|
||||||
|
| coordinator | orchestrate others | yes (sandbox) | general + spawn |
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Investigate code structure:
|
||||||
|
spawn(task: "Examine pkg/auth/ and report middleware pattern and entry points", preset: "scout", label: "auth-scout")
|
||||||
|
|
||||||
|
Implement a feature:
|
||||||
|
spawn(task: "Implement rate limiter in pkg/ratelimit/ with tests. Run go test to verify.", preset: "coder", label: "rate-limiter")
|
||||||
|
|
||||||
|
Get a blocking answer:
|
||||||
|
subagent(task: "Read pkg/config/config.go and list all SubagentsConfig fields", label: "config-check")
|
||||||
|
|
||||||
|
Parallel exploration:
|
||||||
|
spawn(task: "Analyze error handling patterns in pkg/providers/", preset: "analyst", label: "error-patterns")
|
||||||
|
spawn(task: "List all HTTP endpoints in pkg/miniapp/", preset: "scout", label: "endpoints")
|
||||||
|
|
||||||
After spawning, record the assignment in ## Orchestration > Delegated in MEMORY.md.
|
After spawning, record the assignment in ## Orchestration > Delegated in MEMORY.md.
|
||||||
When results come back, synthesize and decide the next fork.`
|
When results come back, synthesize findings and decide the next fork.`
|
||||||
|
|
||||||
type ContextBuilder struct {
|
type ContextBuilder struct {
|
||||||
workspace string
|
workspace string
|
||||||
|
|
@ -123,9 +143,23 @@ func (cb *ContextBuilder) getIdentity() string {
|
||||||
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
// Conditional identity and plan executing rule for orchestration mode
|
||||||
|
identity := "a helpful AI assistant"
|
||||||
|
executingRule := `Work through the current Phase's steps.
|
||||||
|
Mark each "- [x]" via edit_file. The system will auto-advance phases.`
|
||||||
|
if cb.orchestrationEnabled {
|
||||||
|
identity = "a conductor AI agent that orchestrates subagents"
|
||||||
|
executingRule = `Delegate the current Phase's steps to subagents using spawn.
|
||||||
|
For each step: spawn a subagent with the appropriate preset (scout for investigation,
|
||||||
|
coder for implementation, analyst for review). Spawn multiple independent steps in parallel.
|
||||||
|
When a subagent completes, mark "- [x]" via edit_file and record findings in
|
||||||
|
## Orchestration > Findings in MEMORY.md.
|
||||||
|
Only do a step inline if it's a single quick tool call (e.g., reading one file).`
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(prompt+`# picoclaw 🦞
|
return fmt.Sprintf(prompt+`# picoclaw 🦞
|
||||||
|
|
||||||
You are picoclaw, a helpful AI assistant.
|
You are picoclaw, %s.
|
||||||
|
|
||||||
## Workspace
|
## Workspace
|
||||||
Your workspace is at: %s
|
Your workspace is at: %s
|
||||||
|
|
@ -148,8 +182,7 @@ Your workspace is at: %s
|
||||||
After each answer, use edit_file to save findings to ## Context in memory/MEMORY.md.
|
After each answer, use edit_file to save findings to ## Context in memory/MEMORY.md.
|
||||||
When you have enough information, add ## Phase sections with "- [ ]" checkbox steps, and ## Commands section below the header. Then change > Status: to "review".
|
When you have enough information, add ## Phase sections with "- [ ]" checkbox steps, and ## Commands section below the header. Then change > Status: to "review".
|
||||||
- If Status is "review": The plan is awaiting user approval. Do NOT change Status yourself.
|
- If Status is "review": The plan is awaiting user approval. Do NOT change Status yourself.
|
||||||
- If Status is "executing": Work through the current Phase's steps.
|
- If Status is "executing": %s
|
||||||
Mark each "- [x]" via edit_file. The system will auto-advance phases.
|
|
||||||
- Plan format (header is written by the system — do NOT delete it):
|
- Plan format (header is written by the system — do NOT delete it):
|
||||||
# Active Plan
|
# Active Plan
|
||||||
> Task: <description>
|
> Task: <description>
|
||||||
|
|
@ -176,7 +209,7 @@ Your workspace is at: %s
|
||||||
- For architecture/flow, use arrow text: CLI → Pipeline → Adapters
|
- For architecture/flow, use arrow text: CLI → Pipeline → Adapters
|
||||||
|
|
||||||
5. **Context summaries** - Conversation summaries provided as context are approximate references only. They may be incomplete or outdated. Always defer to explicit user instructions over summary content.`,
|
5. **Context summaries** - Conversation summaries provided as context are approximate references only. They may be incomplete or outdated. Always defer to explicit user instructions over summary content.`,
|
||||||
workspacePath, workspacePath, workspacePath, workspacePath, toolsSection)
|
identity, workspacePath, workspacePath, workspacePath, workspacePath, toolsSection, executingRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb *ContextBuilder) buildToolsSection() string {
|
func (cb *ContextBuilder) buildToolsSection() string {
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,9 @@ func registerSharedTools(
|
||||||
return registry.CanSpawnSubagent(currentAgentID, targetAgentID)
|
return registry.CanSpawnSubagent(currentAgentID, targetAgentID)
|
||||||
})
|
})
|
||||||
agent.Tools.Register(spawnTool)
|
agent.Tools.Register(spawnTool)
|
||||||
|
// Register blocking subagent tool alongside spawn
|
||||||
|
subagentTool := tools.NewSubagentTool(subagentManager)
|
||||||
|
agent.Tools.Register(subagentTool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update context builder with the complete tools registry
|
// Update context builder with the complete tools registry
|
||||||
|
|
@ -1259,6 +1262,18 @@ func buildPlanReminder(planStatus string) (providers.Message, bool) {
|
||||||
return providers.Message{Role: "user", Content: content}, true
|
return providers.Message{Role: "user", Content: content}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildOrchReminder returns a reminder to use spawn/subagent during plan execution.
|
||||||
|
// Fires on first iteration and every 3rd iteration to reinforce delegation behavior.
|
||||||
|
func buildOrchReminder(iteration int) (providers.Message, bool) {
|
||||||
|
if iteration != 1 && iteration%3 != 0 {
|
||||||
|
return providers.Message{}, false
|
||||||
|
}
|
||||||
|
content := "[System] ORCHESTRATION mode active. Delegate plan steps to subagents using spawn (async) or subagent (blocking). " +
|
||||||
|
"Do NOT implement steps inline unless they are trivial single-tool-call tasks. " +
|
||||||
|
"Spawn multiple independent steps in parallel for maximum throughput."
|
||||||
|
return providers.Message{Role: "user", Content: content}, true
|
||||||
|
}
|
||||||
|
|
||||||
// cdPrefixPattern matches "cd /some/path && " at the start of a shell command.
|
// cdPrefixPattern matches "cd /some/path && " at the start of a shell command.
|
||||||
// Group 1 captures the target directory path.
|
// Group 1 captures the target directory path.
|
||||||
var cdPrefixPattern = regexp.MustCompile(`^cd\s+(\S+)\s*&&\s*`)
|
var cdPrefixPattern = regexp.MustCompile(`^cd\s+(\S+)\s*&&\s*`)
|
||||||
|
|
@ -2594,6 +2609,18 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject orchestration nudge during plan execution to encourage spawn usage.
|
||||||
|
if planSnapshot == "executing" && agent.Subagents != nil && agent.Subagents.Enabled {
|
||||||
|
if reminder, ok := buildOrchReminder(iteration); ok {
|
||||||
|
messages = append(messages, reminder)
|
||||||
|
logger.DebugCF("agent", "Injected orchestration nudge",
|
||||||
|
map[string]interface{}{
|
||||||
|
"agent_id": agent.ID,
|
||||||
|
"iteration": iteration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh system prompt: tool execution may have changed workDir,
|
// Refresh system prompt: tool execution may have changed workDir,
|
||||||
// memory, plan status, etc. Update messages[0] so the next LLM
|
// memory, plan status, etc. Update messages[0] so the next LLM
|
||||||
// call sees the current state.
|
// call sees the current state.
|
||||||
|
|
|
||||||
|
|
@ -630,6 +630,14 @@ func (ms *MemoryStore) getPlanContextFrom(content string) string {
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Orchestration section: conductor's delegation tracking (Delegated/Findings/Decisions)
|
||||||
|
orchContent := ms.extractSection(content, "Orchestration")
|
||||||
|
if orchContent != "" {
|
||||||
|
sb.WriteString("### Orchestration\n")
|
||||||
|
sb.WriteString(orchContent)
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue