Building AI Agents with ADK Go: A Practical Guide

Featured

Table of Contents

🚀 Introducing the Go ADK

I'm Ivan Cheung, an engineer working on the Agent Development Kit. I want to walk you through a practical, hands on introduction to ADK Go and Go 80K, a handcrafted agent library that is idiomatic for Go. My goal is to get you up and running quickly and to give you the conceptual scaffolding you need to design reliable, maintainable, and powerful AI agents in Go.

ADK Go exists because building agentic systems from scratch quickly becomes complex. If you have ever integrated an LLM directly with business logic you already know the familiar list of problems. You must manage conversation state, memory, tool calls, error handling, control flow, and visibility into what your agent did. Without a framework you frequently reinvent the same code paths and debugging patterns. ADK solves this by providing a modular and flexible open source framework that abstracts these common concerns while letting you write idiomatic Go logic.

When I say ADK Go, think of a toolkit that helps you define agents, compose them into workflows, give them tools such as web search or database queries, persist conversation context, and run everything with sane defaults. ADK Go aims to make the common tasks simple while preserving the ability to customize and extend behavior for advanced use cases.

What is Go 80K

Go 80K is a name I use to describe the handcrafted AI agent library built for Go developers. It is designed to feel natural to Go programmers. It is opinionated in places that matter like type safety for tool inputs and outputs, clear session and state models, and an event oriented runner. But it is flexible enough to let you build simple chat assistants, multi agent orchestrators, and workflow driven systems.

Who should read this

  • Go developers who want to build generative AI applications without implementing low level orchestration from scratch.
  • Teams exploring multi agent systems and looking for patterns to decompose complex tasks into smaller agents.
  • Engineers who need robust session and state management for conversational apps.

🛠️ Build your first Go agent

Let us get practical. The absolute first step for any Go project is adding the dependency. In Go this is simple. You add ADK Go to your module via the standard go get command. Once the dependency is in place, you can start defining agents.

In ADK Go, an agent is typically defined by the following components.

  • a name
  • a short description that tells the model what this agent does
  • an LLM model object which represents the underlying language model
  • an instruction prompt that guides the agent's persona, goals, and style
  • a session service which stores conversation history and state
  • a runner which executes the agent and streams events

To make this more concrete, imagine I want to build an assistant that teaches science to students. I would define an agent with a concise description like "A friendly science teacher who explains concepts using analogies and short examples." I would pick an LLM model object and craft a prompt that specifies the target audience and the level of depth to use. I wire in an in memory session service at first because in development the in memory store is convenient and low friction. Then I create a runner object which will kick off the agent. When the runner executes the agent it returns an iterator of events that describe everything the agent does. These events include the model outputs, the tool calls the agent attempts, and structured metadata that helps me debug or visualize the run.

Running the program is as simple as any other Go program. You call go run, communicate with the agent through the runner, and consume the event stream. I usually test with a few prompts like "Hello" and a domain question such as "Explain photosynthesis to a 12 year old." The agent responds, and the events let me see intermediate reasoning steps and tool usage when applicable.

The runner and event model

The runner is a key concept. It orchestrates the interaction between the model, your tools, and the session. When I run an agent, the runner returns a stream of events rather than a single opaque response. That gives me visibility into the agent's lifecycle: it shows me what prompts were sent to the LLM, what the LLM tried to call, what tools were invoked, and what the final answer was. That event stream is invaluable for debugging and for building UIs that can show users agent decisions as they happen.

ADK Go also includes a Web UI to inspect and interact with agents. After a short setup step I can open the Web UI, choose my science agent, and run conversations from a browser. The UI exposes the same event stream with a clear list of things that happened during the run. I often use it while developing to watch the agent call tools and to ensure the prompts are being applied as intended.

🔍 Understanding Tools

One of the most important concepts when building agents is tools. Large language models are powerful at generating and manipulating text. They are not, however, omniscient connectors to the external world. For tasks that require fresh facts, database access, network calls, or any side effects, you give the agent tools. A tool is a capability the agent can call to extend itself beyond the model's internal knowledge.

Tools are essential when you want agents to handle recent or specialized information. For example, if your agent should answer questions about breaking news it needs web search. If it should fetch the status of an order it needs an API call into your order database. Tools are how you map those external capabilities into an interface the model can use.

How tools work

In ADK Go the flow is consistent and predictable. The agent receives a user request. The LLM examines that request and decides whether it needs to call a tool. If it does, it chooses which tool to use and formulates arguments. The ADK framework intercepts the LLM's function call. The runner executes the corresponding Go code for that tool and returns the tool's result back into the agent's context. The LLM then uses that result to form a final response to the user.

This flow keeps tool execution deterministic and testable because the tool code is plain Go. It also keeps the interface understandable for the LLM by using clear descriptions for each tool and by defining precise argument structures when necessary.

Three types of tools

ADK Go supports three broad classes of tools.

  • Built in tools. These are common utilities packaged with the framework. An example is Google Search which returns recent web results.
  • Third party tools. These are tools provided by external collaborators or servers, such as tools published on MCP servers.
  • Function tools. These are custom Go functions you register with the agent. They wrap arbitrary Go code and make it callable by the LLM.

My advice is to start with function tools. They are the simplest to implement and they let you map your business logic directly into the agent. You define Go structs for inputs and outputs which the framework uses to validate and structure calls. Then you write a short Go function that takes the input struct, performs work such as calling a database or invoking an API, and returns an output struct. You register the function as a tool with a clear description. The LLM will then learn when to call it based on that description.

🧩 Run your agent with tools

To illustrate, imagine a customer service agent that needs to retrieve order status. I create input and output structures like OrderStatusRequest and OrderStatusResponse. I implement a function GetOrderStatus that queries my order system and returns a structured response containing the order id, current state, and expected delivery date. I register that function with ADK as a function tool and I provide a human readable description that the LLM can use to identify when to call it.

When a user asks "Where is my order 12345", the LLM reasons that an external lookup is needed, constructs the function call with the order id as an argument, and the runner executes GetOrderStatus. The result returns. The LLM incorporates that result and writes a friendly summary for the user such as "Your order 12345 is in transit and expected to arrive on Wednesday." The decisive part here is clear tool descriptions and a precise type contract for the function tool. That greatly reduces hallucination because the LLM learns the shape of the result it will receive.

Design tip: be explicit in your tool descriptions. The model will rely on that text to decide whether to call a tool and how to format the arguments. Think of the tool description as part of the model's API documentation. The clearer it is the more reliable the agent will be.

Function tools and type safety

Function tools also help enforce type safety. By defining strict input and output structures in Go you reduce ambiguity. The model knows exactly what fields exist and what types are expected. ADK will marshal arguments into the appropriate Go types and unmarshal the return values when the tool completes. This pattern leads to more stable integration and easier debugging compared to passing raw JSON or freeform text back and forth.

Another advantage of function tools is they are synchronous from the agent's perspective. The agent requests the tool, ADK runs it, and the tool returns. If your tool requires asynchronous long running processing you can implement polling semantics inside the function or expose a separate status tool. The key idea is your logic remains in Go and the agent remains an orchestrator that uses those typed functions as capabilities.

🤝 Multi agent patterns in Go

One of the features I am most excited to talk about is multi agent systems. ADK Go makes it straightforward to combine multiple agents into larger systems. Multi agent architectures are useful when tasks naturally decompose into subtasks or when different agents specialize in distinct domains.

There are different ways to compose agents in ADK. I will walk through two broad patterns. The first pattern is agent as a tool. The second pattern is workflow agents which orchestrate pipelines of agents.

Agent as a tool

The agent tool pattern lets one agent call another agent as if it were a tool. This is powerful because it enables hierarchical and collaborative behaviour. For instance, suppose I have a main assistant that handles general tutoring, and I want a dedicated summarizer that condenses long documents. I can implement the summarizer as a separate agent with its own prompt, model settings, and tooling. Then I wrap that summarizer agent with agent tool factory logic which produces a tool usable by the main agent.

From the main agent's viewpoint the summarizer tool is just another capability. It accepts a document and returns a concise summary. Behind the scenes ADK runs the summarizer agent, returns the events, and provides the final output to the top level agent. This separation of concerns helps me iterate on each agent in isolation and reuse components across different root agents.

Workflow agents

Workflow agents are higher order agents that orchestrate calls to multiple sub agents in a prescribed or dynamic manner. ADK Go ships with a few common workflow patterns out of the box.

  • Sequential agent. This is a linear pipeline where each agent runs in order and passes results to the next. It is useful for processes that have clear stages such as data ingestion, analysis, and summarization.
  • Parallel agent. This runs multiple sub agents concurrently. That is great for broad search or research tasks where I want multiple strategies applied in parallel and then a summarizer or aggregator to consolidate results.
  • Loop agent. This runs a chain of agents and iterates until a stopping condition is met. Looping is helpful for iterative refinement workflows like drafting a story, critiquing it, making improvements, and repeating until a quality gate passes.

Choosing between single agent, multi agent, or workflow depends on the task. If the task requires dynamic reasoning and a small set of tools a single agent may be sufficient. If the task breaks naturally into subdomains or requires different specialist models then multiple agents can increase modularity and maintainability. Workflows are the right choice when you need predictable orchestration across agents and determinism in the pipeline stages.

When to use what

Here are some practical heuristics I use:

  • Use one agent when the problem is tightly scoped and requires LLM reasoning with a few tools.
  • Use multiple agents when subproblems are largely independent or when you want to reuse specialist logic across different root agents.
  • Use workflow agents when you want a prescriptive, repeatable pipeline where each stage performs a distinct operation and latency or ordering guarantees matter.

Keep in mind multi agent systems introduce communication overhead and potential latency. If responsive end user UX is important, balance modularity with performance by caching results, using parallel agents where possible, and avoiding unnecessary cross agent calls.

🗂️ State, Sessions, and Events

Managing context is central to any conversational or agentic application. ADK Go provides a clear model for session management, state, and events that I find very helpful when building robust systems.

Sessions

A session represents a single ongoing conversation thread with a user. Each session has key identifiers such as a unique ID, an application name, and a user ID. These identifiers allow you to route interactions and to maintain multiple independent conversations simultaneously across many users.

Sessions store history and a state map and record the timestamp of the last event. History is stored as a sequence of events that include everything that happened in the conversation. That includes messages, tool calls, tool results, and any agent transfers. The session gives you an auditable trail of the entire interaction and allows agents to reason about past actions.

State

State is the agent's temporary scratch pad, a place to store data specific to a single conversation. State is not long term memory. It is meant to support the current thread. You can prepopulate state when you create the session to change agent behavior. For example, in my science teacher example I make the prompt generic and inject the topic and the audience into the session state. The prompt uses placeholders like {topic} and {audience} that are resolved from state at runtime. That allows the same agent to teach different subjects to different audiences depending on the session initialization parameters.

State gets updated over the course of the conversation by callbacks, by tools, and by a special output key mechanism. This output key mechanism lets agents write structured results into state when they produce outputs. Using state in this way subtly changes how you design prompts because your prompts become templates reading from the session state rather than fixed text that you have to rewrite for every scenario.

Events

As mentioned earlier, ADK Go exposes events for everything that happens. Events are the fundamental unit of observability in the system. Typical event types include:

  • user message
  • model output
  • tool call attempted
  • tool result returned
  • agent transfer or handoff
  • errors and warnings

I use the event stream both in development and in production. In development it helps me understand why the model chose to call a particular tool. In production I archive events to build analytics, to audit behavior for compliance, and to feed learning systems that refine prompts and tool descriptions.

Long term memory

While sessions and state handle conversation level context, many applications need knowledge that persists beyond a single conversation. ADK Go supports a memory service for that purpose. The memory service allows you to store and retrieve long lived facts such as user preferences, previously provided personal details, or an account history. I often integrate a vector store for semantic retrieval when I need to fetch documents and context relevant to a user query. The memory service is pluggable so you can choose the persistence and retrieval mechanism that fits your scale and privacy requirements.

🧭 Prompt quality and agent behavior

One of the most underrated parts of agent design is the prompt. Good prompts shape the agent's persona, clarify the goals, and describe how to use tools. I invest significant time writing robust prompts because they reduce unexpected behavior and they make testing simpler.

When I craft prompts I pay special attention to the following:

  • persona and tone. Should the agent be formal or casual? Friendly or terse? Give explicit instructions.
  • goals and success criteria. Tell the model what a good answer looks like and what should happen if required data is absent.
  • tool usage guidance. Document each tool in the prompt or in the tool description with expected arguments and typical use cases.
  • response format. When you want structured output prefer explicit templates or JSON schemas to reduce parsing errors.
  • error handling. Tell the model how to behave if a tool returns no results or if the model is unsure.

Clear prompt design often beats complex code workarounds. For example, instructing the model to avoid hallucination by explicitly telling it to prefer calling a tool when relevant leads to far fewer erroneous assertions than trying to filter responses after the fact.

🔐 Safety, testing, and reliability

Any production agent must consider safety and reliability. ADK Go gives you tools to make this easier but you still need to design systems responsibly. Here are the practical steps I take when building agents.

  • Define explicit tool contracts. Make sure tools have clear input schemas and output schemas. Validate inputs server side where necessary.
  • Limit tool capabilities where possible. For tools that can cause side effects such as sending emails or making purchases, require multi step confirmations or human in the loop approvals.
  • Log events thoroughly. The event stream is your primary debugging and audit record. Persist it so you can reconstruct any conversation if users report problems.
  • Create test harnesses. Mock LLM responses and tool results to test edge cases deterministically. Unit test workflow agents using fake sub agents so you can verify orchestration logic.
  • Rate limit and monitor resource usage. Agents that call external APIs can incur costs. Monitor usage and build quotas to prevent runaway bills.

By combining careful prompt design with strict tool contracts and good observability you reduce the risk of unexpected behavior and make it easier to diagnose and fix issues when they occur.

📈 Scaling and deployment

Scaling agent based systems has two primary axes: compute and state management. On the compute side you decide how to distribute agent runs. The runner is stateless for the most part so you can run many runners horizontally. For workflows that span multiple steps you can persist intermediate outputs into session storage or a database and resume runs.

For state and memory you choose backing stores that match your needs. In development I use the in memory session service because it is simple. In production I switch to a persisted session store such as a managed database or a document store. Memory services that use vector stores are deployed separately and scaled depending on query volume and index size.

Agent composition and multi agent patterns also influence scaling choices. Agents that call other agents synchronously add latency because you run nested runs. If latency is critical you can precompute results or use parallel agents where possible. ADK Go gives you the primitives to orchestrate both synchronous and asynchronous patterns so you can map your architecture to your performance goals.

🧪 Evaluation and iteration

Iterating on agent behavior is an experimental process. I use a combination of automated tests and human in the loop evaluation. The event logs are invaluable for generating labeled examples because they contain the full context, the model reasoning, and the tool results. I use those logs to refine prompts, adjust tool descriptions, and to train or tune models where applicable.

I also run A B tests for major changes. For instance, if I tweak the prompt to be more concise I can run two parallel deployments and measure metrics such as task success rate, tool call frequency, and mean time to resolution. Those signals help me decide whether to keep the change. Good metrics and solid logging make it possible to improve agents reliably instead of feeling like you are guessing.

📚 Examples and typical use cases

Here are some concrete examples of what I build with ADK Go and Go 80K.

  • Educational assistants. Agents that tailor explanations to different grade levels. They use session state to store topic and audience and tools to fetch current examples or diagrams.
  • Customer service automation. Agents that read orders, call inventory APIs with function tools, and summarize the outcome for the customer.
  • Research assistants. Workflow agents that run multiple search agents in parallel, collect source documents, and then pass them to a summarizer agent for consolidation.
  • Content creation pipelines. Loop agents that iteratively refine drafts using a critique agent until a quality criterion is met.
  • Data enrichment. Agents that call external APIs to fill in missing profile fields, validate addresses, and append structured metadata.

All of these benefit from the structure ADK provides: typed tools for reliable integrations, session and state for context, and the event stream for observability.

🔗 Integration points and extensibility

ADK Go is built to integrate with the ecosystem. There are a few integration points I commonly use.

  • LLM providers. The model object is pluggable so you can use different LLMs or providers. Choose the provider that fits your latency and cost constraints.
  • Tool providers. Built in tools such as search are available, and you can add third party tools via MCP servers. The agent to agent protocol allows agents to talk to each other across services.
  • Persistence layers. Session and memory services are interchangeable. Swap in your database or vector store depending on scale.
  • Observability systems. The event stream can be piped to logging, analytics, or tracing systems to measure agent behavior.

The extensibility is intentional. I want teams to adopt ADK for the parts that accelerate them while still allowing full control for custom needs.

🧭 Design patterns and best practices

Over time I developed a set of patterns and best practices that make agent development smoother. Here are the ones I recommend most strongly.

Keep agents focused

Smaller agents that have a narrow responsibility are easier to test and maintain. If your agent tries to solve too many things it becomes brittle. Prefer composition over monolithic agents.

Prefer typed tools

Function tools with typed inputs and outputs reduce ambiguity and help prevent runtime errors. They also document the tool contract which the model will use.

Use workflows for predictable pipelines

If you need reliable ordering or multi step business logic, implement a workflow agent. Workflows make state transitions explicit and make it easier to reason about retries and failure modes.

Invest in prompt engineering

Good prompts often solve more issues than additional orchestration code. Spend time on examples, edge case instructions, and response formatting templates.

Make critical tools require confirmation

For tools that perform side effects like billing or sending emails, require explicit multi step confirmation from the user and log those confirmations in the session events.

Record and use event logs

Persist event logs for post hoc analysis and for feeding your evaluation pipeline. They are gold when debugging mysterious model behavior.

📦 Getting started checklist

Here is a concise checklist I follow when starting a new ADK Go project.

  1. Add ADK Go via go get and initialize your Go module.
  2. Define a simple agent with a name, description, model object, and instruction prompt.
  3. Start with an in memory session service during development.
  4. Register a small set of function tools that wrap your existing APIs.
  5. Run the agent with the runner and inspect the event stream.
  6. Iterate on prompt text and tool descriptions until behavior is stable.
  7. Replace in memory stores with persisted session and memory services for production.
  8. Add observability by archiving event logs and instrumenting key metrics.

🔄 Troubleshooting common issues

If your agent behaves oddly here are the pragmatic steps I take to diagnose and fix problems.

  • Read the events. Most surprises are visible in the event stream. Look for unexpected tool calls, malformed arguments, or missing context in the prompt.
  • Check tool descriptions. If the model is not calling a tool when it should, the tool description may be unclear or too terse. Expand it and provide examples.
  • Validate argument shapes. If a function tool fails during marshalling, ensure the input struct fields match the arguments the model produces.
  • Mock external APIs. Replace live APIs with mocks to reproduce edge cases and to test failure modes deterministically.
  • Limit generation length during debugging. Shorter responses make it faster to iterate on prompt changes.

📣 Real world considerations and next steps

Building production grade agents requires attention to the broader system aspects beyond the agent itself. Think about authentication when agents call user specific APIs, data governance for what you store in memory services, and compliance for retaining conversation logs. ADK Go gives you a strong foundation, but these operational concerns still require design and implementation specific to your organization.

When I move an agent to production I also set clear fallbacks. A common pattern is a graceful degradation strategy where if the model or tools fail, the agent returns a human readable apology and either queues the request for human follow up or routes the conversation to a human operator.

If you want to prototype rapidly I recommend starting with a narrow scope and realistic tool mocks. Once behavior is stable, swap mocks for real APIs, add persistent storage, and add monitoring. Iterate on prompts using event logs and A B tests to validate major changes.

🧾 Frequently asked questions

What is ADK Go and how is it different from using an LLM API directly

ADK Go is a modular framework for building AI agents in Go. It abstracts common concerns such as session management, tool integration, event streaming, and workflow orchestration. Compared to calling an LLM API directly, ADK Go provides structured mechanisms to register typed tools, manage conversation state and history, orchestrate multiple agents, and persist events for observability. This reduces boilerplate and helps prevent common integration errors like ambiguous tool calls and poorly managed context.

What is Go 80K

Go 80K is a handcrafted agent library written to be idiomatic for Go developers. It is designed to make agent patterns feel natural in Go with typed tools, clear session semantics, and an event based runner. It focuses on being lightweight, extensible, and easy to integrate into existing Go services.

How do tools work in ADK Go

Tools are the external capabilities agents call when they need to perform actions outside the model. There are built in tools, third party tools, and function tools. The LLM decides if a tool is needed and formats the arguments. ADK intercepts the function call, runs the corresponding Go code for the tool, and returns the result back into the agent context for the model to use when composing the final response.

What are function tools and why use them

Function tools are wrappers around ordinary Go functions that expose typed inputs and outputs to the agent. They are useful because they enforce type safety, document tool contracts, and let you implement business logic in Go while the LLM orchestrates when to call them. They reduce ambiguity and runtime errors compared to freeform JSON or text interchange.

How do I choose between a single agent and multiple agents

Use a single agent when the task is tightly scoped, the process cannot be easily decomposed, or when there are only a few tools. Use multiple agents when the problem naturally decomposes into independent subdomains or when you need specialist behavior that you want to isolate. Workflows are preferred when you need deterministic stages or when ordering matters. Consider latency and communication overhead when designing multi agent systems.

How is conversation context managed

Context is managed through sessions and state. Each session holds a history of events and a state map. State is a temporary scratch pad for the current conversation while long term memory is handled by a memory service which is pluggable. Events record every action and message for observability and replay.

What is the event stream and why is it important

The event stream is a chronological record of everything the agent does during a run. It includes messages, tool calls, tool results, and errors. The event stream is crucial for debugging, auditing, and building monitoring around agent behavior. It also facilitates testing because you can replay events to reproduce runs.

Can I run multiple agents in parallel

Yes. ADK Go supports parallel agents that run sub agents concurrently. This is useful for tasks like research where you want multiple strategies applied in parallel and then aggregated. Be mindful of resource and rate limit implications when running many agents simultaneously.

How do I handle side effects like sending emails

Treat side effect tools with extra care. Make them require explicit confirmations or human approvals in the workflow. Log every side effect in the event stream. Implement role based access control and strict input validation to prevent abuse.

What persistence options are available for sessions and memory

ADK Go provides pluggable session and memory services. In development you can use an in memory session store. In production you can swap in a database, document store, or managed persistence layer. For memory you typically integrate a vector store for semantic retrieval. Choose persistence based on your data retention requirements and query performance needs.

How do I test agents reliably

Mock LLM responses and tool implementations to create deterministic tests. Use the event stream to assert that the agent tried specific tool calls and produced expected outputs. Unit test workflow agents by replacing sub agents with fakes. Add integration tests that run against test instances of external APIs to catch real world issues.

Is ADK Go open source and where can I find examples

ADK Go is open source. You can find documentation and samples that demonstrate building agents, registering tools, and composing workflows. Samples include educational assistants, customer service examples, and multi agent research patterns so you can learn by example.

How do I improve prompt quality for better agent behavior

Be explicit about persona, goals, tool usage, and response format. Provide examples and edge case guidance. Make tool descriptions thorough and include expected input and output examples. Use structured output templates when you need parsed results. Iterate on prompts using event logs and A B tests to measure improvements.

What are the primary risks when deploying agents

Key risks include hallucination, incorrect tool usage, exposure of sensitive data in logs or memory, runaway costs from excessive API calls, and unintended side effects from tools. Mitigate these with strict tool contracts, logging and monitoring, data governance, rate limits, and approval gates for sensitive actions.

How do I integrate third party tools

Third party tools can be integrated via MCP servers or other provided interfaces. ADK Go can connect to them as external capabilities the agent can call. Treat them like any other tool with clear descriptions and input output contracts. Monitor and rate limit calls to external services to control costs.

🎯 Final thoughts and next steps

ADK Go and Go 80K provide a practical, Go first approach to building agentic systems. The framework equips you with composable primitives such as typed function tools, session and memory services, workflow agents, and an event oriented runner. Those primitives remove a lot of the friction and repetition that otherwise slows down agent development.

If you are starting a new project I recommend the following next steps. First, prototype a narrow use case using the in memory session service and a few function tools. Second, iterate on your prompt quality and tool descriptions until behavior is stable. Third, add persistent session and memory stores, instrument event logging, and set up monitoring. Finally, consider multi agent designs only after you have a stable single agent implementation unless your task is clearly decomposable from the start.

Building agents is an iterative process. Use the event stream to learn from actual runs. Use typed tools to reduce ambiguity. Use workflows to enforce predictable pipelines. With these patterns you can move from crude chatbots to purpose built agents that integrate with your systems and deliver real value.

Start small, measure often, and iterate rapidly. If you follow these principles you will be able to build robust AI agents in Go that are maintainable, observable, and powerful.

Share this post

AI World Vision

AI and Technology News