Go SDK
Use docker-agent as a Go library to embed AI agents in your applications.
Overview
docker-agent can be used as a Go library, allowing you to build AI agents directly into your Go applications. This gives you full programmatic control over agent creation, tool integration, and execution.
import "github.com/docker/docker-agent/pkg/..."
Core Packages
| Package | Purpose |
|---|---|
pkg/agent |
Agent creation and configuration |
pkg/runtime |
Agent execution and event streaming |
pkg/session |
Conversation state management |
pkg/team |
Multi-agent team composition |
pkg/tools |
Tool interface and utilities |
pkg/tools/builtin |
Built-in tools (shell, filesystem, etc.) |
pkg/model/provider/* |
Model provider clients |
pkg/config/latest |
Configuration types |
pkg/environment |
Environment and secrets |
pkg/tui/components/toolconfirm |
Tool-confirmation policy: Decision enum, BuildPermissionPattern, key bindings, and rejection-reason presets. Share this instead of copying the permission-pattern logic. |
pkg/tui/service |
StaticSessionState — a SessionStateReader with conservative fixed values, for rendering message/tool views outside the full TUI app. Replaces hand-rolled nine-method stubs. |
pkg/tui/animation |
Stopper / StopView — animation lifecycle contract. Call StopAnimation on views removed from the UI to prevent leaked tick subscriptions. |
pkg/tui/components/transcript |
Embedded transcript view with read-only Messages() accessor for observing conversation structure in host tests and persistence layers. |
Embedding TUI Components
When building custom UIs on top of docker-agent’s TUI primitives, four packages define the contracts that keep the runtime and the UI in sync:
pkg/tui/components/toolconfirm— import this package for the permission-decision policy rather than copying the pattern-building logic. TheDecisionenum,BuildPermissionPatternhelper, and rejection-reason presets are the canonical source of truth: whatever pattern is shown to the user in the confirmation dialog is exactly the pattern granted to the runtime.pkg/tui/service— useStaticSessionStateas a stubSessionStateReaderwhen rendering individual message or tool views outside the full TUI app. It returns conservative fixed values for all nine interface methods, eliminating the need for hand-rolled stubs.pkg/tui/animation— implementanimation.Stopperon any view that owns a tick-based animation. CallStopAnimationwhenever a view is removed from the UI hierarchy to prevent leakedtime.Ticksubscriptions from firing against a dead view.pkg/tui/components/transcript— embed the transcript view for displaying conversation history. Use theMessages()method to read the current slice of transcript messages (treat as read-only — mutations desync renders). This is useful for host-side tests asserting on chat history, and for persistence layers that need to snapshot conversation state.
Basic Example
Create a simple agent and run it:
package main
import (
"context"
"fmt"
"log"
"os/signal"
"syscall"
"github.com/docker/docker-agent/pkg/agent"
"github.com/docker/docker-agent/pkg/config/latest"
"github.com/docker/docker-agent/pkg/environment"
"github.com/docker/docker-agent/pkg/model/provider/openai"
"github.com/docker/docker-agent/pkg/runtime"
"github.com/docker/docker-agent/pkg/session"
"github.com/docker/docker-agent/pkg/team"
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer cancel()
if err := run(ctx); err != nil {
log.Fatal(err)
}
}
func run(ctx context.Context) error {
// Create model provider
llm, err := openai.NewClient(
ctx,
&latest.ModelConfig{
Provider: "openai",
Model: "gpt-4o",
},
environment.NewDefaultProvider(),
)
if err != nil {
return err
}
// Create agent
assistant := agent.New(
"root",
"You are a helpful assistant.",
agent.WithModel(llm),
agent.WithDescription("A helpful assistant"),
)
// Create team and runtime
t := team.New(team.WithAgents(assistant))
rt, err := runtime.New(t)
if err != nil {
return err
}
// Run with a user message
sess := session.New(
session.WithUserMessage("What is 2 + 2?"),
)
messages, err := rt.Run(ctx, sess)
if err != nil {
return err
}
// Print the response
fmt.Println(messages[len(messages)-1].Message.Content)
return nil
}
Custom Tools
Define custom tools for your agent:
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/docker/docker-agent/pkg/tools"
)
// Define the tool's input schema
type AddNumbersArgs struct {
A int `json:"a"`
B int `json:"b"`
}
// Implement the tool handler
func addNumbers(_ context.Context, toolCall tools.ToolCall) (*tools.ToolCallResult, error) {
var args AddNumbersArgs
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &args); err != nil {
return nil, err
}
result := args.A + args.B
return tools.ResultSuccess(fmt.Sprintf("%d", result)), nil
}
func main() {
// Create the tool definition
addTool := tools.Tool{
Name: "add",
Category: "math",
Description: "Add two numbers together",
Parameters: tools.MustSchemaFor[AddNumbersArgs](),
Handler: addNumbers,
}
// Use with an agent
calculator := agent.New(
"root",
"You are a calculator. Use the add tool for arithmetic.",
agent.WithModel(llm),
agent.WithTools(addTool),
)
// ...
}
Streaming Responses
Process events as they happen:
func runStreaming(ctx context.Context, rt runtime.Runtime, sess *session.Session) error {
events := rt.RunStream(ctx, sess)
for event := range events {
switch e := event.(type) {
case *runtime.StreamStartedEvent:
fmt.Println("Stream started")
case *runtime.AgentChoiceEvent:
// Print response chunks as they arrive
fmt.Print(e.Content)
case *runtime.ToolCallEvent:
fmt.Printf("\n[Tool call: %s]\n", e.ToolCall.Function.Name)
case *runtime.ToolCallConfirmationEvent:
// Auto-approve tool calls
rt.Resume(ctx, runtime.ResumeRequest{
Type: runtime.ResumeTypeApproveSession,
})
case *runtime.ToolCallResponseEvent:
fmt.Printf("[Tool response: %s]\n", e.Response)
case *runtime.StreamStoppedEvent:
fmt.Println("\nStream stopped")
case *runtime.ErrorEvent:
return fmt.Errorf("error: %s", e.Error)
}
}
return nil
}
Multi-Agent Teams
Create agents that delegate to sub-agents:
package main
import (
"github.com/docker/docker-agent/pkg/agent"
"github.com/docker/docker-agent/pkg/team"
"github.com/docker/docker-agent/pkg/tools/builtin"
)
func createTeam(llm provider.Provider) *team.Team {
// Create a child agent
researcher := agent.New(
"researcher",
"You research topics thoroughly.",
agent.WithModel(llm),
agent.WithDescription("Research specialist"),
)
// Create root agent with sub-agents
coordinator := agent.New(
"root",
"You coordinate research tasks.",
agent.WithModel(llm),
agent.WithDescription("Team coordinator"),
agent.WithSubAgents(researcher),
agent.WithToolSets(builtin.NewTransferTaskTool()),
)
return team.New(team.WithAgents(coordinator, researcher))
}
Built-in Tools
Use docker-agent’s built-in tools:
import (
"github.com/docker/docker-agent/pkg/config"
"github.com/docker/docker-agent/pkg/tools/builtin"
)
func createAgentWithBuiltinTools(llm provider.Provider) *agent.Agent {
// Runtime config for tools that need it
rtConfig := &config.RuntimeConfig{
Config: config.Config{
WorkingDir: "/path/to/workdir",
},
}
return agent.New(
"root",
"You are a developer assistant.",
agent.WithModel(llm),
agent.WithToolSets(
// Shell tool for running commands
builtin.NewShellTool(os.Environ(), rtConfig),
// Filesystem tools
builtin.NewFilesystemTool(rtConfig.Config.WorkingDir),
// Think tool for reasoning
builtin.NewThinkTool(),
// Todo tool for task tracking
builtin.NewTodoTool(),
),
)
}
HTTP Middleware / Transport Wrappers
Use options.WithHTTPTransportWrapper to inject HTTP middleware into the transport chain of all provider clients built by docker-agent. This is useful for request tracing, injecting custom headers, collecting metrics, or any other cross-cutting concern at the HTTP layer.
import (
"net/http"
"github.com/docker/docker-agent/pkg/model/provider/options"
)
type headerTransport struct {
base http.RoundTripper
}
func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req = req.Clone(req.Context())
req.Header.Set("X-Request-Source", "my-app")
return t.base.RoundTrip(req)
}
// Example: add a custom header to every outbound LLM request
wrapper := options.WithHTTPTransportWrapper(
func(base http.RoundTripper) http.RoundTripper {
return &headerTransport{base: base}
},
)
client, err := openai.NewClient(ctx, &latest.ModelConfig{
Provider: "openai",
Model: "gpt-4o",
}, env, wrapper)
The wrapper receives the already-instrumented transport (OpenTelemetry, SSE decompression, Desktop proxy support) as its base argument, so wrapping it preserves all built-in behaviour.
Supported providers: Anthropic, OpenAI, Gemini (GeminiAPI backend), Bedrock. Works in both direct and gateway/proxy mode.
Vertex AI uses an ADC-managed HTTP client that docker-agent cannot intercept. When a transport wrapper is set, docker-agent falls back to the GeminiAPI backend instead of Vertex AI — a debug message is logged.
In gateway mode the wrapper is called on every LLM request because gateway clients are rebuilt each call for short-lived auth tokens. In direct mode it is called once at client construction. Rate-limit responses (HTTP 429) are classified as non-retryable by the runtime and cause the model chain to skip to the next fallback, so wrappers that track per-request outcomes will observe these as failures rather than retried calls.
Returning nil from your wrapper function is not allowed; docker-agent logs a warning and keeps the original transport instead.
Using Different Providers
import (
"github.com/docker/docker-agent/pkg/model/provider/anthropic"
"github.com/docker/docker-agent/pkg/model/provider/gemini"
"github.com/docker/docker-agent/pkg/model/provider/openai"
)
// OpenAI
openaiClient, _ := openai.NewClient(ctx, &latest.ModelConfig{
Provider: "openai",
Model: "gpt-4o",
}, env)
// Anthropic
anthropicClient, _ := anthropic.NewClient(ctx, &latest.ModelConfig{
Provider: "anthropic",
Model: "claude-sonnet-4-5",
}, env)
// Google Gemini
geminiClient, _ := gemini.NewClient(ctx, &latest.ModelConfig{
Provider: "google",
Model: "gemini-3.5-flash",
}, env)
Session Options
import "github.com/docker/docker-agent/pkg/session"
sess := session.New(
// Set a title for the session
session.WithTitle("Code Review Task"),
// Add user message
session.WithUserMessage("Review this code for bugs"),
// Limit iterations
session.WithMaxIterations(20),
)
Error Handling
messages, err := rt.Run(ctx, sess)
if err != nil {
if errors.Is(err, context.Canceled) {
// User cancelled
log.Println("Operation cancelled")
return nil
}
if errors.Is(err, context.DeadlineExceeded) {
// Timeout
log.Println("Operation timed out")
return nil
}
// Other error
return fmt.Errorf("runtime error: %w", err)
}
// Check for errors in the event stream
for event := range rt.RunStream(ctx, sess) {
if errEvent, ok := event.(*runtime.ErrorEvent); ok {
return fmt.Errorf("stream error: %s", errEvent.Error)
}
}
Complete Example
See the examples/golibrary directory for complete working examples:
simple/— Basic agent with no toolstool/— Custom tool implementationstream/— Streaming event handlingmulti/— Multi-agent with sub-agentsbuiltintool/— Using built-in tools