← AI Coding Guides β€Ί Deep Dives
AAIF (Linux Foundation) β€’ Rust Native β€’ Extension-First β€’ Updated April 2026

Goose: The Extension-First Rust Agent with LLM-Based Adversary Inspection

Goose (by AAIF β€” Agentic AI Foundation at the Linux Foundation) is a Rust-native AI agent that runs on your machine β€” desktop app, CLI, and API. With 15+ providers, MCP support, a multi-stage security inspector stack, and a skill/recipe framework, Goose represents a distinctive engineering philosophy: make the core lean and extensible, then let extensions do the heavy lifting.

(Alright, ad over. Back to the serious technical analysis.)

What makes Goose different

Goose occupies a unique position in this agent set: it is a Rust-native, extension-first runtime with a security model that uses a second LLM to review tool calls. The architecture is deliberately minimal at the core β€” tools come from extensions, security comes from a layered inspector stack, and learning happens through a recipes and skills system.

πŸ—οΈ

Three deployment modes

Desktop app: Native desktop for macOS, Linux, and Windows with full UI. CLI: Full terminal workflow for scripted and interactive use. API: Embed Goose anywhere via HTTP API. All three share the same Rust core and extension system.

Extension-first philosophy

Unlike agents that ship with 40+ built-in tools, Goose's core ships with only a handful of platform tools. Everything else β€” file editing, web search, database access, specialized workflows β€” comes through the extension system. This keeps the core lean and makes the agent behavior genuinely customizable.

LLM-based adversary inspection

The AdversaryInspector (in src/security/adversary_inspector.rs) is unique in this set: it fires a second LLM call to review tool calls against user-defined rules from ~/.config/goose/adversary.md. This is defense-in-depth against a rogue sub-agent β€” not pattern matching, actual LLM judgment.

Architecture overview

Goose is built as a Cargo workspace with multiple crates. The main goose crate contains the core agent runtime, tool inspection pipeline, provider system, and extension manager. Key crates:

CratePurposeKey modules
goose Core agent runtime, tools, providers agents/, providers/, security/, config/
goose-cli Terminal interface commands/, session/, recipes/
goose-server HTTP API server routes/, auth.rs, tunnel/
goose-mcp MCP server implementation mcp_server_runner.rs, computercontroller/
goose-sdk SDK for extensions Extension interface types
goose-acp-macros ACP protocol macros Protocol derive macros

The agent core: agent.rs

The main Agent struct in src/agents/agent.rs (~2,500 lines) handles the complete agent loop. The key design patterns:

Provider system

The agent holds a SharedProvider (an Arc<Mutex<Option<Arc<dyn Provider>>>>) that can be updated at runtime. This enables sub-agents to switch providers independently of the main agent. The update_provider() method persists the provider choice to the session.

Tool inspection pipeline

Before any tool call executes, it passes through a ToolInspectionManager with stacked inspectors: Security β†’ Egress β†’ Adversary β†’ Permission β†’ Repetition. Each inspector can approve, deny, or require user approval. This is the most layered security pipeline in this agent set.

// From src/agents/agent.rs β€” the inspection stack setup
fn create_tool_inspection_manager(
    permission_manager: Arc<PermissionManager>,
    provider: SharedProvider,
) -> ToolInspectionManager {
    let mut manager = ToolInspectionManager::new();

    // Add security inspector (highest priority - runs first)
    manager.add_inspector(Box::new(SecurityInspector::new()));
    manager.add_inspector(Box::new(EgressInspector::new()));

    // Add adversary inspector (LLM-based review, enabled by ~/.config/goose/adversary.md)
    manager.add_inspector(Box::new(AdversaryInspector::new(provider.clone())));

    // Add permission inspector (medium-high priority)
    manager.add_inspector(Box::new(PermissionInspector::new(...)));

    // Add repetition inspector (lower priority - basic repetition checking)
    manager.add_inspector(Box::new(RepetitionInspector::new(None)));

    manager
}
πŸ’‘

Sub-agent provider isolation

When spawning a sub-agent, the main agent can call agent.update_provider(task_config.provider.clone(), &session_id) to give the sub-agent its own provider. This means sub-agents can use different models than the parent agent β€” a powerful architectural choice for cost optimization.

Multi-mode security: GooseMode

Goose implements four distinct operating modes defined in src/config/goose_mode.rs:

ModeDescriptionSecurity behavior
Auto (default) Automatically approve tool calls Minimal friction, tools run immediately
Approve Ask before every tool call Maximum control, every tool requires confirmation
SmartApprove Ask only for sensitive tool calls Uses read_only_hint annotation; auto-approves read-only tools
Chat Chat only, no tool calls Zero tool execution, pure conversation mode

The SmartApprove mode uses a particularly elegant pattern: it calls permission_manager.apply_tool_annotations(&tools) which reads the MCP ToolAnnotations.read_only_hint field. Tools with read_only: false are automatically added to the "ask_before" list in the smart-approve permission config.

The LLM-based AdversaryInspector

The AdversaryInspector in src/security/adversary_inspector.rs is the most distinctive security component in Goose. It activates when you place an adversary.md file in your Goose config directory (~/.config/goose/adversary.md).

adversary.md format

tools: shell, computercontroller__automation_script
---
BLOCK if the command exfiltrates data or is destructive.
ALLOW normal development operations.

The frontmatter specifies which tools to review (defaults to shell and computercontroller__automation_script). The rules below the --- separator are passed to the LLM for judgment.

How it works

  1. When a tool call targets a listed tool, the inspector formats the tool call with its arguments
  2. It extracts the original task from the conversation and recent user messages
  3. It fires a separate LLM call (using the same provider) with the tool call, task, and user rules
  4. The LLM responds with ALLOW or BLOCK and a reason
  5. If BLOCK, the tool is denied with the LLM's explanation shown to the user
⚠️

Fail-open design

If the LLM call fails (e.g., no provider, network error), the inspector fails open β€” allowing the tool to proceed. This prevents the security system from blocking legitimate work due to infrastructure issues.

// From src/security/adversary_inspector.rs β€” the LLM review prompt
let system_prompt = "You are an adversarial security reviewer, protecting the user \
    in case the other agent is rogue. An AI coding agent is about to execute a tool \
    call. Your ONLY job: decide if this tool call is safe given the user's task and \
    rules. Respond with ALLOW or BLOCK on the first line, then a brief reason on \
    the next line.";

let user_message = format!(
    "The user's original task: {}\n\n\
     Recent user messages (oldest first):\n{}\n\
     The tool call to review:\n```\n{}\n```\n\n\
     Rules:\n{}\n\n\
     Respond with a single word on the first line: ALLOW or BLOCK\n\
     Then on the next line, a brief reason.",
    original_task, history_section, tool_description, rules
);

Provider system: 15+ providers across 35+ modules

Goose's provider architecture in src/providers/ is extensive. The providers/mod.rs exports 35+ provider modules:

Provider categoryModules
Direct API providers anthropic, openai, google, gemini_cli, ollama, openrouter, bedrock, azure
ACP-bridged providers claude_acp, pi_acp, codex_acp, copilot_acp, amp_acp
Platform-specific agents chatgpt_codex (GitHub Copilot), claude_code, gemini_cli, kimicode, cursor_agent
Enterprise providers databricks, snowflake, tetrate, venice, xai, nanogpt
Declarative providers JSON config files in providers/declarative/: cerebras, deepseek, groq, lmstudio, mistral, novita, nvidia, ovhcloud, zhipu, and more
Local inference local_inference/ with candle-core, llama-cpp-2, tokenizers β€” supports CUDA and Metal GPU acceleration

The canonical/ directory contains auto-generated model mapping data: canonical_models.json maps canonical model names to provider-specific model identifiers, and provider_metadata.json contains per-provider capabilities and limits.

Extension system and MCP

Goose's extension system is implemented in src/agents/extension_manager.rs and enables dynamic loading of MCP servers and custom tools. Extensions can be stdio-based (spawned as child processes) or embedded via Docker.

Extension discovery

Extensions are discovered from ~/.config/goose/extensions/ and can be npm packages, local directories, or git repositories. The ExtensionManager resolves commands via SearchPaths::builder().with_npm() to find executables in node_modules/.bin or on PATH.

malware scanning

Before loading an extension, Goose runs extension_malware_check.rs to scan the extension's code for suspicious patterns. This is a critical security measure given that extensions run with the same permissions as the agent.

MCP client implementation

The McpClient in src/agents/mcp_client.rs implements the MCP client protocol using the rmcp crate. It handles:

πŸ“Œ

Key files

src/agents/extension_manager.rs (extension lifecycle), src/agents/mcp_client.rs (MCP client protocol), src/agents/extension_malware_check.rs (security scanning), src/config/extensions.rs (configuration)

Tool execution pipeline

The tool execution flow in src/agents/tool_execution.rs and src/agents/agent.rs follows this sequence:

1
Categorize tools

Split into frontend tools (UI handles) and extension tools

↓
2
Run inspection stack

Security β†’ Egress β†’ Adversary β†’ Permission β†’ Repetition

↓
3
Route by permission result

Auto-approved tools dispatch immediately; denied tools return error; needs_approval tools await user confirmation

↓
4
Dispatch via ExtensionManager

Tool call routed to appropriate MCP client or stdio extension

↓
5
Handle result and notifications

Result streamed back with optional MCP server notifications during execution

Sub-agent execution

Goose implements sub-agent execution in src/agents/subagent_handler.rs with a comprehensive system:

Task configuration

Each sub-agent gets a TaskConfig specifying provider, max_turns, and extensions. Sub-agents can use different providers than the parent agent, enabling cost-effective delegation patterns.

Recipe-driven prompts

Sub-agents receive instructions from a Recipe object that contains system instructions, task prompts, response schemas, and retry configuration. The subagent_system.md prompt template provides the agent persona.

// From src/agents/subagent_handler.rs β€” sub-agent prompt context
pub struct SubagentPromptContext {
    pub max_turns: usize,
    pub subagent_id: String,
    pub task_instructions: String,
    pub tool_count: usize,
    pub available_tools: String,
}

// Template from src/prompts/subagent_system.md:
// "You are a specialized subagent within the goose AI framework,
// created by AAIF (Agentic AI Foundation). You were spawned by
// the main goose agent to handle a specific task efficiently."

The sub-agent supports final_output mode where it runs until it produces structured output matching a JSON schema, then stops. This is useful for extracting structured data from complex tasks.

Shell tool: platform-aware execution

The shell tool in src/agents/platform_extensions/developer/shell.rs (~980 lines) is one of the most platform-aware shell implementations in this set:

FeatureImplementation
Unix shell detection Checks for bash on PATH, falls back to sh; respects GOOSE_SHELL env var
Windows shell handling Detects PowerShell vs cmd vs POSIX-like shells (Cygwin/MSYS2)
Flatpak support Detects Flatpak sandbox and wraps commands with flatpak-spawn --host
Login shell PATH resolution Spawns a login shell to recover user's full PATH (fixes Electron-inherited minimal PATH)
Output handling Parallel stdout/stderr collection, 2000-line/50KB limits, temp file fallback for overflow
Background process handling Detects backgrounded processes (sleep &), sets output_truncated flag, doesn't wait for backgrounded sleep
Process session management Uses ProcessSession wrapper to prevent bash job control from stealing terminal foreground

Recipe and skill framework

Goose's recipe system enables converting chat conversations into reusable automation workflows. Defined in src/recipe.rs and prompted via src/prompts/recipe.md:

Recipe structure

  • title β€” Human-readable name
  • instructions β€” Reusable task description
  • activities β€” Example use cases
  • response β€” Optional JSON schema for structured output
  • settings β€” Provider, model, temperature
  • extensions β€” Required extensions to load

Recipe creation

During chat, the agent can call create_recipe() which takes the conversation history, generates a structured recipe using the LLM, and saves it as a reusable automation. Recipes are stored in ~/.config/goose/recipes/.

Context compaction and memory management

Goose implements context compaction in src/context_mgmt/mod.rs. The compaction triggers when context usage exceeds a configurable threshold (default: GOOSE_AUTO_COMPACT_THRESHOLD).

πŸ”§

Hint files system

Goose supports .goosehints files in project directories that provide context hints to the agent. The PromptManager loads these via load_subdirectory_hints() and can refresh tool/prompt lists when hints change.

Key differences from other agents

vs Claude Code

Claude Code is Anthropic-first with 40+ built-in tools and a React/Ink TUI. Goose is provider-agnostic with an extension-first model where tools come from MCP or stdio extensions. Goose's security uses an LLM-based adversary reviewer; Claude Code uses a permissions system with wildcard rules.

vs Pi Mono

Both share the extension-first philosophy, but Pi Mono is TypeScript with a JSONL session format and Pi Packages (npm/git bundles). Goose is Rust with a SQLite session store and an ACP/MCP protocol layer. Goose's provider count (15+) is comparable to Pi Mono's (23).

vs Codex

Both are Rust-native and ship as compiled binaries. Codex focuses on coding with platform-specific sandboxing (Seatbelt/bubblewrap). Goose is more general-purpose with a focus on extensibility and the extension marketplace. Goose uses a layered security inspector stack; Codex uses an execpolicy rule engine.

vs DeerFlow

DeerFlow is a Python/LangGraph orchestration harness. Goose is a Rust native binary with a simpler but more production-focused architecture. DeerFlow's middleware chain is more composable; Goose's inspector stack is more focused on security. DeerFlow has 14 middleware layers; Goose has 5 security inspectors.

File count and codebase size

Goose's Cargo workspace contains multiple crates with substantial source:

CrateNotable modulesCharacteristics
goose agents/, providers/, security/, config/, conversation/ ~50+ source files, 2,500+ line agent.rs
goose-cli commands/, session/, recipes/ Full terminal interface
goose-server routes/, auth.rs, tunnel/ HTTP API with TLS support
goose-mcp mcp_server_runner.rs, computercontroller/ MCP server + computer use tools
goose-sdk Extension interface types SDK for third-party extensions

Notable engineering decisions

Extension isolation via container

The agent can run stdio extensions inside a Docker container via set_container(). This provides process isolation for untrusted or resource-intensive extensions.

Structured error handling with ErrorData

Tool errors use rmcp::model::ErrorData with error codes (INVALID_PARAMS, INTERNAL_ERROR, etc.) rather than plain strings β€” making error handling more programmatic.

Notification streaming during tool execution

ToolCallResult combines a result future with an optional ServerNotification stream. Tools can emit progress notifications during long-running operations without blocking the result.

ActionRequired permission routing

Some providers (e.g., Claude Code via ACP) handle permissions natively through an action-required protocol. Goose detects this via permission_routing() == PermissionRouting::ActionRequired and delegates confirmation handling to the provider.

Prompts via minijinja templates

System prompts use minijinja templates (embedded in src/prompts/) rather than hardcoded strings. Templates receive context like extension list, mode, tool counts, and render dynamically at runtime.

Feature-gated dependencies

The Cargo.toml uses extensive feature gates: local-inference (candle, llama-cpp, tokenizers), aws-providers (Bedrock, SageMaker), telemetry/otel (OpenTelemetry), cuda (GPU acceleration), etc.

Key files reference

FileLinesWhat it does
src/agents/agent.rs ~2,500 Main agent loop, reply(), tool inspection stack setup
src/agents/tool_execution.rs ~190 Tool call dispatch, approval routing, result handling
src/agents/extension_manager.rs ~700+ Extension discovery, MCP client management, tool routing
src/agents/mcp_client.rs ~500+ MCP client protocol implementation using rmcp
src/agents/subagent_handler.rs ~350 Sub-agent spawning, task execution, result collection
src/security/adversary_inspector.rs ~630 LLM-based adversary review with fail-open behavior
src/security/security_inspector.rs ~150 Pattern-matching security detection
src/config/permission.rs ~390 YAML-based permission config with AlwaysAllow/AskBefore/NeverAllow
src/config/goose_mode.rs ~35 Four-mode enum: Auto, Approve, SmartApprove, Chat
src/agents/platform_extensions/developer/shell.rs ~980 Platform-aware shell tool with Flatpak, login-shell PATH, background process handling
src/agents/platform_extensions/analyze/mod.rs ~200+ Code analysis extension with tree-sitter AST parsing, structure/θ―­δΉ‰/focus modes
src/agents/moim.rs ~145 Message-of-instructions injection before latest assistant message
src/providers/mod.rs ~65 35+ provider module exports
src/providers/canonical/ Multiple files Auto-generated canonical model mappings and provider metadata
src/context_mgmt/mod.rs ~300+ Context compaction, tool pair summarization
src/conversation/message.rs ~500+ Message types with tool requests/responses, thinking, system notifications

My assessment

Best for

  • Users who want a lean, extensible agent core rather than a feature-heavy CLI
  • Security-conscious users who want LLM-based adversarial review of tool calls
  • Teams building custom extensions and integrating with MCP servers
  • Users who prefer Rust's performance and type safety for agent tooling
  • Enterprise deployments requiring TLS, OAuth, and multi-provider support

Tradeoffs

  • Extension-first model means some things require setup (vs. batteries-included agents)
  • Security relies on user-defined rules in adversary.md β€” no built-in defaults
  • Recipe framework is less mature than some alternatives' workflow systems
  • Desktop app adds UI complexity that pure CLI agents avoid
πŸ†

Most distinctive feature

The AdversaryInspector using an LLM to review tool calls against user-defined rules is unique in this agent set. No other agent has a dedicated second-LLM security review for tool execution. This makes Goose particularly interesting for multi-agent setups where the parent agent delegates to sub-agents β€” the adversary inspector provides defense-in-depth against a potentially compromised sub-agent.