How Goose Planning Mode Creates Structured Step-by-Step Plans
Goose planning mode converts informal user requests into machine-readable, numbered plans by using a dedicated reasoner model that analyzes available tools, renders a specialized system prompt, and classifies the output as either an actionable plan or clarifying questions.
The planning mode in the block/goose repository provides a structured workflow for breaking down complex tasks before execution. By invoking the /plan slash command, users trigger a stateless three-stage pipeline that generates a step-by-step roadmap referencing available tools, which can then be executed automatically or refined through further conversation.
The Three-Stage Planning Pipeline
Stage 1: Initiating Plan Mode
When a user types /plan followed by an optional prompt, the CLI immediately transitions into planning mode. In crates/goose-cli/src/session/mod.rs at lines 63-71, the Session::handle_plan_mode method sets self.run_mode = RunMode::Plan and prints a UI banner via output::render_enter_plan_mode.
This stage establishes a clean context for planning. The optional text following the /plan command is preserved as the initial user message, while the session prepares to invoke a reasoner model rather than the standard execution agent.
Stage 2: Generating the Structured Plan
The core planning logic resides in the agent layer. The Agent::get_plan_prompt method defined in crates/goose/src/agents/agent.rs (lines 8-30) orchestrates the collection of tool metadata and prompt rendering.
First, ExtensionManager::get_prefixed_tools retrieves a list of ToolInfo structs containing each tool’s name, description, and parameter names. This metadata is passed to ExtensionManager::get_planning_prompt in crates/goose/src/agents/extension_manager.rs at lines 73-79, which constructs a HashMap with a single entry "tools" mapped to the JSON-encoded tool list.
The template engine then renders the system prompt. In crates/goose/src/prompt_template.rs at lines 85-111, the render_template function loads the built-in plan.md template and interpolates the tool context. The resulting string becomes the system prompt for the reasoner model.
Finally, Session::plan_with_reasoner_model invokes the selected Provider’s complete method with the rendered prompt and user messages. The model returns a single Message containing either a numbered plan or a bullet list of clarifying questions.
Stage 3: Classifying and Executing the Response
Once the reasoner generates output, the system must determine whether the user can act immediately or needs to provide more information. In crates/goose-cli/src/session/mod.rs at lines 200-232, the classify_planner_response function sends the raw model text to a lightweight classifier LLM with a prompt asking it to categorize the output as either "plan" or "clarifying questions".
The classifier returns a case-insensitive string that maps to PlannerResponseType::Plan or PlannerResponseType::ClarifyingQuestions.
If classified as a plan:
The CLI prompts the user with cliclack::confirm asking whether to clear history and act on the plan. Upon confirmation:
- The current
self.messagesbuffer is cleared - The generated plan is inserted as a user message using
Message::user().with_text(plan) process_agent_responseis invoked in normal run mode, causing the execution AI to follow the plan step-by-step
If the user declines, the plan is appended as an assistant message, allowing iterative refinement.
If classified as clarifying questions:
The question list is appended as an assistant message, pausing the workflow until the user provides answers.
Practical Usage Examples
Interactive CLI Workflow
Trigger planning mode by typing the slash command followed by your objective:
> /plan Summarize the latest 5 GitHub issues and email the summary.
The terminal displays:
Entering plan mode. You can provide instructions to create a plan...
After the model generates the plan, Goose asks:
Do you want to clear message history & act on this plan? (Y/n)
Selecting Y clears the conversation history, injects the plan as a user message, and begins execution immediately. Selecting n preserves the plan as an assistant message for editing.
Programmatic Implementation in Rust
You can invoke the planning pipeline programmatically using the internal APIs:
use goose::agents::Agent;
use goose::providers::Provider;
use anyhow::Result;
async fn create_plan(agent: &Agent, session_id: &str) -> Result<String> {
// Retrieve the planning prompt with tool context
let plan_prompt = agent.get_plan_prompt(session_id).await?;
// Initialize the reasoner provider
let provider: Arc<dyn Provider> = agent.provider().await?;
let model_cfg = provider.get_model_config();
// Generate the plan
let (resp, _) = provider.complete(
&model_cfg,
session_id,
&plan_prompt,
&[goose::Message::user().with_text("Create a deployment script.")],
&[],
).await?;
// Classify the response type
let resp_type = goose_cli::session::classify_planner_response(
session_id,
resp.as_concat_text(),
provider.clone(),
).await?;
// Return formatted results
Ok(match resp_type {
goose_cli::session::PlannerResponseType::Plan => resp.as_concat_text(),
goose_cli::session::PlannerResponseType::ClarifyingQuestions => {
format!("Need clarification:\n{}", resp.as_concat_text())
}
})
}
This mirrors the internal workflow: fetch prompt → invoke reasoner → classify output.
Customizing the Plan Template
The default system prompt lives in crates/goose/src/prompts/plan.md. To override it, create a user-specific version:
mkdir -p ~/.config/goose/prompts
cat > ~/.config/goose/prompts/plan.md <<'EOF'
You are a planner. Generate a JSON array where each element is:
{ "step": <number>, "instruction": "<text>" }
Include any required tool calls as `"tool": "<tool_name>"`.
EOF
Goose automatically loads the user override instead of the built-in template when prompt_template::render_template is called.
Summary
- Goose planning mode uses a three-stage pipeline to transform informal requests into structured, numbered plans.
- The workflow begins with the
/planslash command, handled incrates/goose-cli/src/session/mod.rs, which sets the session toRunMode::Plan. - Tool metadata is collected via
Agent::get_plan_promptand serialized as JSON for the reasoner model. - The plan.md template in
crates/goose/src/prompt_template.rsreceives the tool list and generates the system prompt. - A classifier LLM in
classify_planner_responsedistinguishes between executable plans and clarifying questions. - Confirmed plans are injected as user messages and executed via
process_agent_response, while questions are appended as assistant messages for user response.
Frequently Asked Questions
What file handles the initial /plan command parsing?
The slash command is processed in crates/goose-cli/src/session/mod.rs at lines 63-71, where Session::handle_plan_mode sets the run mode and renders the entry banner.
How does Goose know which tools are available during planning?
The ExtensionManager::get_prefixed_tools method collects ToolInfo structs containing tool names, descriptions, and parameters. This list is JSON-encoded and passed to the plan.md template via ExtensionManager::get_planning_prompt in crates/goose/src/agents/extension_manager.rs.
Can I customize the planning system prompt?
Yes. Create a file at ~/.config/goose/prompts/plan.md to override the built-in template. The prompt_template::render_template function automatically prefers user-defined templates over the defaults packaged in crates/goose/src/prompts/plan.md.
What happens if the model returns questions instead of a plan?
The classify_planner_response function in crates/goose-cli/src/session/mod.rs (lines 200-232) detects clarifying questions using a secondary LLM classifier. These questions are appended as an assistant message, allowing the user to provide answers before regenerating the plan.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →