Skip to content

[Brook] Added the Activity and client stream handles over Core. - #119

Closed
moetemp wants to merge 22 commits into
moe/brook/p3a-workflow-writerfrom
moe/brook/p3b-outside-handles
Closed

moetemp wants to merge 22 commits into
moe/brook/p3a-workflow-writerfrom
moe/brook/p3b-outside-handles

Conversation

@moetemp

@moetemp moetemp commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

What changed?

This PR adds the stream handles an Activity and a client use to read and write a Workflow's stream through Core.

  • get_stream_handle() and activity_handle() return a StreamHandle with read, latest and producer.
  • A producer hashes before the codec, encodes the bodies and calls the generated append. It moves its sequence only when an append succeeds.
  • An Activity sends a typed ActivityProducer and Core derives <activity id>@<scheduling run id>, so every SDK writes the same producer id (DD-44).
  • Reads are long polls that hand back Core's state. Both sides use the Workflow's WorkflowSerializationContext, so a context-aware codec decodes the records.
  • A Local Activity gets StreamUnsupportedError. Its attempt isn't durable, so its first append after a crash would be refused as stale.

Why?

Core owns owner checks, cursors, dedupe and supersession. So a handle is a thin loop over the stub with the codec on both sides (DD-88).

How did you test it?

Link to a test plan if any -

  • Unit Tests
  • Staging
  • End to End Tests

tests/contrib/streams/test_outside_handles.py runs on the dev server.

moetemp added 17 commits October 9, 2026 15:00
An Activity writes to its Workflow's stream as itself, so readers see its
retry, and a lower attempt after a higher one is reported as stale.
# Conflicts:
#	temporalio/contrib/streams/__init__.py
Activity ids repeat across the runs of a chain while the stream and its
dedupe state span the chain, so run 2's first append was refused as stale
or silently matched run 1's position.
Such records reached the consumer as ordinary DATA and only the Worker log said otherwise. The package docs now also say an Activity's read ends with its scheduling run.
# Conflicts:
#	temporalio/contrib/streams/_policy.py
Handles and producers call Core's in-process service, which checks owners, mints cursors and reports supersession. Lang converts values, takes the digest and content hashes before the codec, and runs the codec both ways. An Activity sends its typed identity so Core derives its producer id.
Core checks the cursor and resolves END when the generator first runs, so a reader that must catch the next record positions itself with latest() first.
# Conflicts:
#	tests/contrib/streams/test_workflow_writer.py
The Workflow encodes its records with its own serialization context, so readers and producers without it couldn't decode them with a context-aware codec. An Activity's second producer object could also dedupe a record away, and a Local Activity's attempt isn't durable, so it gets no producer.
@moetemp
moetemp marked this pull request as draft October 12, 2026 06:58
@moetemp moetemp closed this Oct 13, 2026
@moetemp moetemp reopened this Oct 13, 2026
This layer is where a Workflow publishes and outside code reads end to end, so the release note lands here.
@moetemp

moetemp commented Oct 13, 2026

Copy link
Copy Markdown
Owner Author

Replaced by Brook #154 and Ford #164. Brook was split in two: Brook now has streams with Activity and client publish and outside readers, and Ford has the Workflow Task publish path. The code is the same, re-cut into new branches.

@moetemp moetemp closed this Oct 13, 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