# 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

> Source: <https://developers.googleblog.com/announcing-adk-go-20/>
> Published: 2026-06-30 16:51:24.530702+00:00

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 [ ADK for Go 2.0](https://github.com/google/adk-go/releases/tag/v2.0.0). The headline is a brand-new, first-class way to compose multi-agent applications: a

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.

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.**

A node is any unit of work that implements the [Node interface](https://pkg.go.dev/google.golang.org/adk/v2@v2.0.0/workflow#Node). You rarely write that interface by hand — ADK ships typed node constructors for the common cases:

```
workflow.NewFunctionNode("classify",
    func(ctx agent.Context, in string) (Category, error) { ... }, cfg)
```

`emit`

callback, so a single function can 

```
workflow.NewEmittingFunctionNode("progress",
    func(ctx agent.Context, in Job, emit func(*session.Event) error) (Result, error) { ... }, cfg)
```

`agent.Agent`

(like an `LlmAgent`

) into the graph.`tool.Tool`

into a graph step.`NewFunctionNodeFromState`

) pull selected session-state values straight into a typed Params struct via `state:"<key>"`

tags — no manual state plumbing.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/v2@v2.0.0/workflow#Route).

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:

``` php
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).)

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.

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:

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

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.

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.

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:

`agent.Context`

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

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

`IsolationScope()`

and `ResumedInput(id string)`

. Most code embeds the provided implementation and gets these for free.`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`

`task`

-mode agents can't be used as static graph nodes.`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).

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):

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