Skip to content

Added the stream examples, one per path and one composed. - #89

Closed
moetemp wants to merge 1293 commits into
moe/AI-198-un-pyfrom
moe/AI-198-streams-examples
Closed

moetemp wants to merge 1293 commits into
moe/AI-198-un-pyfrom
moe/AI-198-streams-examples

Conversation

@moetemp

@moetemp moetemp commented Oct 5, 2026

Copy link
Copy Markdown
Owner

This PR adds the stream examples, one per path, one composed agent, and the June scenarios.

What changed?

  • examples/streams/path_a_publish.py: a workflow publishes progress and a backend follows it from latest().
  • path_b_produce.py: an Activity appends to its workflow's topic, a backend produces and consumes, and the consumer handles a supersession when the first attempt fails.
  • path_c_consume.py: a workflow consumes a topic fed from outside and runs an Activity per record with the cache off.
  • agent.py and run.py compose the three on every provider and behind the Nexus front. The agent races its reader against the generator, so an exhausted generator fails the run.
  • examples/streams/june_scenarios/ has one demo per scenario in Roey's June notes. Each file opens with the notes' heading, a status and why. run.py runs them all on one provider.
  • s2 runs the three standalone alternatives on the handle, alt 3 with a StreamRef passed into the workflow start. s8 (b) returns a StreamRef from a Nexus operation.
  • README.md maps each path to its call. _setup.py is the one place a store is named, and every example defines its topics once with streams.topic().
  • Outside examples/: a changelog line, examples under the type checker in pyproject.toml, and the Nexus endpoint setup in streams_demo/README.md.

Part of AI-198 (epic AI-37).

Why?

It puts the portability claim in code a reviewer can run, with the same accessor in every context. The June suite answers Roey's scenarios one by one. It says plainly which are implemented, which are emulated and which are unsupported by design. It sits on the union, since it needs every provider at once.

How did you test it?

Link to a test plan if any -

  • Unit Tests
  • Staging
  • End to End Tests

poe lint passes with examples/ under the type checker. Against a local server built from the stream server PRs, the three path examples and run.py exit 0 on workflow_streams, native and redis, and run.py nexus exits 0 through an endpoint on that server. The June suite exits 0 on native, workflow_streams, memory and redis. A provider that can't serve a scenario says so: s2 and s5 (b) and (c) on workflow_streams, s1 (d) off memory, and s7 on memory. The worker and consumer that die in s6 and s8 do so on purpose.

moetemp added 30 commits October 1, 2026 18:17
…the clients.

Core moves to the protos-only branch commit that carries the notification channel: the Notification message, the five channel calls, the subscribe command and event, the notifications on the scheduled Workflow Task event, and the bridge's command and activation job for them. The vendored tree, the payload visitor and the bridge client regenerate from the pin.
A workflow subscribes to a notification channel once per run and gets the notifications the server folded for it with each Workflow Task. They travel in History, so a replay sees the same ones at the same points.
Notify, poll, describe, register and unregister go through the outbound interceptor with one input each. Metadata values are encoded with the data converter on the way out and come back as payloads, the shape workflow code sees.
A describe that races the subscribe is answered not found, and a worker whose Core refuses the subscribe command leaves the task in a timeout loop that the worker's shutdown waits on. The case now tolerates the first, names the second and bounds the shutdown.
…mand.

The protos-only Core refuses the subscribe command, so the case is gated on a conftest constant the native layers flip with their pin. A client-only case covers what the server keeps for pollers: a notify with no listener is retained and answers zero, a counter at or below the latest is not kept, and a bounded poll above the latest comes back empty.
…ients.

Core moves to the core-3 commit that carries the subscribe-notification-channel command machine and the NotificationsReceived job folded from the task's scheduled events. The vendored tree and the payload visitor regenerate from the pin, and the live workflow channel case now runs on this layer.
The server reports a producer conflict as FAILED_PRECONDITION with a reason token, and this layer only knew the INVALID_ARGUMENT a server built before the tokens sent, so the typed error was lost on the current server.
This layer maps the STREAM_CURSOR_BELOW_FLOOR token to StreamCursorError, which is not an RPCError, so the live case written on the wire layer no longer matched the exception it got.
… case.

A channel nobody has touched is not found, and the first notify on a fresh channel shows up in describe with its position and counter before any poll.
moetemp added 27 commits October 3, 2026 04:57
The stream_reader docstring said nothing crosses continue-as-new, which the
Redis provider contradicts: its transport resumes a successor's reader where
the predecessor committed.
The entry still said each topic was an input and an output stream, which the
provider stopped doing when it moved to one shared log per topic.
The main chain carries the channel, the stream interface with its memory, Workflow Streams and Nexus providers, and native streams.
Both chains share the channel surface and the stream interface, so the merge
keeps one copy of each. Core moves to the unified head that carries both
lineages, and the clients are regenerated from it.
The union tests against the time-skipping server too, so it carries the fix
that gives concurrent result waiters one shared unlock.
The workflow info and the payload converter factory grew on main after
the external chain's helpers were written.
On the shared wire zero says a producer doesn't number its records, and the
Redis producer does, so its first record is one.
The shared interface reads and writes DEFAULT_TOPIC when a call names no
topic, and the Redis handle stores it under that name like any other.
Without TEMPORAL_ADDRESS the cases reached the environment's own server,
which has no stream service, and failed on the wire.
The workflow_streams provider answers reads from the workflow itself, so
the read has to happen before the worker stops.
The union carries the native provider, so the cases gated on it no longer
skip.
The Redis producer notifies the stream's channel itself, so one live case
needs no notify by hand. The native case opens its own client with the native
provider on it.
Only the union carries the memory, Workflow Streams, native and Redis providers
together, so the demo's provider switch and its notes live here.
A getattr default is evaluated whether or not the attribute is there, and
protobuf 6 descriptors have is_repeated but no label, so the generator needed
a protobuf 3 interpreter.
The union was rebuilt as one PR on main, so the examples move onto it. The
tree is the union's plus the examples and their docs.
NexusStreams resolves the endpoint by name through the client, so the id the
docs asked for failed the lookup. The docs also give --http as a full URL.
# Conflicts:
#	streams_demo/README.md
#	temporalio/bridge/proto/workflow_activation/workflow_activation_pb2.py
#	temporalio/bridge/proto/workflow_commands/workflow_commands_pb2.py
#	temporalio/bridge/sdk-core
@moetemp

moetemp commented Oct 6, 2026

Copy link
Copy Markdown
Owner Author

Replaced by #112 in the v5 series (phase 1, Signal wake only). The branch stays as a pin.

@moetemp moetemp closed this Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant