203 lines
13 KiB
Markdown
203 lines
13 KiB
Markdown
---
|
||
source_url: "https://developers.googleblog.com/announcing-adk-go-20/"
|
||
ingested: 2026-06-30
|
||
sha256: be287585c38333c779a4abd4ebbf4b94609dc1b71b293665d919d0915cb92c02
|
||
discovered_from:
|
||
platform: discord
|
||
channel_id: '1477793137064935675'
|
||
channel_name: tw
|
||
message_id: '1521626943571886162'
|
||
author_id: '1477793167486226708'
|
||
posted_at: '2026-06-30T21:22:13.809000000Z'
|
||
message_excerpt: 'Google ADK Go 2.0 was highlighted from #tw as a high-value primary source for Go multi-agent workflow graphs, HITL, retry, telemetry, and resumable orchestration.'
|
||
---
|
||
|
||
## Build reliable multi-agent applications with ADK Go 2.0. Discover our new graph-based workflow engine, built-in human-in-the-loop, and dynamic orchestration
|
||
|
||
JUNE 30, 2026
|
||
|
||

|
||
|
||
## ADK for Go 2.0: build agent workflows as a graph
|
||
|
||
Building real-world agent applications is rarely as simple as sending a single prompt. Production agents must classify, branch, fan out, ask a human to approve something, retry on failure, and loop until done. Expressing that complex orchestration as ad-hoc control flow gets brittle fast.
|
||
|
||
Since its 1.0 release, Agent Development Kit (ADK) for Go has helped Go developers build production agents with a clean, idiomatic API — strong typing, `iter.Seq2` event streams, and a runtime that fits naturally into existing Go services. That foundation has been a real success, and it's exactly what made the next step possible.
|
||
|
||
Today we're excited to share . The headline is a brand-new, first-class way to compose multi-agent applications: a **graph-based workflow engine**. Alongside it come **human-in-the-loop (HITL)** as a built-in primitive, **dynamic orchestration written in plain Go**, **LLM agent modes**, and a **unified node runtime** that brings all of this together — single agents and full graphs now run on the same execution model.
|
||
|
||
If you've followed [Python ADK 2.0](https://adk.dev/2.0/), this will feel familiar: it's the same graph-first direction, designed from the ground up to feel like Go.
|
||
|
||
## Why a graph?
|
||
|
||
Real agent applications are rarely a single prompt. They classify, branch, fan out to specialists, gather results, ask a human to approve something, retry on failure, and loop until done. Expressing that as ad-hoc control flow gets brittle fast.
|
||
|
||
ADK 2.0 lets you describe the *shape* of your application as a **graph of nodes connected by edges**, and hands execution to a scheduler that knows how to run it concurrently, persist its state, pause for a human, and resume later — even across process restarts. Here is how simple it is to chain nodes together:
|
||
|
||

|
||
|
||
```
|
||
import "google.golang.org/adk/v2/workflow"
|
||
|
||
upper := workflow.NewFunctionNode("upper", upperFn, cfg)
|
||
suffix := workflow.NewFunctionNode("suffix", suffixFn, cfg)
|
||
|
||
edges := workflow.Chain(workflow.Start, upper, suffix)
|
||
|
||
wf, _ := workflowagent.New(workflowagent.Config{
|
||
Name: "simple_sequence_workflow",
|
||
Edges: edges,
|
||
})
|
||
```
|
||
|
||
That `wf` is just an `agent.Agent`. It runs in the same runner, launcher, and console you already use — no special harness, no new server. **A graph is an agent.**
|
||
|
||
## The building blocks
|
||
|
||
### Nodes for everything
|
||
|
||
A node is any unit of work that implements the [Node interface](https://pkg.go.dev/google.golang.org/adk/[email protected]/workflow#Node). You rarely write that interface by hand — ADK ships typed node constructors for the common cases:
|
||
|
||
- **Function nodes** wrap a plain typed Go function. Generics infer the input/output schemas for you:
|
||
|
||
```
|
||
workflow.NewFunctionNode("classify",
|
||
func(ctx agent.Context, in string) (Category, error) { ... }, cfg)
|
||
```
|
||
|
||
- **Emitting function nodes** are function nodes that also get an `emit` callback, so a single function can **stream events or pause for a human** without dropping down to a dynamic node:
|
||
|
||
```
|
||
workflow.NewEmittingFunctionNode("progress",
|
||
func(ctx agent.Context, in Job, emit func(*session.Event) error) (Result, error) { ... }, cfg)
|
||
```
|
||
|
||
- **Agent nodes** drop any `agent.Agent` (like an `LlmAgent`) into the graph.
|
||
- **Tool nodes** turn a `tool.Tool` into a graph step.
|
||
- **Join nodes** are fan-in barriers: they wait for *all* predecessors and hand you a map of their outputs.
|
||
- **Dynamic nodes** let you orchestrate in code (more on this below).
|
||
- **Workflow nodes** embed an entire sub-workflow as a single node — graphs compose.
|
||
- **Parallel workers** run a node concurrently across every item in a list and aggregate the results.
|
||
- **State-bound nodes** (`NewFunctionNodeFromState`) pull selected session-state values straight into a typed Params struct via `state:"<key>"` tags — no manual state plumbing.
|
||
|
||
### Edges, routing, and the shapes you need
|
||
|
||
Edges connect nodes, and they can carry routing conditions. A node emits a routing value; matching edges fire. That single idea gives you every control-flow shape you need:
|
||
|
||
```
|
||
b := workflow.NewEdgeBuilder()
|
||
b.AddRoutes(router, map[string]workflow.Node{
|
||
"question": answerNode,
|
||
"statement": commentNode,
|
||
"exclamation": reactNode,
|
||
})
|
||
b.AddFanOut(planner, researchA, researchB, researchC) // parallel branches
|
||
b.AddFanIn(join, researchA, researchB, researchC) // gather results
|
||
```
|
||
|
||
Sequential chains, conditional routers, fan-out/fan-in, nested sub-graphs, and even **loops** (a completed node can be re-triggered, so cycles are first-class) — all from edges and routes. Standard routes come in `StringRoute`, `IntRoute`, `BoolRoute`, `MultiRoute`, and a `Default` that fires when nothing else matches. For deeper configuration, leverage the [Route interface](https://pkg.go.dev/google.golang.org/adk/[email protected]/workflow#Route).
|
||
|
||
## Let an LLM steer the graph
|
||
|
||
One of the most useful patterns is using a model as the *brain* of a router. An LlmAgent classifies the user's message; a trivial function emits the matching route; the graph dispatches to the right handler:
|
||
|
||
```
|
||
User -> What time is it? Agent -> question answering question...
|
||
User -> Hello world! Agent -> exclamation reacting to exclamation...
|
||
User -> The sky is blue. Agent -> statement commenting on statement...
|
||
```
|
||
|
||
The model makes the decision; the graph makes it reliable, observable, and resumable. (See [examples/workflow/routing/llm/](https://github.com/google/adk-go/tree/main/examples/workflow/routing/llm).)
|
||
|
||
## Dynamic orchestration — in plain Go
|
||
|
||
Sometimes the execution order isn't known until runtime: it depends on data, on a loop count, on what the model just said. For that, ADK 2.0 gives you **dynamic nodes**, where the orchestration body is ordinary Go code that calls `RunNode(...)` for each child:
|
||
|
||
```
|
||
greeter := workflow.NewDynamicNode("greeter_workflow",
|
||
func(nc agent.Context, in string, emit func(*session.Event) error) (string, error) {
|
||
return workflow.RunNode[string](nc, greeterNode, in)
|
||
},
|
||
workflow.NodeConfig{},
|
||
)
|
||
```
|
||
|
||
Loops, conditionals, accumulation, fan-out across a dynamic list — all expressed with the Go you already know. Options like `WithRunID`, `WithUseSubBranch`, `WithUseAsOutput`, and `WithIsolationScope` give you precise control over child identity, history isolation, and output delegation. This is the Go counterpart to Python ADK's dynamic graphs.
|
||
|
||
## Human-in-the-loop, built in
|
||
|
||
Production agents often need a human to approve, correct, or supply something mid-run. In ADK 2.0, **any node can pause the graph and ask a human a question** — and the workflow durably waits for the answer:
|
||
|
||
```
|
||
event := workflow.NewRequestInputEvent(ctx, session.RequestInput{
|
||
InterruptID: "approve_refund",
|
||
Message: "Approve a $200 refund? (yes/no)",
|
||
ResponseSchema: schema,
|
||
})
|
||
// yield the event; the node moves to "waiting"
|
||
```
|
||
|
||
When the human replies on a later turn, the workflow resumes. You choose how:
|
||
|
||
- **Handoff** — the answer flows straight to the next node.
|
||
- **Re-entry** — the paused node re-runs with the human's response available via `ctx.ResumedInput(...)`.
|
||
|
||
And resume is **durable**. The run state lives in the session, and ADK can even **reconstruct a paused workflow by scanning session history** — so a workflow can resume after a process restart, or even across different runtimes, because the interrupt format is shared with Python ADK. Responses are validated against a schema, resume is idempotent, and you get clear errors (`ErrInvalidResumeResponse`, `ErrNothingToResume`) when something doesn't line up.
|
||
|
||
Both the console launcher and the Web UI understands HITL out of the box, surfacing both tool-confirmation prompts and workflow input requests.
|
||
|
||
## Resilience without the boilerplate
|
||
|
||
Every node can carry a retry policy with exponential backoff and jitter — no external dependency required:
|
||
|
||
```
|
||
cfg := workflow.NodeConfig{ RetryConfig: workflow.DefaultRetryConfig() }
|
||
// 5 attempts, 1s initial delay, 60s cap, 2x backoff, full jitter
|
||
```
|
||
|
||
Add a per-node `Timeout`, cap graph-wide concurrency with `WithMaxConcurrency(n)`, and isolate parallel branches so one branch's chatter never leaks into another's LLM prompt history. The scheduler handles the goroutines, channels, backpressure, and cancellation for you.
|
||
|
||
## Agent modes and one runtime to run them all
|
||
|
||
ADK 2.0 introduces **modes** for LLM agents — `Chat`, `Task`, and `SingleTurn` — so a coordinator can chat with the user while sub-agents quietly complete tasks or run single-shot. The right helper tools (`finish_task`, `single_turn`, `task`) are installed automatically based on each agent's role.
|
||
|
||
Under the hood, the runner now drives a plain `LlmAgent` through the **same node runtime** that powers workflows. The payoff: single-agent apps and full graphs share one execution model, and **human-in-the-loop now works for a plain LLM agent too** — not just inside a workflow.
|
||
|
||
We also smoothed the programming model: `ToolContext` and `CallbackContext` are now a single unified to `agent.Context` — one type to learn, whether you're writing a tool, a callback, or a graph node — and node/agent execution shows up in one consistent telemetry span tree, so you can see exactly what your graph did.
|
||
|
||
## Upgrading from 1.0
|
||
|
||
ADK 2.0 is highly additive — the entire workflow engine is new packages you opt into. There are a few new and breaking changes that come with unifying the runtime; each has a simple, mechanical fix:
|
||
|
||
- **Node and node-function signatures take** **`agent.Context`****.** If you write nodes or node functions, change the first parameter from `agent.InvocationContext` to `agent.Context` (it embeds `InvocationContext`, so every method you used still works):
|
||
|
||
```
|
||
// before: func(ctx agent.InvocationContext, in string) (string, error)
|
||
// after: func(ctx agent.Context, in string) (string, error)
|
||
```
|
||
|
||
- **One unified context.** `ToolContext`, `CallbackContext` are gone – tools, callbacks, and workflow nodes all receive `agent.Context` directly. If you mocked a context in tests, `agent/context_mock.go` is retained; use `StrictContextMock` from that file as your test double.
|
||
- **Custom** **`InvocationContext`** **implementations** need two methods: `IsolationScope()` and `ResumedInput(id string)`. Most code embeds the provided implementation and gets these for free.
|
||
- **Event streams are richer.** Events now carry node fields (`IsolationScope`, `Output`,`Routes`,`RequestedInput`) and a metadata field (`NodeInfo`). If you assert on exact `session.Event` equality in tests, expect the new fields; custom session stores should persist them.
|
||
- **`llmagent.New`** **may install mode-specific tools.** If you set sub-agent modes, the effective tool set reflects them; `task` -mode agents can't be used as static graph nodes.
|
||
- **session.NewEvent takes a context**. The signature is now `NewEvent(ctx context.Context, invocationID string)`. Migrate call sites by passing the `context.Context` already in scope as the first argument.
|
||
|
||
That's the whole list. Public signatures for `runner.Run/RunLive`, `agenttool`, and the llmagent callbacks are unchanged. For step-by-step before/after instructions, see the [**ADK Go 2.0 migration guide**](https://github.com/google/adk-go/blob/main/README-v2.md).
|
||
|
||
## Try it
|
||
|
||
The fastest way to get a feel for ADK 2.0 is the [new workflow examples](https://github.com/google/adk-go/tree/main/examples/workflow):
|
||
|
||
```shell
|
||
go run ./examples/workflow/basic/
|
||
go run ./examples/workflow/routing/llm/ # LLM-as-router
|
||
go run ./examples/workflow/dynamic/hitl/ # dynamic + human-in-the-loop
|
||
go run ./examples/workflow/hitl_rerun/ # HITL with re-entry resume
|
||
go run ./examples/workflow/complex/ # a larger, multi-shape graph
|
||
```
|
||
|
||
ADK 1.0 proved that building serious agents in Go could be clean and productive. ADK 2.0 takes the next step: compose those agents into reliable, observable, resumable **workflows** — as a graph, in idiomatic Go, with humans in the loop when it matters.
|
||
|
||
We can't wait to see what you build.
|
||
|
||
*— The ADK for Go team* |