Skip to content
Development
Agent

adk-go-workflows

Requires `google.golang.org/adk/v2 >= v2.0.0`, which is where the `workflow` package and `agent/workflowagent` first ship.

From plugin
google-agents-cli
5.9k28 skills28 agents
Install
$ npx -y skills add google/agents-cli --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

Requires `google.golang.org/adk/v2 >= v2.0.0`, which is where the `workflow` package and `agent/workflowagent` first ship.

Agent definition

adk-go-workflows.md

ADK Go Workflow API

> Requires `google.golang.org/adk/v2 >= v2.0.0`, which is where the `workflow` package and > `agent/workflowagent` first ship.

The graph runtime in **`google.golang.org/adk/v2/workflow`**, for the cases the linear `sequentialagent` / `parallelagent` / `loopagent` composition in `adk-go.md` cannot express: conditional routing, fan-out with a fan-in barrier, per-node retries or timeouts, or a workflow that pauses for human input and resumes later. For a plain chain of agents, those workflow *agents* are simpler.

Read `adk-go.md` first for the base API.

**Official docs:** [Workflows overview](https://adk.dev/workflows/index.md) · [pkg.go.dev/workflow](https://pkg.go.dev/google.golang.org/adk/v2/workflow) · [runnable examples](https://github.com/google/adk-go/tree/main/examples/workflow)

1. Core concepts

A `Workflow` is a graph-based agent: nodes do work, edges define flow, `workflow.Start` is the entry point.

import (
	"strings"

	"google.golang.org/adk/v2/agent"
	"google.golang.org/adk/v2/agent/workflowagent"
	"google.golang.org/adk/v2/workflow"
)

cfg := workflow.NodeConfig{RetryConfig: workflow.DefaultRetryConfig()}

upper := workflow.NewFunctionNode("upper",
	func(_ agent.Context, in string) (string, error) {
		return strings.ToUpper(in), nil
	}, cfg)

suffix := workflow.NewFunctionNode("suffix",
	func(_ agent.Context, in string) (string, error) {
		return in + " IS AWESOME!", nil
	}, cfg)

rootAgent, err := workflowagent.New(workflowagent.Config{
	Name:        "simple_sequence_workflow",
	Description: "Uppercases a string and appends a suffix",
	Edges:       workflow.Chain(workflow.Start, upper, suffix),
})

The first node receives the user's message as its input. `workflowagent.New` returns a plain `agent.Agent`.

Node kinds

Every constructor takes a `NodeConfig` (retries, timeout — see *Retries, timeouts, errors*) and returns a value you drop into an `Edge`.

`NewFunctionNode` — a typed transform

upper := workflow.NewFunctionNode("upper",
	func(_ agent.Context, in string) (string, error) {
		return strings.ToUpper(in), nil
	}, workflow.NodeConfig{})

The return value becomes `Event.Output` and arrives as the successor's typed input.

`NewEmittingFunctionNode` — emit events, then return

Use it when the node must produce something *besides* its return value: user-visible progress, a state delta, a routing tag, or a HITL prompt.

progress := workflow.NewEmittingFunctionNode("progress",
	func(ctx agent.Context, in string, emit func(*session.Event) error) (any, error) {
		ev := session.NewEvent(ctx, ctx.InvocationID())
		// Content renders in the UI; Output is what the next node receives.
		ev.Content = genai.NewContentFromText("working…", genai.RoleModel)
		if err := emit(ev); err != nil {
			return nil, err
		}
		// Returning a non-nil value emits a terminal event carrying it as Output.
		// Returning nil instead suppresses that terminal event entirely — which is
		// what you want when an event you already emitted carries the output.
		return in, nil
	}, workflow.NodeConfig{})

**What a function node's return value becomes:** a `*session.Event` is yielded as-is (this is how you set `Event.Routes`), a `*genai.Content` becomes `event.Content`, and anything else becomes `event.Output`.

`NewAgentNode` — wrap an agent

drafter, err := workflow.NewAgentNode(draftAgent, workflow.NodeConfig{})
// NewAgentNodeTyped[In, Out](draftAgent, cfg) instead reflects In/Out into JSON
// schemas, so the agent's input and reply are validated against your structs.

An `LlmAgent` with unset `Mode` defaults to single-turn here. Register the wrapped agent in `workflowagent.Config.SubAgents` so event authors resolve.

`NewToolNode` — call a tool as a step

lookup, err := workflow.NewToolNode(weatherTool, workflow.NodeConfig{})
// NewNamedToolNode("weather_step", weatherTool, cfg) to override the node name,
// which otherwise comes from the tool.

`NewWorkflowNode` — nest a sub-workflow

sub, err := workflow.NewWorkflowNode("subflow", workflow.Chain(workflow.Start, upper, suffix))

`NewJoinNode`, `NewDynamicNode` and `NewParallelWorker` are the concurrency kinds — see *Parallelism and fan-in*.

Edges and routing

An edge with no `Route` always fires. A routed edge fires only when the source node tagged its event with a matching value:

edges := []workflow.Edge{
	{From: workflow.Start, To: triage},
	{From: triage, To: answer, Route: workflow.StringRoute("question")},
	{From: triage, To: escalate, Route: workflow.IntRoute(2)},
	{From: triage, To: archive, Route: workflow.BoolRoute(false)},
	{From: triage, To: urgent, Route: workflow.MultiRoute[string]{"p0", "p1"}},
	{From: triage, To: fallback, Route: workflow.Default},
}

Every route is compared as a string against the entries in `Event.Routes`, so `IntRoute(2)` matches the entry `"2"` and `BoolRoute(false)` matches `"false"`. Any type with a `Matches(*session.Event) bool` method works as a route.

**A node signals its branch by setting `Event.Routes []string`** on an event it emits:

func classifyAndRoute(ctx agent.Context, msg string, emit func(*session.Event) error) (any, error) {
	ev := session.NewEvent(ctx, ctx.InvocationID())
	ev.Routes = []string{classify(msg)} // e.g. "question"
	ev.Output = msg                     // feeds the successor's typed input
	if err := emit(ev); err != nil {
		return nil, err
	}
	return nil, nil // nil output suppresses the default terminal event
}

triage := workflow.NewEmittingFunctionNode("triage", classifyAndRoute, workflow.NodeConfig{})

Building edge sets

`EdgeBuilder` is the readable way to express fan-out and fan-in:

eb := workflow.NewEdgeBuilder()
eb.Add(workflow.Start, triage)
eb.AddRoute(triage, answer, workflow.StringRoute("question"))
eb.AddRoutes(triage, map[string]workflow.Node{ // shorthand for several StringRoutes
	"statement":   comment,
	"exclamation": react,
Read more
Ships withgoogle-agents-cli

The CLI and skills that turn any coding assistant into an expert at creating, evaluating, and deploying AI agents on Google Cloud.

Get the whole plugin

Other agents on google-agents-cli.