Skip to content

fix(strands): give the model the activity's cancellation signal - #297

Open
campoy wants to merge 9 commits into
mainfrom
fix/strands-typescript-model-cancel-signal-lost
Open

campoy wants to merge 9 commits into
mainfrom
fix/strands-typescript-model-cancel-signal-lost

Conversation

@campoy

@campoy campoy commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What was changed

The model Activities give the model the Activity's own cancellation signal
(Context.current().cancellationSignal) as cancelSignal, in place of the agent's.

Strands puts the agent's cancelSignal, an AbortSignal, in the model's stream options. It reached
the Activity as a plain object ({}). From @strands-agents/sdk 1.15.0, OpenAIModel hands it to the
OpenAI client, which calls signal.addEventListener, so every OpenAIModel call through the plugin
failed before its request, Responses and Chat Completions alike. That includes the plugin's locked
1.19.0 and the latest 1.22.0.

With the Activity's signal, the request is sent as it is natively. When the model Activity has a
heartbeatTimeout, cancelling it aborts the signal; a model that passes cancelSignal to its client
(in Strands 1.19.0, OpenAIModel and BedrockModel) then stops the provider call, as cancelling the
agent does natively. AnthropicModel, GoogleModel and VercelModel ignore cancelSignal, so their
provider call keeps running and the Activity can still complete with its result. A remote Activity
learns of cancellation only through heartbeats, so without a heartbeatTimeout the model call runs to
completion. The README's new Cancellation section documents this, and how TemporalAgent cancellation
types behave.

Model clients abort with their own errors (OpenAI's APIUserAbortError is named Error), which
Temporal would record as an Activity failure. When the model throws after the Activity's signal
aborted, the Activity throws the signal's CancelledFailure instead, so the Activity ends as
cancelled. One helper in model-activity.ts does this for invokeModel and
invokeModelStreaming. Workflow code and the Activity input do not change.

Why?

See #296.

Compatibility

No version gate is needed, and nothing that works today breaks:

  • Workflow code is unchanged, so every history replays as before.
  • Models that ignored the signal still ignore it and behave as before. Models that accepted the plain
    object without ever aborting (Bedrock's HTTP handler sets onabort on it) now get a signal that
    aborts when the model Activity is cancelled with a heartbeatTimeout set. Natively, cancelling the
    agent aborts their call the same way.
  • A Worker without this change still fails OpenAIModel calls; during a rollout they are retried
    until an upgraded Worker takes them.

Checklist

  1. Closes [Bug] strands-agents (TypeScript): OpenAIModel calls fail in the model Activity on Strands 1.15.0 and later #296

  2. How was this tested:

    • invokeModel gives the model Message instances and an AbortSignal (the existing wire-data
      test, extended) checks that the model gets an AbortSignal. It fails on main
      (expected cancelSignal to be an AbortSignal).
    • cancelling the workflow aborts the model call in invokeModel and cancels the workflow, and
      the same for invokeModelStreaming, run TemporalModel.stream on a real Worker with
      heartbeatTimeout and WAIT_CANCELLATION_COMPLETED, cancel the Workflow mid-call, and use a
      model that throws an Error named Error once its signal aborts, as OpenAI's client does. They
      check that the model's signal aborted and that the history has ActivityTaskCanceled, no
      ActivityTaskFailed, and WorkflowExecutionCanceled. Without the CancelledFailure rethrow
      both fail with expected a CancelledFailure, got ActivityFailure: Activity task failed.
    • cancelling a TemporalAgent workflow with TRY_CANCEL aborts the model call and cancels the workflow, and the same with WAIT_CANCELLATION_COMPLETED, run agent.invoke() on a real Worker
      with heartbeatTimeout and cancel the Workflow mid-call. They check that the model's signal
      aborts while the Worker runs and that the history has ActivityTaskCancelRequested,
      WorkflowExecutionCanceled and no WorkflowTaskFailed (plus ActivityTaskCanceled with
      WAIT_CANCELLATION_COMPLETED). With TemporalAgent's ModelError unwrap disabled, both time out
      with repeated WorkflowTaskFailed.
    • cancelling a TemporalAgent workflow with TRY_CANCEL while the model ignores its signal, and the
      same with WAIT_CANCELLATION_COMPLETED, use a model that keeps going after its signal aborts and
      returns late result. With TRY_CANCEL the run is cancelled. With WAIT_CANCELLATION_COMPLETED
      the run completes with late result, and the history has ActivityTaskCompleted,
      WorkflowExecutionCompleted and no WorkflowExecutionCanceled.
    • [Bug] strands-agents (TypeScript): OpenAIModel calls fail in the model Activity on Strands 1.15.0 and later #296's reproduction, with the real OpenAIModel and a recording fetch, Responses and Chat
      Completions: on main it fails with @strands-agents/sdk 1.15.0, 1.19.0 and 1.22.0 and passes
      with 1.14.0; with the signal change it passes on all four. Also with @temporalio/* 1.25.0 and
      Strands 1.22.0. (This ran before the CancelledFailure rethrow was added.)
    • Which models honor cancelSignal was taken from the locked Strands 1.19.0 sources: only
      OpenAIModel and BedrockModel pass it to their clients.
    • Local, in a Linux container: make sync, make lint, make test (86 tests), make build and
      check_conventions.py pass. The Node 22, macOS and Windows cells did not run locally.
    • Not run against a real model provider, and not with Bedrock.
  3. Any docs updates needed?
    The README gains a Cancellation section with the heartbeatTimeout requirement, the dependence on
    the model honoring cancelSignal, and how TemporalAgent's TRY_CANCEL and
    WAIT_CANCELLATION_COMPLETED cancellation types differ, including that with
    WAIT_CANCELLATION_COMPLETED a model Activity that completes after cancellation lets the run
    complete normally. The Models section's note from fix(strands): run stateful models with their server-side state (breaking) #285, which said OpenAIModel calls work only
    before @strands-agents/sdk 1.15.0, is rewritten: such calls work with current releases, and only
    a Worker without this change still fails them during a rollout.

Not covered

  • agent.cancel() in Workflow code still does not cancel the model Activity; cancel it through
    Temporal (a cancellation scope or a Workflow cancel).
  • Models that ignore cancelSignal (in Strands 1.19.0, AnthropicModel, GoogleModel and
    VercelModel) are not aborted, and their Activity can complete with the model's result after
    cancellation. With WAIT_CANCELLATION_COMPLETED, the agent then continues with that result and the
    run can complete instead of being cancelled.

@campoy
campoy force-pushed the fix/strands-typescript-model-cancel-signal-lost branch from f6c47a9 to 7038d00 Compare October 30, 2026 23:36
@campoy
campoy marked this pull request as ready for review October 31, 2026 01:35
@campoy
campoy requested a review from a team as a code owner October 31, 2026 01:35
Strands passes the agent's cancelSignal, an AbortSignal, in the model's
stream options. The model activity got it as a plain object, so
OpenAIModel, which hands it to the OpenAI client, failed every call with
"signal.addEventListener is not a function" before sending a request,
from @strands-agents/sdk 1.15.0 on.

The model activities now give the model the activity's own cancellation
signal instead. Cancelling the activity then aborts the model call, as
cancelling the agent does natively.
@campoy
campoy force-pushed the fix/strands-typescript-model-cancel-signal-lost branch from 3952aed to ecc7667 Compare October 31, 2026 01:52
Other tests on main use ModelActivity. Rebasing the commit that removed
this PR's own copy of the import also removed main's.

This branch has not been deployed

No deployments
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.

[Bug] strands-agents (TypeScript): OpenAIModel calls fail in the model Activity on Strands 1.15.0 and later

1 participant