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
|
||||
|
||||
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:
|
||||
- Tasks can run in parallel or in the background
|
||||
- Multiple independent tasks can run simultaneously — spawn each one
|
||||
- You don't need the result to decide the next step
|
||||
- The operation is long-running (builds, fetches, analysis, file processing)
|
||||
### spawn (non-blocking) — default choice
|
||||
- Tasks that can run in parallel or in the background
|
||||
- Multiple independent tasks — spawn each one simultaneously
|
||||
- Long-running operations (implementation, test suites, analysis)
|
||||
- Any work that involves more than 2-3 tool calls
|
||||
|
||||
Use **subagent** (blocking) when:
|
||||
- You need the result before you can continue
|
||||
- Correctness of the next step depends on the outcome
|
||||
### subagent (blocking) — when you need the answer now
|
||||
- You need the result before deciding the next step
|
||||
- Correctness of your next action depends on the outcome
|
||||
|
||||
Do inline only when:
|
||||
- It's a single fast tool call (read a file, quick search)
|
||||
- Delegation overhead clearly outweighs the benefit
|
||||
### Inline — only for trivial operations
|
||||
- A single fast tool call (read one file, quick search)
|
||||
- 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.
|
||||
When you spawn, immediately plan what comes next — blocking means you've stopped thinking.
|
||||
Fork aggressively: explore multiple directions simultaneously.
|
||||
### Presets
|
||||
| preset | role | can write | can exec |
|
||||
|--------|------|-----------|----------|
|
||||
| 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.
|
||||
When results come back, synthesize and decide the next fork.`
|
||||
When results come back, synthesize findings and decide the next fork.`
|
||||
|
||||
type ContextBuilder struct {
|
||||
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 🦞
|
||||
|
||||
You are picoclaw, a helpful AI assistant.
|
||||
You are picoclaw, %s.
|
||||
|
||||
## Workspace
|
||||
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.
|
||||
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 "executing": Work through the current Phase's steps.
|
||||
Mark each "- [x]" via edit_file. The system will auto-advance phases.
|
||||
- If Status is "executing": %s
|
||||
- Plan format (header is written by the system — do NOT delete it):
|
||||
# Active Plan
|
||||
> Task: <description>
|
||||
|
|
@ -176,7 +209,7 @@ Your workspace is at: %s
|
|||
- 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.`,
|
||||
workspacePath, workspacePath, workspacePath, workspacePath, toolsSection)
|
||||
identity, workspacePath, workspacePath, workspacePath, workspacePath, toolsSection, executingRule)
|
||||
}
|
||||
|
||||
func (cb *ContextBuilder) buildToolsSection() string {
|
||||
|
|
|
|||
|
|
@ -302,6 +302,9 @@ func registerSharedTools(
|
|||
return registry.CanSpawnSubagent(currentAgentID, targetAgentID)
|
||||
})
|
||||
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
|
||||
|
|
@ -1259,6 +1262,18 @@ func buildPlanReminder(planStatus string) (providers.Message, bool) {
|
|||
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.
|
||||
// Group 1 captures the target directory path.
|
||||
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,
|
||||
// memory, plan status, etc. Update messages[0] so the next LLM
|
||||
// call sees the current state.
|
||||
|
|
|
|||
|
|
@ -630,6 +630,14 @@ func (ms *MemoryStore) getPlanContextFrom(content string) string {
|
|||
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()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue