Build AI Agents
in Go. Fast.
go-agentkit is an open-source framework for building powerful AI agents with Go. Multi-provider LLM support, function calling, agent handoffs, streaming, tracing — all in a lightweight, idiomatic Go SDK.
What is go-agentkit?
An open-source Go SDK for building intelligent AI agents — inspired by OpenAI's Python Agent SDK and Assistants API, ported to idiomatic Go with local LLM support and multi-provider compatibility.
Repository Structure
go-agentkit/ ├── pkg/ │ ├── agent/ // Core Agent type │ ├── runner/ // Agentic loop + WorkflowRunner │ ├── tool/ // FunctionTool, schema, OpenAI compat │ ├── model/ // Model interfaces + streaming │ │ └── providers/ │ │ ├── openai/ // OpenAI provider │ │ ├── anthropic/// Anthropic Claude provider │ │ └── lmstudio/ // LM Studio (local) │ ├── mcp/ // MCP client (local + hosted) │ ├── result/ // RunResult type │ └── tracing/ // Tracing internals ├── examples/ // 10+ runnable examples ├── test/ // Test suite + mocks ├── scripts/ // lint, security, version ├── docs/ // Additional documentation ├── main.go ├── types.go ├── store.go // Complex agentic flow test └── go.mod // Requires Go 1.23+
Installation
Requires Go 1.23 or later. Three ways to add go-agentkit to your project.
Option 1 — go get (Recommended)
go get github.com/muhammadhamd/go-agentkit/
Option 2 — Import and tidy
Add the imports to your Go files, then run:
import ( "github.com/muhammadhamd/go-agentkit/pkg/agent" "github.com/muhammadhamd/go-agentkit/pkg/runner" "github.com/muhammadhamd/go-agentkit/pkg/tool" "github.com/muhammadhamd/go-agentkit/pkg/model/providers/openai" )
go mod tidy
Option 3 — Manual go.mod
require github.com/muhammadhamd/go-agentkit/ latest
New Project Setup
# Create your project mkdir my-agent && cd my-agent go mod init github.com/yourname/my-agent # Install go-agentkit go get github.com/muhammadhamd/go-agentkit/ # Set your API key export OPENAI_API_KEY=sk-...
go get github.com/muhammadhamd/go-agentkit/@v0.10.2
Quick Start
Build your first AI agent in Go — with a real tool call — in under 5 minutes.
package main import ( "context" "fmt" "log" "os" "github.com/muhammadhamd/go-agentkit/pkg/agent" "github.com/muhammadhamd/go-agentkit/pkg/model/providers/openai" "github.com/muhammadhamd/go-agentkit/pkg/runner" "github.com/muhammadhamd/go-agentkit/pkg/tool" ) func main() { // 1. Provider — connects to OpenAI provider := openai.NewProvider(os.Getenv("OPENAI_API_KEY")) provider.SetDefaultModel("gpt-4o-mini") // 2. Tool — a Go function the agent can call getWeather := tool.NewFunctionTool( "get_weather", "Get the current weather for a city", func(ctx context.Context, params map[string]interface{}) (interface{}, error) { city := params["city"].(string) return fmt.Sprintf("Weather in %s: 72°F, sunny.", city), nil }, ).WithSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "city": map[string]interface{}{ "type": "string", "description": "City name", }, }, "required": []string{"city"}, }) // 3. Agent — wraps the LLM with instructions + tools assistant := agent.NewAgent("Weather Assistant", "You are a weather assistant.") assistant.SetModelProvider(provider) assistant.WithModel("gpt-4o-mini") assistant.WithTools(getWeather) // 4. Runner — executes the agentic loop r := runner.NewRunner() r.WithDefaultProvider(provider) result, err := r.RunSync(assistant, &runner.RunOptions{ Input: "What's the weather in Tokyo?", }) if err != nil { log.Fatal(err) } fmt.Println(result.FinalOutput) }
Run it
export OPENAI_API_KEY=sk-... go run main.go
get_weather with city: "Tokyo" → got the result → used it to compose the final answer. All automatic.
Agent
The agent.Agent is the core primitive — it encapsulates an LLM with instructions, tools, output type, and handoff targets.
Creating an Agent
// Basic creation a := agent.NewAgent("My Agent", "You are a helpful assistant.") // Full configuration a.SetModelProvider(provider) a.WithModel("gpt-4o") a.WithTools(tool1, tool2, tool3) // multiple tools at once a.WithHandoffs(billingAgent, supportAgent) // delegation targets a.SetOutputType(MyStruct{}) // structured output
Agent Configuration Options
| Method | Description |
|---|---|
| SetModelProvider(p) | Attach an LLM provider (OpenAI, Anthropic, LM Studio) |
| WithModel(name) | Override the model string (e.g. "gpt-4o", "claude-3-opus") |
| SetSystemInstructions(s) | Set the system prompt / persona |
| WithTools(tools...) | Add one or more FunctionTools |
| WithHandoffs(agents...) | Agents this agent can delegate to |
| SetOutputType(t) | Specify a Go struct for structured output parsing |
| AddToolFromDefinition(d) | Add an OpenAI-compatible tool definition directly |
| AddToolsFromDefinitions(ds) | Batch-add OpenAI tool definitions |
Tool Use Behavior
Control what happens after a tool returns a result:
- ✓run_llm_again (default) — feed tool result back to the LLM and continue
- ✓stop_on_first_tool — return the tool result directly as final output
- ✓custom — define your own handler function
Runner
The Runner drives the agentic loop — managing turns, tool execution, handoffs, and conversation state.
Agentic Loop — How It Works
Next Step Types
| Type | Description |
|---|---|
| NextStepRunAgain | Continue the loop (after tool execution) |
| NextStepHandoff | Transfer control to another agent |
| NextStepFinalOutput | Agent produced a final answer — stop |
| NextStepInterruption | Pause for human approval or input |
Runner API
// Create a runner r := runner.NewRunner() r.WithDefaultProvider(provider) // Synchronous run result, err := r.RunSync(myAgent, &runner.RunOptions{ Input: "Process my request", MaxTurns: 10, // prevent infinite loops Context: myCustomContext, // shared across agents RunConfig: &runner.RunConfig{ TracingDisabled: false, }, }) // Async run with context result, err := r.Run(ctx, myAgent, &runner.RunOptions{...}) // Streaming stream, err := r.RunStreaming(ctx, myAgent, &runner.RunOptions{...}) // Access result fmt.Println(result.FinalOutput) fmt.Println(result.RunContext.(*runner.RunContext).Usage.TotalTokens)
RunOptions Reference
| Field | Type | Description |
|---|---|---|
| Input | string / interface{} | Initial user input or workflow state |
| MaxTurns | int | Maximum agent turns before stopping |
| Context | interface{} | Custom context shared across all agents |
| RunConfig | *RunConfig | Tracing, workflow, and other run-level settings |
| WorkflowConfig | *WorkflowConfig | Retry, state management, validation rules |
Tools
Tools let agents execute Go functions. The SDK handles JSON schema, parameter validation, error propagation, and OpenAI compatibility automatically.
Creating a Function Tool
myTool := tool.NewFunctionTool( "search_database", // tool name (no spaces) "Search the user database", // description for the LLM func(ctx context.Context, params map[string]interface{}) (interface{}, error) { query := params["query"].(string) limit := int(params["limit"].(float64)) // Access tool call ID for tracing if id, ok := ctx.Value("tool_call_id").(string); ok { log.Printf("tool_call_id: %s", id) } results, err := db.Search(query, limit) if err != nil { return nil, err } return results, nil }, ).WithSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "query": map[string]interface{}{ "type": "string", "description": "Search query string", }, "limit": map[string]interface{}{ "type": "integer", "description": "Max results to return", }, }, "required": []string{"query"}, })
Context Values Available in Tools
| Key | Value |
|---|---|
| tool_call_id | The current tool call's unique ID (auto-generated, per-call) |
| tool_name | Name of the currently executing tool |
| run_context | The *runner.RunContext (access your custom context + usage) |
OpenAI Tool Compatibility
// Convert any FunctionTool to OpenAI format openAITool := tool.ToOpenAITool(myTool) // Add OpenAI-format definitions directly to an agent a.AddToolFromDefinition(openAITool) a.AddToolsFromDefinitions([]map[string]interface{}{tool1, tool2})
Tool Approval (Human-in-the-Loop)
// Tools can require human approval before execution // Approval state is tracked in RunContext.ToolApprovals // Use NextStepInterruption to pause for user input
Model Providers
go-agentkit supports three first-class providers plus any OpenAI-compatible API via custom base URLs.
import "github.com/muhammadhamd/go-agentkit/pkg/model/providers/openai" provider := openai.NewProvider(os.Getenv("OPENAI_API_KEY")) provider.SetDefaultModel("gpt-4o") // Available models: gpt-3.5-turbo, gpt-4, gpt-4o, gpt-4o-mini // Debug mode // OPENAI_DEBUG=1 go run .
import "github.com/muhammadhamd/go-agentkit/pkg/model/providers/anthropic" provider := anthropic.NewProvider(os.Getenv("ANTHROPIC_API_KEY")) provider.SetDefaultModel("claude-3-haiku-20240307") // Optional: rate limiting provider.WithRateLimit(40, 80000) // 40 req/min, 80K tokens/min // Optional: retry config provider.WithRetryConfig(3, 2*time.Second) // 3 retries, exponential backoff // Available: claude-3-haiku-20240307, claude-3-sonnet-20240229, claude-3-opus-20240229 // ANTHROPIC_DEBUG=1 go run .
import "github.com/muhammadhamd/go-agentkit/pkg/model/providers/lmstudio" // 1. Download LM Studio from lmstudio.ai // 2. Load a model (Gemma, Llama3, Phi, etc.) // 3. Start Local Server (default: http://127.0.0.1:1234) provider := lmstudio.NewProvider() provider.SetBaseURL("http://127.0.0.1:1234/v1") provider.SetDefaultModel("gemma-3-4b-it") // LMSTUDIO_DEBUG=1 go run .
// Any OpenAI-compatible API — DeepSeek example import "github.com/muhammadhamd/go-agentkit/pkg/model/providers/openai" deepseek := openai.NewProvider(os.Getenv("DEEPSEEK_API_KEY")) deepseek.SetBaseURL("https://api.deepseek.com/v1") deepseek.SetDefaultModel("deepseek-chat") // Works with any endpoint that implements the OpenAI chat completions API
Multi-Agent Workflows
Compose specialized agents that collaborate — hand off tasks, share context, and work together on complex workflows.
Unidirectional Handoff
// Specialized agents mathAgent := agent.NewAgent("Math Agent", "You handle math calculations.") mathAgent.SetModelProvider(provider) mathAgent.WithTools(calculatorTool) weatherAgent := agent.NewAgent("Weather Agent", "You provide weather data.") weatherAgent.SetModelProvider(provider) weatherAgent.WithTools(weatherTool) // Coordinator delegates to specialists coordinator := agent.NewAgent("Coordinator", ` Delegate math to Math Agent, weather to Weather Agent. `) coordinator.SetModelProvider(provider) coordinator.WithHandoffs(mathAgent, weatherAgent) result, err := r.RunSync(coordinator, &runner.RunOptions{ Input: "What's 42/6 and what's the weather in Paris?", MaxTurns: 20, })
Bidirectional Flow
Agents can delegate AND receive results back. The worker returns to the orchestrator after completing its task.
orchestrator := agent.NewAgent("Orchestrator", "Coordinate and analyze.") worker := agent.NewAgent("Worker", "Process and return results.") worker.WithTools(processingTool) // Bidirectional: worker can return to orchestrator orchestrator.WithHandoffs(worker) worker.WithHandoffs(orchestrator) // ← the key line // Key bidirectional primitives in handoff metadata: // TaskID: unique task tracking ID // ReturnToAgent: which agent to return to // IsTaskComplete: signals task is done
Input Filtering During Handoffs
// Filter conversation history during handoffs // Removes tool calls, trims old messages, passes clean context // Configured at the handoff level via InputFilter functions
Context Sharing
RunContext lets you share typed custom data, usage statistics, and approval states across all agents in a workflow.
Defining a Custom Context
// 1. Define your context type type AppContext struct { UserID string OrderID string SessionID string Metadata map[string]interface{} } // 2. Create and pass it to the runner appCtx := &AppContext{ UserID: "user_123", SessionID: fmt.Sprintf("session_%d", time.Now().Unix()), Metadata: make(map[string]interface{}), } result, err := r.Run(ctx, agent, &runner.RunOptions{ Input: "Process order", Context: appCtx, }) // 3. Read updated context from result if rc, ok := result.RunContext.(*runner.RunContext); ok { if myCtx, ok := rc.Context.(*AppContext); ok { fmt.Println(myCtx.UserID) fmt.Println(rc.Usage.TotalTokens) } }
Accessing Context Inside Tools
func myToolFn(ctx context.Context, params map[string]interface{}) (interface{}, error) { // Get RunContext runCtxVal := ctx.Value("run_context") if runCtx, ok := runCtxVal.(*runner.RunContext); ok && runCtx != nil { if appCtx, ok := runCtx.Context.(*AppContext); ok { // Read from context userID := appCtx.UserID // Modify shared context (visible to ALL agents) appCtx.Metadata["last_tool"] = "myToolFn" _ = userID } } return "done", nil }
RunContext Fields
| Field | Type | Description |
|---|---|---|
| Context | interface{} | Your custom context (any struct) |
| Usage.InputTokens | int | Total input tokens consumed |
| Usage.OutputTokens | int | Total output tokens generated |
| Usage.TotalTokens | int | Combined token count |
| ToolApprovals | map | Approval state per tool call |
Streaming
Get real-time token-by-token output from agents using a typed event channel — no polling, no blocking.
import "github.com/muhammadhamd/go-agentkit/pkg/model" streamResult, err := r.RunStreaming(ctx, myAgent, &runner.RunOptions{ Input: "Tell me a long story", }) if err != nil { log.Fatal(err) } for event := range streamResult.Stream { switch event.Type { case model.StreamEventTypeContent: fmt.Print(event.Content) // stream tokens as they arrive case model.StreamEventTypeToolCall: fmt.Printf("\n🔧 Calling: %s\n", event.ToolCall.Name) case model.StreamEventTypeHandoff: fmt.Printf("\n↪ Handoff to: %s\n", event.Handoff.TargetAgent) case model.StreamEventTypeDone: fmt.Println("\n✓ Complete") } }
Stream Event Types
| Event Type | Description | Key Field |
|---|---|---|
| StreamEventTypeContent | A chunk of text output from the LLM | event.Content |
| StreamEventTypeToolCall | The agent is invoking a tool | event.ToolCall |
| StreamEventTypeHandoff | The agent is handing off to another | event.Handoff |
| StreamEventTypeDone | Stream is complete | — |
Tracing
Built-in tracing sends execution data to the OpenAI dashboard — matching the Python and TypeScript SDK behavior exactly.
Enable / Disable
# Disable globally via environment variable export OPENAI_AGENTS_DISABLE_TRACING=1 # or export OPENAI_AGENTS_DISABLE_TRACING=true
// Per-run: disable tracing r.RunSync(agent, &runner.RunOptions{ Input: "hello", RunConfig: &runner.RunConfig{ TracingDisabled: true, }, }) // Per-run: enable with custom workflow name r.RunSync(agent, &runner.RunOptions{ Input: "hello", RunConfig: &runner.RunConfig{ TracingDisabled: false, TracingConfig: &runner.TracingConfig{ WorkflowName: "my_workflow", }, }, })
trace_*.log files are created locally — matching Python/TypeScript SDK behavior.
Debug Flags
# General debug (runner + core) DEBUG=1 go run . # Provider-specific debug OPENAI_DEBUG=1 go run . ANTHROPIC_DEBUG=1 go run . LMSTUDIO_DEBUG=1 go run . # Combine flags DEBUG=1 OPENAI_DEBUG=1 go run .
Workflow State Management
Persist agent state between executions, retry on failure, checkpoint progress, and validate pre-handoff conditions.
import "github.com/muhammadhamd/go-agentkit/test/mocks" // In-memory state store (swap for DB in production) stateStore := mocks.NewInMemoryStateStore() workflowConfig := &runner.WorkflowConfig{ // Retry configuration RetryConfig: &runner.RetryConfig{ MaxRetries: 2, RetryDelay: time.Second, RetryBackoffFactor: 2.0, }, // State persistence StateManagement: &runner.StateManagementConfig{ PersistState: true, StateStore: stateStore, CheckpointFrequency: 5 * time.Second, }, // Pre-handoff validation ValidationConfig: &runner.ValidationConfig{ PreHandoffValidation: []runner.ValidationRule{{ Name: "StateNotNil", Validate: func(data interface{}) (bool, error) { state, ok := data.(*runner.WorkflowState) return ok && state != nil, nil }, ErrorMessage: "Workflow state must not be nil", Severity: runner.ValidationWarning, }}, }, } // WorkflowRunner wraps the base runner wfRunner := runner.NewWorkflowRunner(baseRunner, workflowConfig) state := &runner.WorkflowState{ CurrentPhase: "", CompletedPhases: make([]string, 0), Artifacts: make(map[string]interface{}), LastCheckpoint: time.Now(), Metadata: make(map[string]interface{}), } result, err := wfRunner.RunWorkflow(ctx, agent, &runner.RunOptions{ MaxTurns: 10, WorkflowConfig: workflowConfig, Input: state, })
WorkflowState Fields
| Field | Description |
|---|---|
| CurrentPhase | Name of the workflow phase currently executing |
| CompletedPhases | Ordered list of phases that have finished |
| Artifacts | Key-value store for outputs produced during the workflow |
| LastCheckpoint | Timestamp of the last successful checkpoint |
| Metadata | Arbitrary metadata map for custom data |
MCP Support
Model Context Protocol lets you connect agents to external tool servers — locally via stdio or remotely via HTTP/SSE.
import "github.com/muhammadhamd/go-agentkit/pkg/mcp/local" // Launch a local MCP server via stdio mcpClient := local.NewMCPClient("my-tools", []string{ "./my-mcp-server", }) // Get tools from the MCP server mcpTools, err := mcpClient.GetTools() // Add to agent (auto-converts to FunctionTool) agent.WithTools(mcpTools...)
import "github.com/muhammadhamd/go-agentkit/pkg/mcp/hosted" // Connect to a remote MCP server over HTTP/SSE mcpClient := hosted.NewMCPClient("https://mcp.example.com/sse", &hosted.Config{ APIKey: os.Getenv("MCP_API_KEY"), }) mcpTools, err := mcpClient.GetTools() agent.WithTools(mcpTools...)
// MCP agents can also participate in multi-agent handoffs mcpAgent := agent.NewAgent("MCP Agent", "You use external MCP tools.") mcpAgent.WithTools(mcpTools...) coordinator.WithHandoffs(mcpAgent) // See: examples/mcp/mcp_with_handoffs
MCP Examples
| Example | Path |
|---|---|
| Local MCP | examples/mcp/local_mcp_example |
| Hosted MCP | examples/mcp/hosted_mcp_example |
| MCP + Handoffs | examples/mcp/mcp_with_handoffs |
Package Reference
Full package map from pkg.go.dev
Examples
10+ runnable examples in the examples/ directory — covering every major feature.
| Example | Provider | What it Demonstrates |
|---|---|---|
| multi_agent_example | LM Studio | Specialized agent collaboration with local LLM. Math + Weather agents coordinated by a frontend agent. |
| openai_example | OpenAI | Single agent with function calling. Basic tool invocation with schema generation. |
| openai_multi_agent_example | OpenAI | Multi-agent with OpenAI models. Streaming support and proper tool calling. |
| anthropic_example | Anthropic | Claude with tool calling. Rate limiting and retry config. |
| anthropic_handoff_example | Anthropic | Agent handoffs with Claude models. Context preservation across handoffs. |
| bidirectional_flow_example | OpenAI | Orchestrator ↔ Worker with return handoffs. Task delegation and result passing. |
| typescript_code_review_example | OpenAI | Practical multi-agent code review. Reviewer + Fixer agents with bidirectional handoffs. |
| openai_advanced_workflow | OpenAI | Advanced workflow management with state, retry, and checkpointing. |
| mcp/local_mcp_example | Any | Local MCP server integration via stdio transport. |
| mcp/hosted_mcp_example | Any | Remote MCP server over HTTP/SSE. |
| mcp/mcp_with_handoffs | Any | MCP tools combined with agent handoffs. |
| store.go (root) | OpenAI | Complex agentic flow: multi-agent + context sharing + tools. Run: go run -tags=store store.go |
Running Examples
# OpenAI examples export OPENAI_API_KEY=sk-... cd examples/openai_multi_agent_example && go run . # Anthropic examples export ANTHROPIC_API_KEY=sk-ant-... cd examples/anthropic_handoff_example && go run . # Local LLM (LM Studio must be running) cd examples/multi_agent_example && go run . # Complex flow test from repo root go run -tags=store store.go
Environment Variables
All configurable environment variables recognized by go-agentkit.
1 or true to disable all tracing. Matches Python/TypeScript SDK behavior.1 to enable verbose debug output from the runner and core components1 to enable debug output specific to the OpenAI provider1 to enable debug output for the Anthropic provider1 to enable debug output for the LM Studio local providerContributing
go-agentkit is open source under MIT. Contributions, bug reports, and feature requests are welcome.
Development Setup
# 1. Fork & clone git clone https://github.com/YOUR-USERNAME/go-agentkit.git cd go-agentkit # 2. Install tools ./scripts/ci_setup.sh # 3. Create your branch git checkout -b feature/my-feature
Development Scripts
| Script | Purpose |
|---|---|
| ./scripts/lint.sh | Run gofmt and golangci-lint checks |
| ./scripts/security_check.sh | Run gosec security scanner |
| ./scripts/check_all.sh | Run all checks including tests |
| ./scripts/version.sh bump | Bump the version in version.txt |
| ./scripts/ci_setup.sh | Install all required dev tools |
Running Tests
cd test && make test
# or
./scripts/check_all.sh
Coding Standards
- ✓Follow idiomatic Go patterns and Go best practices
- ✓Use meaningful, descriptive variable and function names
- ✓Write clear comments and update README/docs for new features
- ✓Include tests for all new functionality and bug fixes
- ✓Keep functions focused — small, single-purpose functions
- ✓Pass all linting, security, and test checks before submitting PR
PR Process
- 1Ensure all checks pass (
./scripts/check_all.sh) - 2Update documentation and examples if needed
- 3Write a clear PR description explaining the change
- 4Link related GitHub issues in the PR description
version.txt and bumped via ./scripts/version.sh bump. Releases are automated via .goreleaser.yml.
Version History
7 releases published. Latest stable: v1 (v0.10.2) · November 2025.
Module Path
require github.com/muhammadhamd/go-agentkit v0.10.2 // Import path (case-insensitive on GitHub, but use lowercase) import "github.com/muhammadhamd/go-agentkit/pkg/agent"
Quality Signals (pkg.go.dev)
- ✓Valid go.mod file with proper module declaration
- ✓Redistributable MIT license
- ✓Tagged versions (semantic versioning)
- ✗Not yet on stable v1.x.x (actively developing toward stability)
Cloud Support
A managed cloud service for go-agentkit is in development at go-agent.org.
☁ go-agent Cloud — Coming Soon
Deploy, scale, and monitor your Go agents without managing infrastructure. Join the waitlist for early access.
Community & Support
| Channel | Link |
|---|---|
| Website | go-agent.org |
| GitHub Issues | Report bugs & request features |
| GitHub Discussions | Community conversation |
| Creator (LinkedIn) | Muhammad Hamd |
| pkg.go.dev | Package documentation |
Acknowledgements
go-agentkit is inspired by OpenAI's Assistants API and OpenAI's Python Agent SDK. The goal is to bring the same developer experience to Go — with idiomatic patterns, local LLM support, and multi-provider flexibility. Special thanks to the OpenAI team whose Python and TypeScript SDKs served as the reference implementation.