An open API service indexing awesome lists of open source software.

https://github.com/denizumutdereli/go-deepagent

Deepagents for Go, the easiest way to write LLM-based programs in Go
https://github.com/denizumutdereli/go-deepagent

agent-state agents deepagent langchain llms

Last synced: 3 months ago
JSON representation

Deepagents for Go, the easiest way to write LLM-based programs in Go

Awesome Lists containing this project

README

          

# go-deepagent

A production-grade, recursive multi-agent LLM framework for Go. Build hierarchical AI agent systems with typed state management, concurrent execution, human-in-the-loop controls, and full observability — all with a single recursive config struct.

Built on top of [langchaingo](https://github.com/tmc/langchaingo).

[![Go Reference](https://pkg.go.dev/badge/github.com/denizumutdereli/go-deepagent.svg)](https://pkg.go.dev/github.com/denizumutdereli/go-deepagent)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

## Features

### Core
- **Recursive Agent Hierarchy** — Orchestrator delegates to sub-agents, which can have their own sub-agents. Same config struct at every level.
- **Multi-Provider LLM Support** — OpenAI, Anthropic (Claude), Google (Gemini), XAI (Grok), Groq, Ollama. Mix providers across agents.
- **ReAct Execution Loop** — Agents reason, act (call tools), observe results, and iterate until they reach an answer.
- **Parallel Tool Execution** — Multiple tool calls execute concurrently with goroutines and proper synchronization.
- **Built-in VFS Tools** — Every agent gets `ls`, `read_file`, `write_file`, `edit_file`, `grep`, `glob` out of the box.
- **Skills System** — Load domain knowledge from Markdown files (with YAML frontmatter) and inject into agent prompts.
- **Custom Tools** — Implement the standard `langchaingo/tools.Tool` interface. Agents discover and call them automatically.

### State Management
- **Typed Agent State** — Structured state container with concurrency-safe reducers (messages: append-only, files: merge-by-path, todos: last-writer-wins, skills metadata: merge-by-name, custom: deep merge).
- **Thread Lifecycle** — Full thread lifecycle management with status derivation (idle → busy → interrupted → error), busy locks, and self-healing for orphaned runs.
- **Multitask Strategies** — `reject`, `rollback`, `interrupt`, `enqueue` — controls behavior when a new run is submitted while a thread is busy.
- **WAL Checkpointing** — Write-ahead log checkpoints with parent chains, pending writes, interrupt state, and time-travel debugging via state history.
- **Thread Fork** — Clone a thread with all its messages and checkpoints to a new thread ID.

### Middleware Ecosystem
- **Human-in-the-Loop (HITL)** — Interrupt agent execution before or after specific tool calls for human approval. Per-tool or wildcard interrupt configs.
- **Model Call Limit** — Configurable per-run LLM call limit with graceful end or throw behavior.
- **Memory Persistence** — Load AGENTS.md memory files into system prompts with hot-reload support.
- **Progressive Skills Disclosure** — Only inject skill names and descriptions; agents read full instructions on-demand via `read_file`.
- **Model Retry** — Exponential backoff retry for failed LLM calls (configurable max retries, backoff multiplier).
- **Model Fallback** — Automatic fallback to cheaper models on primary model failure (lazy-initialized fallback chain).
- **Tool Retry** — Retry failed tool calls with configurable strategy.
- **Tool Result Eviction** — Evict large tool outputs to VFS backend, replace with head/tail preview to stay within context limits.
- **Patch Tool Calls** — Automatically fix dangling tool calls (AI messages with tool_calls but no corresponding ToolMessage).
- **Token Usage Tracking** — Per-run token usage accumulation across all LLM calls, thread-safe for concurrent subagents.
- **Auth Middleware** — Multi-tenant authentication and authorization with pluggable providers (API key, custom). Per-tool action whitelisting.
- **Tracing** — OpenTelemetry-compatible span instrumentation for agent invocations and tool calls. Pluggable backends (in-memory, noop, or custom).

### Infrastructure
- **SSE Streaming** — Server-Sent Events with per-run event streams, multiple concurrent subscribers, resumability via monotonic event IDs, and keepalive.
- **Webhook Support** — Async webhook delivery on run completion/failure with retry, exponential backoff, event type filtering, and runtime registration.
- **Agent Protocol HTTP Server** — REST API with SSE streaming, background runs, cancellation. LangGraph Studio compatible.
- **Pluggable Storage** — In-memory (default), MongoDB, Redis (ephemeral runs), or hybrid (MongoDB + Redis). Implement the `Store` interface for your own backend.
- **Cross-Thread VFS** — Persistent key-value backed virtual filesystem (`afero.Fs` interface) for cross-conversation file storage with namespace isolation.
- **General-Purpose Subagent** — Convenience factory for creating general-purpose task delegation subagents.
- **Concurrent Spawn Limits** — Configurable semaphore-based limit on concurrent subagent spawns.

## Installation

```bash
go get github.com/denizumutdereli/go-deepagent
```

## Quick Start

```go
package main

import (
"context"
"fmt"
"os"

"github.com/denizumutdereli/go-deepagent/pkg/agent"
)

func main() {
app, err := agent.New(agent.AgentConfig{
Name: "assistant",
Model: "gpt-4.1",
Prompt: "You are a helpful assistant.",
}, os.Getenv("OPENAI_API_KEY"))
if err != nil {
panic(err)
}

result, err := app.Process(context.Background(), "What is the capital of France?")
if err != nil {
panic(err)
}
fmt.Println(result)
}
```

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────────┤
│ pkg/agent │ pkg/server │ pkg/store │
│ ───────────────── │ ───────────── │ ───────────────── │
│ App │ HTTP Server │ MemoryStore │
│ ReactExecutor │ SSE Streaming │ MongoStore │
│ AgentState + Reducers│ Background Runs │ RedisStore │
│ ThreadManager │ Cancellation │ HybridStore │
│ CheckpointManager │ │ │
│ StreamManager │ pkg/protocol │ │
│ WebhookManager │ ───────────── │ │
│ MiddlewareChain │ Thread, Run │ │
│ StoreBackend (VFS) │ Checkpoint │ │
│ Tracing │ Message │ │
│ Auth │ StoreItem │ │
└─────────────────────────────────────────────────────────────────┘
```

### Package Overview

| Package | Description |
|---------|-------------|
| `pkg/agent` | Core agent engine — ReAct loop, typed state, thread lifecycle, middleware, streaming, webhooks, auth, tracing |
| `pkg/server` | Agent Protocol HTTP server with SSE streaming, background runs, cancellation |
| `pkg/store` | Storage backends — in-memory, MongoDB, Redis (ephemeral), hybrid (MongoDB + Redis) |
| `pkg/protocol` | Shared types — Thread, Run, Checkpoint, Message, StoreItem |

## Agent Configuration

The entire system is configured with a single recursive struct:

```go
type AgentConfig struct {
// Identity
Name string // unique name for this agent
Description string // shown to parent agent for routing

// Prompt (required)
Prompt string

// Model — supports "provider:model" format
Provider string // "openai", "anthropic", "google", "xai", "groq", "ollama"
Model string // "gpt-4.1", "anthropic:claude-sonnet-4-20250514", "xai:grok-4-1-fast-reasoning"
APIKey string // falls back to env vars if empty
BaseURL string // custom API endpoint
Temperature float64 // 0.0 - 1.0

// Capabilities
Tools []tools.Tool // langchaingo tool interface
Skills []Skill // domain knowledge definitions
MaxIter int // max ReAct iterations (default: 25)

// Recursive — sub-agents use the SAME struct
SubAgents []AgentConfig

// Infrastructure
Middleware []Middleware
Backend Backend // VFS backend
Store Store // conversation storage
}
```

### Model Format

Specify providers explicitly or use the `"provider:model"` shorthand:

```go
// Auto-detected as OpenAI
Model: "gpt-4.1"

// Explicit provider
Model: "anthropic:claude-sonnet-4-20250514"

// XAI Grok
Model: "xai:grok-4-1-fast-reasoning"

// Google Gemini
Model: "google:gemini-2.5-flash"

// Ollama (local)
Model: "ollama:llama3",
BaseURL: "http://localhost:11434",
```

### Environment Variables

API keys are resolved in this order:
1. `AgentConfig.APIKey` field
2. Fallback API key passed to `agent.New()`
3. Environment variables: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, `XAI_API_KEY`, `GROQ_API_KEY`

## Multi-Agent Systems

Build hierarchical agent systems where the orchestrator automatically routes tasks to specialized sub-agents:

```go
app, err := agent.New(agent.AgentConfig{
Name: "orchestrator",
Model: "gpt-4.1",
Prompt: "Route tasks to the best sub-agent.",
SubAgents: []agent.AgentConfig{
{
Name: "researcher",
Description: "Searches the web for information",
Model: "xai:grok-4-1-fast-reasoning",
APIKey: xaiKey,
Tools: []tools.Tool{searchTool},
Prompt: "You are a web researcher...",
},
{
Name: "coder",
Description: "Writes and analyzes code",
Model: "anthropic:claude-sonnet-4-20250514",
APIKey: anthropicKey,
Prompt: "You are a software engineer...",
},
{
Name: "casual",
Description: "Handles casual conversation",
Model: "gpt-4.1-mini",
Prompt: "You are a friendly assistant...",
},
},
}, openaiKey)
```

The orchestrator automatically gets a `task` tool that delegates to sub-agents based on their descriptions.

## Streaming Events

Get real-time visibility into agent execution:

```go
eventCh := make(chan agent.ReactEvent, 100)

go func() {
for evt := range eventCh {
switch evt.Type {
case agent.EventIterationStart:
fmt.Printf("⟳ [%s] iteration %d\n", evt.Agent, evt.Iteration)
case agent.EventLLMResponse:
fmt.Printf("💭 [%s] %s\n", evt.Agent, evt.Content)
case agent.EventToolStart:
fmt.Printf("🔧 [%s] calling %s\n", evt.Agent, evt.ToolName)
case agent.EventToolEnd:
fmt.Printf("✓ [%s] %s done\n", evt.Agent, evt.ToolName)
case agent.EventFinalAnswer:
fmt.Printf("✅ [%s] %s\n", evt.Agent, evt.Content)
}
}
}()

result, err := app.SendWithEvents(ctx, threadID, "Analyze this data", eventCh)
close(eventCh)
```

### Event Types

| Event | Description |
|-------|-------------|
| `EventIterationStart` | New ReAct iteration beginning |
| `EventLLMResponse` | LLM thinking/reasoning output (before tool calls) |
| `EventToolStart` | Tool invocation starting |
| `EventToolEnd` | Tool invocation completed with result |
| `EventFinalAnswer` | Agent has reached its final answer |

## Thread-based Conversations

Maintain conversation history across multiple interactions:

```go
// Create a thread
threadID, err := app.CreateThread(ctx, agent.ThreadConfig{
UserID: "user-123",
})

// Send messages — history is managed automatically
result1, _ := app.Send(ctx, threadID, "What is Go?")
result2, _ := app.Send(ctx, threadID, "How does it handle concurrency?") // remembers context

// Read thread history
thread, _ := app.GetThread(ctx, threadID)
for _, msg := range thread.Messages {
fmt.Printf("[%s] %s\n", msg.Role, msg.Content)
}

// List checkpoints (snapshots after each interaction)
checkpoints, _ := app.ListCheckpoints(ctx, threadID, 10)

// List all threads
threads, _ := app.ListThreads(ctx, "user-123", 20, 0)
```

### Thread Lifecycle

Threads have four states: `idle`, `busy`, `interrupted`, `error`. Status is derived automatically from checkpoint and run state.

```go
tm := agent.NewThreadManager(store)

// Submit with multitask strategy
run, err := tm.SubmitRun(ctx, threadID, "assistant", "Hello",
agent.MultitaskReject, // or: MultitaskRollback, MultitaskInterrupt, MultitaskEnqueue
nil,
)

// Lock/unlock for execution
runCtx, attempt, err := tm.LockRun(ctx, threadID, run.RunID)
defer tm.UnlockRun(threadID, run.RunID)

// Cancel a running run
tm.CancelRun(ctx, threadID, run.RunID, agent.CancelActionInterrupt)

// Fork a thread (copy messages + checkpoints)
newThreadID, err := tm.ForkThread(ctx, threadID)

// Derive thread status from checkpoint state
status, _ := tm.DeriveThreadStatus(ctx, threadID, nil)
// idle | busy | interrupted | error
```

### WAL Checkpointing

Write-ahead log checkpoints enable interrupt/resume and time-travel debugging:

```go
cm := agent.NewCheckpointManager(store)
builder := cm.NewCheckpointBuilder(threadID, state)

// Track pending writes
builder.AddWrite("messages", newMessage, "task-1")
builder.AddWrite("files", fileUpdate, "task-1")

// Commit checkpoint with parent chain
cpID, err := builder.Commit(ctx, map[string]interface{}{"step": "tool_call"})

// Commit interrupt (for HITL)
cpID, err := builder.CommitInterrupt(ctx, []string{"write_file"}, interruptErr)

// Restore state from latest checkpoint
state, checkpoint, err := cm.RestoreState(ctx, threadID)

// Time-travel: browse state history
history, err := cm.GetStateHistory(ctx, threadID, 10)
```

## SSE Stream Manager

Per-run event streaming with pub/sub, resumable event IDs, and concurrent subscribers:

```go
sm := agent.NewStreamManager()
sm.CreateStream(runID)

// Publish events (thread-safe, non-blocking for slow subscribers)
sm.Publish(runID, agent.StreamEventMessages, messageData)
sm.Publish(runID, agent.StreamEventValues, stateSnapshot)

// Subscribe with resumability (Last-Event-ID reconnection)
ctx, cancel := context.WithCancel(context.Background())
ch, err := sm.Subscribe(ctx, runID, lastEventID)
for evt := range ch {
sse, _ := evt.MarshalSSE()
w.Write([]byte(sse))
w.(http.Flusher).Flush()
}

// End stream + cleanup old streams
sm.EndStream(runID)
sm.Cleanup(2 * time.Hour)
```

## Webhooks

Async webhook delivery on run completion with retry and backoff:

```go
wm := agent.NewWebhookManager(
agent.WebhookConfig{
URL: "https://api.example.com/hooks",
MaxRetries: 3,
Timeout: 10 * time.Second,
Events: []string{"run.completed", "run.failed"},
Headers: map[string]string{"X-API-Key": "secret"},
},
)
defer wm.Close()

// Add webhooks at runtime
wm.AddWebhook(agent.WebhookConfig{URL: "https://slack.example.com/webhook"})

// Emit events (non-blocking, async delivery via worker goroutine)
wm.Emit(agent.WebhookEvent{
Type: "run.completed",
RunID: runID,
ThreadID: threadID,
Status: "success",
})
```

## Authentication

Multi-tenant auth with pluggable providers:

```go
// API key provider
provider := agent.NewAPIKeyAuthProvider(map[string]*agent.AuthContext{
"key-123": {UserID: "user-1", OrgID: "org-1", Permissions: []string{"*"}},
"key-456": {UserID: "user-2", OrgID: "org-1", Permissions: []string{"execute_tool:read_file"}},
})

// Auth middleware
mw := agent.NewAuthMiddleware(agent.AuthMiddlewareConfig{
Provider: provider,
RequireAuth: true,
AllowedActions: []string{"read_file", "ls"}, // no auth needed for read-only tools
})

// Inject auth context
ctx := agent.WithAuthContext(ctx, &agent.AuthContext{
UserID: "user-1",
OrgID: "org-1",
})
```

## Tracing

OpenTelemetry-compatible instrumentation:

```go
// In-memory tracer (testing/debugging)
tracer := agent.NewInMemoryTracer()
mw := agent.NewTracingMiddleware(tracer)

// After execution
spans := tracer.Spans()
for _, s := range spans {
fmt.Printf("%s [%s] %s → %s\n", s.Name, s.Kind, s.StartTime, s.Status)
}

// Noop tracer (zero overhead in production)
mw := agent.NewTracingMiddleware(nil) // uses NoopTracer

// Custom tracer (implement the Tracer interface for OpenTelemetry, Datadog, etc.)
type Tracer interface {
StartSpan(ctx context.Context, name string, kind SpanKind, attrs map[string]interface{}) (context.Context, *Span)
EndSpan(span *Span, status SpanStatus, err error)
AddEvent(span *Span, name string, attrs map[string]interface{})
Flush(ctx context.Context) error
}
```

## Custom Tools

Implement the standard `langchaingo/tools.Tool` interface:

```go
type WebSearchTool struct {
apiKey string
}

func (t *WebSearchTool) Name() string { return "web_search" }
func (t *WebSearchTool) Description() string { return "Search the web. Input: JSON {\"query\": \"search terms\"}" }

func (t *WebSearchTool) Call(ctx context.Context, input string) (string, error) {
var args struct {
Query string `json:"query"`
}
json.Unmarshal([]byte(input), &args)
// ... perform search ...
return results, nil
}
```

## Skills

Load domain knowledge from Markdown files with YAML frontmatter:

```markdown
---
name: security-audit
description: Smart contract security methodology
---

# Security Audit Process

1. Check for reentrancy vulnerabilities
2. Verify access control patterns
3. Review arithmetic operations for overflow
...
```

```go
// Load from file
skill, err := agent.SkillFromFile("skills/security-audit.md")

// Load from embedded filesystem
skill, err := agent.SkillFromEmbed(embedFS, "skills/security-audit.md")

// Create inline
skill := agent.NewSkill("math", "Math helper", "You can solve equations...")

// Use in agent config
cfg := agent.AgentConfig{
Skills: []agent.Skill{skill},
// ...
}
```

## Middleware

The middleware pipeline supports `BeforeInvoke`, `AfterInvoke`, `BeforeToolCall`, `AfterToolCall`, `ModelCallInterceptor` (wraps LLM calls), and `ToolCallInterceptor` (wraps tool calls) hooks.

### Built-in Middleware

```go
// Execution control
agent.NewModelCallLimitMiddleware(agent.ModelCallLimitConfig{
MaxCalls: 10,
ExitBehavior: "end", // "end" (graceful) or "throw" (error)
})

// Resilience
agent.NewModelRetryMiddleware(agent.ModelRetryConfig{
MaxRetries: 3,
InitialBackoff: time.Second,
BackoffMultiplier: 2.0,
OnFailure: "continue", // or "throw"
})
agent.NewModelFallbackMiddleware(agent.ModelFallbackConfig{
FallbackModels: []string{"openai:gpt-4.1-mini"},
})
agent.NewToolRetryMiddleware(agent.ToolRetryConfig{MaxRetries: 3})

// Human-in-the-Loop
agent.NewHITLMiddleware(agent.HITLConfig{
Interrupts: map[string]agent.InterruptConfig{
"write_file": {Before: true}, // require approval before writes
"*": {After: true, Reason: "audit"}, // audit all tool results
},
})

// Memory & context
agent.NewMemoryMiddleware(agent.MemoryMiddlewareConfig{
Sources: []string{"~/.deepagents/AGENTS.md", "./.deepagents/AGENTS.md"},
})
agent.NewToolResultEvictionMiddleware(agent.ToolEvictionConfig{
MaxResultTokens: 4000,
})
agent.NewPatchToolCallsMiddleware() // fix dangling tool calls

// Observability
agent.NewTokenUsageMiddleware(tracker, "gpt-4.1")
agent.NewTracingMiddleware(tracer) // OpenTelemetry-compatible
agent.NewAuthMiddleware(agent.AuthMiddlewareConfig{
Provider: apiKeyProvider,
RequireAuth: true,
})

// Existing
agent.LoggingMiddleware()
agent.AnthropicSanitizeMiddleware()
agent.TodoListMiddleware()
agent.SummarizationMiddleware(cfg)
```

### Custom Middleware

```go
type MyMiddleware struct{}

func (m *MyMiddleware) Name() string { return "MyMiddleware" }

func (m *MyMiddleware) BeforeInvoke(ctx context.Context, ic *agent.InvokeContext) error {
fmt.Printf("Agent %s, iteration %d\n", ic.AgentName, ic.Iteration)
return nil
}

func (m *MyMiddleware) BeforeToolCall(ctx context.Context, tc *agent.ToolCallContext) error {
fmt.Printf("Tool: %s\n", tc.ToolName)
return nil
}
```

## HTTP Server (Agent Protocol)

Start a production-ready HTTP server compatible with [LangGraph Studio](https://github.com/langchain-ai/langgraph-studio):

```go
import "github.com/denizumutdereli/go-deepagent/pkg/server"

srv, err := server.New(server.ServerConfig{
App: app,
Port: "8080",
Runner: server.RunnerConfig{
MaxConcurrent: 50,
RunTimeout: 5 * time.Minute,
ShutdownTimeout: 30 * time.Second,
},
})

srv.Start()
```

### Endpoints

```
GET /api/health Health check

POST /threads Create thread
POST /threads/search Search threads
GET /threads/{id} Get thread + messages
DELETE /threads/{id} Delete thread
GET /threads/{id}/history Checkpoint history

POST /threads/{id}/runs Background run
POST /threads/{id}/runs/stream Run + SSE stream
POST /threads/{id}/runs/wait Run + wait for result

POST /runs Stateless background run
POST /runs/stream Stateless run + SSE stream
POST /runs/wait Stateless run + wait

GET /runs/{id} Get run status
GET /runs/{id}/stream Reconnect to SSE stream
GET /runs/{id}/wait Wait for completion
POST /runs/{id}/cancel Cancel run
```

### Custom Router & Middleware

```go
r := chi.NewRouter()
r.Use(myAuthMiddleware)
r.Get("/custom", myHandler)

srv, _ := server.New(server.ServerConfig{
App: app,
Router: r, // Agent Protocol routes are mounted on your router
})
```

### SSE Event Formatting

```go
srv, _ := server.New(server.ServerConfig{
App: app,
SSEFormatter: func(evt agent.ReactEvent) (string, []byte) {
// Custom SSE event formatting
return string(evt.Type), json.Marshal(evt)
},
})
```

## Storage

### In-Memory (Default)

```go
// Automatically used when no Store is provided
app, _ := agent.New(cfg, apiKey)
```

### MongoDB

```go
import "github.com/denizumutdereli/go-deepagent/pkg/store"

ms, err := store.ConnectMongo(ctx, "mongodb://localhost:27017", "mydb")
app, _ := agent.New(agent.AgentConfig{
Store: ms,
// ...
}, apiKey)
```

### Redis (Ephemeral Runs)

```go
rs, err := store.NewRedisStore(store.RedisConfig{
Addr: "localhost:6379",
RunTTL: 2 * time.Hour, // automatic cleanup
})
```

### Hybrid (MongoDB + Redis)

Production pattern: persistent threads/checkpoints in MongoDB, ephemeral runs in Redis with TTL-based cleanup.

```go
ms, _ := store.ConnectMongo(ctx, "mongodb://localhost:27017", "mydb")
rs, _ := store.NewRedisStore(store.DefaultRedisConfig())
hybrid := store.NewHybridStore(ms, rs)

app, _ := agent.New(agent.AgentConfig{
Store: hybrid,
// ...
}, apiKey)
```

### Custom Store

Implement the `agent.Store` interface:

```go
type Store interface {
// Threads
CreateThread(ctx context.Context, t *protocol.Thread) error
GetThread(ctx context.Context, id string) (*protocol.Thread, error)
DeleteThread(ctx context.Context, id string) error
SetThreadStatus(ctx context.Context, id string, status protocol.ThreadStatus) error
AppendMessage(ctx context.Context, threadID string, msg protocol.Message) error
SearchThreads(ctx context.Context, userID string, limit, offset int) ([]*protocol.Thread, error)

// Checkpoints
SaveCheckpoint(ctx context.Context, cp *protocol.Checkpoint) error
GetLatestCheckpoint(ctx context.Context, threadID string) (*protocol.Checkpoint, error)
ListCheckpoints(ctx context.Context, threadID string, limit int, before string) ([]*protocol.Checkpoint, error)

// Runs
CreateRun(ctx context.Context, r *protocol.Run) error
GetRun(ctx context.Context, id string) (*protocol.Run, error)
UpdateRun(ctx context.Context, r *protocol.Run) error
ListRunsByThread(ctx context.Context, threadID string, limit, offset int) ([]*protocol.Run, error)
}
```

## Examples

### Researcher

An interactive CLI with X/Twitter research (via XAI Grok), web search (via Tavily), and casual chat — plus an `--serve` mode for HTTP API.

```bash
cd examples/researcher
cp .env.example .env # fill in your keys
go run .
```

Features:
- 3 sub-agents: researcher (XAI), websearch (Tavily), casual (GPT)
- Thread management: `new`, `thread`, `threads`, `checkpoints`
- Server mode: `go run . --serve 8080`
- MongoDB support: `go run . --store mongodb://localhost:27017/researcher`

### On-Chain Auditor

A blockchain security auditor with real on-chain tools — Etherscan API, Alchemy RPC, ABI decoding — across multiple chains.

```bash
cd examples/onchain-auditor
cp .env.example .env # fill in your keys
go run .
```

Features:
- 4 sub-agents: security-auditor (Gemini), token-analyst (GPT), tx-investigator (GPT), chain-scanner (XAI)
- Multi-chain support: Ethereum, Polygon, BSC, Arbitrum, Optimism, Base, Avalanche
- Real on-chain tools: contract source, token transfers, balances, event logs, ABI decoding
- Security audit skills: SWC attack vectors, DeFi patterns, audit methodology

## Built-in VFS Tools

Every agent automatically receives these filesystem tools:

| Tool | Description |
|------|-------------|
| `ls` | List directory contents |
| `read_file` | Read file contents |
| `write_file` | Write content to a file |
| `edit_file` | Find-and-replace edit |
| `grep` | Search file contents |
| `glob` | Find files by pattern |

The VFS backend is pluggable — default is in-memory (`afero.MemMapFs`), but you can use OS filesystem or any `afero.Fs` implementation.

## Testing

```bash
# Run all tests
go test ./...

# Run with verbose output
go test -v ./pkg/agent/...

# Run specific test suites
go test -v ./pkg/agent/ -run TestStreamManager
go test -v ./pkg/agent/ -run TestWebhookManager
go test -v ./pkg/agent/ -run TestAuthMiddleware
go test -v ./pkg/agent/ -run TestTracingMiddleware

# E2E tests (require API keys in .env.test)
cp .env.example .env.test
go test -v ./pkg/agent/ -run TestE2E
```

Test coverage includes:
- **State reducers** — append-only messages, merge-by-path files, skills metadata merge, subagent clone/merge
- **Thread lifecycle** — lock/unlock, multitask strategies, retry counters, status derivation, fork
- **Middleware** — HITL interrupts, model call limits, memory loading, model retry/fallback, tool eviction, patch tool calls, auth, tracing
- **Streaming** — publish/subscribe, resumability, concurrent access, context cancellation, cleanup
- **Token usage** — concurrent accumulation, read/write safety
- **Webhooks** — delivery, retry, event filtering, runtime registration
- **Store backends** — StoreBackend (VFS), MemoryStore operations

## Docker

```bash
# Start test dependencies (Redis + MongoDB)
docker-compose -f docker-compose.test.yml up -d

# Run integration tests
go test ./... -count=1

# Stop
docker-compose -f docker-compose.test.yml down
```

```bash
# Start MongoDB only (for persistent storage in dev)
docker-compose up -d

# Stop
docker-compose down
```

## Concurrency Design

go-deepagent is designed for high-concurrency production use:

- **`sync.RWMutex`** throughout — read-heavy data structures use RLock for concurrent reads (AgentState, TokenUsage, MemoryMiddleware, AuthProvider, StreamManager)
- **Lock ordering** — documented and enforced to prevent deadlocks (snapshot-then-apply pattern in state merges, 2-phase cleanup in stream manager)
- **`sync/atomic`** — lock-free counters for event IDs
- **Buffered channels** — non-blocking event delivery in StreamManager and WebhookManager
- **Worker goroutines** — webhook delivery uses a single worker goroutine to prevent HTTP connection storms
- **Semaphore pattern** — buffered channel limits concurrent subagent spawns
- **Context propagation** — all long-running operations respect `context.Context` for cancellation

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License

[MIT](LICENSE)