Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Goose Hooks Reference

Disclaimer: This document reflects our current understanding of Goose’s extensibility surface. It is a working reference for symposium development, not a substitute for the official docs. Details may be outdated or incomplete — always consult the primary sources.

Primary sources: Extensions · Configuration · GitHub repo

Goose does not implement lifecycle hooks. There are no shell-command or programmatic interception points for tool execution, session start/stop, or prompt submission. No hooks.json equivalent. No JSON stdin/stdout protocol.

What Goose Offers Instead

MCP Extensions

The primary extensibility mechanism. Extensions are MCP servers (stdio or HTTP) that expose new tools, resources, and prompts. Configured in ~/.config/goose/config.yaml under extensions:. Built-in extensions include Developer (shell, file editing), Computer Controller, Memory, and Todo. Custom extensions are standard MCP servers built in Python, TypeScript, or Kotlin. Extensions add capabilities but cannot intercept or modify existing tool behavior.

Permission Modes

The closest analog to hook-based control flow. Static configuration, not programmable logic.

ModeBehavior
autoTools execute without approval (default)
approveEvery tool call requires manual confirmation
smart_approveAI risk assessment auto-approves low-risk, prompts for high-risk
chatNo tool use

Per-tool permissions can be set to Always Allow, Ask Before, or Never Allow.

Goosehints / AGENTS.md

Instruction files injected into the system prompt. Influence LLM behavior through prompting, not deterministic interception.

FileScope
~/.config/goose/.goosehintsGlobal
.goosehints (project root)Project
AGENTS.mdProject

GOOSE_TERMINAL Environment Variable

Shell scripts can detect whether they’re running under Goose and alter behavior (e.g., wrapping git to block git commit). This is a shell-level workaround, not a Goose-native mechanism.

Other Mechanisms

  • .gooseignore — gitignore-style file access restriction
  • Recipes — YAML workflow packages
  • Custom slash commands
  • Subagents
  • ACP integration
  • Tool Router — internal optimization for tool selection

MCP Server Registration

Since Goose has no lifecycle hooks, symposium integrates exclusively via MCP server registration. Symposium registers itself as an extension in the Goose config file.

Configuration structure

The MCP server entry is added under extensions in the YAML config. Goose’s schema has its own vocabulary - a type discriminant, the binary under cmd (not command), and env as an envs map:

extensions:
  symposium:
    name: symposium
    type: stdio
    cmd: /path/to/cargo-agents
    args: [mcp]
    enabled: true
    envs:
      TOKEN: abc

A remote server is type: streamable_http with the endpoint under uri. There is no sse variant - the accepted set is stdio, builtin, platform, streamable_http, frontend, inline_python, which goose recipe validate will list back at you for a wrong one:

extensions:
  remote:
    name: remote
    type: streamable_http
    uri: https://example.com/mcp
    enabled: true
    headers:
      Authorization: Bearer t
  • User-level: ~/.config/goose/config.yaml ($GOOSE_PATH_ROOT/config/config.yaml when that variable is set, else $XDG_CONFIG_HOME/goose/config.yaml when that one is)
  • Project-level: none - Goose reads only the user-level file, so project-scoped registration resolves there.

GOOSE_PATH_ROOT moves that file: when it is set, Goose reads $GOOSE_PATH_ROOT/config/config.yaml, whatever XDG_CONFIG_HOME says, and an empty value counts as unset. Symposium writes its MCP servers there too. Verified against Goose 1.54.0 with a different MCP server in each candidate file, by which one goose run --text hi started: the one under $GOOSE_PATH_ROOT when both variables were set, and the XDG_CONFIG_HOME one when GOOSE_PATH_ROOT was empty. Through cargo agents sync with the variable set, a server left in ~/.config/goose/config.yaml never started, while the one symposium wrote under $GOOSE_PATH_ROOT started and was offered to the model as a tool.

Two ways to check a change without a configured LLM provider: goose recipe validate on a recipe holding the entry (it deserializes the same ExtensionConfig), and goose run --text hi, whose startup warnings name each extension it tried to launch. goose info -v only echoes the raw YAML, so it cannot tell an accepted entry from a rejected one.

Registration is idempotent — if the entry already exists with the correct values, no changes are made. Stale entries are updated in place.