Add Strands Agents plugin (contrib)#1539
Open
brianstrauch wants to merge 49 commits into
Open
Conversation
Also bump `[tool.ruff] target-version` from py39 to py310 to match `requires-python`; the old setting caused 0.15 to reject `match` statements in the codebase.
Restructure the README into Quickstart + per-feature sections (Model, Structured Output, Streaming, Tools, MCP), add an experimental warning, installation instructions, and a link to strandsagents.com. Also default `TemporalModel.model_factory` to `BedrockModel`, matching the Strands `Agent` default, so the common case doesn't need a factory lambda.
Adds activity_as_hook(activity_fn, *, extract, **options): wraps a Temporal activity as a Strands HookCallback so I/O-doing hook callbacks (audit logs, metrics) dispatch off the workflow. Co-locates with activity_as_tool in a new _workflow.py.
Patch Agent.__init__ at import time to force retry_strategy=None and raise ValueError when a strategy is supplied, so retries happen at the Temporal activity layer (RetryPolicy on activity options) rather than blocking inside the activity body. Documents the behavior in README.
tconley1428
reviewed
May 18, 2026
tconley1428
reviewed
May 18, 2026
tconley1428
reviewed
May 18, 2026
tconley1428
reviewed
May 18, 2026
tconley1428
reviewed
May 18, 2026
tconley1428
reviewed
May 18, 2026
tconley1428
reviewed
May 18, 2026
Cross-module consumers can't be seen by basedpyright when the function is underscore-prefixed, producing a false unused-function warning. Promote the name and add a package docstring.
`pydantic` and `temporalio.contrib.strands` are already covered by the SDK's default passthrough (via `pydantic` and `temporalio` in `passthrough_modules_with_temporal`). Update the stale comment in _temporal_mcp_client.py that explained the redundant entry.
Replace the singular model= / mcp_clients=[...] plugin args with name-keyed
dicts: StrandsPlugin(models={name: factory}, mcp_clients={name: transport}).
TemporalModel and TemporalMCPClient become pure workflow-side handles that
reference the worker registration by name and carry only per-call activity
options. A single pair of model activities now dispatches to any number of
backing models by resolving model_name from the activity input.
xumaple
requested changes
May 19, 2026
TemporalAgent(Agent) is the primary user-facing class: it takes model="name" to select a factory registered with StrandsPlugin(models=...), accepts the per-call activity options, and forwards all other kwargs to Strands' Agent. Construction-time validation of retry_strategy and overrides of take_snapshot/load_snapshot replace the previous Agent.__init__ and snapshot monkey-patches in StrandsPlugin. TemporalModel is no longer exported; it remains as internal plumbing for TemporalAgent.
…ADME Per the OpenTelemetry plugin's own guidance, plugins register on the client so workers built from that client pick them up automatically. Update the Observability section accordingly, plus minor wording polish in the Models and Structured Output sections.
Force every workflow task to replay from full history so the strands tests double as a continuous determinism check on the plugin and TemporalAgent. All 7 tests pass under the stricter setting. Also trims a redundant paragraph from StrandsPlugin's docstring.
Drop the leading underscore from populate_cache / clear_cache / build_call_tool_activity in _temporal_mcp_client.py — basedpyright flagged them as unused because it doesn't follow cross-module imports for underscore-prefixed names. Add docstrings since pydocstyle now treats them as public. Also pick up a one-line ruff format fix in _model_activity.py.
Install a failure converter on the plugin's data converter that translates strands InterruptException into an ApplicationError carrying the Interrupt payload in details. TemporalActivityTool.stream() catches the matching ApplicationError, reconstructs the Interrupt, and yields ToolInterruptEvent so AgentResult.interrupts is populated just like the in-workflow case. The path requires StrandsPlugin on the client (not just the worker), since _ActivityWorker reads the data converter from client_config. README HITL section is restructured to cover both hook-based and tool-body surfaces, with a note on the client-attachment requirement. New test_interrupt_exception.py exercises both surfaces end-to-end with signal-driven resume.
…edrockModel Forward agent invocation_state across the model activity boundary so the worker-side model receives it via model.stream(invocation_state=...). Entries that aren't JSON-serializable are dropped before dispatch with a debug log naming the dropped keys. Make model selection optional. StrandsPlugin() with no args registers a single BedrockModel() factory under the name "bedrock" (matching Strands' own implicit default in agent.py:221), and TemporalAgent() with no model resolves to the sole registered factory at activity time. Multi-model setups continue to require an explicit model= on TemporalAgent. README quickstart shrinks accordingly: no BedrockModel import, no models= argument on StrandsPlugin, no model= on TemporalAgent. Model= remains in the multi-model example where it's load-bearing.
…ault Drop the single-entry guess for TemporalAgent(model=None). Implicit resolution is now valid only when StrandsPlugin auto-registers its own BedrockModel default; any user-supplied models= forces every TemporalAgent to pass model= explicitly. Track the gate via a default_name field on ModelActivity that the plugin sets only on the auto-registered path.
Extend StrandsFailureConverter.to_failure to translate Strands' terminal model/session exceptions into ApplicationError(non_retryable=True, type=...): MaxTokensReachedException, ContextWindowOverflowException, StructuredOutputException, SessionException. These deterministic failures won't succeed on retry, so the typed annotation stops Temporal's retry policy from churning on them. ModelThrottledException stays retryable.
tconley1428
reviewed
May 19, 2026
strands-agents 1.39.0 removed _get_encoding and routes count_tokens straight to the chars-per-token heuristic, so the patch is a no-op. Bump the floor pin to 1.39.0 to keep that assumption true.
Also refresh stale Agent(...) references in _temporal_mcp_client and _temporal_model docstrings to point at TemporalAgent(...).
# Conflicts: # pyproject.toml
`warnings-as-errors = true` was failing CI's gen-docs step on invalid RST inline literals and unresolvable cross-references to the optional strands package.
* test_type_errors.py: open test files with encoding="utf-8" so the rglob scan doesn't choke on UTF-8 characters (e.g. the strawberry emoji in test_tool.py) when the host's default codec is cp1252. * test_tool.py: skip the module on Windows; strands_tools.shell pulls in pty -> tty -> termios at import time, which is Unix-only.
strands_tools.shell imports pty/tty/termios at module load, which is Unix-only and broke Windows test collection. file_read on a tmp_path fixture is also non-deterministic (depends on filesystem state), has no in-workflow equivalent, and imports cleanly on every platform.
tconley1428
approved these changes
May 22, 2026
# Conflicts: # uv.lock
Removes the temporalio/contrib/common shared module by duplicating _heartbeat_decorator.py into each plugin and updating the two import sites.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
temporalio.contrib.strandsplugin (experimental) that runs Strands Agents inside Temporal Workflows, routing model invocations, tool calls, and MCP tool calls through activities for durable execution and Temporal-managed retries/timeouts.TemporalModel,TemporalMCPClient,StrandsPlugin, andworkflow.activity_as_tool/workflow.activity_as_hookhelpers, with sandbox/import tweaks for Strands compatibility.Test plan
uv run pytest tests/contrib/strands -vuv run pytest(full suite)