Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,15 @@
/*nexus/ @temporalio/nexus
/tests/nexus*/ @temporalio/nexus
/tests/*nexus/ @temporalio/nexus

# The AI SDK team owns the AI integration samples and their tests. We add
# @temporalio/sdk too, so the SDK team can continue to manage repo-wide concerns.
/google_adk_agents/ @temporalio/sdk @temporalio/ai-sdk
/langgraph_plugin/ @temporalio/sdk @temporalio/ai-sdk
/langsmith_tracing/ @temporalio/sdk @temporalio/ai-sdk
/openai_agents/ @temporalio/sdk @temporalio/ai-sdk
/strands_plugin/ @temporalio/sdk @temporalio/ai-sdk
/tests/google_adk_agents/ @temporalio/sdk @temporalio/ai-sdk
/tests/langgraph_plugin/ @temporalio/sdk @temporalio/ai-sdk
/tests/langsmith_tracing/ @temporalio/sdk @temporalio/ai-sdk
/tests/strands_plugin/ @temporalio/sdk @temporalio/ai-sdk
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,31 +72,41 @@ Some examples require extra dependencies. See each sample's directory for specif
* [external_storage](external_storage) - Offload large payloads to S3-compatible object storage, plus a codec server for the Web UI and CLI.
* [external_storage_redis](external_storage_redis) - Redis driver for external storage
* [gevent_async](gevent_async) - Combine gevent and Temporal.
* [google_adk_agents](google_adk_agents) - Run Google ADK agents as durable Temporal workflows (model calls, tools, multi-agent, MCP, streaming).
* [hello_nexus](hello_nexus) - Define a Nexus service, implement operation handlers, and call them from a workflow.
* [hello_standalone_activity](hello_standalone_activity) - Use activities without using a workflow.
* [langchain](langchain) - Orchestrate workflows for LangChain.
* [lambda_worker](lambda_worker) - Run a Temporal Worker inside an AWS Lambda function.
* [langgraph_plugin](langgraph_plugin) - Run LangGraph workflows as durable Temporal workflows (Graph API and Functional API).
* [langsmith_tracing](langsmith_tracing) - Trace Temporal workflows with LangSmith via the LangSmith plugin.
* [message_passing/introduction](message_passing/introduction/) - Introduction to queries, signals, and updates.
* [message_passing/safe_message_handlers](message_passing/safe_message_handlers/) - Safely handling updates and signals.
* [message_passing/update_with_start/lazy_initialization](message_passing/update_with_start/lazy_initialization/) - Use update-with-start to update a Shopping Cart, starting it if it does not exist.
* [nexus_cancel](nexus_cancel) - Fan out concurrent Nexus operations, take the first result, and cancel the rest.
* [Nexus Messaging](nexus_messaging): Demonstrates how send signal, update and query messages through Nexus.
This contains two samples, one sending messages to an existing workflow and a second that creates a workflow through Nexus
and sends messages to it.
* [nexus_multiple_args](nexus_multiple_args) - Map a Nexus operation to a handler workflow that takes multiple arguments.
* [nexus_standalone_operations](nexus_standalone_operations) - Execute Nexus operations directly from client code,
without wrapping them in a workflow.
* [open_telemetry](open_telemetry) - Trace workflows with OpenTelemetry.
* [openai_agents](openai_agents) - Run OpenAI Agents SDK agents as durable Temporal workflows.
* [patching](patching) - Alter workflows safely with `patch` and `deprecate_patch`.
* [polling](polling) - Recommended implementation of an activity that needs to periodically poll an external resource waiting its successful completion.
* [prometheus](prometheus) - Configure Prometheus metrics on clients/workers.
* [workflow_streams](workflow_streams) - Workflow-hosted durable event stream via `temporalio.contrib.workflow_streams`. **Experimental**
* [pydantic_converter](pydantic_converter) - Data converter for using Pydantic models.
* [pydantic_converter_v1](pydantic_converter_v1) - Data converter for Pydantic v1 models (prefer pydantic_converter for v2).
* [replay](replay) - Verify that workflow code changes are compatible with existing histories.
* [resource_pool](resource_pool) - Allocate a pool of shared resources across workflows.
* [schedules](schedules) - Demonstrates a Workflow Execution that occurs according to a schedule.
* [sentry](sentry) - Report errors to Sentry.
* [sleep_for_days](sleep_for_days) - A workflow that runs forever, sending an email every 30 days.
* [strands_plugin](strands_plugin) - Run Strands Agents as durable Temporal workflows (model calls, tools, MCP, HITL).
* [trio_async](trio_async) - Use asyncio Temporal in Trio-based environments.
* [updatable_timer](updatable_timer) - A timer that can be updated while sleeping.
* [worker_multiprocessing](worker_multiprocessing) - Leverage Python multiprocessing to parallelize workflow tasks and other CPU bound operations by running multiple workers.
* [worker_specific_task_queues](worker_specific_task_queues) - Use unique task queues to ensure activities run on specific workers.
* [worker_versioning](worker_versioning) - Use the Worker Versioning feature to more easily version your workflows & other code.
* [worker_multiprocessing](worker_multiprocessing) - Leverage Python multiprocessing to parallelize workflow tasks and other CPU bound operations by running multiple workers.
* [workflow_streams](workflow_streams) - Workflow-hosted durable event stream via `temporalio.contrib.workflow_streams`. **Experimental**

## Test

Expand Down
53 changes: 53 additions & 0 deletions google_adk_agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Temporal Google ADK Integration

⚠️ **Experimental** — This integration is experimental and its interfaces may
change prior to General Availability.

This directory contains samples demonstrating how to run
[Google ADK](https://google.github.io/adk-docs/) agents durably inside Temporal
workflows using `temporalio.contrib.google_adk_agents`. Each scenario is a
self-contained subdirectory with its own worker, workflow starter, workflow and
activity packages, and README.

## Overview

The integration combines:

- **Temporal workflows** for durable orchestration of agent control flow
- **Google ADK** for agent creation, model calls, tools, and MCP integration

`GoogleAdkPlugin` configures a Pydantic payload converter, sandbox passthrough
for `google.adk` / `google.genai` / `mcp`, a deterministic ADK runtime, and the
model activities. `TemporalModel` runs each LLM call as an activity, so every
model turn is durable and observable.

## Prerequisites

- Temporal server [running locally](https://docs.temporal.io/cli/server#start-dev)
- Dependencies installed via `uv sync --group google-adk`
- Google API key set as an environment variable:
`export GOOGLE_API_KEY=your_key_here`

All scenarios default to the `gemini-2.5-flash` model. ADK also supports other
providers (for example, non-Gemini models via LiteLLM); swap the model name on
`TemporalModel` to use one.

## Scenarios

Each directory contains a complete example with its own README:

| Scenario | What it shows |
| --- | --- |
| [basic](./basic/README.md) | A single ADK agent with `TemporalModel` and one model call — no tools. The minimal end-to-end example. |
| [tools](./tools/README.md) | A Temporal activity wrapped as an ADK tool with `activity_tool`, so tool calls run as their own activities. |
| [agent_patterns](./agent_patterns/README.md) | A coordinator `LlmAgent` with `sub_agents`, each a `TemporalModel` with a per-agent activity summary. |
| [mcp](./mcp/README.md) | A local echo MCP toolset via `TemporalMcpToolSet` / `TemporalMcpToolSetProvider`, running MCP tools as activities. Self-contained, no Node required. |
| [streaming](./streaming/README.md) | Token streaming via `TemporalModel(streaming_topic=...)` + `WorkflowStream`, consumed by a starter with `WorkflowStreamClient`. |

To run any scenario, start its worker in one terminal and its workflow starter
in another:

```bash
uv run python -m google_adk_agents.<scenario>.run_worker
uv run python -m google_adk_agents.<scenario>.run_<name>_workflow
```
Empty file added google_adk_agents/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions google_adk_agents/agent_patterns/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Agent Patterns — Multi-Agent Coordinator

A coordinator `LlmAgent` with `sub_agents=[researcher, writer]`. Each agent uses
its own `TemporalModel` with an `ActivityConfig(summary=...)`, so the agents
show up as named activities in workflow history. This demonstrates ADK's
built-in `transfer_to_agent` handoff running durably, with per-agent activity
summaries.

Before running, review the [prerequisites in the suite README](../README.md)
(Temporal dev server, `uv sync --group google-adk`, and
`export GOOGLE_API_KEY=...`).

## Running

Start the worker in one terminal:

```bash
uv run python -m google_adk_agents.agent_patterns.run_worker
```

Then start the workflow in another terminal:

```bash
uv run python -m google_adk_agents.agent_patterns.run_multi_agent_workflow
```

## What to expect

The starter asks for a haiku about the ocean. The coordinator delegates to the
researcher and then the writer; the starter prints the final haiku.

## In the Temporal UI

Open the workflow `google-adk-agents-agent-patterns-workflow-id`. The
`invoke_model` activities are labeled with their agent summaries —
"Coordinator Agent", "Researcher Agent", "Writer Agent" — so you can follow the
handoffs between agents directly in the history.
Empty file.
24 changes: 24 additions & 0 deletions google_adk_agents/agent_patterns/run_multi_agent_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import asyncio

from temporalio.client import Client
from temporalio.contrib.google_adk_agents import GoogleAdkPlugin

from google_adk_agents.agent_patterns.workflows.multi_agent_workflow import (
MultiAgentWorkflow,
)


async def main():
client = await Client.connect("localhost:7233", plugins=[GoogleAdkPlugin()])

result = await client.execute_workflow(
MultiAgentWorkflow.run,
"the ocean",
id="google-adk-agents-agent-patterns-workflow-id",
task_queue="google-adk-agents-agent-patterns",
)
print(f"Result: {result}")


if __name__ == "__main__":
asyncio.run(main())
31 changes: 31 additions & 0 deletions google_adk_agents/agent_patterns/run_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

import asyncio

from temporalio.client import Client
from temporalio.contrib.google_adk_agents import GoogleAdkPlugin
from temporalio.worker import Worker

from google_adk_agents.agent_patterns.workflows.multi_agent_workflow import (
MultiAgentWorkflow,
)


async def main():
# Build the plugin once and give the same instance to the client and the
# worker.
plugin = GoogleAdkPlugin()

client = await Client.connect("localhost:7233", plugins=[plugin])

worker = Worker(
client,
task_queue="google-adk-agents-agent-patterns",
workflows=[MultiAgentWorkflow],
plugins=[plugin],
)
await worker.run()


if __name__ == "__main__":
asyncio.run(main())
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from datetime import timedelta

from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
from temporalio import workflow
from temporalio.contrib.google_adk_agents import TemporalModel
from temporalio.workflow import ActivityConfig


# @@@SNIPSTART google-adk-agents-agent-patterns-multi-agent-workflow
@workflow.defn
class MultiAgentWorkflow:
@workflow.run
async def run(self, topic: str) -> str:
session_service = InMemorySessionService()
session = await session_service.create_session(
app_name="multi_agent_app", user_id="user"
)

# Give each sub-agent its own TemporalModel with an ActivityConfig
# summary, so its model turns show up as named activities in history.
researcher = LlmAgent(
name="researcher",
model=TemporalModel(
"gemini-2.5-flash",
activity_config=ActivityConfig(summary="Researcher Agent"),
),
instruction="You are a researcher. Find information about the topic.",
)

writer = LlmAgent(
name="writer",
model=TemporalModel(
"gemini-2.5-flash",
activity_config=ActivityConfig(summary="Writer Agent"),
),
instruction="You are a poet. Write a haiku based on the research.",
)

# The coordinator hands off to the sub-agents using ADK's built-in
# transfer_to_agent, which runs durably here.
coordinator = LlmAgent(
name="coordinator",
model=TemporalModel(
"gemini-2.5-flash",
activity_config=ActivityConfig(
start_to_close_timeout=timedelta(seconds=30),
summary="Coordinator Agent",
),
),
instruction="You are a coordinator. Delegate to researcher then writer.",
sub_agents=[researcher, writer],
)

runner = Runner(
agent=coordinator,
app_name="multi_agent_app",
session_service=session_service,
)

final_text = ""
user_msg = types.Content(
role="user",
parts=[
types.Part(
text=f"Write a haiku about {topic}. First research it, then write it."
)
],
)
async for event in runner.run_async(
user_id="user", session_id=session.id, new_message=user_msg
):
if event.content and event.content.parts and event.content.parts[0].text:
final_text = event.content.parts[0].text

return final_text


# @@@SNIPEND
39 changes: 39 additions & 0 deletions google_adk_agents/basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Basic — Single Agent, One Model Call

The minimal Google ADK + Temporal sample: an ordinary ADK `Agent` whose
`model=TemporalModel("gemini-2.5-flash")`, driven by an `InMemoryRunner` inside
a workflow. No tools, no streaming — just one model turn.

It demonstrates that `GoogleAdkPlugin`'s deterministic runtime and Pydantic
payload converter let an unmodified ADK agent run durably in a workflow, with
every LLM call surfaced as an `invoke_model` activity.

Before running, review the [prerequisites in the suite README](../README.md)
(Temporal dev server, `uv sync --group google-adk`, and
`export GOOGLE_API_KEY=...`).

## Running

Start the worker in one terminal:

```bash
uv run python -m google_adk_agents.basic.run_worker
```

Then start the workflow in another terminal:

```bash
uv run python -m google_adk_agents.basic.run_hello_world_workflow
```

## What to expect

The starter prints the agent's response — a haiku about recursion. The agent is
instructed to respond only in haikus.

## In the Temporal UI

Open the workflow `google-adk-agents-basic-workflow-id`. In the history you will
see one `invoke_model` activity for the single model turn. That activity is
where the call to Gemini actually happens — the workflow itself stays
deterministic and replay-safe.
Empty file.
24 changes: 24 additions & 0 deletions google_adk_agents/basic/run_hello_world_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import asyncio

from temporalio.client import Client
from temporalio.contrib.google_adk_agents import GoogleAdkPlugin

from google_adk_agents.basic.workflows.hello_world_workflow import (
HelloWorldAgentWorkflow,
)


async def main():
client = await Client.connect("localhost:7233", plugins=[GoogleAdkPlugin()])

result = await client.execute_workflow(
HelloWorldAgentWorkflow.run,
"Tell me about recursion in programming.",
id="google-adk-agents-basic-workflow-id",
task_queue="google-adk-agents-basic",
)
print(f"Result: {result}")


if __name__ == "__main__":
asyncio.run(main())
31 changes: 31 additions & 0 deletions google_adk_agents/basic/run_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

import asyncio

from temporalio.client import Client
from temporalio.contrib.google_adk_agents import GoogleAdkPlugin
from temporalio.worker import Worker

from google_adk_agents.basic.workflows.hello_world_workflow import (
HelloWorldAgentWorkflow,
)


async def main():
# Build the plugin once and give the same instance to the client and the
# worker.
plugin = GoogleAdkPlugin()

client = await Client.connect("localhost:7233", plugins=[plugin])

worker = Worker(
client,
task_queue="google-adk-agents-basic",
workflows=[HelloWorldAgentWorkflow],
plugins=[plugin],
)
await worker.run()


if __name__ == "__main__":
asyncio.run(main())
Empty file.
Loading
Loading