-
Notifications
You must be signed in to change notification settings - Fork 149
Added Strands Agents sample #472
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
b0aada1
Added Strands Agents sample
brianstrauch 05091f6
Add snipsync markers to Strands Agents samples
brianstrauch 521a0b1
Merge remote-tracking branch 'origin/main' into strands-agents-sample
brianstrauch 412cfeb
Merge remote-tracking branch 'origin/main' into strands-agents-sample
brianstrauch c90ba3e
chore: bump strands-agents sample to 1.21.1
brianstrauch 9b1ffd2
chore: register strands-agents in list-of-samples
brianstrauch 5b381c5
chore: fix CI for strands-agents (lockfile, shared configs, formatting)
brianstrauch 0ea3567
fix: convert strands-agents sample to ESM so CI passes
brianstrauch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| node_modules | ||
| lib | ||
| .eslintrc.js |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| const { builtinModules } = require('module'); | ||
|
|
||
| const ALLOWED_NODE_BUILTINS = new Set(['assert']); | ||
|
|
||
| module.exports = { | ||
| root: true, | ||
| parser: '@typescript-eslint/parser', | ||
| parserOptions: { | ||
| project: './tsconfig.json', | ||
| tsconfigRootDir: __dirname, | ||
| }, | ||
| plugins: ['@typescript-eslint', 'deprecation'], | ||
| extends: [ | ||
| 'eslint:recommended', | ||
| 'plugin:@typescript-eslint/eslint-recommended', | ||
| 'plugin:@typescript-eslint/recommended', | ||
| 'prettier', | ||
| ], | ||
| rules: { | ||
| '@typescript-eslint/no-floating-promises': 'error', | ||
| 'deprecation/deprecation': 'warn', | ||
| 'object-shorthand': ['error', 'always'], | ||
| '@typescript-eslint/no-unused-vars': [ | ||
| 'warn', | ||
| { | ||
| argsIgnorePattern: '^_', | ||
| varsIgnorePattern: '^_', | ||
| }, | ||
| ], | ||
| '@typescript-eslint/no-explicit-any': 'off', | ||
| }, | ||
| overrides: [ | ||
| { | ||
| files: ['src/workflows.ts', 'src/workflows-*.ts', 'src/workflows/*.ts'], | ||
| rules: { | ||
| 'no-restricted-imports': [ | ||
| 'error', | ||
| ...builtinModules.filter((m) => !ALLOWED_NODE_BUILTINS.has(m)).flatMap((m) => [m, `node:${m}`]), | ||
| ], | ||
| }, | ||
| }, | ||
| ], | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| lib | ||
| node_modules |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| package-lock=false |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 22 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| To begin development, install the Temporal CLI: | ||
|
|
||
| Mac: {cyan brew install temporal} | ||
| Other: Download and extract the latest release from https://github.com/temporalio/cli/releases/latest | ||
|
|
||
| Start Temporal Server: | ||
|
|
||
| {cyan temporal server start-dev} | ||
|
|
||
| Use Node version 18+ (v22.x is recommended): | ||
|
|
||
| Mac: {cyan brew install node@22} | ||
| Other: https://nodejs.org/en/download/ | ||
|
|
||
| Then, in the project directory, using two other shells, run these commands: | ||
|
|
||
| {cyan npm run start.watch} | ||
| {cyan npm run workflow} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| lib |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| printWidth: 120 | ||
| singleQuote: true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Strands Agents | ||
|
|
||
| A complete set of samples for the Temporal TypeScript SDK's Strands integration, mirroring the Python samples in [`samples-python/strands_plugin`](https://github.com/temporalio/samples-python/tree/strands-plugin-samples/strands_plugin). | ||
|
|
||
| One worker registers all activities, all workflows, and an MCP server. Each feature has its own client script in `src/`: | ||
|
|
||
| | Feature | Client | What it demonstrates | | ||
| | ------------------ | ------------------------------------- | ------------------------------------------------------------------------------------------- | | ||
| | Hello World | `npm run workflow:hello-world` | Minimal `TemporalAgent` invocation. | | ||
| | Tools | `npm run workflow:tools` | Pure Strands `tool()` + `workflow.activityAsTool` for I/O. | | ||
| | Human in the Loop | `npm run workflow:human-in-the-loop` | Gate a tool call on human approval with a `BeforeToolCallEvent` hook + `event.interrupt()`. | | ||
| | Activity Interrupt | `npm run workflow:activity-interrupt` | Activity-thrown interrupt routed through `StrandsFailureConverter`. | | ||
| | Hooks | `npm run workflow:hooks` | `AfterToolCallEvent` with in-workflow and activity-dispatched callbacks. | | ||
| | MCP | `npm run workflow:mcp` | Connect to a stdio MCP server via `TemporalMCPClient`. | | ||
| | Structured Output | `npm run workflow:structured-output` | Constrain output with a Zod schema. | | ||
| | Streaming | `npm run workflow:streaming` | Forward model chunks to subscribers via `WorkflowStream`. | | ||
| | Continue as New | `npm run workflow:continue-as-new` | Long-lived chat with history-aware continue-as-new. | | ||
|
|
||
| ### Directory layout | ||
|
|
||
| ``` | ||
| src/ | ||
| ├── worker.ts # one worker, registers everything | ||
| ├── workflows.ts # re-exports every workflow in workflows/ | ||
| ├── activities.ts # re-exports every activity in activities/ | ||
| ├── mcp-server.ts # stdio MCP server (used by the mcp feature) | ||
| ├── <feature>.ts # one client script per feature | ||
| ├── workflows/ | ||
| │ └── <feature>.ts # workflow definition for that feature | ||
| ├── activities/ | ||
| │ └── <feature>.ts # activities for that feature (only where needed) | ||
| └── mocha/ | ||
| ├── stub-model.ts # shared StubModel for tests | ||
| └── <feature>.test.ts # one test per feature | ||
| ``` | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| `@temporalio/strands-agents` and its dependency `@temporalio/workflow-streams` live in [`temporalio/sdk-typescript`](https://github.com/temporalio/sdk-typescript) on the `strands` branch and are not yet published to npm. `package.json` references both as `file:` tarballs, assuming `sdk-typescript` is checked out next to `samples-typescript`: | ||
|
|
||
| ``` | ||
| <your-workspace>/ | ||
| ├── samples-typescript/ # this repo | ||
| └── sdk-typescript/ # the SDK repo on branch `strands` | ||
| ``` | ||
|
|
||
| Before installing, build the contrib packages and produce the tarballs (the `workspace:*` deps inside them need to be substituted with concrete versions, which `pnpm pack` does automatically): | ||
|
|
||
| ```bash | ||
| cd /path/to/sdk-typescript | ||
| git checkout strands | ||
| pnpm install | ||
| pnpm build | ||
|
|
||
| cd contrib/strands && pnpm pack && cd ../.. | ||
| cd contrib/workflow-streams && pnpm pack && cd ../.. | ||
| # `@temporalio/common` needs to be packed too because the streaming feature | ||
| # relies on the `INTERNAL_HANDLER_NAME_ALLOWLIST` added on the `strands` branch | ||
| # but not yet published to npm. The sample's `package.json` overrides every | ||
| # transitive `@temporalio/common` with this tarball. | ||
| cd packages/common && pnpm pack && cd ../.. | ||
| ``` | ||
|
|
||
| ### Running | ||
|
|
||
| 1. `npm install` in this directory. | ||
| 1. Configure AWS credentials so the default `BedrockModel` can reach Amazon Bedrock (`AWS_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`). The default model is `global.anthropic.claude-sonnet-4-6`, which requires submitting Anthropic's use-case details form — pick the model in the [Bedrock model catalog](https://console.aws.amazon.com/bedrock/home#/model-catalog) and fill out the form before invoking. To use a different provider — or pin a model that doesn't require the form — swap `BedrockModel` in `src/worker.ts` for `AnthropicModel`, `OpenAIModel`, etc., from `@strands-agents/sdk`, or pass a `modelId` to `BedrockModel`. | ||
| 1. `temporal server start-dev`. | ||
| 1. `npm run start.watch` in one shell to start the worker. | ||
| 1. `npm run workflow:<feature>` in another shell — see the table above. | ||
|
|
||
| ### Tests | ||
|
|
||
| `src/mocha/*.test.ts` exercises each workflow with a `StubModel` and `TestWorkflowEnvironment`, so the suite runs without AWS credentials: | ||
|
|
||
| ```bash | ||
| npm test | ||
| ``` | ||
|
|
||
| > The `mocha@8` version pinned across `samples-typescript` is incompatible with Node 24+ (its bundled `yargs` is loaded as ESM and breaks at startup). Use Node 22 to run tests, matching the rest of `samples-typescript`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| { | ||
| "name": "temporal-strands-agents", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "build": "tsc --build", | ||
| "build.watch": "tsc --build --watch", | ||
| "format": "prettier --write .", | ||
| "format:check": "prettier --check .", | ||
| "lint": "eslint .", | ||
| "start": "tsx src/worker.ts", | ||
| "start.watch": "tsx watch src/worker.ts", | ||
| "workflow:hello-world": "tsx src/hello-world.ts", | ||
| "workflow:tools": "tsx src/tools.ts", | ||
| "workflow:human-in-the-loop": "tsx src/human-in-the-loop.ts", | ||
| "workflow:activity-interrupt": "tsx src/activity-interrupt.ts", | ||
| "workflow:hooks": "tsx src/hooks.ts", | ||
| "workflow:mcp": "tsx src/mcp.ts", | ||
| "workflow:structured-output": "tsx src/structured-output.ts", | ||
| "workflow:streaming": "tsx src/streaming.ts", | ||
| "workflow:continue-as-new": "tsx src/continue-as-new.ts", | ||
| "test": "tsx ./node_modules/mocha/bin/mocha.js --exit \"src/mocha/*.test.ts\"" | ||
| }, | ||
| "dependencies": { | ||
| "@modelcontextprotocol/sdk": "^1.25.2", | ||
| "@strands-agents/sdk": "^1.3.0", | ||
| "@temporalio/activity": "^1.21.1", | ||
| "@temporalio/client": "^1.21.1", | ||
| "@temporalio/common": "^1.21.1", | ||
| "@temporalio/envconfig": "^1.21.1", | ||
| "@temporalio/strands-agents": "^1.21.1", | ||
| "@temporalio/worker": "^1.21.1", | ||
| "@temporalio/workflow": "^1.21.1", | ||
| "@temporalio/workflow-streams": "^1.21.1", | ||
| "zod": "^4.1.12" | ||
| }, | ||
| "devDependencies": { | ||
| "@temporalio/testing": "^1.21.1", | ||
| "@tsconfig/node22": "^22.0.0", | ||
| "@types/mocha": "^10.0.10", | ||
| "@types/node": "^22.9.1", | ||
| "@typescript-eslint/eslint-plugin": "^8.18.0", | ||
| "@typescript-eslint/parser": "^8.18.0", | ||
| "eslint": "^8.57.1", | ||
| "eslint-config-prettier": "^9.1.0", | ||
| "eslint-plugin-deprecation": "^3.0.0", | ||
| "mocha": "^11.7.4", | ||
| "prettier": "^3.4.2", | ||
| "tsx": "^4.20.6", | ||
| "typescript": "^5.6.3" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export { fetchWeather } from './activities/tools'; | ||
| export { deleteThing } from './activities/activity-interrupt'; | ||
| export { persistToolCall } from './activities/hooks'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // @@@SNIPSTART typescript-strands-activity-interrupt-activity | ||
| import { ApplicationFailure } from '@temporalio/common'; | ||
| import { STRANDS_INTERRUPT_TYPE } from '@temporalio/strands-agents'; | ||
|
|
||
| const APPROVED = new Set<string>(); | ||
|
|
||
| export async function deleteThing(input: { name: string }): Promise<string> { | ||
| if (!APPROVED.has(input.name)) { | ||
| // First attempt: mark the name as approved on the way out (simulating the | ||
| // human flipping a flag during the interrupt pause) and stop the agent by | ||
| // raising an interrupt-shaped failure. The plugin's `StrandsFailureConverter` | ||
| // would also recognize a thrown `{ interrupts: [{ toJSON: () => ... }] }`, | ||
| // but throwing `ApplicationFailure` directly avoids any chance of the | ||
| // converter being skipped (and keeps `nonRetryable: true` so the workflow | ||
| // sees the interrupt instead of a retry-then-success). | ||
| APPROVED.add(input.name); | ||
| throw ApplicationFailure.create({ | ||
| message: 'interrupt:approval', | ||
| type: STRANDS_INTERRUPT_TYPE, | ||
| nonRetryable: true, | ||
| details: [ | ||
| { | ||
| id: `delete:${input.name}`, | ||
| name: 'approval', | ||
| reason: `approve delete of protected resource '${input.name}'?`, | ||
| source: 'tool', | ||
| }, | ||
| ], | ||
| }); | ||
| } | ||
| return `deleted ${input.name}`; | ||
| } | ||
| // @@@SNIPEND |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| // @@@SNIPSTART typescript-strands-hooks-activity | ||
| import { log } from '@temporalio/activity'; | ||
|
|
||
| export async function persistToolCall(toolName: string): Promise<void> { | ||
| // In production, write to a database / S3 / your audit pipeline. | ||
| log.info(`audit: tool ${toolName} completed`); | ||
| } | ||
| // @@@SNIPEND |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // @@@SNIPSTART typescript-strands-tools-activity | ||
| export async function fetchWeather(input: { | ||
| city: string; | ||
| }): Promise<{ city: string; temperatureF: number; conditions: string }> { | ||
| return { | ||
| city: input.city, | ||
| temperatureF: 72, | ||
| conditions: 'sunny', | ||
| }; | ||
| } | ||
| // @@@SNIPEND |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { Client, Connection } from '@temporalio/client'; | ||
| import { StrandsPlugin } from '@temporalio/strands-agents'; | ||
| import { activityInterrupt, activityInterruptApproveSignal, activityInterruptPendingApprovalQuery } from './workflows'; | ||
|
|
||
| async function run() { | ||
| const connection = await Connection.connect({ | ||
| address: process.env.TEMPORAL_ADDRESS ?? 'localhost:7233', | ||
| }); | ||
| // The starter goes through the plugin's failure converter so an Interrupt | ||
| // payload deserializes cleanly if it propagates to the client. | ||
| const client = new Client({ connection, plugins: [new StrandsPlugin()] }); | ||
|
|
||
| const handle = await client.workflow.start(activityInterrupt, { | ||
| args: ["Please delete the 'system' user."], | ||
| taskQueue: 'strands-agents', | ||
| workflowId: 'strands-activity-interrupt', | ||
| }); | ||
|
|
||
| let reason: string | null = null; | ||
| while (reason === null) { | ||
| await new Promise((resolve) => setTimeout(resolve, 500)); | ||
| reason = await handle.query(activityInterruptPendingApprovalQuery); | ||
| } | ||
| console.log(`Approval requested: ${reason}`); | ||
|
|
||
| await handle.signal(activityInterruptApproveSignal, 'approve'); | ||
|
|
||
| const result = await handle.result(); | ||
| console.log(`Result: ${result}`); | ||
| } | ||
|
|
||
| run().catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { Client, Connection } from '@temporalio/client'; | ||
| import { chatEnd, chatTurn, chatWorkflow } from './workflows'; | ||
|
|
||
| async function run() { | ||
| const connection = await Connection.connect({ | ||
| address: process.env.TEMPORAL_ADDRESS ?? 'localhost:7233', | ||
| }); | ||
| const client = new Client({ connection }); | ||
|
|
||
| const handle = await client.workflow.start(chatWorkflow, { | ||
| taskQueue: 'strands-agents', | ||
| workflowId: 'strands-continue-as-new', | ||
| }); | ||
|
|
||
| for (const prompt of ['Hi! What is durable execution?', 'Give me a one-sentence summary.']) { | ||
| const reply = await handle.executeUpdate(chatTurn, { args: [prompt] }); | ||
| console.log(`user: ${prompt}`); | ||
| console.log(`assistant: ${reply}\n`); | ||
| } | ||
|
|
||
| await handle.signal(chatEnd); | ||
| await handle.result(); | ||
| } | ||
|
|
||
| run().catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compensating for temporalio/sdk-typescript#2256