23 KiB
Goose (Block) AI Agent — Architecture & Customization Research Report
Last Updated: 2026-02-06
Purpose: Deep research for forking/cloning Goose into a custom agent
License: Apache 2.0 (permissive, allows derivative works & white-labeling)
Table of Contents
- Repository Structure
- Core Architecture Deep Dive
- Desktop UI Architecture
- MCP Integration System
- Extension/Plugin System
- Permission & HITL System
- Forking Strategy
- Community Extensions & Patterns
- Key Findings for Custom Agent Development
- Sources
1. Repository Structure
Repo: https://github.com/block/goose
Goose is a Rust + TypeScript monorepo with the following key structure:
block/goose/
├── crates/
│ ├── goose/ # Core agent logic (the brain)
│ │ └── src/
│ │ ├── agents/
│ │ │ ├── agent.rs # Main agent implementation
│ │ │ └── extension.rs # ExtensionConfig enum (L87-200)
│ │ └── providers/
│ │ └── base.rs # Provider trait definition
│ ├── goose-bench/ # Benchmarking framework
│ ├── goose-cli/ # CLI entry point
│ │ └── src/
│ │ ├── main.rs # CLI entry point
│ │ └── commands/
│ │ ├── configure.rs # Provider/extension configuration
│ │ └── mcp.rs # MCP server command handling
│ ├── goose-server/ # Backend HTTP server (binary: goosed)
│ │ └── src/main.rs # Server entry point
│ ├── goose-mcp/ # Built-in MCP extensions (developer, memory, etc.)
│ │ └── src/
│ │ └── developer/
│ │ └── tools/
│ │ └── shell.rs # Shell tool example
│ ├── goose-test/ # Test utilities
│ ├── mcp-client/ # MCP client implementation
│ ├── mcp-core/ # MCP shared types/primitives
│ └── mcp-server/ # MCP server framework
├── temporal-service/ # Go-based scheduler service
├── ui/desktop/ # Electron + React desktop app
│ ├── package.json # Dependencies & build scripts
│ ├── src/
│ │ ├── main.ts # Electron main process
│ │ ├── App.tsx # React root component
│ │ ├── goosed.ts # Backend connection manager
│ │ ├── built-in-extensions.json # Built-in extension registry
│ │ ├── components/ # React UI components
│ │ │ ├── BaseChat.tsx # Base chat container
│ │ │ ├── ChatInput.tsx # User input component (59KB!)
│ │ │ ├── ChatSessionsContainer.tsx
│ │ │ ├── ElicitationRequest.tsx # MCP elicitation UI
│ │ │ ├── ConfigContext.tsx
│ │ │ ├── ExtensionInstallModal.test.tsx
│ │ │ └── ErrorBoundary.tsx
│ │ ├── api/ # Generated OpenAPI client
│ │ ├── assets/ # Static assets (branding)
│ │ ├── contexts/ # React contexts
│ │ ├── constants/
│ │ └── utils/
│ │ ├── logger.ts
│ │ ├── autoUpdater.ts
│ │ ├── settings.ts
│ │ └── urlSecurity.ts
│ └── openapi.json # Auto-generated, DO NOT EDIT
├── AGENTS.md # AI-agent dev instructions
├── HOWTOAI.md # AI-assisted dev guide
├── GOVERNANCE.md # Project governance
├── LICENSE # Apache 2.0
└── Justfile # Task runner recipes
Entry Points
| Entry Point | File | Purpose |
|---|---|---|
| CLI | crates/goose-cli/src/main.rs |
Terminal interface |
| Server | crates/goose-server/src/main.rs |
Backend daemon (goosed) |
| Desktop UI | ui/desktop/src/main.ts |
Electron main process |
| Agent Core | crates/goose/src/agents/agent.rs |
Core agent logic |
2. Core Architecture Deep Dive
Execution Flow
The core flow is: Message → Agent → Provider (LLM) → Tool execution → Response
The agent operates in an interactive loop:
- Human Request → User provides input via CLI or Desktop
- Provider Chat → Agent sends request + available tools list to LLM provider
- Model Extension Call → LLM returns tool call request (JSON); Goose executes it
- Response to Model → Tool results sent back to LLM; loop repeats if more tools needed
- Context Revision → Old/irrelevant info pruned (token management)
- Model Response → Final text response back to user
Session Management
- Sessions are persisted as JSONL files with automatic backup and recovery
- Session history includes conversation, tool execution results, and user preferences
- Sessions survive restarts via the
session/loadmechanism - Config stored at
~/.config/goose/config.yaml(macOS/Linux) or%APPDATA%\Block\goose\config\config.yaml(Windows)
Provider Abstraction
Goose supports multiple LLM providers through a unified Provider trait:
- Defined in
crates/goose/src/providers/base.rs - Implementations handle auth, request formatting, response streaming
- Multi-model config: supports lead/worker mode, planner model, and tool shim model
- Key config variables:
GOOSE_PROVIDER,GOOSE_MODEL,GOOSE_TEMPERATURE
Backend Server (goosed)
The backend goosed runs as a separate process, currently using a custom REST + SSE streaming API:
- Current: Custom SSE-based streaming consumed by the Electron desktop app
- Upcoming (ACP migration — Issue #6642): Replacing with Agent Communication Protocol (ACP) over HTTP
- JSON-RPC 2.0 transport
POST /acp/session— Create sessionPOST /acp/session/{id}/message— Send JSON-RPC requestGET /acp/session/{id}/stream— SSE stream for all server→client messages- Supports permission flow: server sends
request_permission, client responds withallow_once/deny
Error Handling
- Errors are captured and sent back to the model as tool responses
- Uses
anyhow::Resultthroughout the Rust codebase - Invalid JSON, missing tools, etc. → LLM gets error context to self-correct
3. Desktop UI Architecture
Tech Stack
| Layer | Technology |
|---|---|
| Framework | Electron (v40.1.0) |
| Frontend | React 19 + TypeScript |
| Build | Vite + electron-forge |
| Styling | TailwindCSS 4 + Radix UI primitives |
| State | SWR for data fetching, React contexts |
| Markdown | react-markdown + remark-gfm + rehype-katex |
| Code Highlighting | react-syntax-highlighter |
| Icons | lucide-react + react-icons |
| API Client | Auto-generated from OpenAPI spec (@hey-api/openapi-ts) |
| Testing | Vitest + Playwright (E2E) + Testing Library |
| MCP UI | @mcp-ui/client (v5.17.3) |
Key UI Components
| Component | File | Purpose |
|---|---|---|
| Base Chat | BaseChat.tsx (17.7KB) |
Core chat container, message rendering |
| Chat Input | ChatInput.tsx (59KB) |
User input with slash commands, file attachments |
| Chat Sessions | ChatSessionsContainer.tsx |
Multi-session management |
| Elicitation | ElicitationRequest.tsx |
MCP elicitation request UI |
| Extension Install | ExtensionInstallModal.test.tsx |
Extension installation flow |
| Config | ConfigContext.tsx |
App configuration context |
| Error Boundary | ErrorBoundary.tsx |
Error handling wrapper |
| API Key Tester | ApiKeyTester.tsx |
Provider key validation |
Frontend-Backend Communication
- Electron main process (
main.ts) spawnsgoosedbackend - React frontend communicates via generated OpenAPI client
- Real-time streaming via SSE connections
- IPC bridge for Electron-specific features (file dialogs, system tray, etc.)
Deep Link Protocol
Goose supports goose:// protocol URLs for:
goose://extension?cmd=...— Install extensionsgoose://botorgoose://recipe— Launch recipesgoose://sessions— Open shared sessions
Branding Files
Key branding touchpoints for white-labeling:
ui/desktop/package.json—productName: "Goose",name: "goose-app"ui/desktop/src/assets/— Logo, icons, imagesui/desktop/src/main.ts— Window title, tray icon, protocol handler (goose://)ui/desktop/src/built-in-extensions.json— Default extension list
4. MCP Integration System
Architecture
Goose acts as an MCP Host managing multiple MCP clients, each connected to a server (extension):
┌─────────────────────────────────────┐
│ Goose Host Process │
│ ┌────────────────────────────────┐ │
│ │ Goose Application (Host) │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Client 1 │ │ Client 2 │ │ │
│ │ └────┬─────┘ └────┬─────┘ │ │
│ └──────┼──────────────┼──────────┘ │
└────────┼──────────────┼─────────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Server 1 │ │ Server 2 │
│(Developer)│ │(Memory) │
└──────────┘ └──────────┘
How Goose Discovers & Connects to MCP Servers
- Built-in extensions are compiled into the
goosedbinary (defined incrates/goose-mcp/) - Config-based extensions are defined in
~/.config/goose/config.yaml - Deep links install via
goose://extension?cmd=... - Extension Manager (built-in platform extension) can dynamically discover, enable, and disable extensions during a session
- Smart Extension Recommendation analyzes user tasks and suggests relevant extensions automatically
Transport Mechanisms
| Type | Config Key | Use Case |
|---|---|---|
| STDIO | type: stdio |
Local extensions (process-based, stdin/stdout) |
| SSE | type: sse |
Remote HTTP-based extensions |
| Streamable HTTP | type: streamablehttp |
Modern remote transport |
Extension Config Format (config.yaml)
extensions:
github:
name: GitHub
cmd: npx
args: [-y, @modelcontextprotocol/server-github]
enabled: true
envs: { "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>" }
type: stdio
timeout: 300
MCP UI Rendering (Issue #3562 — Active Development)
Goose is building support for rendering rich UI from MCP tool responses:
- Inline display: Embedded in chat flow (data viz, previews)
- Sidecar display: Side panel for rich interaction (app-like experiences)
- Uses
@mcp-ui/clientlibrary for deterministic rendering - Tool responses include
_meta.goose.toolUImetadata specifying display type and renderer - Example MCP tool returning UI:
server.tool('render_ui_inline', 'Display UI', {}, async () => {
return {
_meta: {
goose: {
toolUI: {
displayType: 'inline',
name: 'inline example',
renderer: 'mcp-ui',
},
},
},
content: [
createUIResource({
uri: 'ui://component-html',
content: { type: 'rawHtml', htmlString: '<p>Hello World</p>' },
encoding: 'text',
}),
],
};
});
5. Extension/Plugin System
Extension Trait (Rust)
#[async_trait]
pub trait Extension: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn instructions(&self) -> &str;
fn tools(&self) -> &[Tool];
async fn status(&self) -> AnyhowResult<HashMap<String, Value>>;
async fn call_tool(&self, tool_name: &str, parameters: HashMap<String, Value>) -> ToolResult<Value>;
}
Built-in Extensions
Defined in ui/desktop/src/built-in-extensions.json:
| ID | Name | Default Enabled | Description |
|---|---|---|---|
developer |
Developer | ✅ | Shell, file editing, project tools |
computercontroller |
Computer Controller | ❌ | Webscraping, file caching, automations |
memory |
Memory | ❌ | Learn and recall user preferences |
autovisualiser |
Auto Visualiser | ❌ | Auto-generate data visualizations |
tutorial |
Tutorial | ❌ | Interactive learning guides |
Platform Extensions (Always Available)
| Extension | Purpose |
|---|---|
| Chat Recall | Search across all session history |
| Code Execution | Execute JavaScript for tool discovery |
| Extension Manager | Dynamic extension discovery/management (enabled by default) |
| Skills | Load agent skills from project/global directories (enabled by default) |
| Todo | Task tracking across sessions (enabled by default) |
Building Custom Extensions
Extensions are MCP servers. The recommended pattern:
- Create an MCP server (Python with
FastMCP, TypeScript with@modelcontextprotocol/sdk, or Rust) - Define tools with
@mcp.tool()decorator (Python) or equivalent - Test with MCP Inspector (
mcp dev server.py) - Register in Goose via Desktop UI, CLI, config file, or deep link
Extension Allowlist (Enterprise)
For corporate environments, administrators can restrict which extensions can be installed:
# Deployed YAML file at GOOSE_ALLOWLIST URL
extensions:
- id: slack
command: uvx mcp_slack
- id: github
command: uvx mcp_github
Set via export GOOSE_ALLOWLIST=https://example.com/goose-allowlist.yaml
6. Permission & HITL System
Permission Modes
| Mode | Config Value | Behavior |
|---|---|---|
| Autonomous | auto |
Full autonomy, no approval needed (default) |
| Manual Approval | approve |
Confirmation before any tool use |
| Smart Approval | smart_approve |
Risk-based: auto-approve low-risk, flag high-risk |
| Chat Only | chat |
No tool use, conversation only |
Configuration: GOOSE_MODE: smart_approve in config.yaml
How Approval Works
In approve and smart_approve modes:
- Goose classifies tools as read or write (best-effort, LLM-interpreted)
- Write tools trigger "Allow" / "Deny" buttons in the UI
- The server sends a
request_permissionJSON-RPC request - Client responds with
{ outcome: "allow_once" }or deny - Only after approval does the tool execute
Additional Safety
.gooseignorefile prevents access to sensitive files (.env*,*.key,target/,.git/).goosehintsprovides project-specific guidance to the agent- Prompt injection detection (
SECURITY_PROMPT_ENABLED: true) - ML-based classifier endpoint for advanced threat detection
- Malware scanning for external extensions before activation
- Maximum turns limit (
GOOSE_MAX_TURNS) prevents runaway loops
7. Forking Strategy
License: Apache 2.0
✅ Permissive license — allows:
- Commercial use
- Modification and derivative works
- Distribution under different terms
- White-labeling and rebranding
- No copyleft/share-alike requirements
Requirements: Must include original copyright notice and license text.
Note: As of Dec 2025, Goose was contributed to the Agentic AI Foundation (AAIF) under the Linux Foundation. The Apache 2.0 license remains; governance is now community-driven.
Key Files to Modify for White-Labeling
| What to Change | Files |
|---|---|
| App Name & Branding | ui/desktop/package.json (productName, name, description) |
| Window Title | ui/desktop/src/main.ts (BrowserWindow title) |
| Protocol Handler | ui/desktop/src/main.ts (goose:// → yourapp://) |
| Logo & Icons | ui/desktop/src/assets/ |
| Built-in Extensions | ui/desktop/src/built-in-extensions.json |
| Default Config | crates/goose-server/ + crates/goose-cli/ default values |
| Config Directory | Code references to ~/.config/goose/ |
| System Prompts | crates/goose/src/agents/ — agent instructions |
| Provider Defaults | GOOSE_PROVIDER, GOOSE_MODEL defaults |
| Update Server | ui/desktop/src/utils/autoUpdater.ts |
| Telemetry | GOOSE_TELEMETRY_ENABLED and endpoints |
| Extension Directory | URL in extension browsing/discovery code |
Adding Custom MCP Servers as Built-in Extensions
Two approaches:
-
Rust built-in (compiled into binary):
- Add to
crates/goose-mcp/src/ - Register in the built-in server matching logic (
crates/goose-cli/src/commands/mcp.rs) - Update
ui/desktop/src/built-in-extensions.json
- Add to
-
Bundled external (shipped alongside but run as separate process):
- Include the MCP server binary/package in the distribution
- Pre-configure in default
config.yaml - Set
bundled: truein extension config
Build Process
# Setup
source bin/activate-hermit
cargo build
# Build release
cargo build --release
just release-binary
# Desktop app
just generate-openapi # After server changes
just run-ui # Development
cd ui/desktop && npm run bundle:default # Production build
8. Community Extensions & Patterns
Extension Discovery
- Official directory: https://block.github.io/goose/extensions/ (web-based catalog)
- Community lists:
appcypher/awesome-mcp-servers,punkpeye/awesome-mcp-servers - Registries: PulseMCP, Playbooks, MCP.so
- Dynamic discovery: Extension Manager extension searches available servers at runtime
Common Extension Patterns
- STDIO MCP servers (most common):
uvx,npx,jbang,dockerlaunchers - Python + FastMCP: Simplest pattern for custom tools
- TypeScript + @modelcontextprotocol/sdk: For Node.js ecosystem
- Rust: For performance-critical or compiled extensions
Notable Community Extensions
| Extension | Command | Purpose |
|---|---|---|
| GitHub | npx -y @modelcontextprotocol/server-github |
GitHub API integration |
| Slack | uvx mcp_slack |
Slack workspace access |
| Postgres | uvx mcp-server-postgres |
Database querying |
| Fetch | uvx mcp-server-fetch |
HTTP request making |
| Filesystem | uv run mcp-filesystem |
File system access |
| Playwright | MCP server | Browser automation |
| Tavily Search | npx -y mcp-tavily-search |
Web search |
| Perplexity | npx -y mcp-perplexity-search |
AI search |
Recipes System
Goose supports recipes — shareable YAML files that package entire workflows:
- Include goal, required extensions, setup steps, example activities
- Configurable via
GOOSE_RECIPE_GITHUB_REPO - Custom slash commands can trigger recipes
- Deep linkable:
goose://recipe?...
Subagents
Goose supports spawning subagents — isolated instances for parallel/long-running tasks:
- Keep main conversation clean
- Process isolation for experimental work
- Parallel execution of independent tasks
9. Key Findings for Custom Agent Development
Why Fork Goose?
| Advantage | Detail |
|---|---|
| Mature MCP integration | Best-in-class MCP host with dynamic discovery |
| Desktop + CLI | Two interfaces already built |
| Apache 2.0 | Full commercial freedom |
| Active development | Frequent releases, large community |
| Multi-provider | Works with any LLM (OpenAI, Anthropic, Google, local) |
| Permission system | Built-in HITL with smart approval |
| Extension ecosystem | Thousands of MCP servers available |
Customization Effort Estimate
| Change | Effort | Complexity |
|---|---|---|
| Rebrand (name, logo, colors) | 1-2 days | Low |
| Custom default extensions | 1 day | Low |
| Custom system prompts | Hours | Low |
| New built-in MCP extension (Rust) | 3-5 days | Medium |
| Custom UI components in chat | 1-2 weeks | Medium-High |
| Custom approval flow | 1 week | Medium |
| Replace backend protocol (ACP) | Already in progress upstream | High |
Desktop App is More Extensible
The Electron desktop app provides:
- Full React component library for custom UI
- Deep link protocol for integrations
- Extension install modal workflow
- MCP UI rendering (inline + sidecar) — under active development
- System tray, keyboard shortcuts, auto-updates
- Multi-window/multi-session support
The CLI is simpler but less extensible for custom UI experiences.
Risks & Considerations
- Active ACP migration (Issue #6642): The backend communication protocol is being replaced. Fork timing matters.
- Governance shift: Now under AAIF/Linux Foundation — community governance may affect upstream direction
- Rapid iteration: Multiple releases per week; staying in sync with upstream requires effort
- Electron size: Desktop app ships Chromium (~200MB+)
- Rust build times: Full build can be slow; incremental builds are faster