stemedb/docs/references/go-adk/reference-guide.md
jordan 3cfaa1e1d3 feat: Complete Phase 1 (The Spine) - storage foundation
Phase 1 delivers the complete durability and storage layer:

- WAL with crash recovery: Append-only journal with BLAKE3 checksums,
  fsync guarantees, and proper seek-to-EOF on reopen
- Storage engine: sled-backed KVStore with scan_prefix for range queries
- Content-addressed storage: H:{hash}, V:{hash}, E:{hash} key patterns
- Ingestor: Background worker tailing WAL, writing to KV with 8-byte
  aligned record headers for rkyv zero-copy deserialization
- Comprehensive tests: 31 tests covering crash recovery, round-trips,
  and multi-cycle durability

New crates: stemedb-wal, stemedb-storage, stemedb-ingest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:15:34 -07:00

20 KiB

ADK-Go Developer Reference Guide

A practical guide for building AI agents with Google's Agent Development Kit for Go.


Quick Start

Prerequisites

  • Go 1.24.4 or later
  • Google API key (AI Studio) or Google Cloud project (Vertex AI)

Installation

# Create new project
mkdir my-agent && cd my-agent
go mod init example.com/my-agent

# Install ADK
go get google.golang.org/adk

Environment Setup

# Option 1: AI Studio (API Key)
export GOOGLE_API_KEY="your-api-key"

# Option 2: Vertex AI (Application Default Credentials)
export GOOGLE_CLOUD_PROJECT="your-project-id"
export GOOGLE_CLOUD_LOCATION="us-central1"
gcloud auth application-default login

Minimal Agent

package main

import (
    "context"
    "log"
    "os"

    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/cmd/launcher/adk"
    "google.golang.org/adk/cmd/launcher/full"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/server/restapi/services"
    "google.golang.org/genai"
)

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

    // Create model
    model, err := gemini.NewModel(ctx, "gemini-3-flash-preview", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })
    if err != nil {
        log.Fatal(err)
    }

    // Create agent
    agent, err := llmagent.New(llmagent.Config{
        Name:        "assistant",
        Model:       model,
        Description: "A helpful assistant",
        Instruction: "You are a helpful assistant. Be concise and accurate.",
    })
    if err != nil {
        log.Fatal(err)
    }

    // Launch
    l := full.NewLauncher()
    cfg := &adk.Config{AgentLoader: services.NewSingleAgentLoader(agent)}
    if err := l.Execute(ctx, cfg, os.Args[1:]); err != nil {
        log.Fatal(err)
    }
}

Running Modes

# Console mode (interactive terminal)
go run main.go

# Web UI + API
go run main.go web api webui
# Opens at http://localhost:8080

# Production (API + A2A protocol)
go run main.go web api a2a

Core Imports

import (
    // Agents
    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/agent/sequentialagent"
    "google.golang.org/adk/agent/parallelagent"
    "google.golang.org/adk/agent/loopagent"

    // Models
    "google.golang.org/adk/model/gemini"
    "google.golang.org/genai"

    // Tools
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/functiontool"
    "google.golang.org/adk/tool/geminitool"
    "google.golang.org/adk/tool/mcptool"

    // Session & Memory
    "google.golang.org/adk/session"
    "google.golang.org/adk/memory"

    // Launcher
    "google.golang.org/adk/cmd/launcher/adk"
    "google.golang.org/adk/cmd/launcher/full"
    "google.golang.org/adk/server/restapi/services"
)

Creating Agents

LLM Agent Configuration

agent, err := llmagent.New(llmagent.Config{
    // Required
    Name:  "my_agent",
    Model: model,

    // Recommended
    Description: "What this agent does (used for routing)",
    Instruction: "System prompt with behavior guidelines",

    // Optional
    Tools:     []tool.Tool{...},      // Available tools
    SubAgents: []agent.Agent{...},    // Agents this can delegate to
    OutputKey: "result",              // Save output to state["result"]
})

Instruction Templates

Use curly braces to inject state values:

llmagent.Config{
    Instruction: `You are a travel assistant.
User preferences: {user:preferences}
Current itinerary: {itinerary}
Budget remaining: {budget}`,
}

Available Models

// Gemini models
"gemini-3-flash-preview"      // Fast, efficient
"gemini-2.5-pro"        // Most capable
"gemini-3-flash-preview"      // Previous generation
"gemini-1.5-pro"        // Long context

Tools

Built-in Tools

import "google.golang.org/adk/tool/geminitool"

agent, _ := llmagent.New(llmagent.Config{
    Tools: []tool.Tool{
        geminitool.GoogleSearch{},   // Web search
        geminitool.CodeExecution{},  // Run code in sandbox
    },
})

Custom Function Tools

import "google.golang.org/adk/tool/functiontool"

// 1. Define input/output structs with JSON tags
type CalculateInput struct {
    Expression string `json:"expression" jsonschema:"Math expression to evaluate"`
}

type CalculateOutput struct {
    Result float64 `json:"result"`
    Error  string  `json:"error,omitempty"`
}

// 2. Implement the function
func calculate(ctx tool.Context, input CalculateInput) CalculateOutput {
    // Your logic here
    return CalculateOutput{Result: 42}
}

// 3. Create the tool
calcTool, err := functiontool.New(
    functiontool.Config{
        Name:        "calculate",
        Description: "Evaluates mathematical expressions",
    },
    calculate,
)

// 4. Add to agent
agent, _ := llmagent.New(llmagent.Config{
    Tools: []tool.Tool{calcTool},
})

Schema Tags Reference

type Input struct {
    // Required field with description
    Query string `json:"query" jsonschema:"Search query to execute"`

    // Optional field (omitempty makes it optional)
    Limit int `json:"limit,omitempty" jsonschema:"Max results (default 10)"`

    // Enum values
    Format string `json:"format" jsonschema:"Output format" jsonschema_enum:"json,xml,csv"`
}

Accessing Context in Tools

func myTool(ctx tool.Context, input MyInput) MyOutput {
    // Access session state
    userID := ctx.Session().State().Get("user:id")

    // Access artifacts
    artifact, err := ctx.Artifacts().Load("document.pdf")

    // Standard context operations
    if ctx.Err() != nil {
        return MyOutput{Error: "cancelled"}
    }

    return MyOutput{...}
}

MCP (Model Context Protocol) Tools

import "google.golang.org/adk/tool/mcptool"

// Stdio-based MCP server
params := mcptool.StdioServerParams{
    Command: "npx",
    Args:    []string{"-y", "@modelcontextprotocol/server-filesystem", "/path"},
}
tools, closer, err := mcptool.FromServer(ctx, params)
defer closer.Close()

// HTTP/SSE-based MCP server
sseParams := mcptool.SseServerParams{
    URL:     "http://localhost:8090/sse",
    Timeout: 5.0,
}
tools, closer, err := mcptool.FromServer(ctx, sseParams)

Multi-Agent Patterns

Sequential Agent (Pipeline)

Executes agents in order. Each agent's output is available to the next.

import "google.golang.org/adk/agent/sequentialagent"

// Agent 1: Research
researcher, _ := llmagent.New(llmagent.Config{
    Name:        "researcher",
    Model:       model,
    Instruction: "Research the given topic thoroughly.",
    Tools:       []tool.Tool{geminitool.GoogleSearch{}},
    OutputKey:   "research",  // Saves to state["research"]
})

// Agent 2: Write (uses research output)
writer, _ := llmagent.New(llmagent.Config{
    Name:        "writer",
    Model:       model,
    Instruction: "Write an article based on: {research}",
    OutputKey:   "draft",
})

// Agent 3: Edit
editor, _ := llmagent.New(llmagent.Config{
    Name:        "editor",
    Model:       model,
    Instruction: "Edit and improve: {draft}",
    OutputKey:   "final",
})

// Combine into pipeline
pipeline, _ := sequentialagent.New(sequentialagent.Config{
    AgentConfig: agent.Config{
        Name:        "content_pipeline",
        Description: "Research, write, and edit content",
        SubAgents:   []agent.Agent{researcher, writer, editor},
    },
})

Parallel Agent (Concurrent Execution)

Executes agents simultaneously. Each runs in an isolated branch.

import "google.golang.org/adk/agent/parallelagent"

// Create specialized agents
webSearcher, _ := llmagent.New(llmagent.Config{
    Name:      "web_searcher",
    Model:     model,
    Tools:     []tool.Tool{geminitool.GoogleSearch{}},
    OutputKey: "web_results",
})

docAnalyzer, _ := llmagent.New(llmagent.Config{
    Name:      "doc_analyzer",
    Model:     model,
    OutputKey: "doc_analysis",
})

// Run in parallel
parallel, _ := parallelagent.New(parallelagent.Config{
    AgentConfig: agent.Config{
        Name:      "parallel_research",
        SubAgents: []agent.Agent{webSearcher, docAnalyzer},
    },
})

Loop Agent (Iterative Refinement)

Repeats agents until max iterations or termination condition.

import "google.golang.org/adk/agent/loopagent"

// Writer produces drafts
writer, _ := llmagent.New(llmagent.Config{
    Name:        "writer",
    Model:       model,
    Instruction: "Write or improve the draft. Current: {draft}",
    OutputKey:   "draft",
})

// Critic evaluates and decides if done
critic, _ := llmagent.New(llmagent.Config{
    Name:  "critic",
    Model: model,
    Instruction: `Evaluate the draft: {draft}
If excellent, respond with exactly: APPROVED
Otherwise, provide specific improvement suggestions.`,
    OutputKey: "feedback",
})

// Loop until approved or max iterations
refiner, _ := loopagent.New(loopagent.Config{
    AgentConfig: agent.Config{
        Name:      "refiner",
        SubAgents: []agent.Agent{writer, critic},
    },
    MaxIterations: 5,
})

Hierarchical Delegation

Parent agent delegates to specialized sub-agents.

// Specialist agents
mathAgent, _ := llmagent.New(llmagent.Config{
    Name:        "math_expert",
    Description: "Handles mathematical calculations and problems",
    Model:       model,
})

codeAgent, _ := llmagent.New(llmagent.Config{
    Name:        "code_expert",
    Description: "Writes and explains code",
    Model:       model,
    Tools:       []tool.Tool{geminitool.CodeExecution{}},
})

// Router agent delegates based on query type
router, _ := llmagent.New(llmagent.Config{
    Name:  "router",
    Model: model,
    Instruction: `Route queries to the appropriate specialist.
Use math_expert for calculations.
Use code_expert for programming questions.`,
    SubAgents: []agent.Agent{mathAgent, codeAgent},
})

Session & State Management

State Prefixes

Prefix Scope Persistence
(none) App Persists across sessions
user: User Persists for specific user
app: Application Global app state
temp: Invocation Cleared after each turn

Reading/Writing State

// In agent instruction (template syntax)
Instruction: "User name: {user:name}, Preferences: {user:preferences}"

// In tool function
func myTool(ctx tool.Context, input Input) Output {
    state := ctx.Session().State()

    // Read
    name := state.Get("user:name")

    // Write
    state.Set("user:preferences", "dark_mode=true")

    // Delete
    state.Delete("temp:scratch")
}

Session Services

import "google.golang.org/adk/session"

// In-memory (development)
sessionSvc := session.InMemoryService()

// Create session
sess, err := sessionSvc.Create(ctx, &session.CreateRequest{
    AppName: "my_app",
    UserID:  "user123",
    State:   map[string]any{"user:name": "Alice"},
})

// Get existing session
sess, err := sessionSvc.Get(ctx, &session.GetRequest{
    AppName:   "my_app",
    UserID:    "user123",
    SessionID: "session-id",
})

// List user sessions
sessions, err := sessionSvc.List(ctx, &session.ListRequest{
    AppName: "my_app",
    UserID:  "user123",
})

Memory Service (Long-term Knowledge)

import "google.golang.org/adk/memory"

// In-memory for development
memorySvc := memory.InMemoryService()

// Store knowledge
err := memorySvc.Add(ctx, &memory.AddRequest{
    AppName: "my_app",
    UserID:  "user123",
    Content: "User prefers vegetarian restaurants",
})

// Search knowledge
results, err := memorySvc.Search(ctx, &memory.SearchRequest{
    AppName: "my_app",
    UserID:  "user123",
    Query:   "food preferences",
})

Running Agents Programmatically

Using the Runner

import "google.golang.org/adk/runner"

// Create runner
r := runner.New(runner.Config{
    Agent:          myAgent,
    SessionService: session.InMemoryService(),
    MemoryService:  memory.InMemoryService(),
})

// Run agent
userMsg := genai.NewContentFromText(genai.RoleUser, "Hello!")

for event, err := range r.Run(ctx, "user123", "session123", userMsg, agent.RunConfig{}) {
    if err != nil {
        log.Printf("Error: %v", err)
        continue
    }

    // Process events
    switch {
    case event.IsFinal():
        fmt.Println("Final:", event.Content())
    case event.Partial:
        fmt.Print(event.Text()) // Streaming output
    case event.ToolCall != nil:
        fmt.Printf("Calling tool: %s\n", event.ToolCall.Name)
    }
}

Streaming Modes

// Streaming modes
agent.RunConfig{
    StreamingMode: agent.StreamingModeNone,  // Wait for complete response
    StreamingMode: agent.StreamingModeSSE,   // Server-sent events
}

Callbacks

Model Callbacks

agent, _ := llmagent.New(llmagent.Config{
    BeforeModelCallback: func(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMRequest, error) {
        // Modify request before sending to model
        log.Printf("Sending to model: %v", req)

        // Block certain queries
        if containsForbiddenContent(req) {
            return nil, errors.New("request blocked")
        }

        return req, nil
    },

    AfterModelCallback: func(ctx agent.CallbackContext, resp *model.LLMResponse) (*model.LLMResponse, error) {
        // Process/modify response
        log.Printf("Received: %v", resp)
        return resp, nil
    },
})

Tool Callbacks

agent, _ := llmagent.New(llmagent.Config{
    BeforeToolCallback: func(ctx agent.CallbackContext, call *tool.Call) (*tool.Call, error) {
        // Validate tool arguments
        log.Printf("Tool call: %s(%v)", call.Name, call.Args)

        // Block dangerous operations
        if call.Name == "delete_file" {
            return nil, errors.New("file deletion not allowed")
        }

        return call, nil
    },

    AfterToolCallback: func(ctx agent.CallbackContext, call *tool.Call, result *tool.Result) (*tool.Result, error) {
        // Log or modify results
        log.Printf("Tool result: %v", result)
        return result, nil
    },
})

Deployment

Local Development

# Console mode
go run main.go

# Web UI for testing
go run main.go web api webui

Cloud Run Deployment

# Build the CLI tool
go build -o adkgo ./cmd/adkgo

# Deploy
./adkgo deploy cloudrun \
    -p $GOOGLE_CLOUD_PROJECT \
    -r us-central1 \
    -s my-agent-service \
    -e ./main.go \
    --a2a --api

Manual Docker Deployment

# Dockerfile
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o agent main.go

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/agent .
EXPOSE 8080
ENV PORT=8080
CMD ["./agent", "web", "api", "a2a"]
# Build and run
docker build -t my-agent .
docker run -p 8080:8080 -e GOOGLE_API_KEY=$GOOGLE_API_KEY my-agent

Environment Variables

Variable Purpose
GOOGLE_API_KEY AI Studio API key
GOOGLE_CLOUD_PROJECT GCP project ID
GOOGLE_CLOUD_LOCATION GCP region (e.g., us-central1)
PORT Server port (default: 8080)

API Endpoints

When running with web api:

Endpoint Method Purpose
/apps/{app}/users/{user}/sessions POST Create session
/apps/{app}/users/{user}/sessions GET List sessions
/apps/{app}/users/{user}/sessions/{id} GET Get session
/apps/{app}/users/{user}/sessions/{id}:run POST Send message
/apps/{app}/users/{user}/sessions/{id}:runSse POST Stream response
/.well-known/agent-card.json GET A2A agent card

Example API Call

# Create session
curl -X POST http://localhost:8080/apps/myapp/users/user1/sessions \
  -H "Content-Type: application/json" \
  -d '{}'

# Send message
curl -X POST http://localhost:8080/apps/myapp/users/user1/sessions/SESSION_ID:run \
  -H "Content-Type: application/json" \
  -d '{"message": {"role": "user", "parts": [{"text": "Hello!"}]}}'

Common Patterns

Error Handling in Tools

func myTool(ctx tool.Context, input Input) Output {
    result, err := externalAPI(input.Query)
    if err != nil {
        // Return error message to the model
        return Output{
            Success: false,
            Error:   fmt.Sprintf("API error: %v", err),
        }
    }
    return Output{Success: true, Data: result}
}

Tool with HTTP Client

func fetchURL(ctx tool.Context, input URLInput) FetchOutput {
    client := &http.Client{Timeout: 10 * time.Second}

    req, _ := http.NewRequestWithContext(ctx, "GET", input.URL, nil)
    resp, err := client.Do(req)
    if err != nil {
        return FetchOutput{Error: err.Error()}
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    return FetchOutput{Content: string(body)}
}

Conditional Tool Availability

// Filter tools based on state
agent, _ := llmagent.New(llmagent.Config{
    ToolFilter: func(ctx agent.InvocationContext, tools []tool.Tool) []tool.Tool {
        userRole := ctx.Session().State().Get("user:role")
        if userRole != "admin" {
            // Remove admin-only tools
            return filterOutAdminTools(tools)
        }
        return tools
    },
})

Graceful Shutdown

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // Handle shutdown signals
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
    go func() {
        <-sigCh
        log.Println("Shutting down...")
        cancel()
    }()

    // Run agent
    l := full.NewLauncher()
    if err := l.Execute(ctx, cfg, os.Args[1:]); err != nil {
        if ctx.Err() == nil {
            log.Fatal(err)
        }
    }
}

Troubleshooting

Common Issues

Problem Solution
module not found Use google.golang.org/adk, not GitHub path
API key invalid Check GOOGLE_API_KEY is set correctly
context deadline exceeded Increase timeout or check network
tool not called Improve tool description; make it clearer
state not persisting Check state prefix; use user: for persistence

Debug Logging

import "log"

agent, _ := llmagent.New(llmagent.Config{
    BeforeModelCallback: func(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMRequest, error) {
        log.Printf("REQUEST: %+v", req)
        return req, nil
    },
    AfterModelCallback: func(ctx agent.CallbackContext, resp *model.LLMResponse) (*model.LLMResponse, error) {
        log.Printf("RESPONSE: %+v", resp)
        return resp, nil
    },
})

Web UI Debugging

Access http://localhost:8080 when running with web api webui:

  • Events tab: See all events in the session
  • Request/Response tab: Raw LLM communication
  • Graph tab: Visualize agent flow
  • State tab: Inspect session state

Resources