agentsdk-go

module
v0.1.8 Latest Latest
Warning

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

Go to latest
Published: Mar 3, 2026 License: MIT

README

中文 | English

agentsdk-go

An Agent SDK implemented in Go that implements core Claude Code-style runtime capabilities, plus an optional middleware interception layer.

Fork of cexll/agentsdk-go — extended with realtime progress events, per-call tool logging, and additional builtin tools (memory read/write/search, skill listing).

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
  • Token Stats Persistence: Aggregated stats are persisted at .claude/token_stats.json
  • 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
  • Compact Tool Context: Model-facing tool descriptions are compacted to reduce static prompt tokens
Extended Builtin Tools
  • memory_write: Unified memory editor with targets today (memory/YYYY-MM-DD.md), projects (memory/projects.md), lessons (memory/lessons.md), or explicit path (restricted to MEMORY.md and memory/*.md).
  • memory_get: Read specific line ranges from memory files (MEMORY.md or memory/*.md) after memory_search to keep context minimal.
  • memory_search: Semantic keyword search across MEMORY.md + memory/*.md, returning ranked snippets with path and line ranges.
  • list_skills: List available skills in the workspace .claude/skills/ directory.
  • Realtime Progress Events: Each tool call emits a progress event with the current tool name and parameters (not cumulative).
  • Web Search Resilience: web_search uses normalized cache keys + short TTL cache and suppresses repeated 401/403/404 failures for a short cooldown.
Auto-Recall (memory injection)

When AutoRecall: true is set in Options, the runtime automatically searches memory files before each agent turn and prepends the top matching snippets as <relevant-memories> context — without injecting the entire file.

user prompt
  ↓
memory_search(prompt, topN=3)  ← keyword search across MEMORY.md + memory/*.md (including projects/lessons/today files)
  ↓
<relevant-memories>
- snippet 1 ...
- snippet 2 ...
</relevant-memories>

[original prompt]
  ↓
agent turn

This mirrors openclaw's pre-turn memory recall. The agent receives only relevant snippets, not the full file, keeping token usage small. AutoRecallMaxResults (default 3) controls how many snippets are injected.

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/05-multimodel - Multi-model configuration demo

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, currently supports Anthropic Claude
  • 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/riverfjs/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"
    "log"
    "os"

    "github.com/cexll/agentsdk-go/pkg/api"
    "github.com/cexll/agentsdk-go/pkg/model"
)

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

    // Create the model provider
    provider := model.NewAnthropicProvider(
        model.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")),
        model.WithModel("claude-sonnet-4-5"),
    )

    // Initialize the runtime
    runtime, err := api.New(ctx, api.Options{
        ProjectRoot:   ".",
        ModelFactory:  provider,
    })
    if err != nil {
        log.Fatal(err)
    }
    defer runtime.Close()

    // Execute a task
    result, err := runtime.Run(ctx, api.Request{
        Prompt:    "List files in the current directory",
        SessionID: "demo",
    })
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("Output: %s", result.Output)
}
Using Middleware
import (
    "context"
    "log"
    "time"

    "github.com/cexll/agentsdk-go/pkg/middleware"
)

// Logging middleware
loggingMiddleware := middleware.Middleware{
    BeforeAgent: func(ctx context.Context, req *middleware.AgentRequest) (*middleware.AgentRequest, error) {
        log.Printf("[REQUEST] %s", req.Input)
        req.Meta["start_time"] = time.Now()
        return req, nil
    },
    AfterAgent: func(ctx context.Context, resp *middleware.AgentResponse) (*middleware.AgentResponse, error) {
        duration := time.Since(resp.Meta["start_time"].(time.Time))
        log.Printf("[RESPONSE] %s (elapsed: %v)", resp.Output, duration)
        return resp, nil
    },
}

// Inject middleware
runtime, err := api.New(ctx, api.Options{
    ProjectRoot:   ".",
    ModelFactory:  provider,
    Middleware:    []middleware.Middleware{loggingMiddleware},
})
if err != nil {
    log.Fatal(err)
}
defer runtime.Close()
Streaming Output
// Use the streaming API to get real-time progress
events := runtime.RunStream(ctx, api.Request{
    Prompt:    "Analyze the repository structure",
    SessionID: "analysis",
})

for event := range events {
    switch event.Type {
    case "content_block_delta":
        fmt.Print(event.Delta.Text)
    case "tool_execution_start":
        fmt.Printf("\n[Tool Execution] %s\n", event.ToolName)
    case "tool_execution_stop":
        fmt.Printf("[Tool Result] %s\n", event.Output)
    case "final_response":
        // Complete api.Response emitted at stream end.
        fmt.Println("\n[Stream Completed]")
    }
}
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
runtime, _ := api.New(ctx, api.Options{
    ProjectRoot:  ".",
    ModelFactory: provider,
})
defer runtime.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()
        result, err := runtime.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
        }
        log.Printf("Task %d completed: %s", id, result.Output)
    }(i)
}
wg.Wait()

// Requests with the same session ID must be serialized by the caller
_, _ = runtime.Run(ctx, api.Request{Prompt: "First", SessionID: "same"})
_, _ = runtime.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
Customize Tool Registration

Choose which built-ins to load and append your own tools:

rt, err := api.New(ctx, api.Options{
    ProjectRoot:         ".",
    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→全部内置;空切片→禁用内置;非空→只启用列出的内置(大小写不敏感,下划线命名)。
  • CustomTools: 追加自定义工具;当 Tools 非空时被忽略。
  • Tools: 旧字段,非空时完全接管工具集(保持向后兼容)。

See a runnable demo in examples/05-custom-tools.

Examples

The repository includes five progressive examples aligned to the new five-layer path:

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

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
│   └── 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
runtime, err := api.New(ctx, api.Options{
    ProjectRoot:  ".",
    ModelFactory: provider,
    // Enable aggregate token tracking
    TokenTracking: true,
    // Optional callback for per-call token usage
    TokenCallback: func(stats api.TokenStats) {
        log.Printf("Tokens: input=%d, output=%d, cache_read=%d",
            stats.InputTokens, stats.OutputTokens, stats.CacheRead)
    },
    // Auto compact settings
    CompactThreshold: 100000, // Trigger compact at 100k tokens
    CompactModel:     "claude-haiku-4-5", // Use cheaper model for summarization
})
Async Bash Execution
// Start background task
result, _ := runtime.Run(ctx, api.Request{
    Prompt:    "Run 'sleep 10 && echo done' in background",
    SessionID: "demo",
})

// Later, check task output
result, _ = runtime.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:

  • agent_start / agent_stop - Agent execution boundaries
  • iteration_start / iteration_stop - Iteration boundaries
  • message_start / message_stop - Message boundaries
  • content_block_delta - Incremental text output
  • tool_execution_start / tool_execution_stop - Tool execution progress

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

Built-in Tools

The SDK ships with the following built-in tools:

Core Tools (under pkg/tool/builtin/)
  • 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
Extended Tools
  • 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
  1. Path whitelist: Restricts filesystem access scope
  2. Symlink resolution: Prevents path traversal attacks
  3. 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

Add a Custom Tool

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{
        Name:   t.Name(),
        Output: "Execution result",
    }, nil
}
Add Middleware
customMiddleware := middleware.Middleware{
    BeforeAgent: func(ctx context.Context, req *middleware.AgentRequest) (*middleware.AgentRequest, error) {
        // Pre-request handling
        return req, nil
    },
    AfterAgent: func(ctx context.Context, resp *middleware.AgentResponse) (*middleware.AgentResponse, error) {
        // Post-response handling
        return resp, nil
    },
    BeforeModel: func(ctx context.Context, msgs []message.Message) ([]message.Message, error) {
        // Before model call
        return msgs, nil
    },
    AfterModel: func(ctx context.Context, output *agent.ModelOutput) (*agent.ModelOutput, error) {
        // After model call
        return output, nil
    },
    BeforeTool: func(ctx context.Context, call *middleware.ToolCall) (*middleware.ToolCall, error) {
        // Before tool execution
        return call, nil
    },
    AfterTool: func(ctx context.Context, result *middleware.ToolResult) (*middleware.ToolResult, error) {
        // After tool execution
        return result, 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.

Directories

Path Synopsis
cmd
cli command
examples
01-basic command
02-cli command
03-http command
04-advanced command
05-custom-tools command
05-embed command
05-feature-test command
05-multimodel command
Package main demonstrates multi-model support with subagent-level model binding.
Package main demonstrates multi-model support with subagent-level model binding.
05-multimodel-llm command
Package main demonstrates multi-LLM provider support.
Package main demonstrates multi-LLM provider support.
06-v0.4.0-features command
Package main demonstrates v0.4.0 new features WITHOUT requiring API calls.
Package main demonstrates v0.4.0 new features WITHOUT requiring API calls.
09-task-system command
10-hooks command
pkg
api
gitignore
Package gitignore provides gitignore pattern matching functionality.
Package gitignore provides gitignore pattern matching functionality.
mcp
prompts
Package prompts provides compile-time parsing of .claude directory contents from an embed.FS, returning ready-to-use registration structures for the SDK.
Package prompts provides compile-time parsing of .claude directory contents from an embed.FS, returning ready-to-use registration structures for the SDK.

Jump to

Keyboard shortcuts

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