{"slug": "build-reliable-multi-agent-applications-with-adk-go-2-0-discover-our-new-graph", "title": "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", "summary": "Google released ADK Go 2.0, a major update to its Agent Development Kit for Go, introducing a graph-based workflow engine for building reliable multi-agent applications. The new version features built-in human-in-the-loop support, dynamic orchestration, and a scheduler that handles concurrent execution, state persistence, and pause-resume across restarts.", "body_md": "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.\n\nSince 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`\n\nevent 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.\n\nToday 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\n\nIf 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.\n\nReal 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.\n\nADK 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:\n\n```\nimport \"google.golang.org/adk/v2/workflow\"\n\nupper  := workflow.NewFunctionNode(\"upper\",  upperFn,  cfg)\nsuffix := workflow.NewFunctionNode(\"suffix\", suffixFn, cfg)\n\nedges := workflow.Chain(workflow.Start, upper, suffix)\n\nwf, _ := workflowagent.New(workflowagent.Config{\n    Name:  \"simple_sequence_workflow\",\n    Edges: edges,\n})\n```\n\nThat `wf`\n\nis just an `agent.Agent`\n\n. It runs in the same runner, launcher, and console you already use — no special harness, no new server. **A graph is an agent.**\n\nA 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:\n\n```\nworkflow.NewFunctionNode(\"classify\",\n    func(ctx agent.Context, in string) (Category, error) { ... }, cfg)\n```\n\n`emit`\n\ncallback, so a single function can \n\n```\nworkflow.NewEmittingFunctionNode(\"progress\",\n    func(ctx agent.Context, in Job, emit func(*session.Event) error) (Result, error) { ... }, cfg)\n```\n\n`agent.Agent`\n\n(like an `LlmAgent`\n\n) into the graph.`tool.Tool`\n\ninto a graph step.`NewFunctionNodeFromState`\n\n) pull selected session-state values straight into a typed Params struct via `state:\"<key>\"`\n\ntags — 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:\n\n```\nb := workflow.NewEdgeBuilder()\nb.AddRoutes(router, map[string]workflow.Node{\n    \"question\":    answerNode,\n    \"statement\":   commentNode,\n    \"exclamation\": reactNode,\n})\nb.AddFanOut(planner, researchA, researchB, researchC) // parallel branches\nb.AddFanIn(join, researchA, researchB, researchC)       // gather results\n```\n\nSequential 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`\n\n, `IntRoute`\n\n, `BoolRoute`\n\n, `MultiRoute`\n\n, and a `Default`\n\nthat 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).\n\nOne 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:\n\n``` php\nUser -> What time is it?    Agent -> question     answering question...\nUser -> Hello world!        Agent -> exclamation  reacting to exclamation...\nUser -> The sky is blue.    Agent -> statement    commenting on statement...\n```\n\nThe 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).)\n\nSometimes 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(...)`\n\nfor each child:\n\n```\ngreeter := workflow.NewDynamicNode(\"greeter_workflow\",\n    func(nc agent.Context, in string, emit func(*session.Event) error) (string, error) {\n        return workflow.RunNode[string](nc, greeterNode, in)\n    },\n    workflow.NodeConfig{},\n)\n```\n\nLoops, conditionals, accumulation, fan-out across a dynamic list — all expressed with the Go you already know. Options like `WithRunID`\n\n, `WithUseSubBranch`\n\n, `WithUseAsOutput`\n\n, and `WithIsolationScope`\n\ngive you precise control over child identity, history isolation, and output delegation. This is the Go counterpart to Python ADK's dynamic graphs.\n\nProduction 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:\n\n```\nevent := workflow.NewRequestInputEvent(ctx, session.RequestInput{\n    InterruptID:    \"approve_refund\",\n    Message:        \"Approve a $200 refund? (yes/no)\",\n    ResponseSchema: schema,\n})\n// yield the event; the node moves to \"waiting\"\n```\n\nWhen the human replies on a later turn, the workflow resumes. You choose how:\n\n`ctx.ResumedInput(...)`\n\n.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`\n\n, `ErrNothingToResume`\n\n) when something doesn't line up.\n\nBoth the console launcher and the Web UI understands HITL out of the box, surfacing both tool-confirmation prompts and workflow input requests.\n\nEvery node can carry a retry policy with exponential backoff and jitter — no external dependency required:\n\n```\ncfg := workflow.NodeConfig{ RetryConfig: workflow.DefaultRetryConfig() }\n// 5 attempts, 1s initial delay, 60s cap, 2x backoff, full jitter\n```\n\nAdd a per-node `Timeout`\n\n, cap graph-wide concurrency with `WithMaxConcurrency(n)`\n\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.\n\nADK 2.0 introduces **modes** for LLM agents — `Chat`\n\n, `Task`\n\n, and `SingleTurn`\n\n— so a coordinator can chat with the user while sub-agents quietly complete tasks or run single-shot. The right helper tools (`finish_task`\n\n, `single_turn`\n\n, `task`\n\n) are installed automatically based on each agent's role.\n\nUnder the hood, the runner now drives a plain `LlmAgent`\n\nthrough 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.\n\nWe also smoothed the programming model: `ToolContext`\n\nand `CallbackContext`\n\nare now a single unified to `agent.Context`\n\n— 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.\n\nADK 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:\n\n`agent.Context`\n\n`agent.InvocationContext`\n\nto `agent.Context`\n\n(it embeds `InvocationContext`\n\n, so every method you used still works):\n\n```\n// before:  func(ctx agent.InvocationContext, in string) (string, error)\n// after:   func(ctx agent.Context,           in string) (string, error)\n```\n\n`ToolContext`\n\n, `CallbackContext`\n\nare gone – tools, callbacks, and workflow nodes all receive `agent.Context`\n\ndirectly. If you mocked a context in tests, `agent/context_mock.go`\n\nis retained; use `StrictContextMock`\n\nfrom that file as your test double.`InvocationContext`\n\n`IsolationScope()`\n\nand `ResumedInput(id string)`\n\n. Most code embeds the provided implementation and gets these for free.`IsolationScope`\n\n, `Output`\n\n,`Routes`\n\n,`RequestedInput`\n\n) and a metadata field (`NodeInfo`\n\n). If you assert on exact `session.Event`\n\nequality in tests, expect the new fields; custom session stores should persist them.`llmagent.New`\n\n`task`\n\n-mode agents can't be used as static graph nodes.`NewEvent(ctx context.Context, invocationID string)`\n\n. Migrate call sites by passing the `context.Context`\n\nalready in scope as the first argument.That's the whole list. Public signatures for `runner.Run/RunLive`\n\n, `agenttool`\n\n, 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).\n\nThe 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):\n\n```\ngo run ./examples/workflow/basic/\ngo run ./examples/workflow/routing/llm/      # LLM-as-router\ngo run ./examples/workflow/dynamic/hitl/     # dynamic + human-in-the-loop\ngo run ./examples/workflow/hitl_rerun/       # HITL with re-entry resume\ngo run ./examples/workflow/complex/          # a larger, multi-shape graph\n```\n\nADK 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.\n\nWe can't wait to see what you build.\n\n*— The ADK for Go team*", "url": "https://wpnews.pro/news/build-reliable-multi-agent-applications-with-adk-go-2-0-discover-our-new-graph", "canonical_source": "https://developers.googleblog.com/announcing-adk-go-20/", "published_at": "2026-06-30 16:51:24.530702+00:00", "updated_at": "2026-06-30 16:51:26.638921+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["Google", "ADK Go", "Agent Development Kit", "Python ADK"], "alternates": {"html": "https://wpnews.pro/news/build-reliable-multi-agent-applications-with-adk-go-2-0-discover-our-new-graph", "markdown": "https://wpnews.pro/news/build-reliable-multi-agent-applications-with-adk-go-2-0-discover-our-new-graph.md", "text": "https://wpnews.pro/news/build-reliable-multi-agent-applications-with-adk-go-2-0-discover-our-new-graph.txt", "jsonld": "https://wpnews.pro/news/build-reliable-multi-agent-applications-with-adk-go-2-0-discover-our-new-graph.jsonld"}}