Build trustworthy AI agents in Go. A state-driven runtime where intelligence is constrained by design, not hope.
engine, _ := agent.New(
agent.WithTools(readFile, writeFile),
agent.WithPlanner(llmPlanner),
agent.WithBudget("tool_calls", 50),
)
run, _ := engine.Run(ctx, "Summarize all markdown files in ./docs")Most agent frameworks treat safety as an afterthought. agent-go makes trust the product:
| Problem | agent-go Solution |
|---|---|
| Agents run arbitrary code | State machine constrains what tools run when |
| No visibility into decisions | Audit ledger records every action |
| Runaway costs | Budget enforcement with hard limits |
| Dangerous operations | Approval workflows for destructive tools |
| Untestable LLM behavior | Deterministic mode with scripted planners |
| Python's GIL limits scale | Native Go concurrency for high throughput |
The key insight: Structure agent behavior through constraints, not prompts.
go get github.com/felixgeelhaar/agent-gopackage main
import (
"context"
"encoding/json"
"fmt"
"log"
agent "github.com/felixgeelhaar/agent-go/interfaces/api"
"github.com/felixgeelhaar/agent-go/domain/tool"
)
func main() {
// 1. Create a simple tool
greetTool := agent.NewToolBuilder("greet").
WithDescription("Greets a person by name").
WithAnnotations(agent.Annotations{ReadOnly: true}).
WithHandler(func(ctx context.Context, input json.RawMessage) (tool.Result, error) {
var in struct{ Name string `json:"name"` }
json.Unmarshal(input, &in)
output, _ := json.Marshal(map[string]string{
"message": fmt.Sprintf("Hello, %s!", in.Name),
})
return tool.Result{Output: output}, nil
}).
MustBuild()
// 2. Create a scripted planner (for testing - swap with LLM in production)
planner := agent.NewScriptedPlanner(
agent.ScriptStep{
ExpectState: agent.StateIntake,
Decision: agent.NewTransitionDecision(agent.StateExplore, "starting"),
},
agent.ScriptStep{
ExpectState: agent.StateExplore,
Decision: agent.NewCallToolDecision("greet", json.RawMessage(`{"name":"World"}`), "greeting user"),
},
agent.ScriptStep{
ExpectState: agent.StateExplore,
Decision: agent.NewFinishDecision("completed", json.RawMessage(`{"status":"done"}`)),
},
)
// 3. Build and run the engine
engine, err := agent.New(
agent.WithTool(greetTool),
agent.WithPlanner(planner),
agent.WithMaxSteps(10),
)
if err != nil {
log.Fatal(err)
}
run, err := engine.Run(context.Background(), "Say hello")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %s\n", run.Status)
fmt.Printf("Result: %s\n", run.Result)
}Run it:
go run main.go
# Output:
# Status: done
# Result: {"status":"done"}Agents operate within a canonical 7-state lifecycle. Each state has explicit semantics:
intake → explore → decide → act → validate → done
↓ ↓
failed ←──────────────┘
| State | Purpose | Side Effects |
|---|---|---|
intake |
Parse and normalize the goal | None |
explore |
Gather information (read-only tools) | None |
decide |
Choose next action | None |
act |
Execute side-effecting tools | Yes |
validate |
Verify results | None |
done |
Success (terminal) | None |
failed |
Failure (terminal) | None |
Why this matters: A tool marked Destructive can only run in act state. Period.
Tools are the agent's capabilities. Each tool declares its behavior through annotations:
writeTool := agent.NewToolBuilder("write_file").
WithAnnotations(agent.Annotations{
ReadOnly: false, // Modifies state
Destructive: true, // May cause irreversible changes
Idempotent: false, // Not safe to retry
RiskLevel: agent.RiskHigh,
}).
WithHandler(writeHandler).
MustBuild()Annotations drive runtime behavior:
ReadOnlytools can run inexploreandvalidateDestructivetools require approval (if configured)Idempotenttools get automatic retry on failureCacheableresults are memoized
Planners decide what the agent does next. Swap implementations without changing agent code:
// Testing: deterministic, no LLM needed
planner := agent.NewScriptedPlanner(steps...)
// Development: local models via Ollama
planner, _ := ollama.New(ollama.WithModel("llama3"))
// Production: Claude, GPT-4, Gemini
planner, _ := anthropic.New(anthropic.WithModel("claude-sonnet-4-20250514"))Hard limits that cannot be overridden by the LLM:
engine, _ := agent.New(
// Budget: Stop after 100 tool calls, no matter what
agent.WithBudget("tool_calls", 100),
// Approval: Human must approve destructive operations
agent.WithApprover(myApprover),
// Eligibility: read_file only in explore, write_file only in act
agent.WithToolEligibility(eligibility),
)Pluggable providers for all major LLMs (via contrib modules):
import "github.com/felixgeelhaar/agent-go/contrib/planner-llm/providers/anthropic"
import "github.com/felixgeelhaar/agent-go/contrib/planner-llm/providers/openai"
import "github.com/felixgeelhaar/agent-go/contrib/planner-llm/providers/gemini"
import "github.com/felixgeelhaar/agent-go/contrib/planner-llm/providers/ollama"
// Each provider implements the same interface
provider, _ := anthropic.New(anthropic.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")))
provider, _ := openai.New(openai.WithAPIKey(os.Getenv("OPENAI_API_KEY")))
provider, _ := gemini.New(gemini.WithAPIKey(os.Getenv("GEMINI_API_KEY")))
provider, _ := ollama.New(ollama.WithBaseURL("http://localhost:11434"))Pre-built tool collections for common domains (94 packs available via contrib modules):
import "github.com/felixgeelhaar/agent-go/contrib/pack-database"
import "github.com/felixgeelhaar/agent-go/contrib/pack-git"
import "github.com/felixgeelhaar/agent-go/contrib/pack-kubernetes"
import "github.com/felixgeelhaar/agent-go/contrib/pack-cloud"
// Database: query, execute, schema inspection
dbPack := database.New(db, database.WithMaxRows(1000))
// Git: status, log, diff, commit
gitPack := git.New("/path/to/repo", git.WithAllowCommit(true))
// Kubernetes: get, list, logs, apply
k8sPack := kubernetes.New(client, kubernetes.WithNamespace("production"))
// Cloud: S3/GCS/Azure blob operations
cloudPack := cloud.New(provider, cloud.WithBucket("my-bucket"))Categories: Archive, Text Processing, Data Formats, Code Analysis, Utilities, Web & Network, Infrastructure, Messaging, AI & ML, Media & Documents, Integrations, Security, Data & Analytics, Geo & Localization, Validation & Parsing.
OpenTelemetry integration for traces and metrics (via contrib/otel):
import "github.com/felixgeelhaar/agent-go/contrib/otel"
tracer, _ := otel.NewTracer("my-agent",
otel.WithOTLPEndpoint("localhost:4317"),
)
engine, _ := agent.New(
agent.WithMiddleware(otel.TracingMiddleware(tracer)),
)
// Automatic spans for: tool execution, state transitions, planner calls
// Automatic metrics for: tool_duration, run_duration, budget_usageInput validation, secret management, and audit logging (via contrib packs):
import "github.com/felixgeelhaar/agent-go/contrib/pack-validate"
import "github.com/felixgeelhaar/agent-go/contrib/pack-secrets"
// Validate tool inputs with pack-validate
validator := validate.NewValidator(
validate.WithRule("path", validate.NoPathTraversal()),
validate.WithRule("query", validate.NoSQLInjection()),
)
// Manage secrets with pack-secrets
secretMgr := secrets.NewEnvManager(secrets.WithPrefix("AGENT_"))Scale across multiple workers (via contrib/distributed):
import "github.com/felixgeelhaar/agent-go/contrib/distributed"
// Create queue (memory for dev, Redis/NATS for prod)
q := distributed.NewMemoryQueue()
// Create distributed lock
l := distributed.NewMemoryLock()
// Start workers
worker := distributed.NewWorker(distributed.WorkerConfig{
Queue: q,
Lock: l,
Concurrency: 4,
})
worker.Start(ctx)agent-go/
├── go.mod # Core module (lean dependencies)
├── go.work # Go workspace file
│
├── domain/ # Core domain (no external deps)
│ ├── agent/ # Run, State, Decision, Evidence
│ ├── tool/ # Tool, Annotations, Schema, Registry
│ ├── policy/ # Budget, Approval, Eligibility
│ └── ledger/ # Audit trail
│
├── application/ # Orchestration
│ └── engine.go # Main engine service
│
├── infrastructure/ # Core implementations
│ ├── storage/memory/ # In-memory stores
│ ├── planner/ # Mock & scripted planners
│ └── resilience/ # Circuit breaker, retry
│
├── interfaces/api/ # Public API
│
├── contrib/ # Optional modules (separate go.mod each)
│ ├── storage-*/ # Storage backends (postgres, redis, mongodb...)
│ ├── pack-*/ # Tool packs (94 packs across 15 categories)
│ ├── planner-llm/ # LLM providers (anthropic, openai, gemini...)
│ ├── otel/ # OpenTelemetry integration
│ ├── distributed/ # Queues, locks, workers
│ ├── mcp/ # Model Context Protocol support
│ ├── dashboard/ # Web dashboard
│ └── approval-slack/ # Slack approval integration
│
└── example/ # Examples
| Feature | agent-go | LangChain | AutoGPT | CrewAI |
|---|---|---|---|---|
| Language | Go | Python | Python | Python |
| Type Safety | Compile-time | Runtime | Runtime | Runtime |
| State Machine | Built-in | Manual | None | None |
| Policy Enforcement | First-class | Partial | None | Partial |
| Audit Trail | Built-in | Manual | None | None |
| Deterministic Testing | ScriptedPlanner | Difficult | Difficult | Difficult |
| Concurrency | Native goroutines | GIL-limited | Limited | Limited |
| Memory Footprint | ~10MB | ~100MB+ | ~200MB+ | ~150MB+ |
- Quick Start Guide - Your first agent in 5 minutes
- Concepts - States, tools, planners, policies
- Architecture - DDD structure, layer responsibilities
- Integration Guides - LLM providers, packs, security
- Examples - Progressive examples from minimal to production
- API Reference - GoDoc
| Example | Description | Complexity |
|---|---|---|
| 01-minimal | Absolute minimum working agent | Beginner |
| 02-tools | Custom tool creation | Beginner |
| 03-policies | Budgets and approvals | Intermediate |
| 04-llm-planner | Real LLM integration | Intermediate |
| 05-observability | Tracing and metrics | Intermediate |
| 06-distributed | Multi-worker setup | Advanced |
| 07-production | Full production setup | Advanced |
# Build
go build ./...
# Test with race detection
go test -race ./...
# Coverage
coverctl check --fail-under=80
# Security scan
verdict scan
# Lint
golangci-lint run ./...Core module (lean, ~15 dependencies):
- statekit - Statechart execution engine
- fortify - Resilience patterns (circuit breaker, retry)
- bolt - High-performance structured logging
- Standard library for everything else
Contrib modules add dependencies only when imported:
- Storage backends bring their drivers (pgx, mongo-driver, redis, etc.)
- LLM providers bring their SDKs (anthropic-sdk, openai-sdk, etc.)
- Tool packs bring domain-specific libraries as needed
Contributions are welcome! Please read CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.