Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
179 commits
Select commit Hold shift + click to select a range
2efc738
Shared one time-skipping unlock between concurrent result waiters.
moetemp Sep 29, 2026
370bfa9
Waited for the target workflow to close before the update that must f…
moetemp Sep 29, 2026
1cff589
Repinned Core to upstream main and brought the bridge up to it.
moetemp Oct 3, 2026
89871a2
Pinned the channel Core and regenerated the channel protos.
moetemp Oct 3, 2026
02d494d
Added Execution and ExecutionType to temporalio.common.
moetemp Oct 3, 2026
f8bf154
Added the notification channel calls to the client.
moetemp Oct 3, 2026
7838fa1
Covered the client channel calls.
moetemp Oct 3, 2026
34cf5f4
Added channel subscriptions to the workflow runtime.
moetemp Oct 3, 2026
f04fe11
Listed a run's channel subscriptions on its description.
moetemp Oct 3, 2026
ef6ca30
Covered the workflow channel surface.
moetemp Oct 3, 2026
8607599
Pinned the Core that vendors the stream record envelope.
moetemp Oct 3, 2026
798c1cb
Added the temporalio.streams interface types.
moetemp Oct 3, 2026
b558185
Carried a stream provider on client, worker and replayer config.
moetemp Oct 3, 2026
3dd3479
Covered the stream interface types.
moetemp Oct 3, 2026
bc77a84
Added MemoryStreams, the in-memory reference provider.
moetemp Oct 3, 2026
a6af595
Added the provider conformance suite and ran it on memory.
moetemp Oct 3, 2026
fdd81d3
Added workflow.stream_reader and workflow.stream_writer.
moetemp Oct 3, 2026
732e0d2
Ran the stream provider's lifecycle hooks around the workflow.
moetemp Oct 3, 2026
7bc1c11
Covered the workflow stream runtime.
moetemp Oct 3, 2026
d220153
Added the stream accessors for activities and clients.
moetemp Oct 3, 2026
d917032
Covered the stream accessors.
moetemp Oct 3, 2026
67efbda
Added stream_channel to name the channel a stream notifies.
moetemp Oct 3, 2026
5460ca2
Added a changelog entry for the stream interface.
moetemp Oct 3, 2026
c4ca2fd
Added a streams demo that runs the agent loop on a provider.
moetemp Oct 3, 2026
536c905
Opened hooks in contrib.workflow_streams for a stream provider.
moetemp Oct 3, 2026
eb5ebfb
Added the Workflow Streams stream provider.
moetemp Oct 3, 2026
99a55aa
Ran the conformance suite on the Workflow Streams provider.
moetemp Oct 3, 2026
9193dd7
Added the Temporal Streams Nexus contract and its generated bindings.
moetemp Oct 3, 2026
b8a7a3c
Added the Nexus front for stream providers.
moetemp Oct 3, 2026
43629ca
Covered the Nexus front.
moetemp Oct 3, 2026
19cbdd7
Added a Nexus operation that consumes a stream through its channel.
moetemp Oct 3, 2026
1646132
Added a standalone aiohttp host for the stream consumer operation.
moetemp Oct 3, 2026
97afa38
Covered the Nexus stream consumer operation.
moetemp Oct 3, 2026
3f93403
Warned when a producer's attempt goes backwards on a read.
moetemp Oct 3, 2026
bbc5b45
Refused a stream publish from a query handler at the call.
moetemp Oct 3, 2026
898c3af
Pinned the stream Core and regenerated the stream api protos.
moetemp Oct 3, 2026
ce19b35
Vendored the server's stream service protos.
moetemp Oct 3, 2026
6f2f9ad
Added a client for the server-side stream service.
moetemp Oct 3, 2026
5220369
Covered the stream service client.
moetemp Oct 3, 2026
ba5a02a
Shared, retried and codec-aware connections in the stream client.
moetemp Oct 3, 2026
2fa3b3c
Added the native stream commands to the workflow runtime.
moetemp Oct 3, 2026
6643bae
Added contrib.server_streams over a native stream.
moetemp Oct 3, 2026
414e3bf
Added the native stream provider.
moetemp Oct 3, 2026
c6a50b8
Replayed workflows that read native streams.
moetemp Oct 3, 2026
bedc0e9
Covered replay of native stream reads.
moetemp Oct 3, 2026
93f03ea
Added a changelog entry for server-side streams.
moetemp Oct 3, 2026
27385ea
Removed a demo run's output from the streams demo.
moetemp Oct 3, 2026
f631aed
Merged moe/AI-198-if-py-6-demo into moe/AI-198-if-py-7-workflow-strea…
moetemp Oct 3, 2026
f6ab743
Merged moe/AI-198-if-py-7-workflow-streams-provider into moe/AI-198-i…
moetemp Oct 3, 2026
8f9dafe
Merged moe/AI-198-if-py-8-nexus-front into moe/AI-198-if-py-9-nexus-c…
moetemp Oct 3, 2026
79dc5f6
Merged moe/AI-198-if-py-11-query-publish-refusal into moe/AI-198-st-p…
moetemp Oct 3, 2026
548d574
Merged moe/AI-198-st-py-1-core-pin into moe/AI-198-st-py-2-native-wire.
moetemp Oct 3, 2026
432b7bd
Merged moe/AI-198-if-py-9-nexus-consumer into moe/AI-198-if-py-10-bac…
moetemp Oct 3, 2026
000bd20
Merged moe/AI-198-st-py-2-native-wire into moe/AI-198-st-py-3-native-…
moetemp Oct 3, 2026
a6c758a
Merged moe/AI-198-if-py-10-backwards-attempt-warning into moe/AI-198-…
moetemp Oct 3, 2026
bbb5159
Merged moe/AI-198-st-py-3-native-provider into moe/AI-198-st-py-4-nat…
moetemp Oct 3, 2026
e6898c5
Pinned Core at the external runtime's channel wake.
moetemp Oct 5, 2026
bbb4aaf
Listed the external-stream fixes the Core pin brings in.
moetemp Oct 5, 2026
2c40524
Move sdk-core submodule to 6e90e6d5 and fix bridge build
mfateev Aug 14, 2026
d8dbfdd
External streams: generated protos, record model, and the Redis fixture
mfateev Aug 15, 2026
bcc67e6
External streams: backend contract, annotation codec, codec, failure …
mfateev Aug 15, 2026
a6b44bc
External streams: parking contract, Redis provider, registry, produce…
mfateev Aug 15, 2026
1d4b22e
External streams: subscription manager and the Workflow-facing API
mfateev Aug 15, 2026
0615ea9
External streams: wire the feature into the Python Worker
mfateev Aug 16, 2026
aadf012
P13: the replay read path
mfateev Aug 16, 2026
ea590b0
P14: the producer wake-signal path
mfateev Aug 16, 2026
301af4d
P6b: publish()'s acknowledged-wake semantics
mfateev Aug 16, 2026
a6507e9
P15: the Continue-As-New cursor
mfateev Aug 16, 2026
4d0aea7
P21: multiple streams, merge, and same-stream subscriptions
mfateev Aug 16, 2026
8c12e9f
P20: the Worker shutdown wake sweep
mfateev Aug 16, 2026
bd8840b
P16b: the Milestone 2 required-test list
mfateev Aug 16, 2026
5fdab62
Fix the failure-taxonomy row for an annotation mismatch, and P20's mi…
mfateev Aug 16, 2026
d0bad85
P16a/P16b: make the milestone gates enforceable
mfateev Aug 16, 2026
7e0bf4a
Core bump for C8/C10, and three more Milestone 1 cases
mfateev Aug 16, 2026
e0b7957
Replay a stream history through the real Replayer
mfateev Aug 16, 2026
892e549
Deliver a record buffered while Workflow code was elsewhere
mfateev Aug 16, 2026
4ad9809
Core bump for C15b, and correct a replay finding made against a stale…
mfateev Aug 16, 2026
9f53650
The livelock does not exist: fix the test helper that caused it
mfateev Aug 17, 2026
74e5fc3
Bound delivery within one activation (ADR-026)
mfateev Aug 17, 2026
72d467d
Fix four defects the required-test list exposed
mfateev Aug 17, 2026
f87e790
Make an unparked wake's sender identity unique per sender
mfateev Aug 17, 2026
3ee3d05
Rollover cases: one test defect fixed, two reasons corrected
mfateev Aug 17, 2026
3c79510
Rollover cases 19 and 23: both were test defects
mfateev Aug 17, 2026
8ab01be
Milestone 1 gate: 53 of 55, and two honest gaps
mfateev Aug 17, 2026
3f68a8a
Case 29: reach the window it names, and state what actually blocks it
mfateev Aug 17, 2026
987c9f7
Fix four more defects the handoff case exposed, and take the gate to …
mfateev Aug 18, 2026
e878e21
Close the deadlock: report the quiescent snapshot even when commands …
mfateev Aug 18, 2026
01fb3fd
Close case 36: an empty stream parks, is evicted, and replays from it…
mfateev Aug 18, 2026
890af9d
Read the required-test lists from their own directory
mfateev Aug 18, 2026
0a157b5
Bind each wait to its own backend, and stop reporting unsent wakes as…
mfateev Aug 18, 2026
7458e1e
Make readiness reporting total, and stop dropping a stale answer
mfateev Aug 18, 2026
08b78f1
Make the Redis key layout injective, and stop a stream name widening …
mfateev Aug 18, 2026
8be6ec4
Make merge fair, and stop consuming a record before it decodes
mfateev Aug 18, 2026
1358c17
Reconcile an inherited park intent, and make parking match Core's wai…
mfateev Aug 18, 2026
f7c65f7
Bind a late subscription, and replay a segment in its recorded order
mfateev Aug 18, 2026
1c7ee60
Require a provider's key derivation to be injective
mfateev Aug 18, 2026
ae8a561
Decode off the Workflow thread, and connect the failure taxonomy
mfateev Aug 18, 2026
0dd5ba9
Ask the converter's public members, not a predicate marked for removal
mfateev Aug 18, 2026
34abed5
Finish close(): stop the watcher and take back the park intent
mfateev Aug 19, 2026
e37a63c
Guard the close() teardown, none of which was tested
mfateev Aug 19, 2026
99729cc
Close the seven follow-up findings, which were all failure paths
mfateev Aug 19, 2026
02eb1ed
Close the third review's six findings, and the seven the fixes introd…
mfateev Aug 19, 2026
90002e2
Settle the Run before asking the status probe to be repeatable
mfateev Aug 19, 2026
668c0c2
Re-announce buffered records on every completion, not only a budget stop
mfateev Aug 19, 2026
2620ee1
Close the fourth review's five findings, and the one its own fix left…
mfateev Aug 19, 2026
952ec2a
Bind the unknown-append recovery to its operation, stream and producer
mfateev Aug 19, 2026
a0ebb24
Keep one canonical operation across repeated unknown-append recoveries
mfateev Aug 20, 2026
a6e7ae2
Close out the agent handoff notes, and the one defect they left open
mfateev Aug 20, 2026
2de43e9
Close the fifth review's four findings
mfateev Aug 20, 2026
7d2f91a
Close the fifth review's remaining findings, including one in its own…
mfateev Aug 21, 2026
81b518d
Let the continuation writer be staged behind its reader
mfateev Aug 21, 2026
b4aaf04
Name the writer stage, and run a chain through it
mfateev Aug 21, 2026
f7fa7c7
Let a merged wait end for every member, not only its winner
mfateev Aug 21, 2026
381e177
State the merged wait's contract where it belongs, and cover its claim
mfateev Aug 21, 2026
bae10ec
Validate recorded stream state against History, not against configura…
mfateev Aug 22, 2026
60f451c
Draw the unparked wake counter from the sender, not from the Run
mfateev Aug 22, 2026
6c71094
Retry an owed park removal without waiting for another event
mfateev Aug 22, 2026
f4a0425
Close six findings in the owed-removal retry, one in its own fix
mfateev Aug 22, 2026
d66f29a
Carry the wake a retired park intent owes past its subscription
mfateev Aug 23, 2026
94a7a23
Stop three streaming tests failing on things they do not test
mfateev Aug 24, 2026
5897602
Coalesce external stream wake cycles
mfateev Aug 24, 2026
e1dba93
Correlate wake retries with task attempts
mfateev Aug 24, 2026
7f063c5
Stabilize sandboxed handoff fixtures
mfateev Aug 24, 2026
9dac2cc
Await streaming worker test shutdown
mfateev Aug 24, 2026
b1cc1a5
Keep pytest rewriting out of workflow sandboxes
mfateev Aug 24, 2026
afd9730
Format streaming issue fixes
mfateev Aug 24, 2026
776bed2
Document and type-check streaming support
mfateev Aug 24, 2026
e68425b
Point streaming docs at current design
mfateev Aug 25, 2026
d00168e
Use a single external stream backend
mfateev Aug 26, 2026
12f35b7
Expose external workflow stream APIs
mfateev Aug 26, 2026
5ed2941
Implement workflow-originated external streams
mfateev Aug 27, 2026
6857a4a
Update Core streaming documentation
mfateev Aug 27, 2026
3e36f0b
Moved the External Workflow Streams changelog entries to Unreleased.
moetemp Oct 5, 2026
b7f096e
Held the server-backed stream cases off the skipping clock.
moetemp Oct 3, 2026
eb27fe7
Made the external stream docstrings build under pydoctor.
moetemp Oct 3, 2026
bead929
Aligned the external stream input and output replay schedules.
moetemp Oct 3, 2026
e3c49e9
Kept a replay marker from earning a drain of its own.
moetemp Oct 3, 2026
3769016
Allowed external-stream publishes from the workflow constructor.
moetemp Oct 3, 2026
56ff0b8
Covered a publish from the workflow constructor in the conformance su…
moetemp Oct 5, 2026
aee1fe0
Answered a legacy query without the stream snapshot beside it.
moetemp Oct 3, 2026
c94d993
Pointed the legacy query id at Core's own constant.
moetemp Oct 3, 2026
2c83ee6
Woke parked external readers through the stream's channel.
moetemp Oct 3, 2026
69c9c20
Covered the external runtime's channel wake.
moetemp Oct 3, 2026
5ddbdba
Covered the channel wake in the conformance suite.
moetemp Oct 5, 2026
933d1e1
Let an external stream subscription name its start and yield offsets.
moetemp Oct 3, 2026
f857d24
Added the Redis provider over External Workflow Streams.
moetemp Oct 3, 2026
06a1ffc
Covered the Redis provider.
moetemp Oct 3, 2026
9cbac11
Said which provider carries a reader across continue-as-new.
moetemp Oct 3, 2026
398e932
Described the Redis provider's one log per topic in the changelog.
moetemp Oct 3, 2026
4f0065c
Trimmed the Redis streams by retention on every append.
moetemp Oct 3, 2026
8022baf
Covered the Redis retention trims.
moetemp Oct 3, 2026
86af499
Held an activity's own streams on the Redis provider.
moetemp Oct 3, 2026
544c021
Covered activity-owned streams on the Redis provider.
moetemp Oct 3, 2026
121eaed
Let an external stream subscription start at the tail or the newest N.
moetemp Oct 3, 2026
88718cb
Let a Redis workflow reader start at the tail or at the newest N reco…
moetemp Oct 3, 2026
1533839
Covered the Redis workflow reader's tail starts.
moetemp Oct 3, 2026
850526c
Bumped the Core pin to the suite-reconciled head.
moetemp Oct 5, 2026
f19765f
Merged moe/AI-198-o7-py-1-core-pin into moe/AI-198-o7-py-2-import.
moetemp Oct 5, 2026
d9a66ec
Merged moe/AI-198-o7-py-2-import into moe/AI-198-o7-py-3-tooling.
moetemp Oct 5, 2026
ee823cc
Merged moe/AI-198-o7-py-3-tooling into moe/AI-198-o7-py-4-replay-sche…
moetemp Oct 5, 2026
1c7bace
Merged moe/AI-198-o7-py-4-replay-schedule into moe/AI-198-o7-py-5-con…
moetemp Oct 5, 2026
7787e55
Merged moe/AI-198-o7-py-5-constructor-publish into moe/AI-198-o7-py-6…
moetemp Oct 5, 2026
ddda20c
Merged moe/AI-198-o7-py-6-parked-run-query into moe/AI-198-o7-py-7-ch…
moetemp Oct 5, 2026
2ad4207
Merged moe/AI-198-o7-py-7-channel-wake into moe/AI-198-o7-py-8-redis-…
moetemp Oct 5, 2026
3978468
Merged moe/AI-198-o7-py-8-redis-provider into moe/AI-198-o7-py-9-redi…
moetemp Oct 5, 2026
52203df
Merged moe/AI-198-o7-py-9-redis-trimming into moe/AI-198-o7-py-10-red…
moetemp Oct 5, 2026
ec87862
Merged moe/AI-198-o7-py-10-redis-activity-owners into moe/AI-198-o7-p…
moetemp Oct 5, 2026
be39a8c
Hosted standalone streams on the Redis provider.
moetemp Oct 3, 2026
4f3ff45
Covered standalone streams on the Redis provider.
moetemp Oct 3, 2026
2b9be04
Aborted a dead output stage when the Worker evicts its run.
moetemp Oct 3, 2026
c44bfde
Covered the dead stage abort on the Redis provider.
moetemp Oct 3, 2026
29aaa94
Merged the native main chain into the union.
moetemp Oct 5, 2026
c40987e
Merged the external chain into the union.
moetemp Oct 5, 2026
5d72e71
Merged the time-skipping unlock fix into the union.
moetemp Oct 5, 2026
02d4ecd
Skipped the native conformance setup without a server address.
moetemp Oct 3, 2026
477b78f
Ran the native provider cases in the union.
moetemp Oct 3, 2026
268215b
Consumed a Redis stream through the channel its producer notifies.
moetemp Oct 3, 2026
29cfd44
Ran the demo on each of the union's four providers.
moetemp Oct 3, 2026
9128bb0
Fixed the visitor generator's repeated-field check.
moetemp Oct 3, 2026
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
50 changes: 50 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,56 @@ jobs:
npx doctoc README.md
[[ -z $(git status --porcelain README.md) ]] || (git diff README.md; echo "README changed"; exit 1)

# The client-side (Redis) stream provider's own evidence. Its tests need a store
# this repo does not otherwise stand up, so without this job nothing that proves
# retention, cursor ownership, the staged commit or the paired producer write ever
# runs anywhere but a developer's machine.
streams-redis:
timeout-minutes: 30
runs-on: ubuntu-latest
services:
redis:
image: redis:8-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.13"
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: temporalio/bridge -> target
key: streams-redis-${{ env.pythonLocation }}
- uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3
with:
version: "23.x"
repo-token: ${{ secrets.GITHUB_TOKEN }}
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- run: uv tool install poethepoet
- run: uv sync --all-extras
- run: poe build-develop
# The dev server comes from the test environment, the store from the service
# above. Run serially: the cases measure real timing and share one Redis.
- run: uv run pytest tests/streams -p no:randomly -s
timeout-minutes: 20
env:
STREAMS_LIVE: redis
TEMPORAL_TEST_REDIS_URL: redis://127.0.0.1:6379
AI198_REDIS_URL: redis://127.0.0.1:6379
# Also without the store, so the gate itself keeps working and the memory
# provider's conformance run stays honest.
- run: uv run pytest tests/streams -p no:randomly -s
timeout-minutes: 10

# Verify the optional FIPS build: the Rust core must link aws-lc-fips-sys
# (aws-lc-rs FIPS mode) and must NOT link `ring` (the cargo-tree guard, ported
# from sdk-ruby PR #466's `fips_tree` guard); then run the test suite against the
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ temporalio/bridge/temporal_sdk_bridge*
tags
/.claude
tmpclaude-*

# Demo run output, written per provider; the numbers live in the doc.
streams_demo/results-*/
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "sdk-core"]
path = temporalio/bridge/sdk-core
url = https://github.com/temporalio/sdk-rust.git
url = https://github.com/moedash/sdk-rust.git
109 changes: 109 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,99 @@ to include examples, links to docs, or any other relevant information.
worker-side factories registered with `StrandsPlugin(sandboxes=...)`.

- Added the `temporalio.contrib.gcp.cloud_run.id` module with the `CloudRunIdPlugin` client plugin to set the worker identity on Cloud Run.
- **Experimental**: notification channels. Requires a server that serves notification channels.
- `Client.notify_channel`, `poll_channel`, `describe_channel`, `register_channel_listener` and
`unregister_channel_listener` reach a named channel on the server. The channel is either
independent or linked to an execution, which `execution=` (`temporalio.common.Execution`) or
the `workflow_id=` shorthand names.
- A workflow subscribes with `workflow.subscribe_channel(name)`, reads a channel linked to it with
`workflow.linked_channel(name)` and ends a subscription with `unsubscribe()`. Notifications
arrive with the workflow's tasks. `WorkflowExecutionDescription.channel_subscriptions` lists
the channels a run listens on.
- **Experimental**: `temporalio.streams` defines one stream interface a workflow
can read, decide on, and write. A provider is registered once as a plugin,
`Client.connect(plugins=[provider])`, and workers built from that client
inherit it; each context then asks for its stream the same way:
`workflow.stream_reader()` and `workflow.stream_writer()` in workflow code,
`activity.stream_handle()` in an activity, and `client.get_stream_handle()`
anywhere a client is held. A topic is a typed definition,
`streams.topic("inputs", Token)`, shared by workflow, activity and client
code; a plain string names a topic decided at runtime, and a call that names
no topic addresses the default topic, `streams.DEFAULT_TOPIC` (`"output"`,
the server's default stream name). The record on the wire
is `temporal.api.stream.v1.StreamRecord` on every provider. A stream is
handed to another process as a `streams.StreamRef`, plain data naming the
owner and the topic, which `client.get_stream_handle(ref)` and
`activity.stream_handle(ref)` open; `client.create_stream(stream_id, ...)`
creates a standalone stream with a retention policy, and its handle's
`close()` seals it. A provider runs record bodies through the client's data
converter, so a payload codec and external storage apply to them.
`temporalio.streams.providers.memory.MemoryStreams` is the in-memory
reference provider the conformance tests run against, and
`temporalio.streams.providers.redis.RedisStreams` serves the same interface
over External Workflow Streams, with one Redis log per topic that the workflow
and outside readers share.
- **Experimental**: server-side streams. A workflow publishes to a stream it
owns with a command the server applies in its Workflow Task's commit, and
reads the ranges the server delivers on its Workflow Tasks, through
`temporalio.workflow.append_stream_records`, `subscribe_stream` and
`read_stream_records`. `temporalio.client_stream` and
`temporalio.contrib.server_streams` reach the same stream from outside a
workflow, and `temporalio.streams.providers.native.NativeStreams` puts it
behind the shared stream interface with one owned stream per topic. Requires
a server that serves the stream service. `Replayer(stream_client=)` replays a
workflow that read such a stream while the server still holds it: History
records only the offsets each task consumed, so the replayer fetches the
records from the stream service and hands them to the replay with the
history. A range the stream no longer holds fails the replay with
`StreamNotFoundError`. A handle without a run id follows a workflow reset as
it follows a continue-as-new, reading the reset run from the floor its stream
reports, and the replayer fetches the ranges recorded before a reset point
from the run the workflow was reset from. For offline replay,
`Replayer.fetch_stream_slices(client, history)` attaches the records to a
`WorkflowHistory` while the stream is retained, `to_json()` and `from_json()`
carry them as `streamSlices` beside the events, and a history that carries
them replays with no server.
- **Experimental**: `temporalio.streams.providers.workflow_streams.WorkflowStreamsProvider`
serves the stream interface over the shipped Workflow Streams transport as a
worker plugin, so a workflow reads and publishes through
`temporalio.contrib.workflow_streams` without naming it. Records are the
`StreamRecord` proto inside the shipped item payload, and a handle without a
run id follows continue-as-new run by run and a reset into the run reset to.
An outside publish is an Update that answers with the batch's position and
refuses a conflicting repeat, falling back to the shipped Signal on a
workflow whose worker predates it. A workflow's activity keeps its own
streams in the workflow's log under `activity/<id>/<name>`.
- **Experimental**: `temporalio.streams.providers.nexus.NexusStreams` puts one
Nexus endpoint in front of a storage provider, so a caller reaches a stream
through the endpoint and never names the store, and
`TemporalStreamsHandler` serves that endpoint by fronting the provider's own
handles. Its contract is defined in `temporal_streams.nexusrpc.yaml` and the
bindings are generated from it; a record crosses as the serialized
`StreamRecord` proto, and both operations address a stream by a `StreamRef`
naming its owner (a workflow, an activity or a standalone stream) and topic,
which the handler maps onto the store's accessor for that owner. Configure
the front with `data_converter=` to run a payload codec on the caller side,
so records are encoded before they leave the process.
- **Experimental**: `temporalio.streams.providers.nexus.stream_consumer_operation` builds an
asynchronous Nexus operation that consumes a stream. It registers a callback listener on the
stream's notification channel, reads on each delivery and completes when the stream closes.
`temporalio.streams.providers.nexus_consumer_service` hosts it on its own and needs the
`streams-nexus` extra.
- `ExternalStreamSubscription.records()` yields each value with the provider
offset it was read from, for a reader that has to name where it got to.
- Added experimental External Workflow Streams in
`temporalio.contrib.external_workflow_streams`. Workflow stream payloads are
stored in a configured external backend instead of Temporal History, with a
Redis Streams provider included. Workflows subscribe with `external_stream`,
external processes publish with `ExternalStreamProducer`, and Workers are
configured with `external_stream_backend`.
- Added the output direction for External Workflow Streams.
Workflows publish with `external_output_stream`, Activities and external
processes use `ExternalOutputStreamProducer`, and external consumers resume
through `ExternalOutputStreamClient`. Workflow output is staged outside
History and becomes readable only after its compact Workflow Task marker is
committed.

### Changed

Expand All @@ -52,6 +145,22 @@ to include examples, links to docs, or any other relevant information.

### Fixed

- Preserve empty activations in the shared External Workflow Streams input and
output replay schedule. Workflows that read input, publish decisions, and
schedule Activities now reproduce that schedule during replay. Inconsistent
prerelease markers are rejected explicitly rather than guessing where omitted
activations belonged.
- Resume external input waits when cold replay encounters a wake in an already
loaded History page, including Workers with workflow caching disabled.
- Avoid an unnecessary output replacement Workflow Task after stream input has
resumed the Workflow and it is waiting on an Activity or timer.
- Keep an incomplete retained external stream task alive when workflow caching
is disabled; evict it after its normal task boundary instead of repeatedly
interrupting input readiness with shutdown markers.
- `WorkflowEnvironment.start_time_skipping()`: concurrent `WorkflowHandle.result()` waiters now
share one time-skipping unlock. The test server holds one lock per in-flight task, so a second
unlock let the clock jump while another workflow still had a task in flight, and that task then
timed out.
- `GoogleAdkPlugin` now passes the optional `anthropic`, `litellm`, and `openai` SDKs through
the workflow sandbox.
- `contrib.deepagents`: prevent duplicate input messages after continue-as-new.
Expand Down
15 changes: 13 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ cloud-run-worker-otel = [
aioboto3 = ["aioboto3>=10.4.0", "types-aioboto3[s3]>=10.4.0"]
google-genai = ["google-genai>=2.21.0,<3.0.0"]
strands-agents = ["strands-agents>=1.51.0"]
streams-nexus = ["aiohttp>=3.9,<4"]

[project.urls]
Homepage = "https://github.com/temporalio/sdk-python"
Expand Down Expand Up @@ -95,6 +96,7 @@ dev = [
"pytest-rerunfailures>=16.1",
"pytest-xdist>=3.6,<4",
"moto[s3,server]>=5",
"redis>=5,<9",
"langgraph>=1.1.0",
"langsmith>=0.7.34,<0.9",
"deepagents>=0.6.12,<0.7; python_version >= '3.11'",
Expand Down Expand Up @@ -125,16 +127,19 @@ format = [
]
gen-docs = "uv run scripts/gen_docs.py"
gen-nexus-system-api = "uv run scripts/gen_nexus_system_api.py"
gen-streams-nexus-api = "uv run scripts/gen_streams_nexus_api.py"
gen-protos = [
{ cmd = "uv run scripts/gen_protos.py" },
{ ref = "gen-nexus-system-api" },
{ ref = "gen-streams-nexus-api" },
{ cmd = "uv run scripts/gen_payload_visitor.py" },
{ cmd = "uv run scripts/gen_bridge_client.py" },
{ ref = "format" },
]
gen-protos-docker = [
{ cmd = "uv run scripts/gen_protos_docker.py" },
{ ref = "gen-nexus-system-api" },
{ ref = "gen-streams-nexus-api" },
{ cmd = "uv run scripts/gen_payload_visitor.py" },
{ cmd = "uv run scripts/gen_bridge_client.py" },
{ ref = "format" },
Expand Down Expand Up @@ -198,16 +203,21 @@ exclude = [
'temporalio/api',
'temporalio/bridge/proto',
'temporalio/nexus/system/workflow_service',
'temporalio/streams/providers/_nexus_generated',
]

[[tool.mypy.overrides]]
module = "temporalio.nexus.system.workflow_service.*"
ignore_errors = true

[[tool.mypy.overrides]]
module = "temporalio.streams.providers._nexus_generated.*"
ignore_errors = true

[tool.pydocstyle]
convention = "google"
# https://github.com/PyCQA/pydocstyle/issues/363#issuecomment-625563088
match_dir = "^(?!(docs|scripts|tests|api|proto|system|\\.)).*"
match_dir = "^(?!(docs|scripts|tests|api|proto|system|_nexus_generated|\\.)).*"
add_ignore = [
# We like to wrap at a certain number of chars, even long summary sentences.
# https://github.com/PyCQA/pydocstyle/issues/184
Expand Down Expand Up @@ -241,6 +251,7 @@ privacy = [
"HIDDEN:temporalio.worker.workflow_sandbox.importer",
"HIDDEN:temporalio.worker.workflow_sandbox.in_sandbox",
"HIDDEN:**.*_pb2*",
"HIDDEN:temporalio.streams.providers._nexus_generated._definitions",
]
project-name = "Temporal Python"
sidebar-expand-depth = 2
Expand All @@ -267,7 +278,7 @@ reportUnnecessaryIsInstance = "none"
reportUnnecessaryTypeIgnoreComment = "none"
reportUnusedCallResult = "none"
reportUnknownLambdaType = "none"
include = ["temporalio", "tests"]
include = ["temporalio", "tests", "streams_demo"]
exclude = [
# Exclude auto generated files
"temporalio/api",
Expand Down
11 changes: 4 additions & 7 deletions scripts/gen_payload_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,10 @@ def name_for(desc: Descriptor) -> str:


def field_is_repeated(field: FieldDescriptor) -> bool:
return bool(
getattr(
field,
"is_repeated",
getattr(field, "label") == FieldDescriptor.LABEL_REPEATED,
)
)
is_repeated = getattr(field, "is_repeated", None)
if is_repeated is not None:
return bool(is_repeated)
return getattr(field, "label") == FieldDescriptor.LABEL_REPEATED


def emit_loop(
Expand Down
Loading
Loading