* adding go getting started to 2.0 docs * updating graphs docs for go; adding in workflow example go snippets * Updating HiTL docs for Go 2.0 * updating Go getting started guide for v2 * Updating Routes docs for Go v2 * Updating data handling docs for Go 2.0 * Updating dynamic workflows docs for Go 2.0 * fix(docs): set ADK Go 2.0 GA date on the 2.0 landing page * fix(docs): standardize ADK Go 2.0 install/import on /v2 @ v2.0.0 * updating announcement banner * routes snippets updates * 2.0 install instructions clean-up * updating collaboration docs for go v2 + new example snippet --------- Co-authored-by: Karol Piotrowicz <kpiotrowicz@google.com>
14 KiB
Data handling for agent workflows
Structuring and managing data between agents and graph-based nodes is critical for building reliable processes with ADK. This guide explains data handling within graph-based workflows and collaboration agents, including how information is transmitted and received between graph nodes. It covers the essential parameters for passing data, content, and state, and explains how to implement structured data transfer for both function and agent nodes using data format schemas and specific instruction syntax.
Workflow data flow
Within a graph-based workflow, nodes pass data to downstream steps through events. A step writes its output to a named event field, and the next step receives it as its typed input.
=== "Python"
In Python, data is exchanged between graph nodes using ***Events***. The key
parameters for node data handling are:
- **`output`**: Parameter for passing information between *nodes*.
- **`message`**: Data intended as a response to a user.
- **`state`**: Data automatically persisted across nodes via ***Events***
throughout an ADK session.
=== "Go"
In ADK Go v2.0.0, the data-passing mechanism depends on which agent style
you use:
**workflow package** (`FunctionNode`, `AgentNode`, `DynamicNode`): nodes
communicate through `session.Event` fields, mirroring Python closely:
- **`Event.Output`**: the node's return value, set automatically by the
framework when a `FunctionNode` returns a non-`*genai.Content` value.
The successor node receives this as its typed `input` parameter.
- **`Event.Routes`**: routing keys set explicitly by an emitting node to
select which conditional edge to follow — the Go equivalent of
Python's `Event(route=...)`.
- **`Event.NodeInfo`**: scheduler metadata (`path`, `MessageAsOutput`,
`OutputFor`). Set by the workflow engine; nodes do not set this
directly.
**Prebuilt workflow agents** (`sequentialagent`, `parallelagent`,
`loopagent`): these agents communicate through session state:
- **`OutputKey`** on `llmagent.Config`: the framework writes the agent's
final text response to `state[OutputKey]` after each turn.
- **`ctx.Session().State().Set` / `.Get`**: write or read arbitrary
values from state inside custom code.
- **`{key}` in `Instruction`**: the framework substitutes `state["key"]`
into the prompt before calling the model.
State keys may carry a prefix that controls their lifetime and scope:
| Prefix constant | Prefix string | Scope |
|---|---|---|
| `session.KeyPrefixApp` | `"app:"` | Shared across all users and sessions for the app |
| `session.KeyPrefixUser` | `"user:"` | Tied to the user, shared across their sessions |
| `session.KeyPrefixTemp` | `"temp:"` | Discarded after the current invocation ends |
| *(none)* | — | Persists for the lifetime of the session |
Node output
Each step in a workflow produces output for its successor.
=== "Python"
Use the ***return*** or ***yield*** syntax to hand off data to the next node:
```python
from google.adk import Event
def my_function_node(node_input: str):
output_value = node_input.upper()
return Event(output=output_value) # "THE RESULT"
```
Use the ***return*** syntax when outputting ***Event*** data that does not
require additional processing. When emitting data that requires additional
processing, or if you are generating more than one data item, you can use
more than one ***yield*** command. Each ***yield*** call adds to a list of
data objects on the Event which is passed to the next node of a graph. A
***return*** or ***yield*** command without a parameter passes a `None` value
to the next node.
=== "Go"
**workflow package**: a `FunctionNode` simply returns a typed Go value.
The framework automatically wraps the return value in a `session.Event`
and sets `Event.Output`. The successor node receives this value as its
typed `input` parameter — no manual event construction needed:
```go
--8<-- "examples/go/snippets/graphs/data-handling/main.go:event-output"
```
**Prebuilt workflow agents**: use `OutputKey` on `llmagent.Config` to
save an agent's text response to session state, then reference it with
`{key}` in downstream agents' `Instruction` templates:
```go
--8<-- "examples/go/snippets/graphs/data-handling/main.go:output-key"
```
Node output: passing structured data
=== "Python"
You can pass longer, structured data in a serializable format:
```python
def my_function_node_3():
yield Event(
output={
"city_name": "Paris",
"city_time": "10:10 AM",
},
)
```
!!! warning "Caution: Event.output limitation"
Nodes are only allowed to emit a single ***Event.output*** data payload
per execution. This limitation means that while you can use more than
one ***yield*** in a node, having two or more ***yield*** commands with
an ***Event.output*** results in a runtime error.
=== "Go"
**workflow package**: a `FunctionNode` can return any JSON-serializable
Go struct. The framework serializes it into `Event.Output` and
deserializes it back into the successor node's typed `input` parameter.
There is no single-payload restriction — each node has exactly one typed
return value:
```go
--8<-- "examples/go/snippets/graphs/data-handling/main.go:structured-output"
```
**Prebuilt workflow agents**: use multiple `OutputKey` values, one per
agent, to store individual fields in session state. Downstream agents
read each field independently via `{key}` in their `Instruction`.
Routing output
=== "Python"
Use the `route` parameter of an ***Event*** to drive conditional edge
dispatch:
```python
def router(node_input: str):
return Event(route="BUG")
```
=== "Go"
**workflow package**: an emitting `FunctionNode` constructs a
`session.Event` directly, sets `Event.Routes` to the desired route keys,
and sets `Event.Output` to forward the payload to the successor. The
workflow engine reads `Event.Routes` at dispatch time to select the
matching edge:
```go
--8<-- "examples/go/snippets/graphs/data-handling/main.go:routing-output"
```
User-facing messages
=== "Python"
Use the ***message*** parameter of an ***Event*** to send a response to a
user rather than pass data to the next node:
```python
async def user_message(node_input: str):
"""Tell user research process is starting."""
yield Event(message="Beginning research process...")
```
=== "Go"
**workflow package**: to emit a user-visible message without advancing
the node's typed output, set `Event.Content` on an intermediate event
emitted via the `emit` callback in an `EmittingFunctionNode`. The
terminal return value (or `nil`) controls `Event.Output`.
**Prebuilt workflow agents**: any `llmagent` step automatically emits its
model response as a user-facing event. For non-LLM steps, write a custom
`Run` function on an `agent.Agent` that yields events whose
`LLMResponse.Content` contains the text.
Session state and state scopes
Session state persists data across turns within a session. It is the primary data-sharing mechanism for the prebuilt workflow agents, and is also available inside tools and callbacks regardless of which agent style you use.
=== "Python"
Use the ***state*** parameter of an ***Event*** to maintain values across
nodes. Nodes can modify state values, and the modified state values are
available to downstream nodes:
```python
async def init_state_node(attempts: int = 0):
yield Event(
state={
"attempts": attempts,
},
)
async def task_attempt_node(node_input: Content, attempts: int):
yield Event(
state={
"attempts": attempts + 1,
},
)
async def read_state_node(ctx: Context):
print(f"attempts state: {ctx.state}") # attempts state: attempts: 1
root_agent = Workflow(
name="root_agent",
edges=[("START", init_state_node, task_attempt_node, read_state_node)],
)
```
!!! warning "Caution: `state` property data limitations"
The state parameter *should not be used to persist large amounts of
data* between nodes. Use artifacts or other data persistence mechanisms,
such as database Tools, to persist large data resources during the life
cycle of a Workflow.
=== "Go"
State is written with `ctx.Session().State().Set(key, value)` and read
with `.Get(key)`. The `session` package defines prefix constants that map
to the same lifetime scopes as Python's state parameter. This pattern
applies to prebuilt workflow agents and to tools and callbacks in any
agent style:
```go
--8<-- "examples/go/snippets/graphs/data-handling/main.go:state-scopes"
```
!!! warning "Caution: state data limitations"
Session state is a lightweight key-value store. Do not use it to persist
large payloads such as file contents or binary data. Use ADK artifacts
or external storage tools instead.
!!! tip "workflow package: prefer Event.Output over state"
For the `workflow` package (`FunctionNode`, `AgentNode`, `DynamicNode`),
pass data between nodes by returning typed values — the framework sets
`Event.Output` automatically. Only use `State().Set` when you need to
share values with tools, callbacks, or agent `Instruction` templates.
Constrain node data with schemas
You can set input and output data schemas to constrain the data formats accepted and produced by any agent node.
=== "Python"
Use `input_schema` and `output_schema` with a class that extends
***BaseModel*** to constrain any agent's input and output:
```python
from google.adk import Agent
from pydantic import BaseModel
class FlightSearchInput(BaseModel):
origin: str # Airport code "SFO"
destination: str # Airport code "CDG"
departure_date: date # date(2026, 3, 15)
passengers: int = 1 # Number of passengers
class FlightSearchOutput(BaseModel):
flights: list[Flight]
cheapest_price: float
flight_searcher = Agent(
name="flight_searcher",
instruction="Search for available flights.",
input_schema=FlightSearchInput,
output_schema=FlightSearchOutput,
tools=[search_flights_api],
mode="single_turn",
...
)
assistant = Agent(
name="assistant",
instruction="You help users plan trips.",
sub_agents=[flight_searcher],
...
)
```
=== "Go"
**workflow package**: use `workflow.NewAgentNodeTyped[Input, Output]` to
attach schemas to an agent node. The generic type parameters are reflected
into `*jsonschema.Schema` automatically — no hand-built schema construction
needed. The node's `Event.Output` carries the structured result to the
successor — no `OutputKey` or state write is needed:
```go
--8<-- "examples/go/snippets/graphs/data-handling/main.go:input-output-schema"
```
**Prebuilt workflow agents**: set `InputSchema` and `OutputSchema` on
`llmagent.Config`. `OutputSchema` forces the model to reply with a JSON
object matching the schema (the agent cannot use tools when `OutputSchema`
is set). Use `OutputKey` to save the JSON string to state for downstream
agents to reference via `{key}` in their `Instruction`.
Access structured data in agents
=== "Python"
Use the curly-brace `{ }` syntax to select properties from the input
schema, or `< >` to select a property and also qualify it by the name
of the source node:
```python
class CityTime(BaseModel):
time_info: str # time information
city: str # city name
def lookup_time_function(city: str):
"""Simulate returning the current time in the specified city."""
return Event(output=CityTime(time_info='10:10 AM', city=city))
city_report_agent = Agent(
name="city_report_agent",
model="gemini-flash-latest",
input_schema=CityTime,
# data selection based on class and parameter
# instruction="""
# Return a sentence in the following format:
# It is {CityTime.time_info} in {CityTime.city} right now.
# """,
# more restrictive data selection based on source node name
instruction="""
Return a sentence in the following format:
It is <CityTime.time_info from lookup_time_function> in
<CityTime.city from lookup_time_function> right now.
""",
)
root_agent = Workflow(
name="root_agent",
edges=[
(START, city_generator_agent, lookup_time_function, city_report_agent)
],
)
```
=== "Go"
In ADK Go v2.0.0, a `FunctionNode` returns a typed struct and the
framework serializes it into `Event.Output`. The successor `AgentNode`
receives the struct as its user content — the fields are available to the
agent's `Instruction` without any `{key}` template syntax. This is the
direct equivalent of Python's `input_schema=CityTime` with
`{CityTime.time_info}` template placeholders: the struct fields are
delivered as typed input rather than looked up by name from state.
```go
--8<-- "examples/go/snippets/graphs/data-handling/main.go:structured-output"
```
For a complete example of this workflow, see Graph-based agent workflows.