documentation/docs/goose-architecture/extensions-design.md
This document describes the design and implementation of the Extensions framework in goose, which enables AI agents to interact with different extensions through a unified tool-based interface.
An Extension represents any component that can be operated by an AI agent. Extensions expose their capabilities through Tools and maintain their own state. The core interface is defined by the Extension trait:
#[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>;
}
Tools are the primary way Extensions expose functionality to agents. Each tool has:
A tool must take a Value and return an AgentResult<Value> (it must also be async). This
is what makes it compatible with the tool calling framework from the agent.
async fn echo(&self, params: Value) -> AgentResult<Value>
The system uses two main error types:
ErrorData: Specific errors related to tool executionanyhow::Error: General purpose errors for extension status and other operationsThis split allows precise error handling for tool execution while maintaining flexibility for general extension operations.
? operator with ErrorData for tool executionHere's a complete example of a simple extension:
use goose_macros::tool;
struct FileSystem {
registry: ToolRegistry,
root_path: PathBuf,
}
impl FileSystem {
#[tool(
name = "read_file",
description = "Read contents of a file"
)]
async fn read_file(&self, path: String) -> ToolResult<Value> {
let full_path = self.root_path.join(path);
let content = tokio::fs::read_to_string(full_path)
.await
.map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(e.to_string(),
data: None,
}))?;
Ok(json!({ "content": content }))
}
}
#[async_trait]
impl Extension for FileSystem {
// ... implement trait methods ...
}
Extensions should be tested at multiple levels:
Example test:
#[tokio::test]
async fn test_echo_tool() {
let extension = TestExtension::new();
let result = extension.call_tool(
"echo",
hashmap!{ "message" => json!("hello") }
).await;
assert_eq!(result.unwrap(), json!({ "response": "hello" }));
}