feat: add --orchestration startup flag to gate spawn tool

Orchestration (subagent spawning) is now opt-in rather than always-on.
The spawn tool is only registered when orchestration is explicitly enabled,
preventing the LLM from having a tool it has no guidance to use.

Changes:
- config: SubagentsConfig.Enabled bool (opt-in per agent via JSON config)
- config: AgentDefaults.Orchestration bool (env PICOCLAW_AGENTS_DEFAULTS_ORCHESTRATION)
- agent/instance: apply defaults.Orchestration to agent.Subagents at construction
- agent/loop: gate spawn tool registration on agent.Subagents.Enabled
- cmd/agent: --orchestration flag sets cfg.Agents.Defaults.Orchestration = true

Usage: picoclaw agent --orchestration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-25 01:54:49 +09:00
parent 55d3a08fcd
commit bfabdea8b7
4 changed files with 29 additions and 9 deletions

View file

@ -24,6 +24,7 @@ func agentCmd() {
message := "" message := ""
sessionKey := "cli:default" sessionKey := "cli:default"
modelOverride := "" modelOverride := ""
orchestrationEnabled := false
args := os.Args[2:] args := os.Args[2:]
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
@ -46,6 +47,8 @@ func agentCmd() {
modelOverride = args[i+1] modelOverride = args[i+1]
i++ i++
} }
case "--orchestration":
orchestrationEnabled = true
} }
} }
@ -59,6 +62,10 @@ func agentCmd() {
cfg.Agents.Defaults.Model = modelOverride cfg.Agents.Defaults.Model = modelOverride
} }
if orchestrationEnabled {
cfg.Agents.Defaults.Orchestration = true
}
provider, modelID, err := providers.CreateProvider(cfg) provider, modelID, err := providers.CreateProvider(cfg)
if err != nil { if err != nil {
fmt.Printf("Error creating provider: %v\n", err) fmt.Printf("Error creating provider: %v\n", err)

View file

@ -91,6 +91,15 @@ func NewAgentInstance(
skillsFilter = agentCfg.Skills skillsFilter = agentCfg.Skills
} }
// Apply defaults.Orchestration: if the flag is set, ensure orchestration is enabled.
if defaults.Orchestration {
if subagents == nil {
subagents = &config.SubagentsConfig{Enabled: true}
} else {
subagents.Enabled = true
}
}
maxIter := defaults.MaxToolIterations maxIter := defaults.MaxToolIterations
if maxIter == 0 { if maxIter == 0 {
maxIter = 20 maxIter = 20

View file

@ -216,15 +216,17 @@ func registerSharedTools(
agent.Tools.Register(tools.NewFindSkillsTool(registryMgr, searchCache)) agent.Tools.Register(tools.NewFindSkillsTool(registryMgr, searchCache))
agent.Tools.Register(tools.NewInstallSkillTool(registryMgr, agent.Workspace)) agent.Tools.Register(tools.NewInstallSkillTool(registryMgr, agent.Workspace))
// Spawn tool with allowlist checker // Spawn tool — only registered when orchestration is explicitly enabled.
subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace, msgBus) if agent.Subagents != nil && agent.Subagents.Enabled {
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature) subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace, msgBus)
spawnTool := tools.NewSpawnTool(subagentManager) subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature)
currentAgentID := agentID spawnTool := tools.NewSpawnTool(subagentManager)
spawnTool.SetAllowlistChecker(func(targetAgentID string) bool { currentAgentID := agentID
return registry.CanSpawnSubagent(currentAgentID, targetAgentID) spawnTool.SetAllowlistChecker(func(targetAgentID string) bool {
}) return registry.CanSpawnSubagent(currentAgentID, targetAgentID)
agent.Tools.Register(spawnTool) })
agent.Tools.Register(spawnTool)
}
// Update context builder with the complete tools registry // Update context builder with the complete tools registry
agent.ContextBuilder.SetToolsRegistry(agent.Tools) agent.ContextBuilder.SetToolsRegistry(agent.Tools)

View file

@ -141,6 +141,7 @@ type AgentConfig struct {
} }
type SubagentsConfig struct { type SubagentsConfig struct {
Enabled bool `json:"enabled,omitempty"`
AllowAgents []string `json:"allow_agents,omitempty"` AllowAgents []string `json:"allow_agents,omitempty"`
Model *AgentModelConfig `json:"model,omitempty"` Model *AgentModelConfig `json:"model,omitempty"`
} }
@ -182,6 +183,7 @@ type AgentDefaults struct {
Temperature *float64 `json:"temperature,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"` Temperature *float64 `json:"temperature,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
TaskReminderInterval int `json:"task_reminder_interval" env:"PICOCLAW_AGENTS_DEFAULTS_TASK_REMINDER_INTERVAL"` TaskReminderInterval int `json:"task_reminder_interval" env:"PICOCLAW_AGENTS_DEFAULTS_TASK_REMINDER_INTERVAL"`
Orchestration bool `json:"orchestration,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_ORCHESTRATION"`
} }
type ChannelsConfig struct { type ChannelsConfig struct {