This document explains how the Python and Go Agent SDKs differ at the architectural level, helping developers understand design decisions and trade-offs.
The Python SDK uses asyncio for concurrent operations with an event-loop based, single-threaded model:
async def main():
client = Agent()
response = await client.query("What is 2+2?")
async for message in response:
print(message)
# Handle concurrent requests
results = await asyncio.gather(
client.query("First question"),
client.query("Second question"),
)
The Go SDK uses goroutines for concurrent operations with lightweight M:N threading:
func main() {
client := Agent{}
// Non-blocking request returns channel
messages := client.Query(ctx, "What is 2+2?")
// Receive from channel
for message := range messages {
fmt.Println(message)
}
}
| Aspect | Python | Go |
|---|---|---|
| Concurrency Model | Event loop (single-threaded) | Goroutines (multi-threaded) |
| Thread Safety | GIL ensures safety | Explicit synchronization (mutexes) |
| Memory per unit | ~100KB per event loop | ~1KB per goroutine |
| Concurrent requests | Limited (1-100 typical) | Unlimited (1M+ common) |
| Context propagation | Exception handling | context.Context package |
| Blocking operations | Blocks entire loop | Only blocks single goroutine |
Python uses declarative dataclasses with runtime type validation:
options = AgentOptions(
model="claude-opus",
allowed_tools=["bash", "read"],
system_prompt="You are helpful"
)
Go uses imperative method chaining for readable, type-safe configuration:
options := NewClaudeAgentOptions().
WithModel("claude-opus").
WithAllowedTools("bash", "read").
WithSystemPrompt("You are helpful")
| Aspect | Python | Go |
|---|---|---|
| Style | Declarative (class definition) | Imperative (method calls) |
| Syntax | AgentOptions(key=value) | .WithKey(value) |
| Type checking | Runtime (Pydantic) | Compile-time |
| Error detection | Runtime | Compile-time |
Python streams messages using async generators with async for syntax (pull model, lazy evaluation).
Go streams messages using channels with for ... range syntax (push model, runs in separate goroutine).
messages, err := Query(ctx, "Hello")
for message := range messages {
if msg, ok := message.(*AssistantMessage); ok {
fmt.Println(msg.Content)
} else if msg, ok := message.(*ResultMessage); ok {
fmt.Printf("Tokens: %d\n", msg.CostSummary.InputTokens)
}
}
| Aspect | Python | Go |
|---|---|---|
| Iteration | async for | for ... range |
| Data source | Generator function | Channel |
| Threading | Single-threaded | Separate goroutine |
| Error handling | Exceptions | Error returns before stream |
| Cancellation | Task cancellation | context.Context |
Python uses exception hierarchies with try/except, while Go uses error type predicates with errors.As():
result, err := client.Query(ctx, "Hello")
var permError *PermissionDeniedError
var cliError *CLINotFoundError
if errors.As(err, &permError) {
fmt.Printf("Permission denied: %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) {
fmt.Println("Permission denied")
}
Go Error Types: ValidationError, CLINotFoundError, CLIConnectionError, PermissionDeniedError, SessionNotFoundError — all implement the error interface.
Python uses dynamic typing with optional type hints, while Go uses static typing with interfaces:
// Sealed interfaces using private methods
type Message interface {
GetMessageType() string
ShouldDisplayToUser() bool
isMessage() // Private - prevents external impl
}
// Type-safe switch at compile time
func Process(msg Message) string {
switch msg.(type) {
case *AssistantMessage:
return msg.(*AssistantMessage).Text
case *ResultMessage:
return fmt.Sprintf("Cost: %d", msg.(*ResultMessage).Cost)
}
}
| Aspect | Python | Go |
|---|---|---|
| Type checking | Runtime (optional static) | Compile-time |
| Polymorphism | Inheritance / Duck typing | Interfaces |
| Null safety | None everywhere | Explicit *Type |
| Refactoring | IDE might miss things | Compiler catches all |
| Scenario | Python | Go | Winner |
|---|---|---|---|
| Startup time | 100-500ms | 10-50ms | Go (10x faster) |
| Single request | 50ms | 40ms | Similar |
| 1000 concurrent | 30-50 concurrent* | 1000+ concurrent | Go (30x more) |
| Memory (100 req) | ~50MB | ~10MB | Go (5x less) |
| GC pause | 1-10ms | <1ms | Go |
*Python limited by GIL and event loop design
Read Next: Migration from Python for code examples and patterns.