# 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

```bash
# 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

```bash
# 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

```go
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

```bash
# 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

```go
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

```go
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:

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

### Available Models

```go
// 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

```go
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

```go
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

```go
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

```go
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

```go
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.

```go
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.

```go
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.

```go
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.

```go
// 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

```go
// 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

```go
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)

```go
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

```go
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

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

---

## Callbacks

### Model Callbacks

```go
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

```go
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

```bash
# Console mode
go run main.go

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

### Cloud Run Deployment

```bash
# 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
# 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"]
```

```bash
# 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

```bash
# 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

```go
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

```go
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

```go
// 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

```go
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

```go
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

- **GitHub**: https://github.com/google/adk-go
- **Documentation**: https://google.github.io/adk-docs/
- **Go Package**: https://pkg.go.dev/google.golang.org/adk
- **Examples**: https://github.com/google/adk-go/tree/main/examples
- **Python ADK**: https://github.com/google/adk-python
- **Java ADK**: https://github.com/google/adk-java
