--- 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 ![Gemini_Gen_ADKGo20_banner_blog](https://storage.googleapis.com/gweb-developer-goog-blog-assets/images/Gemini_Gen_ADKGo20_banner_blog.original.jpg) ## 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: ![workflow_graph](https://storage.googleapis.com/gweb-developer-goog-blog-assets/images/workflow_graph.original.png) ``` 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/v2@v2.0.0/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:""` 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/v2@v2.0.0/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*