Skip to content

openai-agents: keep the caller's run context when resuming a RunState - #240

Draft
campoy wants to merge 1 commit into
mainfrom
fix/openai-agents-typescript-resume-replaces-run-context
Draft

campoy wants to merge 1 commit into
mainfrom
fix/openai-agents-typescript-resume-replaces-run-context

Conversation

@campoy

@campoy campoy commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

What was changed

When TemporalOpenAIRunner.run resumes a RunState, the resumed run now keeps the state's own context object, as the upstream Runner does. Tools that run after an approval resume write the caller's object (or the one injected with RunState.fromStringWithContext), so later reads and the next turn see their writes.

  • For a RunState input, the runner still rebuilds the state from its string so the state points at the converted agent graph, but with RunState.fromStringWithContext(converted, input.toString(), input._context) instead of RunState.fromString. With the default merge strategy, the approvals in the string are merged into that same context, so approved calls still run.
  • The new path is behind patched('openai-agents.keep-resume-context'), asked only when the input is a RunState.

API change: none. No option or signature changes.

Replay and rollback: resumed tools now write the caller's object in Workflow code, so a Workflow that reads it after the resume can take other commands than it did before. Runs that resumed before this change keep the JSON copy on replay. Runs that resume on this version record the marker at the resume, and an earlier plugin version can't replay them ([TMPRL1100] on the marker). To roll back, keep this version and decline the patch with the Worker's patchActivationCallback (experimental in SDK 1.24), or use Worker Versioning. Runs that never resume a RunState record no marker.

Why?

See #239. The runner rebuilt a resumed state with RunState.fromString, which builds a new RunContext from the context's JSON. The resumed run used that copy, so an approved tool's write never reached the caller's object, and the next turn started with that object didn't see it. Context members JSON can't carry, such as functions or Maps, were also missing in the resumed run. Nothing failed or warned. Passing { context: ctx } again with the state didn't help, because the inner Runner ignores context for a RunState input.

Checklist

  1. Closes [Bug] openai-agents (TypeScript): after an approval resume, tools write a copy of the run context, not the caller's object #239

  2. How was this tested:

    • New integration test Approval resume keeps the caller context object in src/__tests__/test-openai-agents.ts. Its Workflow (approvalResumeContextWorkflow) has one tool that needs approval and writes the run context; it approves, resumes the same RunState, then reads the caller's object. It sleeps only when the write reached that object, so where the write lands also changes the Workflow's commands.
    • New test Approval resume recorded before the context fix still replays. It replays a History of that Workflow recorded on main (src/__tests__/histories/approval-resume-context-before-fix.json), where the write missed the caller's object and the Workflow didn't sleep.
    • On main with only the new tests copied in, the integration test fails: { resumedUsesCallerObject: false } instead of { callerAfter: 'S-after', resumedUsesCallerObject: true }. The replay test passes there.
    • Without the gate (always the new path), the replay test fails with [TMPRL1100] Nondeterminism error: Timer machine does not handle this event: HistoryEvent(id: 17, WorkflowExecutionCompleted), and the integration test passes.
    • With this change, both pass.
    • Locally, in Docker (Node 24.22.0, pnpm 10.27.0, Linux arm64): make sync, make lint (8 existing warnings, none in files this PR touches), make test (209 tests; 207 on main), make build, scripts/ci/check_npm_package.py, node ../_shared/smoke.mjs and scripts/ci/check_conventions.py pass, and Prettier with the SDK settings reports the changed files clean.
    • [Bug] openai-agents (TypeScript): after an approval resume, tools write a copy of the run context, not the caller's object #239's reproduction exits 1 on npm 1.24.0 and on main at c664a34, and 0 on this branch, where both resume forms (the same RunState, and a state rebuilt with an injected context) keep the object as the upstream Runner does.
    • Replay check, with a separate harness: Workers with no Workflow cache, the model Activity on its own queue so waiting runs survive Worker changes, and a Workflow that sleeps only when the write reached the object it reads. Histories from main and from this branch for both resume forms, finished, held in the model call after the resume, and waiting for an approval Signal:
      • This branch replays all 17 histories: main's, its own, the rollback build's, and the runs carried across versions.
      • Upgrade: main's runs held after their resume finish on this branch with the old behavior (no marker, no sleep, 0 failed Workflow Tasks). main's run waiting for approval resumes on this branch with the new behavior (marker, sleep).
      • Rollback build (this branch, with patchActivationCallback declining the patch): this branch's held runs finish with the new behavior, 0 failed Workflow Tasks; its waiting run, and a new run, resume on the old path with no marker.
      • main replays the 10 histories without the marker and fails the 7 with it with [TMPRL1100] Nondeterminism error: Non-deprecated patch marker encountered for change openai-agents.keep-resume-context, as expected for patched().
      • Without the gate, main's finished histories (and its held runs finished on this branch) fail with [TMPRL1100] ... Timer machine does not handle this event; its 3 unfinished histories replay.
      • A control that renames the model Activity in a history fails with [TMPRL1100] Nondeterminism error: Activity type of scheduled event 'invokeRenamedActivity' does not match activity type of activity command 'invokeModelActivity'.

    CI covers the other Node versions and platforms. Not run against a real model provider.

  3. Any docs updates needed?
    No. The README already says the runner accepts a RunState "matching the upstream runner". Release notes should say that a resumed run now keeps the state's context object in new runs, and that runs which resumed on this version can't be replayed by an earlier one.

Not covered:

  • Streaming resumes (stream: true with a RunState) take the same code path but were not run.
  • A context holding functions, Maps or class instances was not run; with the state's own context those members are kept, as with the upstream Runner.

Related: #236 also adds a patched import and a patch constant at the top of src/workflow/runner.ts, and the other open drafts for this plugin (#209, #210, #215, #216, #217, #218, #236) add tests at the end of src/__tests__/test-openai-agents.ts and src/__tests__/workflows/openai-agents.ts, so whichever merges later may need a small rebase there.

Open questions:

  • Is a marker on every RunState resume acceptable? The gate can't tell whether the Workflow reads the caller's object after the resume, so every resume on this version records it, and rolling back to an earlier version then needs patchActivationCallback (experimental) or Worker Versioning.
  • input._context is the framework's underscore-named RunState field (public in its type). There is no other accessor for a state's RunContext; is relying on it fine with the ~0.14.3 pin?

Draft while these questions are open.

TemporalOpenAIRunner rebuilt a resumed RunState with RunState.fromString,
which gives the resumed run a JSON copy of its context. Tools that ran
after an approval resume wrote that copy, so the caller's context object
(or one injected with RunState.fromStringWithContext) and the next turn
missed their writes. The upstream Runner uses the state's own context.

Rebuild the state with RunState.fromStringWithContext and the state's own
RunContext. Runs that resumed before this change keep the JSON copy on
replay, behind patched('openai-agents.keep-resume-context').

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] openai-agents (TypeScript): after an approval resume, tools write a copy of the run context, not the caller's object

1 participant