Agent Planning Subagent Tool
Overview¶
UsePlanningSubagent exposes planning as a tool (plan_with_subagent) that the parent
agent can call before implementation. The parent generates a task specification, the planner
subagent can use an isolated tool set, and it returns a dense markdown plan back to the
parent for execution.
This pattern provides:
- Planner isolation: planning runs in a separate subagent context
- Tool scoping: planner tools can differ from the parent tool set
- No recursion: planner toolset automatically removes
spawn_subagentandplan_with_subagent - Prompt-level guidance: capability appends instructions describing required specification sections
Key concepts:
- UsePlanningSubagent: installs plan_with_subagent and planning instructions
- parentInstructions: system prompt fragment telling the parent when/how to call planner
- parent tool constraints: listed in system prompt so plans stay executable
- plannerSystemPrompt: specialist prompt for the planning subagent
- plannerTools: optional allowlist of tools available to planner subagent
- plannerAdditionalTools: planner-only tools not available to parent execution
- plannerBudget: optional guard budget for the planner execution
Example¶
<?php
require 'examples/boot.php';
use Cognesy\Agents\Builder\AgentBuilder;
use Cognesy\Agents\Capability\Bash\BashTool;
use Cognesy\Agents\Capability\Core\UseGuards;
use Cognesy\Agents\Capability\Core\UseTools;
use Cognesy\Agents\Capability\File\ListDirTool;
use Cognesy\Agents\Capability\File\ReadFileTool;
use Cognesy\Agents\Capability\File\SearchFilesTool;
use Cognesy\Agents\Capability\PlanningSubagent\UsePlanningSubagent;
use Cognesy\Agents\Collections\NameList;
use Cognesy\Agents\Collections\Tools;
use Cognesy\Agents\Data\AgentState;
use Cognesy\Agents\Data\ExecutionBudget;
use Cognesy\Agents\Enums\ExecutionStatus;
use Cognesy\Agents\Events\Support\AgentEventConsoleObserver;
use Cognesy\Messages\Messages;
$logger = new AgentEventConsoleObserver(
useColors: true,
showTimestamps: true,
showContinuation: true,
showToolArgs: true,
);
$workDir = dirname(__DIR__, 3);
$parentExecutionTools = ['read_file'];
$parentToolsList = implode(', ', $parentExecutionTools);
$agent = AgentBuilder::base()
->withCapability(new UseTools(
ReadFileTool::inDirectory($workDir),
))
->withCapability(new UsePlanningSubagent(
parentInstructions: <<<PROMPT
For tasks that involve multiple implementation steps, call `plan_with_subagent` first.
Parent execution tools available (hard constraint): {$parentToolsList}
When you execute after planning, use only the parent execution tools listed above.
When calling `plan_with_subagent`, provide a task specification with these sections:
- Goal
- Context
- Constraints
- Expected Outcomes
- Acceptance Criteria
PROMPT,
plannerSystemPrompt: <<<PROMPT
You are a planning specialist.
Create a dense markdown plan with explicit checkpoints and tool-aware execution steps.
Use discovery tools (`bash`, `search_files`, `list_dir`) to inspect local context if needed.
Use `bash` with `rg` for fast discovery when helpful.
Do not implement changes.
Parent execution tools (must be respected by the final plan): {$parentToolsList}
All execution steps in the final plan must be feasible using only those parent tools.
PROMPT,
plannerTools: new NameList('bash', 'search_files', 'list_dir', 'read_file'),
plannerAdditionalTools: new Tools(
BashTool::inDirectory($workDir),
SearchFilesTool::inDirectory($workDir),
ListDirTool::inDirectory($workDir),
),
plannerBudget: new ExecutionBudget(maxSteps: 3, maxTokens: 3072, maxSeconds: 30),
))
->withCapability(new UseGuards(maxSteps: 8, maxTokens: 12288, maxExecutionTime: 90))
->build()
->wiretap($logger->wiretap());
$task = <<<'TASK'
Prepare a realistic plan for a small docs update about planning capability in this repository.
First, create a plan.
Then execute only the analysis part of the plan and provide a concise proposal:
1. Capability purpose section outline
2. Configuration options outline
3. Example usage outline
Constraints:
- planner subagent may use bash/search for discovery
- parent execution may use only read_file
- do not modify any files
- keep the final response under 180 words
TASK;
$state = AgentState::empty()->withMessages(
Messages::fromString($task)
);
echo "=== Agent Execution Log ===\n";
echo "Task: Plan first, then execute\n\n";
$finalState = $agent->execute($state);
echo "\n=== Result ===\n";
$answer = $finalState->finalResponse()->toString() ?: 'No answer';
echo "Answer: {$answer}\n";
echo "Steps: {$finalState->stepCount()}\n";
echo "Tokens: {$finalState->usage()->total()}\n";
echo "Status: {$finalState->status()->value}\n";
if ($finalState->status()->value === 'failed') {
echo "Skipping assertions because execution status is failed.\n";
exit(1);
}
$hasAnswer = trim($finalState->finalResponse()->toString()) !== '';
$isStopped = $finalState->status() === ExecutionStatus::Stopped;
assert($hasAnswer || $isStopped, 'Expected non-empty response or stopped status');
assert($finalState->stepCount() >= 1, 'Expected at least 1 step');
assert($finalState->usage()->total() > 0, 'Expected token usage > 0');
?>