Architectural Differences: Python vs Go

This document explains how the Python and Go Agent SDKs differ at the architectural level, helping developers understand design decisions and trade-offs.


Concurrency Model

Python: Async/Await with asyncio

The Python SDK uses asyncio for concurrent operations with an event-loop based, single-threaded model:

python
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"),
    )

Go: Goroutines with Channels

The Go SDK uses goroutines for concurrent operations with lightweight M:N threading:

go
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)
    }
}
AspectPythonGo
Concurrency ModelEvent loop (single-threaded)Goroutines (multi-threaded)
Thread SafetyGIL ensures safetyExplicit synchronization (mutexes)
Memory per unit~100KB per event loop~1KB per goroutine
Concurrent requestsLimited (1-100 typical)Unlimited (1M+ common)
Context propagationException handlingcontext.Context package
Blocking operationsBlocks entire loopOnly blocks single goroutine

Configuration System

Python: Pydantic Dataclasses

Python uses declarative dataclasses with runtime type validation:

python
options = AgentOptions(
    model="claude-opus",
    allowed_tools=["bash", "read"],
    system_prompt="You are helpful"
)

Go: Builder Pattern with Fluent API

Go uses imperative method chaining for readable, type-safe configuration:

go
options := NewClaudeAgentOptions().
    WithModel("claude-opus").
    WithAllowedTools("bash", "read").
    WithSystemPrompt("You are helpful")
AspectPythonGo
StyleDeclarative (class definition)Imperative (method calls)
SyntaxAgentOptions(key=value).WithKey(value)
Type checkingRuntime (Pydantic)Compile-time
Error detectionRuntimeCompile-time

Message Handling

Python: Async Generators

Python streams messages using async generators with async for syntax (pull model, lazy evaluation).

Go: Channels with Range Loops

Go streams messages using channels with for ... range syntax (push model, runs in separate goroutine).

go
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)
    }
}
AspectPythonGo
Iterationasync forfor ... range
Data sourceGenerator functionChannel
ThreadingSingle-threadedSeparate goroutine
Error handlingExceptionsError returns before stream
CancellationTask cancellationcontext.Context

Error Handling

Python uses exception hierarchies with try/except, while Go uses error type predicates with errors.As():

go
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.


Type System

Python uses dynamic typing with optional type hints, while Go uses static typing with interfaces:

go
// 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)
    }
}
AspectPythonGo
Type checkingRuntime (optional static)Compile-time
PolymorphismInheritance / Duck typingInterfaces
Null safetyNone everywhereExplicit *Type
RefactoringIDE might miss thingsCompiler catches all

Memory & Performance

ScenarioPythonGoWinner
Startup time100-500ms10-50msGo (10x faster)
Single request50ms40msSimilar
1000 concurrent30-50 concurrent*1000+ concurrentGo (30x more)
Memory (100 req)~50MB~10MBGo (5x less)
GC pause1-10ms<1msGo

*Python limited by GIL and event loop design


Design Philosophy

Python SDK

  • Pythonic: Follows Python conventions and idioms
  • Simple: Easy for beginners and rapid development
  • Flexible: Dynamic typing allows experimentation
  • Async-first: Built with async from the ground up

Go SDK

  • Idiomatic: Follows Go conventions
  • Explicit: Clear intent and error handling
  • Type-safe: Compile-time verification
  • Concurrent: Natural goroutine-based concurrency

Read Next: Migration from Python for code examples and patterns.