Skip to content
Open
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
9 changes: 5 additions & 4 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# someone opens a pull request.
* @temporalio/sdk

/contrib/ai-sdk/ @temporalio/sdk @temporalio/ai-sdk
/contrib/openai-agents/ @temporalio/sdk @temporalio/ai-sdk
/contrib/strands/ @temporalio/sdk @temporalio/ai-sdk
/contrib/workflow-streams/ @temporalio/sdk @temporalio/ai-sdk
/contrib/ai-sdk/ @temporalio/sdk @temporalio/ai-sdk
/contrib/google-adk-agents/ @temporalio/sdk @temporalio/ai-sdk
/contrib/openai-agents/ @temporalio/sdk @temporalio/ai-sdk
/contrib/strands/ @temporalio/sdk @temporalio/ai-sdk
/contrib/workflow-streams/ @temporalio/sdk @temporalio/ai-sdk
44 changes: 25 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,31 @@ Refer to our [contributing guide](CONTRIBUTING.md) for details on build and test

This monorepo contains the following packages:

| Subfolder | Package |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| [`packages/client/`](packages/client/) | [`@temporalio/client`](https://www.npmjs.com/package/@temporalio/client) |
| [`packages/worker/`](packages/worker/) | [`@temporalio/worker`](https://www.npmjs.com/package/@temporalio/worker) |
| [`packages/workflow/`](packages/workflow/) | [`@temporalio/workflow`](https://www.npmjs.com/package/@temporalio/workflow) |
| [`packages/activity/`](packages/activity/) | [`@temporalio/activity`](https://www.npmjs.com/package/@temporalio/activity) |
| [`packages/testing/`](packages/testing/) | [`@temporalio/testing`](https://www.npmjs.com/package/@temporalio/testing) |
| [`packages/common/`](packages/common/) | [`@temporalio/common`](https://www.npmjs.com/package/@temporalio/common) |
| [`packages/proto/`](packages/proto/) | [`@temporalio/proto`](https://www.npmjs.com/package/@temporalio/proto) |
| [`packages/test/`](packages/test/) | SDK internal tests |
| [`packages/create-project/`](packages/create-project/) | [`@temporalio/create`](https://www.npmjs.com/package/@temporalio/create-project) |
| [`packages/docs/`](packages/docs/) | [API docs](https://typescript.temporal.io/) |
| [`packages/core-bridge/`](packages/core-bridge/) | [`@temporalio/core-bridge`](https://www.npmjs.com/package/@temporalio/proto) |
| [`packages/envconfig/`](packages/envconfig/) | [`@temporalio/envconfig`](https://www.npmjs.com/package/@temporalio/envconfig) |
| [`packages/nyc-test-coverage/`](packages/nyc-test-coverage/) | [`@temporalio/nyc-test-coverage`](https://www.npmjs.com/package/@temporalio/nyc-test-coverage) |
| [`packages/plugin/`](packages/plugin/) | [`@temporalio/plugin`](https://www.npmjs.com/package/@temporalio/plugin) |
| [`packages/nexus/`](packages/nexus/) | [`@temporalio/nexus`](https://www.npmjs.com/package/@temporalio/nexus) |
| [`contrib/interceptors-opentelemetry/`](contrib/interceptors-opentelemetry/) | [`@temporalio/interceptors-opentelemetry`](https://www.npmjs.com/package/@temporalio/interceptors-opentelemetry) |
| [`contrib/ai-sdk/`](contrib/ai-sdk/) | [`@temporalio/ai-sdk`](https://www.npmjs.com/package/@temporalio/ai-sdk) |
| Subfolder | Package |
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| [`packages/client/`](packages/client/) | [`@temporalio/client`](https://www.npmjs.com/package/@temporalio/client) |
| [`packages/worker/`](packages/worker/) | [`@temporalio/worker`](https://www.npmjs.com/package/@temporalio/worker) |
| [`packages/workflow/`](packages/workflow/) | [`@temporalio/workflow`](https://www.npmjs.com/package/@temporalio/workflow) |
| [`packages/activity/`](packages/activity/) | [`@temporalio/activity`](https://www.npmjs.com/package/@temporalio/activity) |
| [`packages/testing/`](packages/testing/) | [`@temporalio/testing`](https://www.npmjs.com/package/@temporalio/testing) |
| [`packages/common/`](packages/common/) | [`@temporalio/common`](https://www.npmjs.com/package/@temporalio/common) |
| [`packages/proto/`](packages/proto/) | [`@temporalio/proto`](https://www.npmjs.com/package/@temporalio/proto) |
| [`packages/test/`](packages/test/) | SDK internal tests |
| [`packages/create-project/`](packages/create-project/) | [`@temporalio/create`](https://www.npmjs.com/package/@temporalio/create-project) |
| [`packages/docs/`](packages/docs/) | [API docs](https://typescript.temporal.io/) |
| [`packages/core-bridge/`](packages/core-bridge/) | [`@temporalio/core-bridge`](https://www.npmjs.com/package/@temporalio/proto) |
| [`packages/envconfig/`](packages/envconfig/) | [`@temporalio/envconfig`](https://www.npmjs.com/package/@temporalio/envconfig) |
| [`packages/nyc-test-coverage/`](packages/nyc-test-coverage/) | [`@temporalio/nyc-test-coverage`](https://www.npmjs.com/package/@temporalio/nyc-test-coverage) |
| [`packages/plugin/`](packages/plugin/) | [`@temporalio/plugin`](https://www.npmjs.com/package/@temporalio/plugin) |
| [`packages/nexus/`](packages/nexus/) | [`@temporalio/nexus`](https://www.npmjs.com/package/@temporalio/nexus) |
| [`contrib/ai-sdk/`](contrib/ai-sdk/) | [`@temporalio/ai-sdk`](https://www.npmjs.com/package/@temporalio/ai-sdk) |
| [`contrib/google-adk-agents/`](contrib/google-adk-agents/) | [`@temporalio/google-adk-agents`](https://www.npmjs.com/package/@temporalio/google-adk-agents) |
| [`contrib/interceptors-opentelemetry/`](contrib/interceptors-opentelemetry/) | [`@temporalio/interceptors-opentelemetry`](https://www.npmjs.com/package/@temporalio/interceptors-opentelemetry) |
| [`contrib/interceptors-opentelemetry-v2/`](contrib/interceptors-opentelemetry-v2/) | [`@temporalio/interceptors-opentelemetry-v2`](https://www.npmjs.com/package/@temporalio/interceptors-opentelemetry-v2) |
| [`contrib/langsmith/`](contrib/langsmith/) | [`@temporalio/langsmith`](https://www.npmjs.com/package/@temporalio/langsmith) |
| [`contrib/openai-agents/`](contrib/openai-agents/) | [`@temporalio/openai-agents`](https://www.npmjs.com/package/@temporalio/openai-agents) |
| [`contrib/strands/`](contrib/strands/) | [`@temporalio/strands-agents`](https://www.npmjs.com/package/@temporalio/strands-agents) |
| [`contrib/workflow-streams/`](contrib/workflow-streams/) | [`@temporalio/workflow-streams`](https://www.npmjs.com/package/@temporalio/workflow-streams) |

## Contributing

Expand Down
205 changes: 205 additions & 0 deletions contrib/google-adk-agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# @temporalio/google-adk-agents

Run [Google Agent Development Kit](https://github.com/google/adk-js) (`@google/adk`)
agents as durable [Temporal](https://temporal.io) Workflows.

Your ADK agent graph — `LlmAgent`, `SequentialAgent`/`ParallelAgent`/`LoopAgent`,
`FunctionTool`s, `MCPToolset`s, the `Runner` loop — runs **inside the Workflow**
and replays deterministically. Only the non-deterministic I/O boundaries are
routed out to Activities:

- every **model call** (`generateContentAsync`) becomes a retryable, observable
Activity, and
- every **MCP tool call** (list-tools / call-tool) becomes an Activity.

Regular `FunctionTool`s run in the Workflow; to have a tool run as an Activity
instead, expose an existing Temporal Activity to the agent with `activityAsTool`.

Temporal then gives you automatic retries, timeouts, heartbeating, and
crash-safe replay for the whole run.

## Install

```bash
npm install @temporalio/google-adk-agents
```

Peer dependency: `@google/adk` `^1.2.0` (and its `@google/genai`). Provide your
Gemini credentials to the **worker** as usual (e.g. `GOOGLE_API_KEY` /
`GEMINI_API_KEY`) — credentials are never placed in workflow or activity inputs.

## Hello world

Take an agent you already have and change **one line** — wrap its model in
`TemporalModel` — then register the plugin.

### `workflows.ts`

```typescript
import { InMemoryRunner, LlmAgent, isFinalResponse, stringifyContent } from '@google/adk';
import { TemporalModel } from '@temporalio/google-adk-agents/workflow';

export async function askAgent(prompt: string): Promise<string> {
const agent = new LlmAgent({
name: 'assistant',
// The only change from a vanilla ADK agent:
model: new TemporalModel('gemini-2.5-flash'),
instruction: 'You are a helpful assistant.',
});

const runner = new InMemoryRunner({ agent });

let text = '';
for await (const event of runner.runEphemeral({
userId: 'user',
newMessage: { role: 'user', parts: [{ text: prompt }] },
})) {
if (isFinalResponse(event)) text = stringifyContent(event);
}
return text;
}
```

### `worker.ts`

```typescript
import { Worker } from '@temporalio/worker';
import { GoogleAdkPlugin } from '@temporalio/google-adk-agents';

const worker = await Worker.create({
taskQueue: 'adk',
workflowsPath: require.resolve('./workflows'),
// Registers invokeModel / invokeModelStreaming for you.
plugins: [new GoogleAdkPlugin()],
});
await worker.run();
```

### `client.ts`

```typescript
import { Client } from '@temporalio/client';
import { GoogleAdkPlugin } from '@temporalio/google-adk-agents';
import { askAgent } from './workflows';

// Passing the plugin to the Client auto-propagates it to Workers created from
// this Client — register it on EITHER the Client OR the Worker, not both.
const client = new Client({ plugins: [new GoogleAdkPlugin()] });

const result = await client.workflow.execute(askAgent, {
taskQueue: 'adk',
workflowId: 'adk-hello',
args: ['Write a haiku about durable execution.'],
});
console.log(result);
```

## What this plugin gives you

- **Durable model calls.** Swap `model: 'gemini-2.5-flash'` for
`model: new TemporalModel('gemini-2.5-flash')` and each inference runs as a
Temporal Activity with a per-model `RetryPolicy`, `startToCloseTimeout`, and
auto-heartbeat for slow / thinking-mode calls. Upstream `429`/`5xx` and
`retry-after` headers are honored; non-retryable `4xx` fail fast.
- **Durable MCP tools.** `new TemporalMCPToolset({ name })` routes tool
discovery and tool calls through Activities. The full tool schema (name,
description, **parameters**) round-trips, so the model still sees argument
schemas. MCP connection params stay on the worker:

```typescript
// worker
new GoogleAdkPlugin({
mcpToolsets: {
filesystem: () => ({
type: 'StdioConnectionParams',
serverParams: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '/data'] },
}),
},
});

// workflow / agent
const agent = new LlmAgent({
name: 'fs',
model: new TemporalModel('gemini-2.5-flash'),
tools: [new TemporalMCPToolset({ name: 'filesystem' })],
});
```

- **Existing Activities as tools.** Already have a Temporal Activity? Expose it
to the agent with `activityAsTool` instead of re-declaring it:

```typescript
import { activityAsTool } from '@temporalio/google-adk-agents/workflow';
import { Type } from '@google/genai';

const lookupTool = activityAsTool({
name: 'lookupOrder', // a registered Activity on your worker
description: 'Look up an order by id.',
parameters: { type: Type.OBJECT, properties: { orderId: { type: Type.STRING } } },
});
```

- **Streaming (SSE).** Streaming requires `streamingTopic` on `TemporalModel`:
set it to publish incremental `LlmResponse` chunks via
`@temporalio/workflow-streams` while the Workflow still receives the full
transcript. Requesting streaming without a `streamingTopic` throws a
non-retryable `GoogleAdkStreamingTopicRequired` error.
Comment on lines +142 to +146

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this have its own section with a code snippet?

- **Human-in-the-loop.** Because the agent loop runs in the Workflow body, a
`LongRunningFunctionTool` can `await` a Temporal Signal or Update carrying a
human's result — no special shim required.
- **Deterministic replay.** ADK's event IDs and timestamps funnel through
`Math.random()` / `Date.now()`, which the Temporal Workflow sandbox makes
deterministic — so the agent loop replays without a custom determinism hook.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would a custom determinism hook entail? Since we don't have one, maybe we can just leave that part out so we don't confuse anyone.


### Testing your workflows

Import test doubles from the `./testing` entry point to unit-test agents
without a live model or MCP server:

```typescript
import { fakeModelProvider, mockMCPToolset } from '@temporalio/google-adk-agents/testing';

const plugin = new GoogleAdkPlugin({
modelProvider: fakeModelProvider(),
mcpToolsets: {
weather: mockMCPToolset([
/* tool defs */
]),
},
});
```

## Under the hood

### Retries

Temporal's `RetryPolicy` is the **sole** retry authority for model calls. Inside
`invokeModel`, the plugin pins the reconstructed `@google/genai` client's
`httpOptions.retryOptions.attempts = 1` so the SDK does not run a second retry
loop _inside_ each Activity attempt (which would multiply latency/request volume
and hide the real failure from Temporal). One Activity attempt is exactly one
model request; Temporal owns backoff, `retry-after` handling, and the retry
budget. Tune it per model via `TemporalModelOptions.activity.retry`.

## Composing with other plugins

Temporal applies `plugins: [...]` in order. Place observability and governance
plugins **before** this one so model/tool Activities are wrapped by them:

```typescript
new Client({
plugins: [
new OpenTelemetryPlugin(), // 1. observability (outermost)
new GovernancePlugin(), // 2. governance

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have an example plugin for this? If not, rename OpenTelemetryPlugin() to ObservabilityPlugin() so users aren't looking for this.

new GoogleAdkPlugin(), // 3. this plugin
],
});
```

This plugin carries no trace context of its own — compose it with
`@temporalio/interceptors-opentelemetry` for distributed tracing across the
model/tool Activity boundary.

## License

MIT
103 changes: 103 additions & 0 deletions contrib/google-adk-agents/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"name": "@temporalio/google-adk-agents",
"version": "0.1.0",
"description": "Temporal plugin for the Google Agent Development Kit (@google/adk): run ADK agents as durable Temporal Workflows.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"typesVersions": {
"*": {
"workflow": [
"./lib/workflow.d.ts"
],
"testing": [
"./lib/testing.d.ts"
]
}
},
"exports": {
".": {
"types": "./lib/index.d.ts",
"import": "./lib/index.js",
"require": "./lib/index.js",
"default": "./lib/index.js"
},
"./workflow": {
"types": "./lib/workflow.d.ts",
"import": "./lib/workflow.js",
"require": "./lib/workflow.js",
"default": "./lib/workflow.js"
},
"./testing": {
"types": "./lib/testing.d.ts",
"import": "./lib/testing.js",
"require": "./lib/testing.js",
"default": "./lib/testing.js"
}
},
"scripts": {
"build": "tsc --build",
"build.watch": "tsc --build --watch",
"test": "ava ./lib/__tests__/*.test.js"
},
"ava": {
"timeout": "120s",
"concurrency": 1,
"workerThreads": false
},
"keywords": [
"temporal",
"google",
"adk",
"agents",
"ai",
"durable",
"workflow"
],
"author": "Temporal Technologies Inc. <sdk@temporal.io>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@temporalio/activity": "workspace:*",
"@temporalio/common": "workspace:*",
"@temporalio/plugin": "workspace:*",
"@temporalio/workflow": "workspace:*",
"@temporalio/workflow-streams": "workspace:*",
"@ungap/structured-clone": "^1.2.0",
"headers-polyfill": "^4.0.0",
"web-streams-polyfill": "^4.0.0"
},
"peerDependencies": {
"@google/adk": "^1.2.0",
"@google/genai": "^2.8.0"
},
"devDependencies": {
"@google/adk": "^1.2.0",
"@google/genai": "^2.8.0",
"@temporalio/client": "workspace:*",
"@temporalio/testing": "workspace:*",
"@temporalio/worker": "workspace:*",
"@types/node": "^20.0.0",
"ava": "^5.3.1",
"typescript": "^5.4.0"
},
"engines": {
"node": ">= 20.0.0"
},
"bugs": {
"url": "https://github.com/temporalio/sdk-typescript/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/temporalio/sdk-typescript.git",
"directory": "contrib/google-adk-agents"
},
"homepage": "https://github.com/temporalio/sdk-typescript/tree/main/contrib/google-adk-agents",
"files": [
"src",
"lib",
"!src/__tests__",
"!lib/__tests__"
]
}
Loading
Loading