中文 | English
agentsdk-go
An Agent SDK implemented in Go that implements core Claude Code-style runtime capabilities, plus an optional middleware interception layer.
Overview
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.
Dependencies
- External dependencies: anthropic-sdk-go, fsnotify, gopkg.in/yaml.v3, google/uuid, golang.org/x/mod, golang.org/x/net
Features
Core Capabilities
- Multi-model Support: Subagent-level model binding via
ModelFactory interface
- 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
Concurrency Model
- Thread-Safe Runtime: Runtime guards mutable state with internal locks.
- Per-Session Mutual Exclusion: Concurrent
Run/RunStream calls on the same SessionID return ErrConcurrentExecution (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
examples/01-basic - Minimal request/response
examples/02-cli - Interactive REPL with session history
examples/03-http - REST + SSE server on :8080
examples/04-advanced - Full pipeline with middleware, hooks, MCP, sandbox, skills, subagents
examples/05-custom-tools - Selective built-in tools and custom tool registration
examples/07-multimodel - Multi-model configuration demo
examples/06-embed - Embedded filesystem demo
examples/09-task-system - Task tracking and dependencies
examples/10-hooks - Hooks lifecycle events
examples/11-reasoning - Reasoning/thinking model support
examples/12-multimodal - Image and document input
System Architecture
Core Layer
pkg/agent - Agent execution loop coordinating model calls and tool execution
pkg/middleware - Six interception points for extending the request/response lifecycle
pkg/model - Model adapters (Anthropic Claude, OpenAI-compatible)
pkg/tool - Tool registration and execution, including built-in tools and MCP tool support
pkg/message - Message history management with an LRU-based session cache
pkg/api - Unified API surface exposing SDK features
Feature Layer
pkg/core/hooks - Hooks executor covering seven lifecycle events with custom extensions
pkg/mcp - MCP (Model Context Protocol) client bridging external tools (stdio/SSE) with automatic registration
pkg/sandbox - Sandbox isolation layer controlling filesystem and network access policies
pkg/runtime/skills - Skills management supporting scriptable loading and hot reload
pkg/runtime/subagents - Subagent management for multi-agent orchestration and scheduling
pkg/runtime/commands - Commands parser handling slash-command routing and parameter validation
pkg/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).
Architecture Diagram
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
Middleware Interception Points
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
Installation
Requirements
- Go 1.24.0 or later
- Anthropic API Key (required to run examples)
Get the SDK
go get github.com/godeps/agentsdk-go
Quick Start
Basic Example (examples/01-basic)
Run 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-basic
package main
import (
"context"
"fmt"
"log"
"github.com/godeps/agentsdk-go/pkg/api"
"github.com/godeps/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)
}
}
Using Middleware
import (
"context"
"log"
"time"
"github.com/godeps/agentsdk-go/pkg/api"
"github.com/godeps/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()
Streaming Output
// 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)
}
}
Concurrent Usage
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
Runtime methods 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 when Tools is 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.
Examples
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 .claude configs.
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.
Project Structure
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
Configuration
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.
Configuration Example
{
"permissions": {
"allow": ["Bash(ls:*)", "Bash(pwd:*)"],
"deny": ["Read(.env)", "Read(secrets/**)"]
},
"disallowedTools": ["web_search", "web_fetch"],
"env": {
"MY_VAR": "value"
},
"sandbox": {
"enabled": false
}
}
Token Statistics & Auto Compact
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
},
})
Async Bash Execution
// 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",
})
HTTP API
The SDK provides an HTTP server implementation with SSE streaming.
Start the Server
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 probe
POST /v1/run - Synchronous execution returning the full result
POST /v1/run/stream - SSE streaming with real-time progress
Streaming API Example
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 boundaries
content_block_start / content_block_stop - Content block boundaries
content_block_delta - Incremental text output
message_delta - Message-level deltas (usage, stop reason)
agent_start / agent_stop - Agent execution boundaries
iteration_start / iteration_stop - Iteration boundaries
tool_execution_start / tool_execution_result - Tool execution progress
tool_execution_output - Streaming tool output (stdout/stderr)
Testing
Run Tests
# 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.out
Coverage
Coverage numbers change over time; generate a report with go test -coverprofile=coverage.out ./....
Build
Makefile Commands
# 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 clean
The SDK ships with the following built-in tools:
bash - Execute shell commands with working directory and timeout configuration
file_read - Read file contents with offset/limit support
file_write - Write file contents (create or overwrite)
file_edit - Edit files with string replacement
grep - Regex search with recursion and file filtering
glob - File pattern matching with multiple patterns
web_fetch - Fetch web content with prompt-based extraction
web_search - Web search with domain filtering
bash_output - Read output from background bash processes
bash_status - Poll status of background bash processes
kill_task - Terminate a running background bash process
task_create - Create a new task
task_list - List tasks
task_get - Get a task by ID
task_update - Update task status and dependencies
ask_user_question - Ask the user questions during execution
skill - 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.
Security Mechanisms
Three Layers of Defense
- Path whitelist: Restricts filesystem access scope
- Symlink resolution: Prevents path traversal attacks
- Command validation: Blocks execution of dangerous commands
Command Validator
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)
Development Guide
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
}
Add Middleware
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
},
}
Design Principles
KISS (Keep It Simple, Stupid)
- Single responsibility; each module has a clear role
- Avoid overdesign and unnecessary abstractions
Configuration-Driven
- Manage all configuration under
.claude/
- Supports hot reload without restarting the service
- Declarative configuration preferred over imperative code
Modularity
- Independent packages with loose coupling
- Clear interface boundaries
- Easy to test and maintain
Extensibility
- Middleware mechanism for flexible extensions
- Tool system supports custom tool registration
- MCP protocol integrates external tools
Documentation
Tech Stack
License
See LICENSE.