|
| 1 | +# Google ADK on Temporal |
| 2 | + |
| 3 | +Run native [Google ADK Java](https://github.com/google/adk-java) agents in Temporal Workflows using |
| 4 | +the standalone [`io.temporal:google-adk:0.1.0`](https://central.sonatype.com/artifact/io.temporal/google-adk) |
| 5 | +integration. This integration is **Public Preview**, not Generally Available. APIs and configuration |
| 6 | +may change before General Availability. |
| 7 | + |
| 8 | +Model calls and MCP operations run as Temporal Activities. Agent orchestration, ordinary function |
| 9 | +tools, and session state stay in the Workflow. Use `TemporalAdk.runnerBuilder()` to keep ADK's time, |
| 10 | +identifiers, and parallel execution deterministic, and `TemporalAdk.run()` to await an agent turn. |
| 11 | + |
| 12 | +## Requirements |
| 13 | + |
| 14 | +- Java 17 or later. |
| 15 | +- `io.temporal:google-adk:0.1.0`, which brings Google ADK Java 1.12.0. |
| 16 | +- Temporal Java SDK and testing library 1.40.0 or later. The module declares a strict `[1.40.0,2.0)` |
| 17 | + version range, preferring 1.40.0, independently of the other samples' SDK version. |
| 18 | +- For a live run, a local Temporal Service (`temporal server start-dev`) and `GOOGLE_API_KEY` in the |
| 19 | + Worker's environment. The Google GenAI client also supports Vertex AI's environment configuration. |
| 20 | + |
| 21 | +The plugin's version is independent of the SDK version. The plugin does not supply `temporal-sdk`; |
| 22 | +the application adds it explicitly. Dependencies come from Maven Central. |
| 23 | + |
| 24 | +## Run a sample |
| 25 | + |
| 26 | +Start a local Temporal Service: |
| 27 | + |
| 28 | +```bash |
| 29 | +temporal server start-dev |
| 30 | +``` |
| 31 | + |
| 32 | +In another terminal, start the shared Worker from the repository root: |
| 33 | + |
| 34 | +```bash |
| 35 | +export GOOGLE_API_KEY=your_key |
| 36 | +./gradlew :googleadk:worker |
| 37 | +``` |
| 38 | + |
| 39 | +Then run one of these commands in a third terminal: |
| 40 | + |
| 41 | +```bash |
| 42 | +./gradlew :googleadk:starter --args='basic' |
| 43 | +./gradlew :googleadk:starter --args='tools' |
| 44 | +./gradlew :googleadk:starter --args='multiagent' |
| 45 | +./gradlew :googleadk:starter --args='mcp' |
| 46 | +``` |
| 47 | + |
| 48 | +The starter prints the agent's answer. All samples share the `google-adk-samples` Task Queue and |
| 49 | +start a new Workflow ID for each invocation. Inspect their Activities at `http://localhost:8233`. |
| 50 | + |
| 51 | +| Scenario | Source | What it shows | |
| 52 | +| --- | --- | --- | |
| 53 | +| Basic | [`basic/`](src/main/java/io/temporal/samples/googleadk/basic) | A native `LlmAgent` with `TemporalModel`; one model call becomes `googleadk.InvokeModel`. | |
| 54 | +| Activity tools | [`tools/`](src/main/java/io/temporal/samples/googleadk/tools) | `ActivityTools.create` exposes a weather Activity to the agent. The Activity returns sample weather data; replace its body with provider I/O. | |
| 55 | +| Multiple agents | [`multiagent/`](src/main/java/io/temporal/samples/googleadk/multiagent) | A `ParallelAgent` schedules two model Activities concurrently on Workflow threads. | |
| 56 | +| MCP | [`mcp/`](src/main/java/io/temporal/samples/googleadk/mcp) | A self-contained Java stdio echo server, started on first use by the Worker. Listing and calling tools become Activities. No Node or separately running MCP service is required. | |
| 57 | +| Human approval | [`humanintheloop/`](src/main/java/io/temporal/samples/googleadk/humanintheloop) | An Activity tool waits for a human decision delivered by Signal. The shipping Activity returns sample data and makes no fulfillment request. | |
| 58 | +| Conversation | [`chat/`](src/main/java/io/temporal/samples/googleadk/chat) | Signals queue messages, a Query returns the latest reply, and a session snapshot preserves the conversation through Continue-As-New. | |
| 59 | + |
| 60 | +[`SampleWorker`](src/main/java/io/temporal/samples/googleadk/SampleWorker.java) registers the |
| 61 | +`GoogleAdkPlugin` on the Temporal Client, so its Workers inherit the model/MCP Activities and payload |
| 62 | +converter. Credentials stay on the Worker. Native GenAI retries are disabled with one HTTP attempt; |
| 63 | +Temporal Activity retries handle model failures. Register the plugin on external clients too, as |
| 64 | +[`SampleStarter`](src/main/java/io/temporal/samples/googleadk/SampleStarter.java) does. |
| 65 | + |
| 66 | +## Approve or reject a tool call |
| 67 | + |
| 68 | +Start a fulfillment Workflow: |
| 69 | + |
| 70 | +```bash |
| 71 | +./gradlew :googleadk:starter --args='approval' |
| 72 | +``` |
| 73 | + |
| 74 | +Use the printed Workflow ID in place of `WORKFLOW_ID`. Query the pending tool name and arguments, |
| 75 | +then approve or reject the calls from that turn: |
| 76 | + |
| 77 | +```bash |
| 78 | +./gradlew :googleadk:starter --args='pending WORKFLOW_ID' |
| 79 | +./gradlew :googleadk:starter --args='decide WORKFLOW_ID true' |
| 80 | +# Or reject instead: |
| 81 | +./gradlew :googleadk:starter --args='decide WORKFLOW_ID false' |
| 82 | +./gradlew :googleadk:starter --args='result WORKFLOW_ID' |
| 83 | +``` |
| 84 | + |
| 85 | +`pending` can initially be empty while the model runs; query again once the agent asks for approval. |
| 86 | +The decision command sends a Signal; `result` waits for the final answer. The Workflow uses `Workflow.await` while paused, |
| 87 | +then supplies `Hitl.confirmationResponse` to the next ADK turn. A rejected call never schedules the |
| 88 | +shipping Activity. If the model asks for another confirmation, query and decide again. |
| 89 | + |
| 90 | +## Continue a conversation as new |
| 91 | + |
| 92 | +```bash |
| 93 | +./gradlew :googleadk:starter --args='chat' |
| 94 | +./gradlew :googleadk:starter --args='message WORKFLOW_ID My name is Ada.' |
| 95 | +./gradlew :googleadk:starter --args='reply WORKFLOW_ID' |
| 96 | +./gradlew :googleadk:starter --args='message WORKFLOW_ID Suggest a trip to Chicago.' |
| 97 | +./gradlew :googleadk:starter --args='message WORKFLOW_ID What should I pack?' |
| 98 | +./gradlew :googleadk:starter --args='message WORKFLOW_ID What is my name?' |
| 99 | +./gradlew :googleadk:starter --args='reply WORKFLOW_ID' |
| 100 | +./gradlew :googleadk:starter --args='finish WORKFLOW_ID' |
| 101 | +``` |
| 102 | + |
| 103 | +`message` queues a turn without waiting for the model. Query again after the model Activity completes |
| 104 | +to see its new reply. After three turns, the Workflow exports its ADK session, continues as new, and |
| 105 | +imports the session in the next Run. Queued messages and the latest reply travel with the snapshot. |
| 106 | +`finish` drains pending messages and returns the final answer. Continue-As-New limits each Run's |
| 107 | +history; the snapshot still contains the conversation and must fit within Temporal's payload limits. |
| 108 | + |
| 109 | +## Test without provider credentials |
| 110 | + |
| 111 | +```bash |
| 112 | +./gradlew :googleadk:test :googleadk:spotlessCheck |
| 113 | +``` |
| 114 | + |
| 115 | +The tests use `ScriptedModel` from the published integration and `TestWorkflowEnvironment`; they |
| 116 | +need no API keys or external Temporal Service. The MCP test starts the real local Java echo server. |
| 117 | +Tests verify Activity retries and results, concurrent model scheduling, approval and rejection, |
| 118 | +session snapshots and queued messages across Continue-As-New, and replay on a fresh Worker without |
| 119 | +repeating model or tool calls. |
| 120 | + |
| 121 | +## Limitations |
| 122 | + |
| 123 | +- Plain ADK function tools run in the Workflow and must be deterministic. Put network, file, and |
| 124 | + other I/O in Activities or MCP tools. |
| 125 | +- Build Runners with `TemporalAdk.runnerBuilder()`. Do not override agent-level schedulers or |
| 126 | + executors, or use RxJava blocking APIs inside a Workflow. |
| 127 | +- Streaming chunks arrive after the model Activity completes. Token streaming out of a Workflow |
| 128 | + and live bidirectional mode are unsupported. |
| 129 | +- Session snapshots preserve session-scoped state and events. They do not carry app-, user-, or |
| 130 | + temporary-scoped state. |
| 131 | +- ADK OpenTelemetry spans are emitted again during replay when a tracing SDK is installed. |
| 132 | + |
| 133 | +See the [integration README](https://github.com/temporalio/ai-integrations/tree/main/java/google-adk) |
| 134 | +for the full API and limitations. |
0 commit comments