Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: maxjneto/samples-python
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: temporalio/samples-python
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 16 commits
  • 223 files changed
  • 13 contributors

Commits on May 1, 2026

  1. Configuration menu
    Copy the full SHA
    cd2a8c5 View commit details
    Browse the repository at this point in the history

Commits on May 4, 2026

  1. Add workflow_streams samples (temporalio#300)

    * Add workflow_streams samples: order_workflow scenario
    
    Initial samples directory for temporalio.contrib.workflow_streams,
    the workflow-hosted durable event stream contrib (experimental,
    contrib/pubsub branch of sdk-python).
    
    The order_workflow scenario covers the basic publisher path: a
    workflow binds a typed topic in @workflow.init, an activity
    publishes events via the topic handle, and a starter subscribes
    with WorkflowStreamClient and prints events as they arrive.
    
    Also enables the uv supply-chain cooldown options in the lockfile.
    
    * samples: workflow_stream: add reconnecting-subscriber scenario
    
    Adds a second scenario demonstrating the central Workflow Streams use
    case: a consumer disconnects mid-stream and resumes later via
    subscribe(from_offset=...), with no events lost or duplicated. The
    existing OrderWorkflow finishes too quickly to make the pattern
    visible, so this introduces a multi-stage PipelineWorkflow paced with
    workflow.sleep between stages.
    
    The runner reads a couple of events, persists item.offset + 1 to a
    temp file, sleeps "disconnected" while the workflow keeps publishing,
    then opens a fresh Client + WorkflowStreamClient and resumes from the
    persisted offset — the same shape that works across actual process
    restarts.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * samples: workflow_stream: add external-publisher scenario
    
    Adds a third scenario covering the third publisher shape: a backend
    service or scheduled job pushing events into a workflow it didn't
    itself start. The earlier scenarios publish either from inside the
    workflow or from one of its activities; this one uses
    WorkflowStreamClient.create() externally.
    
    HubWorkflow is a passive stream host — it does no work of its own and
    just waits to be told to close, fitting the event-bus pattern. The
    runner publishes a series of news headlines, runs a subscriber task
    alongside, signals close, and exits when both tasks complete.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * samples: workflow_stream: add truncating-ticker scenario
    
    Adds a fourth scenario for long-running workflows that need to bound
    their event log: the workflow publishes events at a fixed cadence and
    calls self.stream.truncate(...) periodically to keep only the most
    recent entries.
    
    The runner subscribes twice — fast and slow — to make the trade
    visible: the fast subscriber sees every offset in order; the slow one
    falls behind a truncation, has its iterator transparently jump forward
    to the new base offset, and shows the offset gap that intermediate
    events fell into. This is the model for high-volume long-running
    streams: bounded log size, slow consumers may miss intermediate events
    but always see the most recent state.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * samples: rename workflow_stream → workflow_streams; migrate to topic handles
    
    - Directory and module path renamed to plural to match sdk-python
      `temporalio.contrib.workflow_streams` rename.
    - Workflow-side: bind a typed topic handle in `@workflow.init` and call
      `topic.publish(value)` — the removed `WorkflowStream.publish` form is
      gone. Same change applied to the activity and external-publisher.
    - Activity: `WorkflowStreamClient.from_activity()` →
      `from_within_activity()`.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * samples: workflow_streams review polish
    
    - README: fix scenario count (two -> four), document subscriber start
      position and continue-as-new semantics for stream_state
    - hub_workflow: drop stale comment referencing a README race note
      that does not exist in this sample
    - payment_activity: trim long publisher_id/dedup caveat — moved out
      of the first sample's docstring to keep it approachable
    
    * workflow_streams: deliver terminal events + fix run_publisher subscribe shape
    
    End-to-end runs of the four workflow_streams scenarios surfaced two
    sample-side issues, both fixed here.
    
    run_publisher's consumer asserted ``isinstance(item.data, Payload)`` and
    called ``payload_converter.from_payload(item.data, T)``. The contrib's
    ``subscribe()`` defaults to converter-decoded data, not raw payloads,
    so this assertion fired on the first run. Switch to
    ``result_type=RawValue`` (the documented escape hatch for heterogeneous
    topics) and read ``item.data.payload``.
    
    Items published in the same workflow task that returns from
    ``@workflow.run`` were not delivered to subscribers — the in-memory
    log dies with the workflow and the next subscriber poll lands on a
    completed workflow. Fix: each scenario now uses an in-band terminator
    that subscribers break on, and each workflow holds the run open with
    ``await workflow.sleep(timedelta(milliseconds=500))`` so that final
    publish is fetched before the workflow exits:
    
    - OrderWorkflow / PipelineWorkflow: the workflow's own
      ``StatusEvent(kind="complete")`` / ``StageEvent(stage="complete")``
      is the terminator (consumers already broke on it).
    - HubWorkflow: the *publisher* in run_external_publisher emits a
      sentinel ``NewsEvent(headline="__done__")`` immediately before
      signaling close; the consumer breaks on the sentinel.
    - TickerWorkflow: the final tick (n == count - 1) is the terminator;
      ``keep_last`` guarantees that offset survives the last truncation,
      so even slow consumers reach it.
    
    Because subscribers stop polling on the terminator, by the time
    ``workflow.run`` returns there are no in-flight poll handlers — no
    ``UnfinishedUpdateHandlersWarning`` from the SDK and no need for
    ``detach_pollers()`` / ``wait_condition(all_handlers_finished)`` in
    the workflow exit path.
    
    Two consecutive end-to-end runs of all four scenarios pass cleanly
    against ``temporal server start-dev --headless``.
    
    * workflow_streams README: document the stream-end pattern
    
    Subscribers don't exit on their own when the host workflow completes —
    they need an in-band terminator, and the workflow needs to hold open
    briefly so the final publish is fetched before run() returns. Both
    pieces show up in every scenario here, so document them in one place
    and update scenario 3's description to mention the sentinel headline
    the publisher emits.
    
    * samples: workflow_streams: README and wheel packages cleanup
    
    Now that temporalio 1.27.0 has shipped (and main has bumped to it in
    temporalio#302), drop the README's "install sdk-python from a branch" callout
    and point at >=1.27.0 instead. Also add workflow_streams to the wheel
    packages list alongside the other samples.
    
    * samples: workflow_streams: drop force_flush=True from charge_card
    
    The activity's final publish was using force_flush=True, which sets the
    flush_event so the background flusher fires immediately. Triggering a
    flush right before __aexit__ runs the activity into the
    WorkflowStreamClient's cancel-mid-flush path: __aexit__ cancels the
    flusher task while it's awaiting the publish signal RPC, the cancel
    propagates into the in-flight signal, and the activity hangs until the
    StartToClose timeout fires. Empirically the workflow then retries the
    activity indefinitely.
    
    Without force_flush=True the buffered "card charged" event flushes via
    the regular 200ms batch interval and the flusher is sleeping in
    wait_for(...) when __aexit__ cancels it — a clean cancellation path.
    The user-visible publish ordering is unchanged.
    
    The underlying SDK bug should be fixed separately by switching __aexit__
    from cancel() to a cooperative-stop flag so the in-flight signal
    completes before the flusher exits.
    
    * samples: workflow_streams: drop temp-file resume offset; add stats column
    
    The reconnecting-subscriber demo previously persisted its resume offset
    to a temp file between phases. Inside one process that's theatrical:
    the disconnect/reconnect shape comes from creating a fresh Client +
    WorkflowStreamClient with from_offset=N, not from where N happens to be
    stored. Replace the file with a local int and a comment about durable
    storage in production (a DB row keyed by user_id/run_id, etc.).
    
    Restructure output around a stats column so the demo conveys what's
    happening to the stream at all times, not just between phases. A
    background poller calls WorkflowStreamClient.get_offset() throughout
    and emits a heartbeat line once a second; every emit prints current
    proc/avail/pend in a left column followed by the phase or event
    message. Watching pend grow during the disconnect window and shrink
    again as phase 2 catches up is the demo's core point.
    
    * samples: workflow_streams: surface multiple truncation jumps in ticker
    
    The truncating-ticker demo is meant to make the bounded-log trade
    visible: fast subscriber sees every event, slow subscriber loses
    intermediate ones to truncation. The previous parameters
    (truncate_every=5, keep_last=3, interval_ms=400, slow_delay=1.5s)
    produced at most one tiny jump near the end of the run — easy to miss.
    
    Tighter parameters (truncate_every=2, keep_last=1, interval_ms=200,
    count=30) keep the workflow log at one or two entries between
    truncations. That shrinks the slow subscriber's per-poll batch, so it
    re-polls more often, and most polls land after a truncation that has
    passed its position. The result is several visible jumps over the
    demo, not a single batched one at the end.
    
    Switch the output to two lanes (fast on the left, slow on the right
    with explicit "↪ jumped offset=N → M (K dropped)" markers) so the
    divergence reads at a glance instead of being lost in interleaved
    single-stream output. Also extend the docstring to call out the
    opposite trade — never truncating means slow consumers eventually
    catch up at the cost of unbounded workflow history — so readers know
    when this pattern is the wrong fit.
    
    * samples: workflow_streams: add LLM-streaming scenario
    
    Adds a fifth scenario to workflow_streams/ that streams an OpenAI
    chat completion to the terminal through a Workflow Stream. Activity
    is the publisher (it owns the non-deterministic API call), workflow
    hosts the stream and runs the activity, runner subscribes and
    renders to stdout as deltas arrive.
    
    Layout:
    
    * `chat_shared.py` — types and topics for this scenario, kept out of
      the cross-scenario `shared.py` because no other scenario uses them
    * `workflows/chat_workflow.py` — `ChatWorkflow` runs `stream_completion`
      with `RetryPolicy(maximum_attempts=3)` and the same 500ms hold-open
      pattern the other four samples use
    * `activities/chat_activity.py` — `stream_completion` calls
      `AsyncOpenAI(...).chat.completions.create(stream=True)` with
      `gpt-5-mini`, publishes each token chunk on the `delta` topic, the
      full text on `complete`, and a `RetryEvent` on `retry` when running
      on attempt > 1. `force_flush=True` is intentionally omitted to
      avoid the `__aexit__` cancel-mid-flight hang in
      `temporalio.contrib.workflow_streams` 1.27.0; the 200ms
      `batch_interval` is fast enough for an interactive feel.
    * `run_chat.py` — subscribes to all three topics, prints deltas to
      stdout as they stream, and on a retry event uses plain ANSI
      escapes (`\033[<n>A`, `\033[J`) to rewind the rendered output
      before the retried attempt re-publishes
    * `run_chat_worker.py` — runs on its own task queue
      (`workflow-stream-chat-task-queue`), registering only
      `ChatWorkflow` and `stream_completion`; the openai dependency and
      the `OPENAI_API_KEY` requirement stay isolated to this one
      scenario
    
    The split worker also makes the retry-handling demo trivial to run:
    the user kills the chat worker mid-stream, brings it back up, and
    the activity retries — no synthetic failure injection needed.
    
    Adds `chat-stream = ["openai>=1.0,<2"]` as a new optional
    dependency group; `uv sync --group chat-stream` and an
    `OPENAI_API_KEY` are documented in the README.
    
    * samples: workflow_streams: drop chat-stream openai upper cap
    
    openai-agents (the existing langsmith-tracing / openai-agents extras)
    already pulls openai>=2.26.0. Capping chat-stream at openai<2 made
    the two extras unsatisfiable together. Drop the cap; the chat
    activity uses APIs that are stable across openai 1.x and 2.x.
    
    * samples: workflow_streams: chat consumer header + cursor save/restore
    
    Two display fixes for run_chat.py:
    
    1. Print a header line right after start_workflow so the user sees
       immediate feedback ("[chat <id>] streaming response from gpt-5-mini,
       awaiting first token...") instead of a blank screen until the first
       delta arrives.
    
    2. Replace the newline-counting ANSI clear with cursor save/restore
       (\033[s / \033[u\033[J). The previous version counted text newlines
       to decide how far up to move the cursor on retry, which undercounts
       when the terminal has wrapped long lines — the failed attempt's
       first wrapped lines stayed on screen above the retry marker.
       save/restore rewinds to a fixed position regardless of wrapping.
    
    Bumps the prompt to a 500-word distributed-systems comparison
    (Paxos vs Raft vs Viewstamped Replication) so there is enough output
    to comfortably kill the worker mid-stream and watch the retried
    attempt re-render from scratch.
    
    * samples: workflow_streams: rename chat -> llm in scenario 5
    
    "Chat" implies multi-turn conversation. The new scenario is a
    one-shot LLM completion stream, not a chat. Rename to make the
    scope clear:
    
    - chat_shared.py             -> llm_shared.py
    - workflows/chat_workflow.py -> workflows/llm_workflow.py
    - activities/chat_activity.py -> activities/llm_activity.py
    - run_chat.py                -> run_llm.py
    - run_chat_worker.py         -> run_llm_worker.py
    - ChatInput / ChatWorkflow   -> LLMInput / LLMWorkflow
    - CHAT_TASK_QUEUE            -> LLM_TASK_QUEUE
      ("workflow-stream-chat-task-queue" -> "workflow-stream-llm-task-queue")
    - chat-stream extra          -> llm-stream
    - workflow id prefix
      workflow-stream-chat-...   -> workflow-stream-llm-...
    
    The activity's `stream_completion` defn name and the topic
    constants (`delta`, `complete`, `retry`) stay the same — those
    already describe what they do without the "chat" framing.
    README, docstrings, and run instructions updated to match.
    
    * samples: workflow_streams: race the LLM consumer with workflow result
    
    If the LLM activity exhausts its retries (bad OPENAI_API_KEY,
    provider outage, etc.), the workflow fails before the activity
    publishes the `complete` terminator. The consumer's previous
    async-for loop only exited on `complete`, so the script blocked
    indefinitely on a terminator that would never arrive instead of
    surfacing the workflow failure.
    
    Wrap the subscriber in a `consume()` coroutine and run it through
    the existing `race_with_workflow` helper (the same pattern
    `run_publisher.py` uses): if the workflow finishes first the
    subscriber gets cancelled and the workflow's exception propagates;
    if the subscriber sees `complete` first, the helper waits for the
    workflow result and returns it.
    
    Found in a Codex code review of today's workflow_streams changes.
    
    * samples: workflow_streams: drop race_with_workflow helper
    
    The helper wrapped the consumer in an asyncio.gather that cancelled
    the subscriber when the workflow result settled — defensive logic
    for a case the SDK already handles. WorkflowStreamClient.subscribe()
    exits cleanly on every workflow terminal state (return,
    continue-as-new, failure) via its AcceptedUpdateCompletedWorkflow,
    WorkflowUpdateRPCTimeoutOrCancelledError, and NOT_FOUND branches in
    sdk-python. The async-for loop ends naturally when the workflow
    terminates without a publish, so we don't need a separate task to
    race against handle.result().
    
    Replace the helper with the obvious shape in both runners:
    
        async for item in stream.subscribe(...):
            ...
            if item.is_terminator:
                break
    
        result = await handle.result()  # raises on workflow failure
    
    Either path reaches handle.result(): an explicit break on the
    in-band terminator (workflow still running, hold-open lets the
    poll deliver the event), or the iterator naturally exhausting when
    the workflow has already terminated. handle.result() then either
    returns or raises the workflow's failure — covering the LLM
    "activity exhausted retries" case that prompted the helper to be
    added in the first place.
    
    Smoke tested:
    
        uv run workflow_streams/run_publisher.py
        uv run workflow_streams/run_llm.py
    
    * samples: workflow_streams: reorganize README; drop closing section
    
    Two fixes:
    
    1. Reorganize so the README doesn't jump back and forth between
       scenarios. The previous shape introduced 1-4, then put scenario
       5's full description plus its setup and run instructions inline,
       then jumped back to a "Run it" section that only covered 1-4.
       New shape: all five scenarios up front (parallel structure), one
       unified "Run it" section that covers worker setup for both
       groups and all five runner scripts in one block, then expected
       output, then notes.
    
    2. Drop the inline "Ending the stream" section. The same material
       is in documentation/docs/develop/python/libraries/workflow-streams.mdx
       under the "Closing the stream" anchor, so the README links there
       from the Notes block instead of duplicating the explanation.
    
    The scenario 5 "split-out worker" rationale (extra dependency,
    secret, retry-via-Ctrl-C) collapses to a single sentence at the end
    of its bullet block.
    
    * samples: workflow_streams: drop README Notes section
    
    The Notes block (subscriber start position, continue-as-new,
    closing the stream) was a small docs summary tacked onto the end of
    the README. The samples themselves cover these points: docstrings
    in each runner / workflow / activity explain the from_offset
    behavior, the stream_state field, and the in-band terminator +
    hold-open pattern. Readers who want the full conceptual treatment
    go to the docs page; the README sticks to "what the scenarios are
    and how to run them".
    
    * samples: workflow_streams: lock llm-stream dependency group
    
    The llm-stream dependency group was introduced in pyproject.toml
    without a corresponding uv.lock update, so `uv sync --frozen --group
    llm-stream` would fail or force a relock before scenario 5 could
    run. Add the two missing entries (the package-optional-dependencies
    list and the package-metadata requires-dev list) so frozen installs
    work against the committed lock.
    
    Found in a Codex review of the day's workflow_streams changes.
    
    * samples: workflow_streams: fix lint failures (ruff isort + format)
    
    CI's `poe lint` step was failing on three small things across four
    files:
    
    * `run_external_publisher.py`, `ticker_workflow.py`: ruff isort
      (`I001`) wanted the `workflow_streams.shared` imports re-sorted
      and a stray blank line removed. Apply the auto-fix.
    * `run_external_publisher.py`, `run_reconnecting_subscriber.py`,
      `run_truncating_ticker.py`: ruff format wanted three line-wrapped
      function calls collapsed back to single lines. Apply the
      formatter.
    * `run_truncating_ticker.py`: the formatter joined an adjacent
      pair of f-strings into an awkward `f"..." f"..."` one-liner.
      Consolidate them into a single f-string for readability — the
      resulting line is comfortably under the 88-char limit.
    
    `poe lint` (ruff isort + ruff format --check + mypy
    --all-groups --check-untyped-defs) now passes locally.
    
    * samples: workflow_streams: drop BFF jargon and Expected output block
    
    Two README/comment cleanups:
    
    * "BFF" (backend-for-frontend) is not a widely-known term outside
      certain front-end-architecture circles. Replace with the more
      obvious "web backends" in the README intro and "production web
      backend" in the run_reconnecting_subscriber.py comment about
      where the resume offset would live durably.
    
    * Drop the "Expected output" section. It only covered scenarios 1
      and 2; with five scenarios it is no longer pulling its weight.
      Anyone running the script can see the output for themselves.
    
    * Apply suggestion from @brianstrauch
    
    * Apply suggestion from @brianstrauch
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
    Co-authored-by: Brian Strauch <[email protected]>
    3 people authored May 4, 2026
    Configuration menu
    Copy the full SHA
    118083b View commit details
    Browse the repository at this point in the history
  2. Sample code for Nexus messaging (temporalio#290)

    * Working on adding Nexus messaging sample code
    
    * Fix to the tests
    
    * Updates from a code review
    
    * Working on adding Nexus messaging sample code
    
    * Fix to the tests
    
    * Updates from a code review
    
    * Bump pandas to 2.3.3 for Python 3.14 wheel support
    
    * removed some unneeded code
    
    * Removing  workflow.unsafe.imports_passed_through
    
    * Bringing in changes from main
    
    * Linting
    
    * Nexus samples: use business IDs for workflow IDs (temporalio#297)
    
    * Nexus samples: use business IDs for workflow IDs.
    
    Stop using the Nexus request ID (nexus_cancel) or bare uuid4 (hello_nexus,
    nexus_multiple_args) as the backing workflow ID. Build a meaningful business
    ID from the operation input and append a uuid4 suffix for uniqueness.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * remove UUID from the nexus sample workflow ids to better communicate intent
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
    
    * Changes from code reviews
    
    * Update SDK to 1.27.0 (temporalio#302)
    
    * Add workflow_streams samples (temporalio#300)
    
    * Add workflow_streams samples: order_workflow scenario
    
    Initial samples directory for temporalio.contrib.workflow_streams,
    the workflow-hosted durable event stream contrib (experimental,
    contrib/pubsub branch of sdk-python).
    
    The order_workflow scenario covers the basic publisher path: a
    workflow binds a typed topic in @workflow.init, an activity
    publishes events via the topic handle, and a starter subscribes
    with WorkflowStreamClient and prints events as they arrive.
    
    Also enables the uv supply-chain cooldown options in the lockfile.
    
    * samples: workflow_stream: add reconnecting-subscriber scenario
    
    Adds a second scenario demonstrating the central Workflow Streams use
    case: a consumer disconnects mid-stream and resumes later via
    subscribe(from_offset=...), with no events lost or duplicated. The
    existing OrderWorkflow finishes too quickly to make the pattern
    visible, so this introduces a multi-stage PipelineWorkflow paced with
    workflow.sleep between stages.
    
    The runner reads a couple of events, persists item.offset + 1 to a
    temp file, sleeps "disconnected" while the workflow keeps publishing,
    then opens a fresh Client + WorkflowStreamClient and resumes from the
    persisted offset — the same shape that works across actual process
    restarts.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * samples: workflow_stream: add external-publisher scenario
    
    Adds a third scenario covering the third publisher shape: a backend
    service or scheduled job pushing events into a workflow it didn't
    itself start. The earlier scenarios publish either from inside the
    workflow or from one of its activities; this one uses
    WorkflowStreamClient.create() externally.
    
    HubWorkflow is a passive stream host — it does no work of its own and
    just waits to be told to close, fitting the event-bus pattern. The
    runner publishes a series of news headlines, runs a subscriber task
    alongside, signals close, and exits when both tasks complete.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * samples: workflow_stream: add truncating-ticker scenario
    
    Adds a fourth scenario for long-running workflows that need to bound
    their event log: the workflow publishes events at a fixed cadence and
    calls self.stream.truncate(...) periodically to keep only the most
    recent entries.
    
    The runner subscribes twice — fast and slow — to make the trade
    visible: the fast subscriber sees every offset in order; the slow one
    falls behind a truncation, has its iterator transparently jump forward
    to the new base offset, and shows the offset gap that intermediate
    events fell into. This is the model for high-volume long-running
    streams: bounded log size, slow consumers may miss intermediate events
    but always see the most recent state.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * samples: rename workflow_stream → workflow_streams; migrate to topic handles
    
    - Directory and module path renamed to plural to match sdk-python
      `temporalio.contrib.workflow_streams` rename.
    - Workflow-side: bind a typed topic handle in `@workflow.init` and call
      `topic.publish(value)` — the removed `WorkflowStream.publish` form is
      gone. Same change applied to the activity and external-publisher.
    - Activity: `WorkflowStreamClient.from_activity()` →
      `from_within_activity()`.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * samples: workflow_streams review polish
    
    - README: fix scenario count (two -> four), document subscriber start
      position and continue-as-new semantics for stream_state
    - hub_workflow: drop stale comment referencing a README race note
      that does not exist in this sample
    - payment_activity: trim long publisher_id/dedup caveat — moved out
      of the first sample's docstring to keep it approachable
    
    * workflow_streams: deliver terminal events + fix run_publisher subscribe shape
    
    End-to-end runs of the four workflow_streams scenarios surfaced two
    sample-side issues, both fixed here.
    
    run_publisher's consumer asserted ``isinstance(item.data, Payload)`` and
    called ``payload_converter.from_payload(item.data, T)``. The contrib's
    ``subscribe()`` defaults to converter-decoded data, not raw payloads,
    so this assertion fired on the first run. Switch to
    ``result_type=RawValue`` (the documented escape hatch for heterogeneous
    topics) and read ``item.data.payload``.
    
    Items published in the same workflow task that returns from
    ``@workflow.run`` were not delivered to subscribers — the in-memory
    log dies with the workflow and the next subscriber poll lands on a
    completed workflow. Fix: each scenario now uses an in-band terminator
    that subscribers break on, and each workflow holds the run open with
    ``await workflow.sleep(timedelta(milliseconds=500))`` so that final
    publish is fetched before the workflow exits:
    
    - OrderWorkflow / PipelineWorkflow: the workflow's own
      ``StatusEvent(kind="complete")`` / ``StageEvent(stage="complete")``
      is the terminator (consumers already broke on it).
    - HubWorkflow: the *publisher* in run_external_publisher emits a
      sentinel ``NewsEvent(headline="__done__")`` immediately before
      signaling close; the consumer breaks on the sentinel.
    - TickerWorkflow: the final tick (n == count - 1) is the terminator;
      ``keep_last`` guarantees that offset survives the last truncation,
      so even slow consumers reach it.
    
    Because subscribers stop polling on the terminator, by the time
    ``workflow.run`` returns there are no in-flight poll handlers — no
    ``UnfinishedUpdateHandlersWarning`` from the SDK and no need for
    ``detach_pollers()`` / ``wait_condition(all_handlers_finished)`` in
    the workflow exit path.
    
    Two consecutive end-to-end runs of all four scenarios pass cleanly
    against ``temporal server start-dev --headless``.
    
    * workflow_streams README: document the stream-end pattern
    
    Subscribers don't exit on their own when the host workflow completes —
    they need an in-band terminator, and the workflow needs to hold open
    briefly so the final publish is fetched before run() returns. Both
    pieces show up in every scenario here, so document them in one place
    and update scenario 3's description to mention the sentinel headline
    the publisher emits.
    
    * samples: workflow_streams: README and wheel packages cleanup
    
    Now that temporalio 1.27.0 has shipped (and main has bumped to it in
    temporalio#302), drop the README's "install sdk-python from a branch" callout
    and point at >=1.27.0 instead. Also add workflow_streams to the wheel
    packages list alongside the other samples.
    
    * samples: workflow_streams: drop force_flush=True from charge_card
    
    The activity's final publish was using force_flush=True, which sets the
    flush_event so the background flusher fires immediately. Triggering a
    flush right before __aexit__ runs the activity into the
    WorkflowStreamClient's cancel-mid-flush path: __aexit__ cancels the
    flusher task while it's awaiting the publish signal RPC, the cancel
    propagates into the in-flight signal, and the activity hangs until the
    StartToClose timeout fires. Empirically the workflow then retries the
    activity indefinitely.
    
    Without force_flush=True the buffered "card charged" event flushes via
    the regular 200ms batch interval and the flusher is sleeping in
    wait_for(...) when __aexit__ cancels it — a clean cancellation path.
    The user-visible publish ordering is unchanged.
    
    The underlying SDK bug should be fixed separately by switching __aexit__
    from cancel() to a cooperative-stop flag so the in-flight signal
    completes before the flusher exits.
    
    * samples: workflow_streams: drop temp-file resume offset; add stats column
    
    The reconnecting-subscriber demo previously persisted its resume offset
    to a temp file between phases. Inside one process that's theatrical:
    the disconnect/reconnect shape comes from creating a fresh Client +
    WorkflowStreamClient with from_offset=N, not from where N happens to be
    stored. Replace the file with a local int and a comment about durable
    storage in production (a DB row keyed by user_id/run_id, etc.).
    
    Restructure output around a stats column so the demo conveys what's
    happening to the stream at all times, not just between phases. A
    background poller calls WorkflowStreamClient.get_offset() throughout
    and emits a heartbeat line once a second; every emit prints current
    proc/avail/pend in a left column followed by the phase or event
    message. Watching pend grow during the disconnect window and shrink
    again as phase 2 catches up is the demo's core point.
    
    * samples: workflow_streams: surface multiple truncation jumps in ticker
    
    The truncating-ticker demo is meant to make the bounded-log trade
    visible: fast subscriber sees every event, slow subscriber loses
    intermediate ones to truncation. The previous parameters
    (truncate_every=5, keep_last=3, interval_ms=400, slow_delay=1.5s)
    produced at most one tiny jump near the end of the run — easy to miss.
    
    Tighter parameters (truncate_every=2, keep_last=1, interval_ms=200,
    count=30) keep the workflow log at one or two entries between
    truncations. That shrinks the slow subscriber's per-poll batch, so it
    re-polls more often, and most polls land after a truncation that has
    passed its position. The result is several visible jumps over the
    demo, not a single batched one at the end.
    
    Switch the output to two lanes (fast on the left, slow on the right
    with explicit "↪ jumped offset=N → M (K dropped)" markers) so the
    divergence reads at a glance instead of being lost in interleaved
    single-stream output. Also extend the docstring to call out the
    opposite trade — never truncating means slow consumers eventually
    catch up at the cost of unbounded workflow history — so readers know
    when this pattern is the wrong fit.
    
    * samples: workflow_streams: add LLM-streaming scenario
    
    Adds a fifth scenario to workflow_streams/ that streams an OpenAI
    chat completion to the terminal through a Workflow Stream. Activity
    is the publisher (it owns the non-deterministic API call), workflow
    hosts the stream and runs the activity, runner subscribes and
    renders to stdout as deltas arrive.
    
    Layout:
    
    * `chat_shared.py` — types and topics for this scenario, kept out of
      the cross-scenario `shared.py` because no other scenario uses them
    * `workflows/chat_workflow.py` — `ChatWorkflow` runs `stream_completion`
      with `RetryPolicy(maximum_attempts=3)` and the same 500ms hold-open
      pattern the other four samples use
    * `activities/chat_activity.py` — `stream_completion` calls
      `AsyncOpenAI(...).chat.completions.create(stream=True)` with
      `gpt-5-mini`, publishes each token chunk on the `delta` topic, the
      full text on `complete`, and a `RetryEvent` on `retry` when running
      on attempt > 1. `force_flush=True` is intentionally omitted to
      avoid the `__aexit__` cancel-mid-flight hang in
      `temporalio.contrib.workflow_streams` 1.27.0; the 200ms
      `batch_interval` is fast enough for an interactive feel.
    * `run_chat.py` — subscribes to all three topics, prints deltas to
      stdout as they stream, and on a retry event uses plain ANSI
      escapes (`\033[<n>A`, `\033[J`) to rewind the rendered output
      before the retried attempt re-publishes
    * `run_chat_worker.py` — runs on its own task queue
      (`workflow-stream-chat-task-queue`), registering only
      `ChatWorkflow` and `stream_completion`; the openai dependency and
      the `OPENAI_API_KEY` requirement stay isolated to this one
      scenario
    
    The split worker also makes the retry-handling demo trivial to run:
    the user kills the chat worker mid-stream, brings it back up, and
    the activity retries — no synthetic failure injection needed.
    
    Adds `chat-stream = ["openai>=1.0,<2"]` as a new optional
    dependency group; `uv sync --group chat-stream` and an
    `OPENAI_API_KEY` are documented in the README.
    
    * samples: workflow_streams: drop chat-stream openai upper cap
    
    openai-agents (the existing langsmith-tracing / openai-agents extras)
    already pulls openai>=2.26.0. Capping chat-stream at openai<2 made
    the two extras unsatisfiable together. Drop the cap; the chat
    activity uses APIs that are stable across openai 1.x and 2.x.
    
    * samples: workflow_streams: chat consumer header + cursor save/restore
    
    Two display fixes for run_chat.py:
    
    1. Print a header line right after start_workflow so the user sees
       immediate feedback ("[chat <id>] streaming response from gpt-5-mini,
       awaiting first token...") instead of a blank screen until the first
       delta arrives.
    
    2. Replace the newline-counting ANSI clear with cursor save/restore
       (\033[s / \033[u\033[J). The previous version counted text newlines
       to decide how far up to move the cursor on retry, which undercounts
       when the terminal has wrapped long lines — the failed attempt's
       first wrapped lines stayed on screen above the retry marker.
       save/restore rewinds to a fixed position regardless of wrapping.
    
    Bumps the prompt to a 500-word distributed-systems comparison
    (Paxos vs Raft vs Viewstamped Replication) so there is enough output
    to comfortably kill the worker mid-stream and watch the retried
    attempt re-render from scratch.
    
    * samples: workflow_streams: rename chat -> llm in scenario 5
    
    "Chat" implies multi-turn conversation. The new scenario is a
    one-shot LLM completion stream, not a chat. Rename to make the
    scope clear:
    
    - chat_shared.py             -> llm_shared.py
    - workflows/chat_workflow.py -> workflows/llm_workflow.py
    - activities/chat_activity.py -> activities/llm_activity.py
    - run_chat.py                -> run_llm.py
    - run_chat_worker.py         -> run_llm_worker.py
    - ChatInput / ChatWorkflow   -> LLMInput / LLMWorkflow
    - CHAT_TASK_QUEUE            -> LLM_TASK_QUEUE
      ("workflow-stream-chat-task-queue" -> "workflow-stream-llm-task-queue")
    - chat-stream extra          -> llm-stream
    - workflow id prefix
      workflow-stream-chat-...   -> workflow-stream-llm-...
    
    The activity's `stream_completion` defn name and the topic
    constants (`delta`, `complete`, `retry`) stay the same — those
    already describe what they do without the "chat" framing.
    README, docstrings, and run instructions updated to match.
    
    * samples: workflow_streams: race the LLM consumer with workflow result
    
    If the LLM activity exhausts its retries (bad OPENAI_API_KEY,
    provider outage, etc.), the workflow fails before the activity
    publishes the `complete` terminator. The consumer's previous
    async-for loop only exited on `complete`, so the script blocked
    indefinitely on a terminator that would never arrive instead of
    surfacing the workflow failure.
    
    Wrap the subscriber in a `consume()` coroutine and run it through
    the existing `race_with_workflow` helper (the same pattern
    `run_publisher.py` uses): if the workflow finishes first the
    subscriber gets cancelled and the workflow's exception propagates;
    if the subscriber sees `complete` first, the helper waits for the
    workflow result and returns it.
    
    Found in a Codex code review of today's workflow_streams changes.
    
    * samples: workflow_streams: drop race_with_workflow helper
    
    The helper wrapped the consumer in an asyncio.gather that cancelled
    the subscriber when the workflow result settled — defensive logic
    for a case the SDK already handles. WorkflowStreamClient.subscribe()
    exits cleanly on every workflow terminal state (return,
    continue-as-new, failure) via its AcceptedUpdateCompletedWorkflow,
    WorkflowUpdateRPCTimeoutOrCancelledError, and NOT_FOUND branches in
    sdk-python. The async-for loop ends naturally when the workflow
    terminates without a publish, so we don't need a separate task to
    race against handle.result().
    
    Replace the helper with the obvious shape in both runners:
    
        async for item in stream.subscribe(...):
            ...
            if item.is_terminator:
                break
    
        result = await handle.result()  # raises on workflow failure
    
    Either path reaches handle.result(): an explicit break on the
    in-band terminator (workflow still running, hold-open lets the
    poll deliver the event), or the iterator naturally exhausting when
    the workflow has already terminated. handle.result() then either
    returns or raises the workflow's failure — covering the LLM
    "activity exhausted retries" case that prompted the helper to be
    added in the first place.
    
    Smoke tested:
    
        uv run workflow_streams/run_publisher.py
        uv run workflow_streams/run_llm.py
    
    * samples: workflow_streams: reorganize README; drop closing section
    
    Two fixes:
    
    1. Reorganize so the README doesn't jump back and forth between
       scenarios. The previous shape introduced 1-4, then put scenario
       5's full description plus its setup and run instructions inline,
       then jumped back to a "Run it" section that only covered 1-4.
       New shape: all five scenarios up front (parallel structure), one
       unified "Run it" section that covers worker setup for both
       groups and all five runner scripts in one block, then expected
       output, then notes.
    
    2. Drop the inline "Ending the stream" section. The same material
       is in documentation/docs/develop/python/libraries/workflow-streams.mdx
       under the "Closing the stream" anchor, so the README links there
       from the Notes block instead of duplicating the explanation.
    
    The scenario 5 "split-out worker" rationale (extra dependency,
    secret, retry-via-Ctrl-C) collapses to a single sentence at the end
    of its bullet block.
    
    * samples: workflow_streams: drop README Notes section
    
    The Notes block (subscriber start position, continue-as-new,
    closing the stream) was a small docs summary tacked onto the end of
    the README. The samples themselves cover these points: docstrings
    in each runner / workflow / activity explain the from_offset
    behavior, the stream_state field, and the in-band terminator +
    hold-open pattern. Readers who want the full conceptual treatment
    go to the docs page; the README sticks to "what the scenarios are
    and how to run them".
    
    * samples: workflow_streams: lock llm-stream dependency group
    
    The llm-stream dependency group was introduced in pyproject.toml
    without a corresponding uv.lock update, so `uv sync --frozen --group
    llm-stream` would fail or force a relock before scenario 5 could
    run. Add the two missing entries (the package-optional-dependencies
    list and the package-metadata requires-dev list) so frozen installs
    work against the committed lock.
    
    Found in a Codex review of the day's workflow_streams changes.
    
    * samples: workflow_streams: fix lint failures (ruff isort + format)
    
    CI's `poe lint` step was failing on three small things across four
    files:
    
    * `run_external_publisher.py`, `ticker_workflow.py`: ruff isort
      (`I001`) wanted the `workflow_streams.shared` imports re-sorted
      and a stray blank line removed. Apply the auto-fix.
    * `run_external_publisher.py`, `run_reconnecting_subscriber.py`,
      `run_truncating_ticker.py`: ruff format wanted three line-wrapped
      function calls collapsed back to single lines. Apply the
      formatter.
    * `run_truncating_ticker.py`: the formatter joined an adjacent
      pair of f-strings into an awkward `f"..." f"..."` one-liner.
      Consolidate them into a single f-string for readability — the
      resulting line is comfortably under the 88-char limit.
    
    `poe lint` (ruff isort + ruff format --check + mypy
    --all-groups --check-untyped-defs) now passes locally.
    
    * samples: workflow_streams: drop BFF jargon and Expected output block
    
    Two README/comment cleanups:
    
    * "BFF" (backend-for-frontend) is not a widely-known term outside
      certain front-end-architecture circles. Replace with the more
      obvious "web backends" in the README intro and "production web
      backend" in the run_reconnecting_subscriber.py comment about
      where the resume offset would live durably.
    
    * Drop the "Expected output" section. It only covered scenarios 1
      and 2; with five scenarios it is no longer pulling its weight.
      Anyone running the script can see the output for themselves.
    
    * Apply suggestion from @brianstrauch
    
    * Apply suggestion from @brianstrauch
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
    Co-authored-by: Brian Strauch <[email protected]>
    
    * Working on adding Nexus messaging sample code
    
    * Fix to the tests
    
    * Updates from a code review
    
    * Working on adding Nexus messaging sample code
    
    * Bump pandas to 2.3.3 for Python 3.14 wheel support
    
    * removed some unneeded code
    
    * Removing  workflow.unsafe.imports_passed_through
    
    * Bringing in changes from main
    
    * Linting
    
    * Changes from code reviews
    
    ---------
    
    Co-authored-by: Alex Mazzeo <[email protected]>
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
    Co-authored-by: Justin Anderson <[email protected]>
    Co-authored-by: Johann Schleier-Smith <[email protected]>
    Co-authored-by: Brian Strauch <[email protected]>
    6 people authored May 4, 2026
    Configuration menu
    Copy the full SHA
    6b8e441 View commit details
    Browse the repository at this point in the history
  3. [AI-72] LangGraph plugin samples (temporalio#289)

    * Add LangGraph plugin samples
    
    Seven samples demonstrating the Temporal LangGraph plugin across both the
    Graph API and Functional API:
    
    - Human-in-the-loop: interrupt() + Temporal signals for chatbot approval
    - Continue-as-new: task result caching across workflow boundaries
    - ReAct agent: tool-calling loop with conditional edges / while loop
    - Control flow (Functional API only): parallel, for-loop, if/else
    
    Related SDK PR: temporalio/sdk-python#1448
    
    * Remove explicit langchain deps from langgraph group to avoid conflict
    
    * Declare langchain and langgraph as conflicting dependency groups
    
    * Fix import sorting and formatting
    
    * Update dependencies and lint config (temporarily - waiting for SDK PR merge) so that CI otherwise passes
    
    * Add hello_world samples, per-sample READMEs, tests, __init__ docstrings, and configurable client
    
    * Address review: consistent v2 API, unique thread_id, SDK prereq note
    
    * Remove SDK prereq note - PR won't merge until plugin is released
    
    * Add LangSmith tracing sample (Graph API + Functional API)
    
    Combines LangGraphPlugin (durable execution) with LangSmithPlugin
    (observability) for full tracing of LLM calls through Temporal workflows.
    
    * Update all samples to match SDK PR #1448 API
    
    - graphs parameter is now a list, not a dict
    - No graph()/entrypoint() lookup functions — reference objects directly
    - No entrypoints parameter on LangGraphPlugin
    - Use set_cache()/get_cache() for continue-as-new caching
    - Update tests and READMEs accordingly
    
    * Update LangGraph samples to current sdk-python main API
    
    The plugin API has evolved past PR #1448:
    - LangGraphPlugin(graphs=...) now takes dict[str, StateGraph], not list
    - Same for entrypoints (dict[str, Pregel])
    - Each node/task must declare execute_in: "activity" | "workflow" in metadata
    - Workflows look up registered graphs/entrypoints via graph(name) /
      entrypoint(name) instead of importing them directly
    - get_cache/set_cache replaced by cache() and graph(name, cache=...) /
      entrypoint(name, cache=...)
    
    Other changes:
    - State schemas converted from primitives (str, int) to TypedDict, since
      langgraph's StateT is bound to TypedDict / dataclass / pydantic models
    - Module-level graph instances replaced with make_<name>_graph() factories
      because LangGraphPlugin mutates node metadata in-place at construction
      time, which prevented reuse across tests
    - langgraph dep group now pulls langchain, langchain-anthropic, and
      temporalio[langgraph,langsmith] so the langsmith_tracing samples and
      human_in_the_loop's RunnableConfig usage actually resolve
    
    * CI: install protoc for building temporalio from sdk-python git source
    
    * Skip LangGraph functional API + interrupt tests on Python < 3.11
    
    These features rely on contextvars propagation through asyncio.create_task(),
    which is only available in Python 3.11+. On 3.10 they hang rather than fail
    cleanly, so CI sat for an hour before today's fix landed.
    
    Mirrors the pytestmark skipif used in sdk-python's own contrib/langgraph
    tests.
    
    * Address review feedback on LangGraph samples
    
    - Drop cross-references between Functional API and Graph API READMEs so
      each stands alone (review comment 1)
    - Combine run_worker.py + run_workflow.py into a single main.py for the
      langsmith_tracing samples to reduce setup steps (review comment 2)
    - Fix workflow -> Workflow casing (review comment 3)
    - Alias temporalio's entrypoint and graph helpers as temporal_entrypoint /
      temporal_graph at import sites to avoid the langgraph.func.entrypoint
      collision (review comment 4)
    - Demonstrate @Traceable in three places in both langsmith_tracing samples:
      on the Activity/task itself, on a helper called from the Activity, and
      on a helper called from the Workflow (review comment 5)
    - Drop unnecessary single-task list comprehension for activity_options in
      hello_world and langsmith_tracing functional samples (review comment 6)
    - Spell out 'temporal server start-dev' in every sample README's
      prerequisites (review comment 7)
    - Fix stale README references to get_cache() now that the API is cache()
      (review comment 8)
    - Rename arbitrarily-named ETL pipeline functions in continue_as_new to
      honest math names (double / add_50 / triple) per review comment 9
    
    * Update LangGraph samples for temporalio 1.27.0 release
    
    - Drop the [tool.uv.sources] git pin; consume temporalio from PyPI now
      that 1.27.0 ships the LangGraph plugin.
    - Keep the protoc CI step around as a permanent fixture so we don't have
      to add and remove it each time we iterate on a new plugin sample.
    - Replace dict-comprehension activity_options with explicit per-task
      literals in the Functional API samples.
    - Emphasize workflow.wait_condition() in the human-in-the-loop READMEs
      as the durable wait primitive that pairs with interrupt() and signals.
    - Clarify that the task result cache is provided by the LangGraph plugin
      (Temporal), not by upstream LangGraph.
    - Drop "Same pattern as the Graph API version" cross-references from
      Functional API workflow docstrings; readers pick one API style.
    - Tighten the Functional API LangSmith tracing README copy.
    - Point the parent README to the released contrib package docs.
    DABH authored May 4, 2026
    Configuration menu
    Copy the full SHA
    601f5d4 View commit details
    Browse the repository at this point in the history

Commits on May 5, 2026

  1. Configuration menu
    Copy the full SHA
    c0b97bd View commit details
    Browse the repository at this point in the history

Commits on May 11, 2026

  1. AI-180: Disable LangSmith network calls in samples tests via mock cli…

    …ent (temporalio#304)
    
    The langsmith_tracing tests previously instantiated `LangSmithPlugin()`
    with no client. Internally that constructs a real `langsmith.Client`
    which authenticates against `api.smith.langchain.com` using whatever
    `LANGSMITH_API_KEY` is in env, producing 401/403 warnings on every
    test run when a stale or invalid key was present.
    
    The samples tests don't assert on trace output, so the right fix is
    to bypass the network entirely. Added a `mock_ls_client` pytest
    fixture in `tests/langsmith_tracing/conftest.py` and wired it into
    `test_basic.py` and `test_chatbot.py` via `LangSmithPlugin(client=mock_ls_client)`.
    
    Verified empirically with `pytest-socket --disable-socket
    --allow-hosts=127.0.0.1,::1,localhost` and `LANGSMITH_API_KEY` set:
    zero non-localhost connections attempted during the test run.
    
    Drive-by: corrected a pre-existing inaccurate docstring on
    `test_chatbot_read_note` (the test saves a note first, then reads
    it back; the original elided the save phase).
    
    The underlying SDK behavior — `LangSmithInterceptor` force-sets
    `tracing_context(enabled=True)`, which overrides the standard
    `LANGSMITH_TRACING=false` disable mechanism — is filed separately
    as AI-183.
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
    xumaple and claude authored May 11, 2026
    Configuration menu
    Copy the full SHA
    0bca0bf View commit details
    Browse the repository at this point in the history

Commits on May 13, 2026

  1. Configuration menu
    Copy the full SHA
    bc1e74d View commit details
    Browse the repository at this point in the history

Commits on May 14, 2026

  1. Configuration menu
    Copy the full SHA
    24035cc View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    dfff599 View commit details
    Browse the repository at this point in the history

Commits on May 15, 2026

  1. Configuration menu
    Copy the full SHA
    6168ebc View commit details
    Browse the repository at this point in the history

Commits on May 21, 2026

  1. Redis external storage driver (temporalio#287)

    * Redis external storage driver
    
    * Fix lints
    
    * Fix CI tests
    
    * Fix yarl error in CI
    
    * Revert accidental README.md edit, and add external_storage_redis.
    
    * Move redis test workflow to tests/
    
    * Update test_redis_worker.py
    
    * Constrain langsmith to allow CI to pass
    eamsden authored May 21, 2026
    Configuration menu
    Copy the full SHA
    fded925 View commit details
    Browse the repository at this point in the history
  2. Use datetime.timedelta in openai_agents samples (temporalio#312)

    workflow.timedelta was an incidental re-export from temporalio's
    old workflow.py module. The refactor to a workflow package in
    temporalio/sdk-python#1488 dropped it, so use datetime.timedelta
    directly (matches the rest of the repo).
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
    brianstrauch and claude authored May 21, 2026
    Configuration menu
    Copy the full SHA
    0f7d257 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    4d453de View commit details
    Browse the repository at this point in the history

Commits on Jun 5, 2026

  1. Configuration menu
    Copy the full SHA
    f14d16a View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    000d67d View commit details
    Browse the repository at this point in the history
  3. Add Strands plugin samples (temporalio#310)

    * Add Strands plugin samples
    
    Demonstrates the Temporal Strands plugin: hello world, tools (in-workflow,
    custom activity, strands_tools), HITL, hooks, MCP, structured output,
    streaming, interrupt, and continue-as-new.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * Convert continue_as_new chat to workflow update
    
    `user_says` → `turn`, now a `@workflow.update` returning the assistant's
    reply directly so callers no longer need a separate `messages` query to
    discover what the agent said. The run loop drains in-flight handlers via
    `workflow.all_handlers_finished` before continue-as-new.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * Fix interrupt sample: make delete_thing idempotent
    
    The activity was toggling its `_APPROVED` state (add on raise, discard on
    success), so after the human approved once, a follow-up tool call from the
    model would interrupt again with no further approval coming — hanging the
    test. Drop the discard so once approved, the name stays approved.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * Fix ruff import order in strands mock model
    
    ruff's `I001` rule groups `temporalio` with other third-party imports
    rather than its own block, which broke `poe lint` on CI.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * Rename Strands sample extra
    
    * Use Windows-compatible Strands tool sample
    
    * Bump sdk-python pin to pick up Strands MCP client fix
    
    Picks up c84320c6 on the strands branch, which rewrites the MCP
    populate_cache and call_tool activity to use ClientSession directly
    instead of MCPClient.start/stop. The old code created a background
    event loop on one thread and closed it from another, which deadlocked
    mcp_test on Python 3.10 once trio_asyncio's policy was installed at
    import time by tests/trio_async/workflow_test.py.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * Rename Strands interrupt sample to activity_interrupt
    
    Disambiguates from the hook-based human_in_the_loop sample by naming this
    one after where the interrupt is raised (a Temporal activity). Also fixes
    the parent README's broken `tool_interrupt` link.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * Fix uv sync group name in Strands READMEs
    
    The dependency group is named strands-agents, not strands.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
    
    * Test the continue-as-new path in the Strands chat sample
    
    Replace test_continue_as_new_chat, which never tripped the continue-as-new
    branch, with test_continue_as_new_carries_history. A low
    limit.historyCount.suggestContinueAsNew threshold makes the server suggest
    continue-as-new after one turn, so the test verifies the original run ends in
    CONTINUED_AS_NEW and the fresh run resumes with the carried-over messages
    before taking another turn and ending the chat.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
    
    * add snipsync lines
    
    * Remove unnecessary langsmith constraint
    
    temporalio 1.28.0's langsmith extra already pins langsmith>=0.7.34,<0.9,
    so the manual constraint-dependencies entry is redundant.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
    
    * Fix formatting in strands_plugin workflows
    
    ruff format requires two blank lines before the trailing @@@SNIPEND
    markers added in 12f9d5d.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
    Co-authored-by: Lenny Chen <[email protected]>
    Co-authored-by: Lenny Chen <[email protected]>
    4 people authored Jun 5, 2026
    Configuration menu
    Copy the full SHA
    5ceb3c8 View commit details
    Browse the repository at this point in the history
Loading