Files
google__adk-docs/docs/graphs/index.md
Alexey Kalenkevich 1203686bb4 Add TypeScript tabs to the graph workflow pages (#2167)
* Add TypeScript tabs to the graph workflow pages

The five /graphs/ pages documented graph workflows for Python and Go only,
so a TypeScript reader had to infer the API from the Python tab — which
does not translate: TypeScript has no `@node` decorator, schemas are Zod
objects rather than pydantic models, state is written through `ctx.state`
instead of returned on an event, and a user-facing message is the event's
`content` rather than a `message` field.

Every section that has a Python tab now has a TypeScript tab before the Go
one, backed by 26 snippet files under examples/typescript/snippets/graphs/.
The snippets are ported from the runnable samples in adk-js
(samples/workflows/), which already map 1:1 to these section anchors, and
they all type-check against the adk-js workflow API.

The tabs also call out the behaviours that are easy to get wrong and have
no Python equivalent: `ctx.runNode()` resolves to a node result rather than
the output, and does not throw when a child interrupts; a second event
carrying `output` silently overwrites the first; `LlmAgent.inputSchema` is
not the node's input contract inside a graph.

* Drop inline comments from the graph workflow snippets

The `//` annotations inside the snippet regions duplicated the prose that
already introduces each tab, and they were the first thing a reader saw in a
rendered sample rather than the API itself.

Removes the 83 `//` comments inside the `--8<--` regions across all 26 files.
JSDoc blocks stay, since they document what a function or schema is rather
than annotating a line; the Apache headers and the per-file orientation
comments above each region are untouched, and neither renders on the docs
site anyway.

Verified comment-only: compiling every file before and after with
`tsc --removeComments` produces byte-identical `.js` and `.d.ts` output
across all 52 emitted files.

* Use single-quoted strings in the graph workflow snippets

The 26 files landed double-quoted, which reads as a deliberate choice next to
the existing TypeScript snippets under examples/typescript/snippets/ — those
are predominantly single-quoted (49 of 68 imports). The repo has no prettier
config, so nothing enforces either style; this just stops the new directory
looking different from its neighbours.

Formatting only: `prettier --no-config --single-quote`, and every changed line
differs from the original by a quote character alone. Compiling before and
after with `tsc --removeComments` produces output whose only differences are
the same quote swaps, since tsc preserves the source quote style.

* Restore the upstream titleCase guard in the nested workflow snippet

Porting samples/workflows/routes/nested_workflow inlined the `titleCase`
helper and dropped the check that a character's uppercase form is a single
code point, along with the comment explaining why it is there. That changed
behaviour for word-initial characters whose uppercase expands: "first draft"
became "FIrst Draft" and "ßeta test" became "SSeta Test", where upstream
leaves both alone.

Restores the helper, the guard and the rationale. Because the helper sits
inside the --8<-- region, the explanation now renders on the page as well, so
the next person to touch it can see what the guard is for.

Also restores an unused `_ctx` parameter in the user_message snippet, the only
other place the port had drifted from upstream.

Verified by compiling each of the 26 snippets and its upstream counterpart at
adk-v2.0.0 with `tsc --removeComments` and comparing the emitted JavaScript:
all 26 are now semantically identical to samples/workflows/.

* Address review: plainer wording, and lift shared cautions out of the tabs

Wording, across all TypeScript tabs:

- No sentence starts with code syntax. "`route` is independent of..." becomes
  "The `route` value is independent of...", and the same for the other cases.
- Removed informal and editorial phrasing: "earn their keep", "dropped
  straight into", "reach for", "hands you", "two things to know going in",
  "kick the children off", "fails loudly".
- Spelled out "/" as "and" in the `inputSchema` and `outputSchema` sentence.
- Described the `ctx.runNode()` interrupt behaviour in full rather than only
  as "does not throw": it returns normally with `interruptIds` populated and
  `output` undefined, and an orchestrator that skips the check continues with
  a value the user never supplied.
- Explained what a JoinNode waits for instead of referring to "the barrier".
- Tied the `rerunOnResume` option back to the code sample it follows, and
  introduced the two orchestrator details by saying when they matter.

Structure:

- The "Response schema input limitations" note appeared in both the Python
  and TypeScript tabs. Replaced both with one language-neutral note after the
  code examples.
- The "Stuck JoinNode" caution appeared in all three tabs. Replaced them with
  one caution after the code examples, stating the rule that every node
  feeding a join must produce an output.
- Moved the unbounded-cycle caution out of the TypeScript tab to the end of
  the section, since it is not language specific.

Snippet header comments got the same wording pass. Verified afterwards: the
26 snippets still type-check, all 53 snippet includes resolve, and every
snippet is still semantically identical to samples/workflows/ at adk-v2.0.0.

* Apply suggestion from @joefernandez

---------

Co-authored-by: Joe Fernandez <931947+joefernandez@users.noreply.github.com>
2026-08-28 15:39:52 -07:00

11 KiB

Graph-based agent workflows

Supported in ADKPython v2.0.0TypeScript v2.0.0Go v2.0.0

Graph-based agent workflows in ADK let you build agents with more precise control, creating deterministic processes that combine code logic and AI reasoning capabilities. Graph-based workflows allow you to define your agent logic as a graph of execution nodes and edges, combining AI-powered agent reasoning with deterministic tools and code.

Graph-based flight upgrade agent

Figure 1. A graph-based agent design for flight upgrades, combining workflow nodes of different types, including Functions, human input, Tools, and LLM capabilities.

Prebuilt ADK template workflows, such as Sequential Agents, provide a defined process flow control only across a set of agents. You can continue to build standard ADK agents with long prompts, tools, and use them in graph-based workflow agents. When you need more precise control, workflow agent graphs give you more flexibility over how tasks are routed and executed. Graph-based workflows provide the following advantages:

  • Define precise logic: Explicitly map out routing logic to manage transitions between different nodes.
  • Implement complex structures: Build agent workflows that support branching and state management.
  • Run chains of functions without AI: Call agent tools and your own code without invoking a generative AI model.
  • Enhance reliability: Improve the predictability of your agents by relying on structured node definitions rather than prompts alone.

!!! note "Workflow styles in ADK"

ADK offers three complementary ways to compose multi-step work:

-   **Graph-based workflows** (this section): a declarative graph of nodes
    and edges with explicit routing — best for deterministic, structured
    processes.
-   **[Dynamic workflows](/graphs/dynamic/):** programmatic orchestration
    in your own code (loops, conditionals, recursion) — best when the
    control flow is too complex or iterative for a static graph.
-   **[Prebuilt workflow agents](/agents/workflow-agents/)** (sequential,
    parallel, loop): higher-level building blocks for common patterns
    without assembling a graph yourself.

Get started

This section describes how to get started with graph-based agents. The following example shows how to create a sequential graph-based agent workflow that generates a city name, looks up the current time in that city with a code function, and the final agent reports the information.

=== "Python"

```python
from google.adk import Agent
from google.adk import Workflow
from google.adk import Event
from pydantic import BaseModel

city_generator_agent = Agent(
    name="city_generator_agent",
    model="gemini-flash-latest",
    instruction="""Return the name of a random city.
      Return only the name, nothing else.""",
    output_schema=str,
)

class CityTime(BaseModel):
    time_info: str  # time information
    city: str       # city name

def lookup_time_function(node_input: str):
    """Simulate returning the current time in the specified city."""
    return CityTime(time_info="10:10 AM", city=node_input)

city_report_agent = Agent(
    name="city_report_agent",
    model="gemini-flash-latest",
    input_schema=CityTime,
    instruction="""Output following line:
    It is {CityTime.time_info} in {CityTime.city} right now.""",
    output_schema=str,
)

def completed_message_function(node_input: str):
    return Event(
        message=f"{node_input}\n WORKFLOW COMPLETED.",
    )

root_agent = Workflow(
    name="root_agent",
    edges=[
        ("START", city_generator_agent, lookup_time_function,
          city_report_agent, completed_message_function)
    ],
)
```

=== "TypeScript"

In ADK TypeScript v2.0.0, a `Workflow` takes an `edges` array. Each row
lists the nodes to run in order. The `node()` function wraps a function,
an agent, a tool, or another `Workflow` as a graph node, and sets the
node's name and its `inputSchema` and `outputSchema` contracts. Schemas
are Zod objects or a genai `Schema`. Each node's return value is passed
to the next node as its input, so you do not need to write to session
state.

```typescript
--8<-- "examples/typescript/snippets/graphs/index/get_started.ts:get-started"
```

=== "Go"

In ADK Go v2.0.0, sequential workflows use the graph engine:
`workflow.NewFunctionNode` wraps each step, and `workflow.Chain` wires
the nodes into a sequential `edges` slice. The framework automatically
passes each node's typed return value to the next node via
`event.Output` — no session state writes are needed. The whole graph is
wrapped in `workflowagent.New`, which produces a standard `agent.Agent`.

```go
--8<-- "examples/go/snippets/graphs/index/main.go:sequential-get-started"
```

This sample code demonstrates how you can assemble a simple, sequential workflow and alternate between agent processing and code execution. While you could perform these steps using a single agent with a longer prompt and a tool call, the graph-based approach gives you precise control over the task execution order and the data output from each step.

For more information about data handling with graph-based workflows, see Data handling with workflow nodes and agents.

Build processes with graphs

You can use prompt-based agents to define multiple step processes with descriptions of tasks and procedures using the instructions field of an ADK agent. However, as your instructions and procedures become longer and more complicated, making sure that the agent is following each step and guideline becomes more complicated and less reliable.

Graph-based workflow agents provide a significant advantage over prompt-based agents by allowing you to specifically define the overall process workflow in code. With graph-based agent workflows, each step of the process can be defined as an execution Node in a graph and each node can be an AI agent, Tool, or your programmed code. The following diagram illustrates how a simple prompt-based agent would translate into a workflow agent graph:

Prompt-based agent to graph-based workflow

Figure 2. Structure of prompt-based agent instructions translated into a graph-based workflow.

Moving from prompt-based agents to graph-based workflow agents allows you to explicitly break out the tasks of a procedure to define a specific execution flow. Once defined, the agent application flows the steps in the graph, switching between non-deterministic AI-powered agents and deterministic code as needed.

The following code sample shows how the workflow graph in Figure 2 could be translated into a graph-based agent:

=== "Python"

```python
process_message = Agent(
    name="process_message",
    model="gemini-flash-latest",
    instruction="""Classify user message into either "BUG", "CUSTOMER_SUPPORT",
      or "LOGISTICS". If you think a message applies to more than one category,
      reply with a comma separated list of categories.
   """,
    output_schema=str,
)

def router(node_input: str):
    routes = node_input.split(",")
    routes = [route.strip() for route in routes]
    return Event(route=routes)

def response_1_bug():
    return Event(message="Handling bug...")

def response_2_support():
    return Event(message="Handling customer support...")

def response_3_logistics():
    return Event(message="Handling logistics...")

root_agent = Workflow(
   name="routing_workflow",
   edges=[
       ("START", process_message, router),
       ( router,
           {
               "BUG": response_1_bug,
               "CUSTOMER_SUPPORT": response_2_support,
               "LOGISTICS": response_3_logistics,
           }
       )
   ],
)
```

=== "TypeScript"

In ADK TypeScript v2.0.0, a router node returns an event carrying a
`route` value, created with `createEvent({route})`. A second edge row
maps each route value to the node that handles it. Setting `route` to an
array dispatches to every matching branch, which lets the classifier in
this example return more than one category. The `DEFAULT_ROUTE` setting
catches any value that no branch matched.

```typescript
--8<-- "examples/typescript/snippets/graphs/index/process_pipeline.ts:process-pipeline"
```

=== "Go"

In ADK Go v2.0.0, conditional routing uses `workflow.NewEmittingFunctionNode`
to set `event.Routes` and `workflow.StringRoute` edges to dispatch to the
matching handler — the direct equivalent of Python's `router` function and
dict dispatch. `workflow.Concat` merges the chain and the conditional edges
into a single `edges` slice passed to `workflowagent.New`.

```go
--8<-- "examples/go/snippets/graphs/index/main.go:process-pipeline"
```

This sample code demonstrates how you can compose a sequence of agents to define a graph with routes between a set of nodes, which are discrete tasks that can include agents, Tools, your code, and even additional workflow agents. For information about building advanced pipelines, see Build graph routes for workflow agents.

Known limitations

There are some known limitations with graph-based workflows. They are not compatible with the following ADK features:

  • Integrations: Some third-party integrations may not be compatible with graph-based workflows.

!!! note "Go: graph workflow API"

The `workflow` package in ADK Go v2.0.0 is the direct equivalent of the
Python `Workflow` class. Use `workflow.NewFunctionNode` and
`workflow.NewAgentNode` to define nodes, `workflow.Chain` or
`workflow.Concat` with `[]workflow.Edge` to wire them, and
`workflowagent.New` to wrap the graph as a runnable agent. Conditional
routing uses `workflow.StringRoute`, `workflow.IntRoute`, or
`workflow.BoolRoute` matched against `event.Routes`. Fan-in is handled by
`workflow.NewJoinNode`.

For advanced routing patterns and fan-out/join examples, see
[Build graph routes for workflow agents](/graphs/routes/). For prebuilt
higher-level alternatives (sequential, parallel, loop), see
[Prebuilt workflow agents](/agents/workflow-agents/).