A Go SDK for building agentic applications powered by Claude Code CLI.
go get github.com/character-ai/claude-agent-sdk-goPrerequisites:
- Claude Code CLI must be installed and authenticated (for
ClientandAgent) ANTHROPIC_API_KEYenvironment variable must be set (forAPIAgent)
package main
import (
"context"
"fmt"
"log"
claude "github.com/character-ai/claude-agent-sdk-go"
)
func main() {
ctx := context.Background()
text, result, err := claude.QuerySync(ctx, "What is 2 + 2?")
if err != nil {
log.Fatal(err)
}
fmt.Println("Response:", text)
fmt.Printf("Cost: $%.4f\n", result.Cost)
}events, err := claude.Query(ctx, "Explain quantum computing", claude.Options{
MaxTurns: 5,
})
if err != nil {
log.Fatal(err)
}
for event := range events {
if event.Text != "" {
fmt.Print(event.Text)
}
}opts := claude.Options{
AllowedTools: []string{"Read", "Write", "Bash"},
PermissionMode: claude.PermissionAcceptEdits,
MaxTurns: 10,
}
client := claude.NewClient(opts)
events, err := client.Query(ctx, "Create a hello.txt file")The APIAgent calls the Anthropic API directly (no CLI needed). It reads ANTHROPIC_API_KEY from the environment by default, or you can pass it explicitly via APIAgentConfig.APIKey.
tools := claude.NewToolRegistry()
claude.RegisterFunc(tools, claude.ToolDefinition{
Name: "generate_image",
Description: "Generate an image from a text prompt",
InputSchema: claude.ObjectSchema(map[string]any{
"prompt": claude.StringParam("Image description"),
"style": claude.EnumParam("Style", "photo", "anime", "illustration"),
}, "prompt"),
}, func(ctx context.Context, input struct {
Prompt string `json:"prompt"`
Style string `json:"style"`
}) (string, error) {
// Your image generation logic
return `{"url": "https://example.com/image.png"}`, nil
})
agent := claude.NewAPIAgent(claude.APIAgentConfig{
Model: "claude-sonnet-4-20250514",
SystemPrompt: "You are a creative AI assistant.",
Tools: tools,
MaxTurns: 5,
})
events, err := agent.Run(ctx, "Generate a sunset image")http.HandleFunc("/api/generate", func(w http.ResponseWriter, r *http.Request) {
sse, _ := claude.NewSSEWriter(w)
agent := claude.NewAgent(claude.AgentConfig{
Options: claude.Options{
PermissionMode: claude.PermissionAcceptEdits,
},
Tools: tools,
MaxTurns: 10,
})
events, _ := agent.Run(r.Context(), prompt)
for event := range events {
sse.WriteAgentEvent(event)
}
sse.Close()
})The CanUseToolFunc callback gives you interactive control over tool execution. It is called before hooks, allowing you to approve, deny, or modify tool invocations programmatically.
agent := claude.NewAgent(claude.AgentConfig{
Options: claude.Options{
PermissionMode: claude.PermissionAcceptEdits,
},
Tools: tools,
MaxTurns: 10,
CanUseTool: func(ctx context.Context, toolName, toolUseID string, input json.RawMessage) claude.PermissionDecision {
// Deny all Bash commands
if toolName == "Bash" {
return claude.PermissionDecision{
Allow: false,
Reason: "Bash is not allowed in this environment",
}
}
// Modify input for specific tools
if toolName == "Write" {
// Example: inject a safety header into the input
return claude.PermissionDecision{
Allow: true,
ModifiedInput: input, // optionally modify input here
}
}
// Allow everything else
return claude.PermissionDecision{Allow: true}
},
})The CanUseToolFunc is also available on APIAgentConfig for API-based agents.
Hooks allow you to intercept and control tool execution. This is useful for:
- Blocking dangerous operations
- Modifying tool inputs
- Logging and auditing
- Implementing custom permission logic
- Monitoring lifecycle events
hooks := claude.NewHooks()
// Block specific tools
hooks.OnTool("Bash").Before(func(ctx context.Context, hc claude.HookContext) claude.HookResult {
var input struct {
Command string `json:"command"`
}
json.Unmarshal(hc.Input, &input)
// Block rm commands
if strings.Contains(input.Command, "rm -rf") {
return claude.DenyHook("Destructive commands are not allowed")
}
return claude.AllowHook()
})
// Log all tool executions
hooks.OnAllTools().Before(func(ctx context.Context, hc claude.HookContext) claude.HookResult {
log.Printf("Tool called: %s", hc.ToolName)
return claude.AllowHook()
})
// Modify tool input
hooks.OnTool("Write").Before(func(ctx context.Context, hc claude.HookContext) claude.HookResult {
// Example: modify the input before execution
return claude.ModifyHook(hc.Input) // optionally transform hc.Input here
})
agent := claude.NewAgent(claude.AgentConfig{
Hooks: hooks,
Tools: tools,
})hooks.OnAllTools().After(func(ctx context.Context, hc claude.HookContext, result string, isError bool) claude.HookResult {
if isError {
log.Printf("Tool %s failed: %s", hc.ToolName, result)
}
return claude.AllowHook()
})Match tools by regular expression pattern instead of exact name:
// Match all MCP tools
hooks.OnToolRegex(`^mcp__.*`).Before(func(ctx context.Context, hc claude.HookContext) claude.HookResult {
log.Printf("MCP tool called: %s", hc.ToolName)
return claude.AllowHook()
})
// Match tools by prefix pattern
hooks.OnToolRegex(`^(Read|Write|Edit)$`).Before(func(ctx context.Context, hc claude.HookContext) claude.HookResult {
log.Printf("File operation: %s", hc.ToolName)
return claude.AllowHook()
})Protect against slow hooks with timeout support:
hooks.OnTool("external_api").WithTimeout(5 * time.Second).Before(func(ctx context.Context, hc claude.HookContext) claude.HookResult {
// If this takes longer than 5 seconds, the hook is denied with "hook timed out"
// Perform validation logic here (e.g., call an external service)
log.Printf("Validating tool input for %s", hc.ToolName)
return claude.AllowHook()
})Hook results support additional fields for richer control:
hooks.OnTool("search").Before(func(ctx context.Context, hc claude.HookContext) claude.HookResult {
return claude.HookResult{
Decision: claude.HookAllow,
AdditionalContext: "Remember to cite sources", // Injected into conversation
SystemMessage: "Be concise in responses", // System-level instruction
Continue: true, // Continue execution
SuppressOutput: false, // Show tool output
}
})Monitor agent lifecycle events beyond tool execution:
hooks := claude.NewHooks()
hooks.OnEvent(claude.HookSessionStart, func(ctx context.Context, data claude.HookEventData) {
log.Printf("Session started: %s", data.SessionID)
})
hooks.OnEvent(claude.HookSessionEnd, func(ctx context.Context, data claude.HookEventData) {
log.Printf("Session ended: %s", data.Message)
})
hooks.OnEvent(claude.HookStop, func(ctx context.Context, data claude.HookEventData) {
log.Printf("Agent stopped: %s", data.Message)
})
hooks.OnEvent(claude.HookPostToolUseFailure, func(ctx context.Context, data claude.HookEventData) {
log.Printf("Tool %s failed: %s", data.ToolName, data.Error)
})
hooks.OnEvent(claude.HookSubagentStart, func(ctx context.Context, data claude.HookEventData) {
log.Printf("Subagent %s started", data.SubagentName)
})
hooks.OnEvent(claude.HookSubagentStop, func(ctx context.Context, data claude.HookEventData) {
log.Printf("Subagent %s stopped", data.SubagentName)
})
hooks.OnEvent(claude.HookNotification, func(ctx context.Context, data claude.HookEventData) {
log.Printf("Notification: %s", data.Message)
})Available Hook Events:
| Event | Description |
|---|---|
HookPreToolUse |
Before a tool is executed |
HookPostToolUse |
After a tool is executed successfully |
HookPostToolUseFailure |
After a tool execution fails |
HookStop |
When the agent stops (e.g., max turns reached) |
HookSubagentStart |
When a subagent begins execution |
HookSubagentStop |
When a subagent finishes execution |
HookNotification |
General notifications |
HookSessionStart |
When a session begins |
HookSessionEnd |
When a session ends |
The MetricsCollector gathers per-turn LLM latency and per-tool execution stats with no overhead when not configured. Attach it via AgentConfig.Metrics or APIAgentConfig.Metrics.
mc := claude.NewMetricsCollector()
agent := claude.NewAPIAgent(claude.APIAgentConfig{
Model: "claude-sonnet-4-20250514",
Tools: tools,
Metrics: mc,
})
events, _ := agent.Run(ctx, "...")
for range events {}
snap := mc.Snapshot()
fmt.Printf("Session duration: %v\n", snap.SessionDuration)
fmt.Printf("Turns completed: %d\n", len(snap.Turns))
for _, t := range snap.Turns {
fmt.Printf(" turn %d: LLM=%v tools=%v\n", t.TurnIndex, t.LLMLatency, t.ToolsInvoked)
}
for name, s := range snap.ToolStats {
fmt.Printf(" %-20s calls=%d failures=%d avg=%v\n", name, s.Calls, s.Failures, s.AvgTime())
}Snapshot() is safe to call concurrently with a running agent and returns a deep copy.
When a MetricsCollector is configured, every AgentEventTurnComplete event carries a populated TurnMetrics field so you can react to each turn's data immediately:
for event := range events {
if event.Type == claude.AgentEventTurnComplete && event.TurnMetrics != nil {
tm := event.TurnMetrics
log.Printf("turn %d: LLM latency=%v tools=%v", tm.TurnIndex, tm.LLMLatency, tm.ToolsInvoked)
}
}| Type | Fields |
|---|---|
LoopMetrics |
SessionDuration, Turns []TurnMetrics, ToolStats map[string]*ToolStats |
TurnMetrics |
TurnIndex int, LLMLatency time.Duration, ToolsInvoked []string |
ToolStats |
Name, Calls, Failures, TotalTime, AvgTime() time.Duration |
By default, tool calls within a turn execute sequentially. Set ParallelTools: true to run them all concurrently:
agent := claude.NewAgent(claude.AgentConfig{
Tools: tools,
ParallelTools: true, // all tools in a turn run concurrently
})This is also available on APIAgentConfig.ParallelTools.
Results always preserve input order — if Claude invokes [search, write, read], the results slice is always [search_result, write_result, read_result] regardless of completion order.
Only enable this for independent, side-effect-free tools. If your tools share state, write to the same files, or must run in a specific order, keep the default (false).
Example speedup: 3 tools each taking 100 ms run in ~100 ms parallel vs ~300 ms sequential.
RetryConfig adds automatic retry with exponential backoff to tool execution. Configure it globally on the agent or per-tool on ToolDefinition — the per-tool setting takes precedence.
// Global retry: all tools retry up to 3 times with 500 ms base backoff
agent := claude.NewAPIAgent(claude.APIAgentConfig{
Tools: tools,
Retry: &claude.RetryConfig{
MaxAttempts: 3,
Backoff: 500 * time.Millisecond,
RetryOn: func(err error) bool {
// Only retry on transient errors; stop immediately on permanent ones
return strings.Contains(err.Error(), "rate limit") ||
strings.Contains(err.Error(), "timeout")
},
},
})Per-tool override — useful when one tool is flaky but others should fail fast:
claude.RegisterFunc(tools, claude.ToolDefinition{
Name: "external_api",
Description: "Call an external API",
InputSchema: claude.ObjectSchema(map[string]any{
"endpoint": claude.StringParam("API endpoint"),
}, "endpoint"),
RetryConfig: &claude.RetryConfig{
MaxAttempts: 5,
Backoff: time.Second,
// nil RetryOn means retry on any error
},
}, handler)Backoff schedule for Backoff: 500ms, MaxAttempts: 4:
0 ms → fail → 500 ms → fail → 1 s → fail → 2 s → fail → error returned
When RetryOn is nil, all errors are retried. Set MaxAttempts: 1 (or 0) to disable retry entirely.
BudgetConfig stops the session with a *BudgetExceededError when any resource limit is hit. All three limits are independent; any zero value means unlimited.
agent := claude.NewAPIAgent(claude.APIAgentConfig{
Budget: &claude.BudgetConfig{
MaxTokens: 50_000, // cumulative input+output tokens
MaxCostUSD: 0.50, // cumulative cost in USD (Agent/CLI only)
MaxDuration: 2 * time.Minute, // wall-clock session time
},
})Detect the error in the event stream:
for event := range events {
if event.Type == claude.AgentEventError {
var budgetErr *claude.BudgetExceededError
if errors.As(event.Error, &budgetErr) {
log.Printf("session stopped: %v", budgetErr)
}
}
}| Limit | Agent (CLI) |
APIAgent |
|---|---|---|
MaxTokens |
✓ via ResultMessage.Usage |
✓ via streaming events |
MaxCostUSD |
✓ via ResultMessage.Cost |
— (not available) |
MaxDuration |
✓ | ✓ |
HistoryConfig prevents context-window growth in long sessions by compacting the conversation history sent to the LLM on each turn. The full history is always kept in memory — only the LLM's view is trimmed.
agent := claude.NewAgent(claude.AgentConfig{
History: &claude.HistoryConfig{
// Only include the last 5 turns in each LLM call.
// The initial user prompt is always preserved.
MaxTurns: 5,
// Replace tool-result content in older turns with a placeholder.
// Saves tokens while keeping the conversation structure valid.
// (CLI Agent only; use MaxTurns for APIAgent.)
DropToolResults: true,
},
})With MaxTurns: 3 and a 6-turn history, the LLM receives:
[user] initial prompt ← always kept
[assistant] turn 4 response
[tool] turn 4 result
[assistant] turn 5 response
[tool] turn 5 result
[assistant] turn 6 response
[tool] turn 6 result
Turns 1–3 are omitted. With DropToolResults: true, the tool-result lines in older kept turns are replaced with [tool result omitted].
Enable the built-in write_todos and read_todos tools so the agent can plan its work and track progress. The host app receives AgentEventTodosUpdated events whenever the list changes. The read_todos tool lets the agent refresh its view of pending work after long conversations where earlier context may have been compressed by HistoryConfig.
agent := claude.NewAPIAgent(claude.APIAgentConfig{
SystemPrompt: "Plan your work using write_todos before starting.",
EnableTodos: true,
})
events, _ := agent.Run(ctx, "Build a REST API in Go")
for event := range events {
if event.Type == claude.AgentEventTodosUpdated {
for _, todo := range event.Todos {
fmt.Printf("[%s] %s — %s\n", todo.Status, todo.ID, todo.Description)
}
}
}Pass a pre-existing TodoStore to share state between parent and child agents:
shared := claude.NewTodoStore()
parent := claude.NewAgent(claude.AgentConfig{
EnableTodos: true,
TodoStore: shared,
})
// Read the live todo list at any time
todos := shared.List()| Field | Type | Description |
|---|---|---|
ID |
string |
Unique identifier |
Description |
string |
What needs to be done |
Status |
TodoStatus |
pending, in_progress, or completed |
Priority |
TodoPriority |
high, medium, or low |
ParentID |
string |
Optional parent ID for subtasks (validated to exist in same batch) |
Subagents allow you to define specialized child agents that can be invoked via a Task tool. When you configure subagents, a Task tool is automatically registered that Claude can use to delegate work.
// Create tools for the researcher subagent
researchTools := claude.NewToolRegistry()
claude.RegisterFunc(researchTools, claude.ToolDefinition{
Name: "search",
Description: "Search for information",
InputSchema: claude.ObjectSchema(map[string]any{
"query": claude.StringParam("Search query"),
}, "query"),
}, func(ctx context.Context, input struct{ Query string `json:"query"` }) (string, error) {
return fmt.Sprintf("Results for: %s", input.Query), nil
})
// Define subagents
subagents := claude.NewSubagentConfig()
subagents.Add(&claude.AgentDefinition{
Name: "researcher",
Description: "Researches topics and returns summarized findings",
Prompt: "You are a research assistant. Use the search tool to find information.",
Tools: researchTools,
Model: "haiku", // Use a faster model for research
MaxTurns: 5,
})
subagents.Add(&claude.AgentDefinition{
Name: "coder",
Description: "Writes code based on specifications",
Prompt: "You are a coding assistant. Write clean, documented code.",
Model: "sonnet",
MaxTurns: 10,
})
// Create the main agent with subagents
agent := claude.NewAgent(claude.AgentConfig{
Options: claude.Options{
Model: "claude-sonnet-4-20250514",
PermissionMode: claude.PermissionAcceptEdits,
},
MaxTurns: 10,
Subagents: subagents,
})
// Claude can now use the Task tool to delegate to subagents:
// Task(description="Research Go generics patterns", subagent_name="researcher")
events, err := agent.Run(ctx, "Research Go generics and write an example")| Field | Type | Description |
|---|---|---|
Name |
string |
Unique identifier for the subagent |
Description |
string |
Explains what the subagent does (shown to Claude) |
Prompt |
string |
System prompt for the subagent |
Tools |
*ToolRegistry |
Tool registry for the subagent |
Model |
string |
Model to use: "sonnet", "opus", "haiku", or "inherit" (parent's model) |
MaxTurns |
int |
Max turns for the subagent (default: 10) |
Hooks |
*Hooks |
Lifecycle hooks for the subagent |
Agent events include subagent metadata when events originate from a child agent:
for event := range events {
if event.SubagentName != "" {
fmt.Printf("[%s] ", event.SubagentName)
}
if event.Content != "" {
fmt.Print(event.Content)
}
}Enable file checkpointing to rewind file changes to previous states:
agent := claude.NewAgent(claude.AgentConfig{
Options: claude.Options{
EnableFileCheckpointing: true,
},
MaxTurns: 10,
})
events, _ := agent.Run(ctx, "Refactor the codebase")
// Later, rewind file changes to a specific message
err := agent.RewindFiles(ctx, "msg_01234567")You can also use CheckpointManager directly:
cm := claude.NewCheckpointManager(sessionID, "", ".")
err := cm.RewindFiles(ctx, userMessageID)All tools, skills, and hooks are stored in a unified go-memdb-backed store with indexed lookups and thread-safe access. Components can share a store for cross-cutting queries.
// Create a shared store.
store := claude.NewStore()
// Create registries that share the store.
tools := claude.NewToolRegistryWithStore(store)
hooks := claude.NewHooksWithStore(store)
skills := claude.NewSkillRegistry(store)
// Query tools by source or tag.
mcpTools, _ := store.ListToolsBySource("mcp:my-server")
webTools, _ := store.ListToolsByTag("web")
// Point-in-time snapshot for consistent reads.
snap := store.Snapshot()
defer snap.Close()
allTools, _ := snap.Tools()
allSkills, _ := snap.Skills()Skills are composable capability bundles — groups of related tools with rich metadata (tags, categories, dependencies, examples). They enable semantic tool selection and modular agent composition.
store := claude.NewStore()
skillReg := claude.NewSkillRegistry(store)
// Create tools for the skill.
webTools := claude.NewToolRegistry()
claude.RegisterFunc(webTools, claude.ToolDefinition{
Name: "web_search",
Description: "Search the web for information",
InputSchema: claude.ObjectSchema(map[string]any{
"query": claude.StringParam("The search query"),
}, "query"),
}, func(ctx context.Context, input struct {
Query string `json:"query"`
}) (string, error) {
return fmt.Sprintf("Results for: %s", input.Query), nil
})
// Register the skill with its tools.
err := skillReg.Register(claude.Skill{
Name: "web-research",
Description: "Search the web and fetch page content",
Tags: []string{"web", "search", "research"},
Category: "research",
Examples: []claude.SkillExample{
{Query: "Find the latest news about Go", ToolsUsed: []string{"web_search"}},
},
}, webTools)// By tag.
webSkills, _ := skillReg.ByTag("web")
// By category.
researchSkills, _ := skillReg.ByCategory("research")
// All skills.
all, _ := skillReg.All()Skills can declare dependencies. Resolve performs transitive resolution with cycle detection:
// "web-research" depends on "text-processing".
skillReg.Register(claude.Skill{
Name: "web-research",
Dependencies: []string{"text-processing"},
}, webTools)
// Resolve returns a ToolRegistry with tools from both skills.
resolved, _ := skillReg.Resolve("web-research")The SDK includes a zero-dependency BM25 keyword search index for semantic skill/tool selection:
index := claude.NewBM25Index()
// Index skills by description + tags.
_ = index.Index("web-research", "Search the web and fetch page content", []string{"web", "search"})
_ = index.Index("math", "Perform mathematical calculations", []string{"math", "calculation"})
// Search returns ranked results.
results := index.Search("search the web for information", 3)
for _, r := range results {
fmt.Printf(" %s (score: %.3f)\n", r.ID, r.Score)
}You can also implement the SkillIndex interface for custom search backends (e.g., vector search).
The ContextBuilder selects relevant tools per turn using BM25 search over skill descriptions. This keeps the tool list small and focused, improving model performance.
store := claude.NewStore()
index := claude.NewBM25Index()
skillReg := claude.NewSkillRegistry(store)
// ... register skills and index them ...
cb := claude.NewContextBuilder(store, claude.WithIndex(index), claude.WithMaxTools(10))
// Use with APIAgent for dynamic per-turn tool selection.
agent := claude.NewAPIAgent(claude.APIAgentConfig{
Tools: claude.NewToolRegistryWithStore(store),
Skills: skillReg,
ContextBuilder: cb,
SystemPrompt: "You are a helpful assistant.",
})When ContextBuilder is configured:
- Each turn, tools are selected based on the current query context
- Dependencies are resolved transitively with decaying relevance scores
- Falls back to all tools if no index is configured or query is empty
The SDK provides both immediate and graceful shutdown methods:
agent := claude.NewAgent(cfg)
events, _ := agent.Run(ctx, prompt)
// Graceful shutdown: sends SIGINT, waits up to 5s, then SIGKILL
err := agent.Close()
// Immediate shutdown: cancels context
agent.Stop()For the low-level client:
client := claude.NewClient(opts)
events, _ := client.Query(ctx, prompt)
// Graceful: SIGINT → wait 5s → SIGKILL
client.Close()
// Immediate: SIGKILL
client.Stop()Send follow-up messages to a running agent via stdin:
client := claude.NewClient(opts)
events, _ := client.Query(ctx, "Start a conversation")
// Send additional input while the query is running
client.Send("Here is some additional context")
// Agent-level wrapper
agent := claude.NewAgent(cfg)
events, _ := agent.Run(ctx, "Start working")
agent.Send(ctx, "Also consider edge cases")The SDK supports Model Context Protocol (MCP) servers for custom tool integration.
// Create an in-process MCP server
server := claude.NewSDKMCPServer("my-tools", "1.0.0")
// Add a tool with typed input
type GreetInput struct {
Name string `json:"name"`
}
claude.AddToolFunc(server, claude.MCPTool{
Name: "greet",
Description: "Greet a user by name",
InputSchema: claude.ObjectSchema(map[string]any{
"name": claude.StringParam("The user's name"),
}, "name"),
}, func(ctx context.Context, args GreetInput) (string, error) {
return fmt.Sprintf("Hello, %s!", args.Name), nil
})
// Add another tool
type CalcInput struct {
A float64 `json:"a"`
B float64 `json:"b"`
Op string `json:"op"`
}
claude.AddToolFunc(server, claude.MCPTool{
Name: "calculate",
Description: "Perform math operations",
InputSchema: claude.ObjectSchema(map[string]any{
"a": claude.IntParam("First number"),
"b": claude.IntParam("Second number"),
"op": claude.EnumParam("Operation", "add", "subtract", "multiply"),
}, "a", "b", "op"),
}, func(ctx context.Context, args CalcInput) (string, error) {
var result float64
switch args.Op {
case "add":
result = args.A + args.B
case "subtract":
result = args.A - args.B
case "multiply":
result = args.A * args.B
}
return fmt.Sprintf("%.2f", result), nil
})
// Configure MCP servers
mcpServers := claude.NewMCPServers()
mcpServers.AddInProcess("tools", server)
// Convert to tool registry for use with Agent
registry := claude.NewMCPToolRegistry(mcpServers).ToToolRegistry()
// Tools are named: mcp__<server>__<tool>
// e.g., mcp__tools__greet, mcp__tools__calculateProvide hints about tool behavior using annotations:
server.AddTool(claude.MCPTool{
Name: "read_file",
Description: "Read a file from disk",
InputSchema: schema,
Annotations: &claude.MCPToolAnnotations{
ReadOnlyHint: true, // Only reads data, no side effects
DestructiveHint: false, // Does not cause irreversible changes
IdempotentHint: true, // Same input always produces same result
OpenWorldHint: true, // Interacts with the filesystem
},
}, handler)// Create custom tools
customTools := claude.NewToolRegistry()
claude.RegisterFunc(customTools, claude.ToolDefinition{
Name: "custom_tool",
// ...
}, handler)
// Create MCP tools
mcpRegistry := claude.NewMCPToolRegistry(mcpServers).ToToolRegistry()
// Merge all tools
allTools := claude.MergeToolRegistries(customTools, mcpRegistry)
agent := claude.NewAgent(claude.AgentConfig{
Tools: allTools,
})server := claude.NewSDKMCPServer("tools", "1.0.0")
// ... add tools ...
fmt.Println(server.ToolCount()) // Number of registered tools
fmt.Println(server.HasTool("greet")) // Check if a tool exists| Field | Type | Description |
|---|---|---|
Cwd |
string |
Working directory for the agent |
CLIPath |
string |
Path to Claude CLI (defaults to "claude" in PATH) |
Model |
string |
Model to use (e.g., "claude-sonnet-4-20250514") |
PermissionMode |
PermissionMode |
Tool permission handling |
AllowedTools |
[]string |
List of allowed tools (filter) |
DisallowedTools |
[]string |
List of disallowed tools (filter) |
MaxTurns |
int |
Maximum conversation turns |
SystemPrompt |
string |
System prompt override |
SessionID |
string |
Continue from previous session |
MCPServers |
*MCPServers |
MCP server configuration |
ExtraArgs |
[]string |
Additional CLI arguments |
Tools |
*ToolsConfig |
Built-in tool specification (preset or explicit names) |
CustomSessionID |
string |
Explicit session ID instead of auto-generated |
ForkSession |
bool |
Fork the session specified by SessionID |
Debug |
bool |
Enable debug logging |
DebugFile |
string |
Write debug output to file instead of stderr |
Betas |
[]string |
Enable beta features by name |
AdditionalDirectories |
[]string |
Extra directories to add to agent context |
SettingSources |
[]string |
Additional settings source files |
Plugins |
[]PluginConfig |
Plugins to load |
EnableFileCheckpointing |
bool |
Enable file checkpointing for rewind |
| Field | Type | Description |
|---|---|---|
Options |
Options |
Base client options |
Tools |
*ToolRegistry |
Custom tool registry |
Hooks |
*Hooks |
Hook handlers for tool lifecycle |
MaxTurns |
int |
Max turns (default: 10) |
CanUseTool |
CanUseToolFunc |
Permission callback (called before hooks) |
Subagents |
*SubagentConfig |
Subagent definitions for Task tool |
Skills |
*SkillRegistry |
Skill-based tool organization |
ContextBuilder |
*ContextBuilder |
Dynamic per-turn tool selection |
Metrics |
*MetricsCollector |
Collect per-turn and per-tool metrics (nil = disabled) |
ParallelTools |
bool |
Run multiple tool calls per turn concurrently (default: false) |
Retry |
*RetryConfig |
Global retry policy for tool execution (nil = no retry) |
Budget |
*BudgetConfig |
Resource limits: tokens, cost, time (nil = unlimited) |
History |
*HistoryConfig |
History compaction to bound context window (nil = disabled) |
EnableTodos |
bool |
Register write_todos tool for agent self-planning (default: false) |
TodoStore |
*TodoStore |
Shared todo store; auto-created if nil and EnableTodos is true |
| Field | Type | Description |
|---|---|---|
APIKey |
string |
Anthropic API key (defaults to ANTHROPIC_API_KEY env var) |
Model |
string |
Model to use (default: claude-sonnet-4-20250514) |
SystemPrompt |
string |
System prompt |
Tools |
*ToolRegistry |
Custom tool registry |
Hooks |
*Hooks |
Hook handlers for tool lifecycle |
MaxTurns |
int |
Max turns (default: 10) |
CanUseTool |
CanUseToolFunc |
Permission callback (called before hooks) |
Subagents |
*SubagentConfig |
Subagent definitions for Task tool |
Skills |
*SkillRegistry |
Skill-based tool organization |
ContextBuilder |
*ContextBuilder |
Dynamic per-turn tool selection |
Metrics |
*MetricsCollector |
Collect per-turn and per-tool metrics (nil = disabled) |
ParallelTools |
bool |
Run multiple tool calls per turn concurrently (default: false) |
Retry |
*RetryConfig |
Global retry policy for tool execution (nil = no retry) |
Budget |
*BudgetConfig |
Resource limits: tokens, time (nil = unlimited) |
History |
*HistoryConfig |
History compaction to bound context window (nil = disabled) |
EnableTodos |
bool |
Register write_todos tool for agent self-planning (default: false) |
TodoStore |
*TodoStore |
Shared todo store; auto-created if nil and EnableTodos is true |
Use ToolsConfig to specify exactly which built-in tools are available (different from AllowedTools/DisallowedTools which are filters):
opts := claude.Options{
Tools: &claude.ToolsConfig{
Preset: "code", // Use a preset
Names: []string{"Read", "Write", "Bash"}, // Or specify individual tools
},
}Load plugins from files:
opts := claude.Options{
Plugins: []claude.PluginConfig{
{Type: "file", Path: "/path/to/plugin.js"},
},
}PermissionDefault- Default permission handlingPermissionAcceptEdits- Auto-accept file editsPermissionPlan- Plan mode onlyPermissionBypassAll- Bypass all permissions (use with caution)
import "errors"
events, err := claude.Query(ctx, "Hello")
if err != nil {
if errors.Is(err, claude.ErrCLINotFound) {
log.Fatal("Claude CLI not installed")
}
if errors.Is(err, claude.ErrAlreadyRunning) {
log.Fatal("Client already running")
}
log.Fatal(err)
}
for event := range events {
if event.Error != nil {
var procErr *claude.ProcessError
if errors.As(event.Error, &procErr) {
log.Printf("Process failed (exit %d): %s", procErr.ExitCode, procErr.Stderr)
}
var jsonErr *claude.JSONDecodeError
if errors.As(event.Error, &jsonErr) {
log.Printf("JSON decode error: %v", jsonErr.Err)
}
}
}| Error | Description |
|---|---|
ErrCLINotFound |
Claude CLI not found in PATH |
ErrAlreadyRunning |
Client is already processing a query |
ErrNotRunning |
No query in progress |
ProcessError |
CLI process exited with error (has ExitCode, Stderr) |
JSONDecodeError |
Failed to parse JSON response |
ToolNotFoundError |
Tool not found in registry |
When streaming, you'll receive events of these types:
EventMessageStart- New assistant message startingEventContentBlockStart- Content block (text or tool use) startingEventContentBlockDelta- Incremental content updateEventContentBlockStop- Content block finishedEventToolResult- Result from tool executionEventResult- Final result with cost and token counts
AgentEventMessageStart- New message startingAgentEventContentDelta- Text content deltaAgentEventMessageEnd- Message finishedAgentEventToolUseStart- Tool invocation startingAgentEventToolUseDelta- Tool input streamingAgentEventToolUseEnd- Tool invocation completeAgentEventToolResult- Tool execution resultAgentEventTodosUpdated- Todo list changed (includesTodos []TodoItem); requiresEnableTodosAgentEventTurnComplete- Turn finished (tool results sent back); includesTurnMetricswhen aMetricsCollectoris configuredAgentEventComplete- Agent finished (includesResultwithStopReason)AgentEventError- Error occurred
See the examples directory:
- simple - Basic synchronous query
- streaming - Real-time event streaming
- tools - Working with built-in tools
- generation - Custom tools for image/video generation (API agent)
- hooks - Hook patterns: regex, timeout, lifecycle events, permissions
- subagents - Subagent definitions with the Task tool
- skills - Skills, BM25 search, context builder, and dynamic tool selection
- metrics - Per-turn LLM latency, per-tool stats, and parallel tool execution
- resilience - Retry logic, budget controls, and history compaction
- todos - Built-in todo tracking with AgentEventTodosUpdated
- artifacts - Artifact generation (HTML/JSX/text) with API agent
This Go SDK aims for feature parity with the official Python Claude Agent SDK.
| Feature | Python SDK | Go SDK | Notes |
|---|---|---|---|
| Core API | |||
| Simple query function | query() |
Query() / QuerySync() |
|
| Streaming responses | AsyncIterator |
<-chan Event |
Go-idiomatic channels |
| Stateful client | ClaudeSDKClient |
Client |
|
| Agents | |||
| CLI-based agent | via ClaudeSDKClient | Agent |
|
| Direct API agent | - | APIAgent |
Go-only feature |
| Subagents | SubagentConfig |
SubagentConfig |
Auto-registers Task tool |
| Tools | |||
| Built-in tools | Read, Write, Bash | Read, Write, Bash | |
| Built-in tool control | tools |
ToolsConfig |
Preset or explicit names |
| Custom tools | @tool decorator |
RegisterFunc |
Type-safe generics |
| Tool registry | implicit | ToolRegistry |
Explicit registry |
| Permissions | |||
| Permission callback | can_use_tool |
CanUseToolFunc |
Called before hooks |
| Permission modes | permission_mode |
PermissionMode |
|
| MCP Integration | |||
| In-process MCP servers | create_sdk_mcp_server |
NewSDKMCPServer |
|
| External MCP servers | stdio config | MCPServerConfig |
|
| Tool naming | mcp__server__tool |
mcp__server__tool |
Same convention |
| Tool annotations | MCPToolAnnotations |
MCPToolAnnotations |
Behavior hints |
| Hooks | |||
| PreToolUse | HookMatcher |
hooks.OnTool().Before() |
Fluent API |
| PostToolUse | - | hooks.OnTool().After() |
|
| Hook decisions | allow/deny | AllowHook/DenyHook/ModifyHook |
|
| Wildcard matching | "*" |
OnAllTools() |
|
| Regex matching | regex matchers | OnToolRegex() |
|
| Hook timeout | timeout config | WithTimeout() |
|
| Lifecycle events | event handlers | OnEvent() / EmitEvent() |
9 event types |
| Enhanced results | additional fields | AdditionalContext, SuppressOutput, etc. |
|
| Sessions | |||
| Session continuation | session_id |
SessionID |
|
| Custom session ID | custom_session_id |
CustomSessionID |
|
| Session forking | fork_session |
ForkSession |
|
| File checkpointing | enable_file_checkpointing |
EnableFileCheckpointing |
|
| File rewind | rewind_files() |
RewindFiles() |
|
| Configuration | |||
| Working directory | cwd |
Cwd |
|
| CLI path override | cli_path |
CLIPath |
|
| System prompt | system_prompt |
SystemPrompt |
|
| Max turns | max_turns |
MaxTurns |
|
| Debug mode | debug |
Debug / DebugFile |
|
| Betas | betas |
Betas |
|
| Additional directories | additional_directories |
AdditionalDirectories |
|
| Setting sources | setting_sources |
SettingSources |
|
| Plugins | plugins |
Plugins |
|
| Lifecycle | |||
| Graceful shutdown | close() |
Close() |
SIGINT → SIGKILL |
| Streaming input | send() |
Send() |
Stdin pipe |
| Stop reason | stop_reason |
StopReason |
In ResultMessage |
| Error Handling | |||
| CLI not found | CLINotFoundError |
ErrCLINotFound |
|
| Process error | ProcessError |
ProcessError |
|
| JSON decode error | CLIJSONDecodeError |
JSONDecodeError |
|
| Connection error | CLIConnectionError |
- | Not implemented |
| Content Types | |||
| TextBlock | ✓ | ✓ | |
| ToolUseBlock | ✓ | ✓ | |
| ToolResultBlock | ✓ | ✓ | |
| Skills & Context | |||
| Skill registry | - | SkillRegistry |
Go-only: composable capability bundles |
| BM25 search | - | BM25Index |
Go-only: zero-dependency keyword search |
| Context builder | - | ContextBuilder |
Go-only: dynamic per-turn tool selection |
| Unified store | - | Store |
Go-only: go-memdb backed indexed storage |
| Metrics collection | - | MetricsCollector |
Go-only: per-turn LLM latency + per-tool stats |
| Parallel tool execution | - | ParallelTools |
Go-only: concurrent tool calls within a turn |
| Retry logic | - | RetryConfig |
Go-only: per-tool exponential backoff |
| Budget controls | - | BudgetConfig |
Go-only: token, cost, and time limits |
| History compaction | - | HistoryConfig |
Go-only: rolling window + tool-result pruning |
| Todo tracking | - | EnableTodos / TodoStore |
Go-only: built-in write_todos tool for agent self-planning |
| Artifact generation | - | ArtifactRegistry |
Go-only: in-memory HTML/JSX/text artifact system |
| Extras | |||
| SSE HTTP helpers | - | SSEWriter |
Go-only feature |
| HTTP handler | - | AgentHTTPHandler |
Go-only feature |
Features available in the Go SDK but not in Python:
- Direct API Agent (
APIAgent) - Bypasses CLI and calls Anthropic API directly - SSE Helpers - Built-in Server-Sent Events support for HTTP streaming
- HTTP Handler - Ready-to-use HTTP handler for agent endpoints
- Type-Safe Tool Registration - Generics-based
RegisterFunc[T] - Skills & Context Builder - Composable capability bundles with BM25-based dynamic tool selection
- Unified Store -
go-memdb-backed indexed storage for tools, skills, and hooks - Metrics Collection -
MetricsCollectorfor per-turn LLM latency and per-tool execution stats - Parallel Tool Execution -
ParallelToolsflag for concurrent tool calls within a turn - Retry Logic -
RetryConfigwith exponential backoff, configurable globally or per-tool - Budget Controls -
BudgetConfigwith token, cost, and time limits per session - History Compaction -
HistoryConfigrolling window to keep context window bounded - Todo Tracking -
EnableTodos/TodoStorefor agent self-planning withAgentEventTodosUpdatedevents - Artifact Generation -
ArtifactRegistryfor generating self-contained HTML, JSX, or text artifacts
Features in the Python SDK not yet in Go:
- CLIConnectionError - Specific error type for connection issues
MIT