A practical, code-first handbook for designing, running, and operating autonomous agents in Go. The loop, the tools, the memory, the orchestration — and the production tradeoffs nobody writes down.
// the entire agent loop, in 18 lines of Go.
func Run(ctx context.Context, goal string) error {
msgs := []Message{{Role: "user", Content: goal}}
for turn := 0; turn < 12; turn++ {
resp, err := llm.Complete(ctx, msgs, tools)
if err != nil { return err }
if resp.Done {
log.Info("agent finished", "answer", resp.Text)
return nil
}
// model wants to call tools — execute & feed results back.
results := tools.Dispatch(ctx, resp.Calls)
msgs = append(msgs, resp.AsMessage(), results...)
}
return errors.New("agent: max turns exceeded")
}
Provider-agnostic. Works with the Go SDKs you already use.
An agent is a long-running, concurrent, network-bound service that calls tools, holds state, retries, streams, and crashes in interesting ways. Go was built for exactly this shape of program — without the runtime overhead Python brings.
An agent fans out to N tool calls, streams an LLM response, watches a context cancellation, and emits telemetry — all on the same goroutine budget. Goroutines + channels are the right primitive.
No virtualenv, no requirements.txt drift, no Dockerfile gymnastics. go build produces one file you can scp anywhere — including a Lambda, a sidecar, or a hardened container.
Tool arguments and structured outputs map cleanly to Go structs and JSON tags. The compiler catches drift between what the model emits and what your code expects, before it ever ships.
Cancellation, timeouts, deadlines, request-scoped values — every long-running agent loop needs them, and Go gives them to you in the standard library. No bolt-on async framework.
OpenTelemetry, structured slog, pprof, expvar — all in stdlib or one import away. Tracing an agent's tool calls and token spend is a 20-line job, not a platform decision.
Agents are stochastic enough on the model side. The framework around them should be deterministic, debuggable, and dull. Go's culture of "no magic" pairs unusually well with the chaos LLMs introduce.
Whatever the framework, whatever the model — when you peel back the abstractions you find the same shape: a model decides, tools execute, results return, the model decides again. Master this loop and the rest is decoration.
Read the foundations chapter →Model receives messages + available tools, returns either a final answer or one or more tool calls.
Dispatcher executes tool calls in parallel — HTTP, SQL, filesystem, MCP servers, whatever the agent has access to.
Tool results (or errors) are appended to the message history as tool_result turns.
Loop back to step 1 until the model returns a stop turn, hits a budget, or trips a guardrail.
type SearchArgs struct {
Query string `json:"query" jsonschema:"required"`
MaxHits int `json:"max_hits,omitempty"`
}
var WebSearch = agent.Tool[SearchArgs]{
Name: "web_search",
Description: "Query the web. Returns a list of titled snippets.",
Run: func(ctx context.Context, a SearchArgs) (any, error) {
if a.MaxHits == 0 { a.MaxHits = 5 }
hits, err := brave.Search(ctx, a.Query, a.MaxHits)
if err != nil {
return nil, fmt.Errorf("search: %w", err)
}
return hits, nil
},
}
A tool is a struct with a name, a description, and a function from typed args to a result. The framework derives the JSON schema for you, the model picks one to call, and the dispatcher routes it back into Go. No hand-written schemas. No string-typed argument bags.
Most "agent" demos are actually deterministic workflows. Most "workflows" should actually be agents. Choosing the right shape is the single highest-leverage decision in the project.
A fixed sequence of LLM calls and tool calls. Cheap, fast, predictable, and the right choice for 70% of "AI features." If your task fits in a flowchart, this is it.
Examples — summarization, classification, structured extraction, RAG retrieve-then-answer, content moderation pipelines.
Workflow patterns →The model decides which tool to call next inside a single loop, until it produces a final answer. The right shape when the path through the problem isn't known up-front.
Examples — coding assistants, customer-support resolvers, researchers, autonomous CLI helpers, deep-search agents.
Single-agent patterns →An orchestrator delegates to specialist sub-agents — researcher, writer, critic. Powerful, but the coordination tax is real. Most systems are better served by a single agent with more tools.
Examples — multi-document research, parallel codebase analysis, role-played simulations, supervisor + workers patterns.
Multi-agent patterns →Each project ships as a self-contained module — go run ./cmd/<project> and you have a working agent. Each one isolates a single architectural lesson worth learning in production.
A bash + read + edit + run-tests agent that mirrors how Claude Code works internally. Demonstrates the canonical four-tool loop, persistent diffs, and how to scope blast radius with a chroot-like sandbox.
Ingest markdown → chunk → embed → store in pgvector → query. The boring, deterministic backbone of 80% of "AI search" features, written in Go because the ingest pipeline lives next to your other services.
A draft → critique → revise loop using two distinct system prompts on the same model. A clean demonstration of how a single extra LLM turn dramatically improves output quality at predictable cost.
An orchestrator agent spawns N parallel research sub-agents, each with their own context window, then hands findings to a writer. Demonstrates the "wide context, narrow context" pattern.
A net/http server that streams model output and tool-call events to the browser via Server-Sent Events. The reference shape for any "chat with my data" product surface.
The same single-loop agent — wrapped in OTel tracing, exponential-backoff retries, token-cost accounting, and circuit breakers. The boring layers that turn a demo into a service that runs at 3am.
The agent loop is provider-agnostic — but the concrete SDK matters when you're wiring up tool use, streaming, and structured output. Here's how the four most-used Go SDKs stack up for agent work.
| Provider | Module | Tool use | Streaming | Structured output | Best for |
|---|---|---|---|---|---|
| Anthropic official | anthropic-sdk-go | First-class, parallel | SSE w/ events | Tool-shaped JSON | Long-running agentic loops |
| OpenAI official | openai-go | Function calling | SSE | response_format | Cheapest tool-use, broad ecosystem |
| Gemini | google.golang.org/genai | Function calling | Yes | Schema-typed | Multimodal, long context |
| Ollama (local) | github.com/ollama/ollama/api | Limited (model-dep.) | NDJSON | JSON mode | Air-gapped, dev/test, edge |
| langchaingo | github.com/tmc/langchaingo | Wrapper-level | Wrapper-level | Output parsers | Switching providers, prototyping |
Recommendation — start with the official SDK for the model you're using. Wrap it behind a tiny LLM interface in your own code. Don't reach for langchaingo until you actually have a portability problem to solve.
"The hardest part of an agent isn't the loop — it's deciding what the agent shouldn't do. Tool scope, context budget, and stop conditions are where production systems live or die." — from Patterns: stop conditions and budgets
Every example shown on this site ships in one zip. Drop it on disk, go test ./..., go run ./cmd/hello, and you're inside a working agent loop in under a minute.
Plain Go 1.22. Zero external dependencies. The whole thing builds offline. Includes the agent loop, a typed tool registry with auto-generated JSON Schema, a sandboxed file-tools layer, and two API-key-free LLM stand-ins (Scripted + Toy) so every example runs end-to-end without a network call.
Every chapter in this guide is backed by a runnable Go module. Clone the repo, go mod tidy, and read the agent loop with a debugger attached. That's how this stuff actually clicks.