Repository navigation
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was changed
When
TemporalOpenAIRunner.runresumes aRunState, the resumed run now keeps the state's own context object, as the upstreamRunnerdoes. Tools that run after an approval resume write the caller's object (or the one injected withRunState.fromStringWithContext), so later reads and the next turn see their writes.RunStateinput, the runner still rebuilds the state from its string so the state points at the converted agent graph, but withRunState.fromStringWithContext(converted, input.toString(), input._context)instead ofRunState.fromString. With the defaultmergestrategy, the approvals in the string are merged into that same context, so approved calls still run.patched('openai-agents.keep-resume-context'), asked only when the input is aRunState.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'spatchActivationCallback(experimental in SDK 1.24), or use Worker Versioning. Runs that never resume aRunStaterecord no marker.Why?
See #239. The runner rebuilt a resumed state with
RunState.fromString, which builds a newRunContextfrom 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 orMaps, were also missing in the resumed run. Nothing failed or warned. Passing{ context: ctx }again with the state didn't help, because the innerRunnerignorescontextfor aRunStateinput.Checklist
Closes [Bug] openai-agents (TypeScript): after an approval resume, tools write a copy of the run context, not the caller's object #239
How was this tested:
Approval resume keeps the caller context objectinsrc/__tests__/test-openai-agents.ts. Its Workflow (approvalResumeContextWorkflow) has one tool that needs approval and writes the run context; it approves, resumes the sameRunState, 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.Approval resume recorded before the context fix still replays. It replays a History of that Workflow recorded onmain(src/__tests__/histories/approval-resume-context-before-fix.json), where the write missed the caller's object and the Workflow didn't sleep.mainwith only the new tests copied in, the integration test fails:{ resumedUsesCallerObject: false }instead of{ callerAfter: 'S-after', resumedUsesCallerObject: true }. The replay test passes there.[TMPRL1100] Nondeterminism error: Timer machine does not handle this event: HistoryEvent(id: 17, WorkflowExecutionCompleted), and the integration test passes.make sync,make lint(8 existing warnings, none in files this PR touches),make test(209 tests; 207 onmain),make build,scripts/ci/check_npm_package.py,node ../_shared/smoke.mjsandscripts/ci/check_conventions.pypass, and Prettier with the SDK settings reports the changed files clean.mainatc664a34, and 0 on this branch, where both resume forms (the sameRunState, and a state rebuilt with an injected context) keep the object as the upstreamRunnerdoes.mainand from this branch for both resume forms, finished, held in the model call after the resume, and waiting for an approval Signal:main's, its own, the rollback build's, and the runs carried across versions.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).patchActivationCallbackdeclining 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.mainreplays 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 forpatched().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.[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.
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:
stream: truewith aRunState) take the same code path but were not run.Maps or class instances was not run; with the state's own context those members are kept, as with the upstreamRunner.Related: #236 also adds a
patchedimport and a patch constant at the top ofsrc/workflow/runner.ts, and the other open drafts for this plugin (#209, #210, #215, #216, #217, #218, #236) add tests at the end ofsrc/__tests__/test-openai-agents.tsandsrc/__tests__/workflows/openai-agents.ts, so whichever merges later may need a small rebase there.Open questions:
RunStateresume 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 needspatchActivationCallback(experimental) or Worker Versioning.input._contextis the framework's underscore-namedRunStatefield (public in its type). There is no other accessor for a state'sRunContext; is relying on it fine with the~0.14.3pin?Draft while these questions are open.