claude-agent-sdk-go

module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 20, 2026 License: MIT

README

Claude Agent SDK for Go

A Go SDK for Claude Code that lets you run Claude as a programmable agent in your Go applications. This is a Go port of the official Python SDK.

The SDK wraps the claude CLI binary as a subprocess and communicates over newline-delimited JSON streams, exposing a clean Go API with Go 1.23 range iterators.

Upstream: ported from claude-agent-sdk-python at 566e41f (2026-03-30), which is 447 commits behind upstream as of 2026-09-20. The feature surface is the Python SDK as it stood in March 2026 — a test cannot fail for a feature that was never ported. Version 0.2.0 also shipped a real bug the green suite could not see: every server-side tool result was discarded, because the SDK matched a wire type the CLI does not send. Fixed in v0.3.0 — upgrade if you are on 0.2.0. See UPSTREAM.md for what the gap means for you, and ROADMAP.md for how it is being closed.

Requirements

Installation

go get github.com/taumatix/claude-agent-sdk-go

Quick start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/taumatix/claude-agent-sdk-go/domains/agent"
    "github.com/taumatix/claude-agent-sdk-go/domains/messages"
)

func main() {
    ctx := context.Background()

    for msg, err := range agent.Query(ctx, "What is 2+2?", agent.DefaultOptions()) {
        if err != nil {
            log.Fatal(err)
        }
        if msg.Assistant != nil {
            for _, block := range msg.Assistant.Content {
                if block.Text != nil {
                    fmt.Print(block.Text.Text)
                }
            }
        }
        if msg.Result != nil {
            fmt.Printf("\nDone in %dms\n", msg.Result.DurationMS)
        }
    }
}

Run the bundled example:

go run ./cmd/example "Explain Go interfaces in one sentence."

Usage

One-shot queries

agent.Query is the simplest entry point. It spawns a claude subprocess, sends the prompt, streams all response messages, and cleans up automatically.

opts := agent.DefaultOptions()
opts.Model = "claude-opus-4-6"
opts.MaxTurns = intPtr(3)
opts.PermissionMode = agent.PermissionModeAcceptEdits

for msg, err := range agent.Query(ctx, "Refactor this function to be more idiomatic", opts) {
    if err != nil {
        log.Fatal(err)
    }
    switch {
    case msg.Assistant != nil:
        printBlocks(msg.Assistant.Content)
    case msg.Result != nil:
        fmt.Printf("Cost: $%.6f\n", *msg.Result.TotalCostUSD)
    }
}

Iteration ends when either a Result message is yielded (clean completion) or an error occurs. Always check err on each iteration.

Stateful multi-turn sessions

Use agent.Client when you need a persistent connection across multiple prompts:

client := agent.NewClient(agent.DefaultOptions())
if err := client.Connect(ctx); err != nil {
    log.Fatal(err)
}
defer client.Disconnect()

for _, prompt := range []string{
    "What files are in the current directory?",
    "Which of those are Go source files?",
    "Summarise what the largest one does.",
} {
    for msg, err := range client.Query(ctx, prompt) {
        if err != nil {
            log.Fatal(err)
        }
        if msg.Assistant != nil {
            printBlocks(msg.Assistant.Content)
        }
    }
}
Handling message types

Every messages.Message has exactly one non-nil field:

for msg, err := range agent.Query(ctx, prompt, opts) {
    if err != nil { log.Fatal(err) }

    switch {
    case msg.User != nil:
        fmt.Println("[user]", textContent(msg.User.Content))

    case msg.Assistant != nil:
        for _, block := range msg.Assistant.Content {
            switch {
            case block.Text != nil:
                fmt.Print(block.Text.Text)
            case block.ToolUse != nil:
                fmt.Printf("[tool: %s]\n", block.ToolUse.Name)
            case block.Thinking != nil:
                fmt.Println("[thinking]", block.Thinking.Thinking)
            }
        }

    case msg.System != nil:
        fmt.Printf("[system: %s]\n", msg.System.Subtype)

    case msg.Result != nil:
        fmt.Printf("[done] turns=%d cost=$%.4f\n",
            msg.Result.NumTurns, *msg.Result.TotalCostUSD)

    case msg.RateLimit != nil:
        fmt.Println("[rate limit]", msg.RateLimit.RateLimitInfo.Status)
    }
}
Tool permission callbacks

Intercept and approve or deny tool calls at runtime:

opts := agent.DefaultOptions()
opts.ToolPermissionHandler = func(
    ctx       context.Context,
    toolName  string,
    input     json.RawMessage,
    toolUseID string,
) (allow bool, reason string) {
    if toolName == "Bash" {
        var in struct{ Command string `json:"command"` }
        json.Unmarshal(input, &in)
        if strings.Contains(in.Command, "rm") {
            return false, "destructive commands are not allowed"
        }
    }
    return true, ""
}
Hook handlers

Register callbacks for lifecycle events (PreToolUse, PostToolUse, Stop, etc.):

opts := agent.DefaultOptions()
opts.HookHandlers = map[string][]agent.HookMatcher{
    "PreToolUse": {
        {
            Matcher: "Bash",   // only fire for Bash tool; "" matches all
            Handler: func(ctx context.Context, callbackID string, input json.RawMessage) (map[string]interface{}, error) {
                fmt.Println("about to run Bash tool")
                return map[string]interface{}{"continue_": true}, nil
            },
        },
    },
    "Stop": {
        {
            Handler: func(ctx context.Context, callbackID string, input json.RawMessage) (map[string]interface{}, error) {
                fmt.Println("session ended")
                return nil, nil
            },
        },
    },
}

The map key is any Claude Code hook event name. The continue_ key in the response map is automatically renamed to continue before being sent to the CLI (Go reserved-word workaround).

MCP servers

Attach external MCP servers to a session:

opts := agent.DefaultOptions()
opts.MCPServers = []agent.MCPServerConfig{
    {
        Name:    "my-tools",
        Type:    "stdio",
        Command: "my-mcp-server",
        Args:    []string{"--port", "8080"},
        Env:     map[string]string{"API_KEY": os.Getenv("API_KEY")},
    },
}

HTTP/SSE servers are also supported (Type: "http" or Type: "sse" with a URL).

Session management
import "github.com/taumatix/claude-agent-sdk-go/domains/sessions"

store, _ := sessions.NewFilesystemStore("")  // defaults to ~/.claude/projects

// List all sessions
list, _ := sessions.ListSessions(store, "")

// Read a specific session
info, msgs, _ := sessions.GetSession(store, sessionID)

// Rename / tag
sessions.RenameSession(store, sessionID, "My important session")
sessions.TagSession(store, sessionID, "archived")

// Fork at a specific message index
newID, _ := sessions.ForkSession(store, sessionID, 5)

// Delete
sessions.DeleteSession(store, sessionID)
Error handling
import (
    sdkerrors "github.com/taumatix/claude-agent-sdk-go/domains/errors"
    "errors"
)

for msg, err := range agent.Query(ctx, prompt, opts) {
    if err != nil {
        var notFound *sdkerrors.CLINotFoundError
        var procErr  *sdkerrors.ProcessError
        var initErr  *sdkerrors.InitializeError

        switch {
        case errors.As(err, &notFound):
            fmt.Println("claude CLI not found — install with: npm install -g @anthropic-ai/claude-code")
        case errors.As(err, &procErr):
            fmt.Printf("process exited %d: %s\n", procErr.ExitCode, procErr.Stderr)
        case errors.As(err, &initErr):
            fmt.Println("handshake failed:", initErr.Msg)
        default:
            log.Fatal(err)
        }
        return
    }
}
Custom transport (testing)

Inject a fake transport to test code that drives the SDK without spawning a real process:

type FakeTransport struct {
    messages [][]byte
    idx      int
}

func (f *FakeTransport) Send(_ context.Context, _ []byte) error { return nil }
func (f *FakeTransport) Receive(_ context.Context) ([]byte, error) {
    if f.idx >= len(f.messages) {
        <-make(chan struct{}) // block
    }
    b := f.messages[f.idx]; f.idx++
    return b, nil
}
func (f *FakeTransport) Close() error { return nil }

// In tests:
opts := agent.DefaultOptions()
opts.Transport = &FakeTransport{messages: [][]byte{...}}

See domains/agent/client_test.go for a complete worked example with automatic initialize-handshake handling.

Runtime controls
client := agent.NewClient(opts)
client.Connect(ctx)

// Interrupt the currently running generation
client.Interrupt(ctx)

// Change permission mode mid-session
client.SetPermissionMode(ctx, agent.PermissionModeAcceptEdits)

// Swap models mid-session
client.SetModel(ctx, "claude-haiku-4-5")

Configuration reference

See docs/api.md for the full API reference.

See docs/architecture.md for architectural decisions and internals.

Project structure

domains/
  agent/           # Public API — Query(), Client, Options, callbacks
  messages/        # Message and ContentBlock types
  transport/       # Transport interface
    subprocess/    # Real CLI subprocess transport
  sessions/        # Session management (list, rename, tag, fork, delete)
  errors/          # Typed error hierarchy
shared/
  jsonlines/       # Newline-delimited JSON I/O
  reqid/           # Unique request ID generation
cmd/
  example/         # Runnable example

Versioning and stability

This module follows Semantic Versioning. While the major version is 0, the public API is still settling, but breaking changes will not be made casually:

  • Additive changes (new functions, new option fields) land in minor releases.
  • Any incompatible change to an exported symbol requires a minor bump while 0.x, and is called out in CHANGELOG.md with the migration path.
  • Every pull request runs apidiff against its base and fails on incompatible public API changes, so a break is always a deliberate decision rather than an accident.

Pin a version in your go.mod and read the changelog before upgrading.

License

MIT — see LICENSE.

This project is a Go port of the official Claude Agent SDK for Python by Anthropic, PBC.

Directories

Path Synopsis
cmd
example command
Package main demonstrates basic usage of the Claude Agent SDK.
Package main demonstrates basic usage of the Claude Agent SDK.
domains
errors
Package errors defines all SDK error types.
Package errors defines all SDK error types.
messages
Package messages defines the public SDK message types, converted from wire protocol types.
Package messages defines the public SDK message types, converted from wire protocol types.
protocol
Package protocol defines the wire JSON types for the Claude Code CLI control protocol.
Package protocol defines the wire JSON types for the Claude Code CLI control protocol.
sessions
Package sessions provides types and operations for managing Claude session history.
Package sessions provides types and operations for managing Claude session history.
transport
Package transport defines the Transport interface used by the agent SDK.
Package transport defines the Transport interface used by the agent SDK.
transport/subprocess
Package subprocess implements a Transport that communicates with the Claude Code CLI via stdin/stdout using newline-delimited JSON.
Package subprocess implements a Transport that communicates with the Claude Code CLI via stdin/stdout using newline-delimited JSON.
shared
jsonlines
Package jsonlines provides utilities for reading and writing newline-delimited JSON.
Package jsonlines provides utilities for reading and writing newline-delimited JSON.
reqid
Package reqid generates unique request IDs for correlation.
Package reqid generates unique request IDs for correlation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL