A Go SDK for building AI agents with Claude Code.
A clean, idiomatic Go interface for interacting with Claude's agentic capabilities.
- Idiomatic Go API - Functional options, channels, and context support
- Streaming Messages - Real-time message streaming via Go channels
- Hook System - Intercept and modify tool usage with typed hooks
- Full Type Safety - Strongly typed messages, content blocks, and options
- Subprocess Transport - Manages Claude CLI lifecycle automatically
- Control Protocol - Full support for SDK-CLI communication
- Go 1.24 or later
- Claude Code CLI installed
go get github.com/panbanda/claude-agent-sdk-gopackage main
import (
"context"
"fmt"
"log"
"github.com/panbanda/claude-agent-sdk-go/claude"
)
func main() {
ctx := context.Background()
// One-shot query with streaming
msgs, err := claude.Query(ctx, "What is 2+2?",
claude.WithModel("claude-sonnet-4-5"),
)
if err != nil {
log.Fatal(err)
}
for msg := range msgs {
switch m := msg.(type) {
case *claude.AssistantMessage:
for _, block := range m.Content {
if block.IsText() {
fmt.Println(block.Text)
}
}
case *claude.ResultMessage:
fmt.Printf("Cost: $%.4f\n", m.TotalCostUSD)
}
}
}result, err := claude.QueryResult(ctx, "Explain quantum computing",
claude.WithModel("claude-sonnet-4-5"),
claude.WithMaxTurns(5),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Response: %s\n", result.Result)
fmt.Printf("Cost: $%.4f\n", result.TotalCostUSD)client := claude.NewClient(
claude.WithModel("claude-sonnet-4-5"),
claude.WithMaxTurns(10),
claude.WithSystemPrompt("You are a helpful coding assistant."),
)
if err := client.Connect(ctx); err != nil {
log.Fatal(err)
}
defer client.Close()
// Send a query
if err := client.Query(ctx, "Write a hello world in Go"); err != nil {
log.Fatal(err)
}
// Stream responses
for msg := range client.Messages() {
switch m := msg.(type) {
case *claude.AssistantMessage:
// Handle assistant response
case *claude.SystemMessage:
// Handle system events
case *claude.ResultMessage:
// Query complete
}
}The examples/ directory contains runnable examples demonstrating SDK capabilities:
| Example | Description |
|---|---|
| basic-query | Simplest usage - send a prompt, print response |
| result-only | Get just the final answer, skip streaming |
| streaming | Real-time display of all message types |
| multi-turn | Interactive chat using the Client |
| tool-control | Read-only agent (restrict available tools) |
| hooks-security | Block dangerous commands with pre-tool hooks |
| hooks-logging | Audit all tool usage with timing |
| code-reviewer | Practical agent that reviews code for issues |
| extended-thinking | Enable Claude's reasoning mode |
Run any example:
go run ./examples/basic-query
go run ./examples/code-reviewer --path=./claudeclaude.WithModel("claude-sonnet-4-5") // Primary model
claude.WithFallbackModel("claude-haiku") // Fallback if primary unavailableclaude.WithMaxTurns(10) // Maximum conversation turns
claude.WithMaxBudgetUSD(1.0) // Spending limit in USD
claude.WithMaxThinkingTokens(500) // Token budget for extended thinkingclaude.WithPermissionMode(claude.PermissionDefault) // Default prompting
claude.WithPermissionMode(claude.PermissionAcceptEdits) // Auto-accept edits
claude.WithPermissionMode(claude.PermissionPlan) // Plan mode
claude.WithPermissionMode(claude.PermissionBypass) // Bypass all checksclaude.WithAllowedTools("Read", "Write", "Bash") // Whitelist tools
claude.WithDisallowedTools("Bash") // Blacklist toolsclaude.WithWorkingDir("/path/to/project") // Working directory
claude.WithCLIPath("/custom/path/to/claude") // Custom CLI path
claude.WithEnv(map[string]string{"KEY": "value"}) // Environment variablesclaude.WithContinueConversation(true) // Continue prior conversation
claude.WithResume("session-id") // Resume specific sessionHooks allow you to intercept and modify Claude's behavior at key points.
Intercept tool calls before execution:
client := claude.NewClient(
claude.WithPreToolUseHook("Bash", func(ctx context.Context, input *claude.PreToolUseInput, hookCtx *claude.HookContext) (*claude.HookOutput, error) {
// Block dangerous commands
if cmd, ok := input.ToolInput["command"].(string); ok {
if strings.Contains(cmd, "rm -rf") {
return &claude.HookOutput{
Decision: claude.HookDecisionDeny,
Reason: "Dangerous command blocked",
}, nil
}
}
return &claude.HookOutput{Decision: claude.HookDecisionAllow}, nil
}),
)React to tool results:
claude.WithPostToolUseHook("", func(ctx context.Context, input *claude.PostToolUseInput, hookCtx *claude.HookContext) (*claude.HookOutput, error) {
if input.IsError {
log.Printf("Tool %s failed: %v", input.ToolName, input.ToolResponse)
}
return &claude.HookOutput{}, nil
})| Event | Description |
|---|---|
PreToolUse |
Before tool execution |
PostToolUse |
After tool execution |
UserPromptSubmit |
When user sends a prompt |
Stop |
When agent stops |
SubagentStop |
When a subagent stops |
PreCompact |
Before conversation compaction |
Contains Claude's response with content blocks:
case *claude.AssistantMessage:
for _, block := range m.Content {
switch {
case block.IsText():
fmt.Println(block.Text)
case block.IsThinking():
fmt.Printf("Thinking: %s\n", block.Thinking)
case block.IsToolUse():
fmt.Printf("Using tool: %s\n", block.Name)
case block.IsToolResult():
fmt.Printf("Tool result: %v\n", block.Content)
}
}Final message with usage and cost information:
case *claude.ResultMessage:
fmt.Printf("Session: %s\n", m.SessionID)
fmt.Printf("Turns: %d\n", m.NumTurns)
fmt.Printf("Duration: %dms\n", m.DurationMS)
fmt.Printf("Cost: $%.4f\n", m.TotalCostUSD)System events and metadata:
case *claude.SystemMessage:
fmt.Printf("Event: %s, Data: %v\n", m.Subtype, m.Data)import "errors"
msgs, 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.ErrNotConnected) {
log.Fatal("Not connected to Claude")
}
if procErr, ok := err.(*claude.ProcessError); ok {
log.Printf("Process failed (exit %d): %s", procErr.ExitCode, procErr.Stderr)
}
log.Fatal(err)
}ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
msgs, err := claude.Query(ctx, "Long running task...")client := claude.NewClient()
if err := client.Connect(ctx); err != nil {
log.Fatal(err)
}
defer client.Close() // Always close to clean up subprocessfor msg := range client.Messages() {
switch m := msg.(type) {
case *claude.UserMessage:
// Echo of user input
case *claude.AssistantMessage:
// Claude's response
case *claude.SystemMessage:
// System events
case *claude.ResultMessage:
// Final result
default:
log.Printf("Unknown message type: %T", msg)
}
}client := claude.NewClient(
claude.WithMaxTurns(20), // Prevent runaway conversations
claude.WithMaxBudgetUSD(5.0), // Cost control
)The SDK includes a mock transport for testing:
func TestMyAgent(t *testing.T) {
// Create mock transport
mt := claude.NewMockTransport()
// Queue expected responses
mt.QueueMessage([]byte(`{"type": "assistant", "message": {...}}`))
mt.QueueMessage([]byte(`{"type": "result", "subtype": "success"}`))
mt.CloseMessages()
// Use mock in client
client := claude.NewClient(claude.WithTransport(mt))
// ... test your code
}Contributions are welcome! Please read our Contributing Guidelines before submitting a PR.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
go test ./...) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Claude Code - The CLI this SDK wraps
- claude-agent-sdk-python - Official Python SDK
