中文 | English
An Agent SDK implemented in Go that implements core Claude Code-style runtime capabilities, plus an optional middleware interception layer.
agentsdk-go is a modular agent development framework that implements core Claude Code-style runtime capabilities (Hooks, MCP, Sandbox, Skills, Subagents, Commands, Tasks) and optionally exposes a six-point middleware interception mechanism. The SDK supports deployment scenarios across CLI, CI/CD, and enterprise platforms.
- External dependencies: anthropic-sdk-go, fsnotify, gopkg.in/yaml.v3, google/uuid, golang.org/x/mod, golang.org/x/net
- Multi-model Support: Subagent-level model binding via
ModelFactoryinterface - Token Statistics: Comprehensive token usage tracking with automatic accumulation
- Auto Compact: Automatic context compression when token threshold reached
- Async Bash: Background command execution with task management
- Rules Configuration:
.claude/rules/directory support with hot-reload - OpenTelemetry: Distributed tracing with span propagation
- UUID Tracking: Request-level UUID for observability
- Thread-Safe Runtime: Runtime guards mutable state with internal locks.
- Per-Session Mutual Exclusion: Concurrent
Run/RunStreamcalls on the sameSessionIDreturnErrConcurrentExecution(callers can queue/retry if they want serialization). - Shutdown:
Runtime.Close()waits for in-flight requests to complete. - Validation: run
go test -race ./...after changes.
examples/01-basic- Minimal request/responseexamples/02-cli- Interactive REPL with session historyexamples/03-http- REST + SSE server on :8080examples/04-advanced- Full pipeline with middleware, hooks, MCP, sandbox, skills, subagentsexamples/05-custom-tools- Selective built-in tools and custom tool registrationexamples/07-multimodel- Multi-model configuration demoexamples/06-embed- Embedded filesystem demoexamples/09-task-system- Task tracking and dependenciesexamples/10-hooks- Hooks lifecycle eventsexamples/11-reasoning- Reasoning/thinking model supportexamples/12-multimodal- Image and document input
pkg/agent- Agent execution loop coordinating model calls and tool executionpkg/middleware- Six interception points for extending the request/response lifecyclepkg/model- Model adapters (Anthropic Claude, OpenAI-compatible)pkg/tool- Tool registration and execution, including built-in tools and MCP tool supportpkg/message- Message history management with an LRU-based session cachepkg/api- Unified API surface exposing SDK features
pkg/core/hooks- Hooks executor covering seven lifecycle events with custom extensionspkg/mcp- MCP (Model Context Protocol) client bridging external tools (stdio/SSE) with automatic registrationpkg/sandbox- Sandbox isolation layer controlling filesystem and network access policiespkg/runtime/skills- Skills management supporting scriptable loading and hot reloadpkg/runtime/subagents- Subagent management for multi-agent orchestration and schedulingpkg/runtime/commands- Commands parser handling slash-command routing and parameter validationpkg/runtime/tasks- Task tracking and dependency management
In addition, the feature layer includes supporting packages such as pkg/config (configuration loading/hot reload), pkg/core/events (event bus), and pkg/security (command and path validation).
flowchart TB
subgraph Core
API[pkg/api] --> Agent[pkg/agent]
Agent --> Model[pkg/model]
Agent --> Tool[pkg/tool]
Agent --> Message[pkg/message]
Middleware[pkg/middleware] -. intercepts .-> Agent
end
subgraph Feature
Config[pkg/config]
Hooks[pkg/core/hooks]
Events[pkg/core/events]
Runtime[pkg/runtime/*]
MCP[pkg/mcp]
Sandbox[pkg/sandbox]
Security[pkg/security]
end
Config --> API
Hooks --> Agent
Events --> Agent
Runtime --> Agent
MCP --> Tool
Tool --> Sandbox
Tool --> Security
The SDK exposes interception at critical stages of request handling:
User request
↓
before_agent ← Request validation, audit logging
↓
Agent loop
↓
before_model ← Prompt processing, context optimization
↓
Model invocation
↓
after_model ← Result filtering, content checks
↓
before_tool ← Tool parameter validation
↓
Tool execution
↓
after_tool ← Result post-processing
↓
after_agent ← Response formatting, metrics collection
↓
User response
- Go 1.24.0 or later
- Anthropic API Key (required to run examples)
go get github.com/cexll/agentsdk-goRun the minimal starter in examples/01-basic:
# 1. Set up environment
cp .env.example .env
# Edit .env: ANTHROPIC_API_KEY=sk-ant-your-key-here
source .env
# 2. Run the example
go run ./examples/01-basicpackage main
import (
"context"
"fmt"
"log"
"github.com/cexll/agentsdk-go/pkg/api"
"github.com/cexll/agentsdk-go/pkg/model"
)
func main() {
ctx := context.Background()
// Create the model provider (reads ANTHROPIC_API_KEY from env automatically)
provider := &model.AnthropicProvider{ModelName: "claude-sonnet-4-5-20250929"}
// Initialize the runtime
rt, err := api.New(ctx, api.Options{
ModelFactory: provider,
})
if err != nil {
log.Fatal(err)
}
defer rt.Close()
// Execute a task
resp, err := rt.Run(ctx, api.Request{
Prompt: "List files in the current directory",
SessionID: "demo",
})
if err != nil {
log.Fatal(err)
}
if resp.Result != nil {
fmt.Println(resp.Result.Output)
}
}import (
"context"
"log"
"time"
"github.com/cexll/agentsdk-go/pkg/api"
"github.com/cexll/agentsdk-go/pkg/middleware"
)
// Logging middleware using the Funcs helper
loggingMiddleware := middleware.Funcs{
Identifier: "logging",
OnBeforeAgent: func(ctx context.Context, st *middleware.State) error {
st.Values["start_time"] = time.Now()
log.Println("[REQUEST] agent starting")
return nil
},
OnAfterAgent: func(ctx context.Context, st *middleware.State) error {
if start, ok := st.Values["start_time"].(time.Time); ok {
log.Printf("[RESPONSE] elapsed: %v", time.Since(start))
}
return nil
},
}
// Inject middleware
rt, err := api.New(ctx, api.Options{
ModelFactory: provider,
Middleware: []middleware.Middleware{loggingMiddleware},
})
if err != nil {
log.Fatal(err)
}
defer rt.Close()// Use the streaming API to get real-time progress
events, err := rt.RunStream(ctx, api.Request{
Prompt: "Analyze the repository structure",
SessionID: "analysis",
})
if err != nil {
log.Fatal(err)
}
for event := range events {
switch event.Type {
case api.EventContentBlockDelta:
if event.Delta != nil {
fmt.Print(event.Delta.Text)
}
case api.EventToolExecutionStart:
fmt.Printf("\n[Tool] %s\n", event.Name)
case api.EventToolExecutionResult:
fmt.Printf("[Result] %v\n", event.Output)
}
}Runtime supports concurrent calls across different SessionIDs. Calls sharing the same SessionID are mutually exclusive.
// Same runtime can be safely used from multiple goroutines
rt, _ := api.New(ctx, api.Options{
ModelFactory: provider,
})
defer rt.Close()
// Concurrent requests with different sessions execute in parallel
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
resp, err := rt.Run(ctx, api.Request{
Prompt: fmt.Sprintf("Task %d", id),
SessionID: fmt.Sprintf("session-%d", id), // Different sessions run concurrently
})
if err != nil {
log.Printf("Task %d failed: %v", id, err)
return
}
if resp.Result != nil {
log.Printf("Task %d completed: %s", id, resp.Result.Output)
}
}(i)
}
wg.Wait()
// Requests with the same session ID must be serialized by the caller
_, _ = rt.Run(ctx, api.Request{Prompt: "First", SessionID: "same"})
_, _ = rt.Run(ctx, api.Request{Prompt: "Second", SessionID: "same"})Concurrency Guarantees:
- All
Runtimemethods are safe for concurrent use across sessions - Same-session concurrent requests return
ErrConcurrentExecution - Different-session requests execute in parallel
Runtime.Close()gracefully waits for all in-flight requests- No manual locking required for different sessions; serialize/queue same-session calls in the caller
Choose which built-ins to load and append your own tools:
rt, err := api.New(ctx, api.Options{
ModelFactory: provider,
EnabledBuiltinTools: []string{"bash", "file_read"}, // nil = all, empty = none
CustomTools: []tool.Tool{&EchoTool{}}, // appended when Tools is empty
})
if err != nil {
log.Fatal(err)
}
defer rt.Close()EnabledBuiltinTools: nil → all built-ins; empty slice → none; non-empty → only the listed names (case-insensitive, underscore naming).CustomTools: appended after built-ins; ignored whenToolsis non-empty.Tools: legacy field — when non-empty it takes over the entire tool set (kept for backward compatibility).
See a runnable demo in examples/05-custom-tools.
The repository includes progressive examples:
01-basic– minimal single request/response.02-cli– interactive REPL with session history and optional config load.03-http– REST + SSE server on:8080.04-advanced– full pipeline exercising middleware, hooks, MCP, sandbox, skills, and subagents.05-custom-tools– selective built-ins plus custom tool registration.07-multimodel– multi-model tier configuration.06-embed– embedded filesystem for bundled.claudeconfigs.09-task-system– task tracking and dependency management.10-hooks– hooks lifecycle events.11-reasoning– reasoning/thinking model support.12-multimodal– image and document input.
agentsdk-go/
├── pkg/ # Core packages
│ ├── agent/ # Agent core loop
│ ├── middleware/ # Middleware system
│ ├── model/ # Model adapters
│ ├── tool/ # Tool system
│ │ └── builtin/ # Built-in tools (bash, file, grep, glob)
│ ├── message/ # Message history management
│ ├── api/ # Unified SDK interface
│ ├── config/ # Configuration loading
│ ├── core/
│ │ ├── events/ # Event bus
│ │ └── hooks/ # Hooks executor
│ ├── sandbox/ # Sandbox isolation
│ ├── mcp/ # MCP client
│ ├── runtime/
│ │ ├── skills/ # Skills management
│ │ ├── subagents/ # Subagents management
│ │ ├── commands/ # Commands parsing
│ │ └── tasks/ # Task tracking and dependencies
│ └── security/ # Security utilities
├── cmd/cli/ # CLI entrypoint
├── examples/ # Example code
│ ├── 01-basic/ # Minimal single request/response
│ ├── 02-cli/ # CLI REPL with session history
│ ├── 03-http/ # HTTP server (REST + SSE)
│ ├── 04-advanced/ # Full pipeline (middleware, hooks, MCP, sandbox, skills, subagents)
│ └── 05-custom-tools/ # Custom tool registration and selective built-in tools
├── test/integration/ # Integration tests
└── docs/ # Documentation
The SDK uses the .claude/ directory for configuration, compatible with Claude Code:
.claude/
├── settings.json # Project configuration
├── settings.local.json # Local overrides (gitignored)
├── rules/ # Rules definitions (markdown)
├── skills/ # Skills definitions
├── commands/ # Slash command definitions
└── agents/ # Subagents definitions
Configuration precedence (high → low):
- Runtime overrides (CLI/API-provided)
.claude/settings.local.json.claude/settings.json- Built-in defaults (shipped with the SDK)
~/.claude is no longer read; use project-scoped files for all configuration.
{
"permissions": {
"allow": ["Bash(ls:*)", "Bash(pwd:*)"],
"deny": ["Read(.env)", "Read(secrets/**)"]
},
"disallowedTools": ["web_search", "web_fetch"],
"env": {
"MY_VAR": "value"
},
"sandbox": {
"enabled": false
}
}rt, err := api.New(ctx, api.Options{
ModelFactory: provider,
// Token tracking with callback
TokenTracking: true,
TokenCallback: func(stats api.TokenStats) {
log.Printf("Tokens: input=%d, output=%d, cache_read=%d",
stats.InputTokens, stats.OutputTokens, stats.CacheRead)
},
// Auto compact settings
AutoCompact: api.CompactConfig{
Enabled: true,
Threshold: 0.8, // Trigger compact at 80% of token limit
PreserveCount: 5, // Keep last 5 messages intact
SummaryModel: "claude-haiku-4-5", // Use cheaper model for summarization
},
})// Start background task
resp, _ := rt.Run(ctx, api.Request{
Prompt: "Run 'sleep 10 && echo done' in background",
SessionID: "demo",
})
// Later, check task output
resp, _ = rt.Run(ctx, api.Request{
Prompt: "Get output of background task",
SessionID: "demo",
})The SDK provides an HTTP server implementation with SSE streaming.
export ANTHROPIC_API_KEY=sk-ant-...
cd examples/03-http
go run .The server listens on :8080 by default and exposes these endpoints:
GET /health- Liveness probePOST /v1/run- Synchronous execution returning the full resultPOST /v1/run/stream- SSE streaming with real-time progress
curl -N -X POST http://localhost:8080/v1/run/stream \
-H 'Content-Type: application/json' \
-d '{
"prompt": "List the current directory",
"session_id": "demo"
}'The response format follows the Anthropic Messages API and includes these event types:
message_start/message_stop- Message boundariescontent_block_start/content_block_stop- Content block boundariescontent_block_delta- Incremental text outputmessage_delta- Message-level deltas (usage, stop reason)agent_start/agent_stop- Agent execution boundariesiteration_start/iteration_stop- Iteration boundariestool_execution_start/tool_execution_result- Tool execution progresstool_execution_output- Streaming tool output (stdout/stderr)
# All tests
go test ./...
# Core module tests
go test ./pkg/agent/... ./pkg/middleware/... ./pkg/model/...
# Integration tests
go test ./test/integration/...
# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.outCoverage numbers change over time; generate a report with go test -coverprofile=coverage.out ./....
# Run tests
make test
# Generate coverage report
make coverage
# Lint code
make lint
# Build CLI tool
make agentctl
# Install into GOPATH
make install
# Clean build artifacts
make cleanThe SDK ships with the following built-in tools:
bash- Execute shell commands with working directory and timeout configurationfile_read- Read file contents with offset/limit supportfile_write- Write file contents (create or overwrite)file_edit- Edit files with string replacementgrep- Regex search with recursion and file filteringglob- File pattern matching with multiple patterns
web_fetch- Fetch web content with prompt-based extractionweb_search- Web search with domain filteringbash_output- Read output from background bash processesbash_status- Poll status of background bash processeskill_task- Terminate a running background bash processtask_create- Create a new tasktask_list- List taskstask_get- Get a task by IDtask_update- Update task status and dependenciesask_user_question- Ask the user questions during executionskill- Execute skills from.claude/skills/slash_command- Execute slash commands from.claude/commands/task- Spawn subagents for complex tasks (CLI/Platform entrypoints only)
All built-in tools obey sandbox policies and are constrained by the path whitelist and command validator. Use EnabledBuiltinTools to selectively enable tools or CustomTools to register your own implementations.
- Path whitelist: Restricts filesystem access scope
- Symlink resolution: Prevents path traversal attacks
- Command validation: Blocks execution of dangerous commands
Located at pkg/security/validator.go, it blocks the following by default:
- Destructive commands:
dd,mkfs,fdisk,shutdown,reboot - Hazardous delete patterns:
rm -rf,rm -r,rmdir -p - Shell metacharacters:
|,;,&,>,<,`(in Platform mode)
Implement the tool.Tool interface:
type CustomTool struct{}
func (t *CustomTool) Name() string {
return "custom_tool"
}
func (t *CustomTool) Description() string {
return "Tool description"
}
func (t *CustomTool) Schema() *tool.JSONSchema {
return &tool.JSONSchema{
Type: "object",
Properties: map[string]interface{}{
"param": map[string]interface{}{
"type": "string",
"description": "Parameter description",
},
},
Required: []string{"param"},
}
}
func (t *CustomTool) Execute(ctx context.Context, params map[string]any) (*tool.ToolResult, error) {
// Tool implementation
return &tool.ToolResult{
Success: true,
Output: "Execution result",
}, nil
}customMiddleware := middleware.Funcs{
Identifier: "custom",
OnBeforeAgent: func(ctx context.Context, st *middleware.State) error {
// Pre-request handling
return nil
},
OnAfterAgent: func(ctx context.Context, st *middleware.State) error {
// Post-response handling
return nil
},
OnBeforeModel: func(ctx context.Context, st *middleware.State) error {
// Before model call; st.ModelInput holds the model.Request
return nil
},
OnAfterModel: func(ctx context.Context, st *middleware.State) error {
// After model call; st.ModelOutput holds the *model.Response
return nil
},
OnBeforeTool: func(ctx context.Context, st *middleware.State) error {
// Before tool execution; st.ToolCall holds the agent.ToolCall
return nil
},
OnAfterTool: func(ctx context.Context, st *middleware.State) error {
// After tool execution; st.ToolResult holds the agent.ToolResult
return nil
},
}- Single responsibility; each module has a clear role
- Avoid overdesign and unnecessary abstractions
- Manage all configuration under
.claude/ - Supports hot reload without restarting the service
- Declarative configuration preferred over imperative code
- Independent packages with loose coupling
- Clear interface boundaries
- Easy to test and maintain
- Middleware mechanism for flexible extensions
- Tool system supports custom tool registration
- MCP protocol integrates external tools
- Architecture - Detailed architecture analysis
- Getting Started - Step-by-step tutorial
- API Reference - API documentation
- Security - Security configuration guide
- Custom Tools Guide - Custom tool registration and usage
- Trace System - OpenTelemetry and HTTP trace setup
- Smart Defaults - Auto-configuration by EntryPoint
- HTTP API Guide - HTTP server instructions
- Go 1.24.0+
- anthropic-sdk-go - Anthropic official SDK
- modelcontextprotocol/go-sdk - Official MCP SDK
- fsnotify - Filesystem watchers
- yaml.v3 - YAML parser
- google/uuid - UUID utilities
- golang.org/x/mod - Module utilities
- golang.org/x/net - Extended net packages
See LICENSE.