This guide helps Python developers transition to the Go Agent SDK. While the APIs differ due to language differences, the concepts are the same.
import asyncio
from claude_agent_sdk import Agent
async def main():
client = Agent()
async for message in client.query("What is 2+2?"):
print(f"{message.type}: {message.content}")
asyncio.run(main())
package main
import (
"context"
"fmt"
sdk "github.com/schlunsen/claude-agent-sdk-go"
"github.com/schlunsen/claude-agent-sdk-go/types"
)
func main() {
ctx := context.Background()
messages, _ := sdk.Query(ctx, "What is 2+2?", nil)
for message := range messages {
switch m := message.(type) {
case *types.AssistantMessage:
fmt.Printf("Assistant: %v\n", m.Content)
}
}
}
Python uses dataclass constructors, Go uses a builder pattern with method chaining:
# Python: Dataclass constructor
options = AgentOptions(
model="claude-opus-4-20250514",
allowed_tools=["Bash", "Read", "Write"],
system_prompt="You are a helpful assistant",
max_turns=10,
)
// Go: Builder pattern
options := types.NewClaudeAgentOptions().
WithModel("claude-opus-4-20250514").
WithAllowedTools("Bash", "Read", "Write").
WithSystemPrompt("You are a helpful assistant").
WithMaxTurns(10)
Key difference: Go configuration is verified at compile-time, not runtime. Each
With*()method returns the options object for chaining.
async def main():
client = Agent()
await client.connect()
await client.query("What is the capital of France?")
async for msg in client.receive_response():
messages.append(msg)
await client.query("And Germany?")
async for msg in client.receive_response():
messages.append(msg)
await client.close()
func main() {
ctx := context.Background()
client, _ := sdk.NewClient(ctx, options)
defer client.Close(ctx)
client.Connect(ctx)
client.Query(ctx, "What is the capital of France?")
for msg := range client.ReceiveResponse(ctx) {
messages = append(messages, msg)
}
client.Query(ctx, "And Germany?")
for msg := range client.ReceiveResponse(ctx) {
messages = append(messages, msg)
}
}
Python uses decorators, Go uses explicit hook callbacks in options:
# Python: Decorator-based hooks
@client.hook(HookEvent.PRE_TOOL_USE)
async def log_before_tool(input_data, hook_context):
print(f"About to call: {hook_context.tool_name}")
return {"continue": True}
// Go: Explicit hook matchers in options
preHook := types.HookMatcher{
Matcher: stringPtr(".*"),
Hooks: []types.HookCallbackFunc{
func(ctx context.Context, input interface{},
toolUseID *string, hookCtx types.HookContext,
) (interface{}, error) {
fmt.Printf("About to call: %s\n", hookCtx.ToolName)
return map[string]interface{}{"continue": true}, nil
},
},
}
options := types.NewClaudeAgentOptions().
WithHook(types.HookEventPreToolUse, preHook)
func checkPermissions(ctx context.Context,
toolName string,
input map[string]interface{},
permCtx types.ToolPermissionContext,
) (interface{}, error) {
if toolName == "Bash" {
cmd := input["command"].(string)
if strings.Contains(cmd, "rm -rf") {
return &types.PermissionResultDeny{
Behavior: "deny",
Message: "Dangerous command blocked",
}, nil
}
}
return &types.PermissionResultAllow{Behavior: "allow"}, nil
}
options := types.NewClaudeAgentOptions().
WithCanUseTool(checkPermissions)
// Go: Factory function for MCP servers
calculator, _ := types.NewSDKMCPServer("calculator",
types.Tool{
Name: "add",
Description: "Add two numbers",
Handler: func(ctx context.Context, args map[string]any) (any, error) {
a, _ := args["a"].(float64)
b, _ := args["b"].(float64)
return a + b, nil
},
},
)
options := types.NewClaudeAgentOptions().
WithMCPServer("calculator", calculator)
Python uses try/except with exception classes. Go uses errors.As() type assertions:
messages, err := sdk.Query(ctx, "Hello", nil)
var permError *types.PermissionDeniedError
var cliError *types.CLINotFoundError
if errors.As(err, &permError) {
fmt.Printf("Permission denied for %s\n", permError.ToolName)
} else if errors.As(err, &cliError) {
fmt.Printf("CLI not found: %s\n", cliError.Message)
}
// Or use helper predicates
if types.IsPermissionDeniedError(err) { /* ... */ }
if types.IsCLINotFoundError(err) { /* ... */ }
| Task | Python | Go |
|---|---|---|
| Async functions | async def | func |
| Await calls | await func() | <-chan |
| Async iteration | async for | for ... range |
| Configuration | Options(key=val) | .WithKey(val) |
| Error handling | except ErrorType | errors.As(err, &var) |
| Hooks | @client.hook() | .WithHook(event, matcher) |
| Callbacks | Async functions | Sync functions |
| Context | Exception handling | context.Context |
| Concurrency | asyncio.gather() | Goroutines + channels |
| Install | pip install | go get |
| Aspect | Python | Go |
|---|---|---|
| Startup | ~100ms | ~10ms |
| Memory overhead | Higher | Lower |
| Concurrency | Limited by GIL | 1M+ goroutines |
| Deployment | Runtime required | Single binary |
| Dev speed | Faster prototyping | Slower but safer |
Further reading: Architecture Differences | Feature Parity