Repository navigation
fix(code-execution): deliver inner-call results only to the interpreter that asked - #242
Merged
Merged
Conversation
…er that asked Inner tool-call ids (t1, t2, ...) and wake ids restart in every interpreter, and a late result was sent to whichever interpreter was current when it arrived. After a script was aborted, killed at its time limit, or crashed while a call was in flight, and the next script started in a fresh interpreter, the old call's result resolved the new script's t1: the new script went on with the wrong data, and its own result was dropped. Each tool_call / wake is now answered on the child process that sent it; an answer whose interpreter is gone is dropped and logged. Refs #235, #203. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t that made it An inner tool result that ends the turn was recorded per agent and applied to whichever foreground script finished next. A script stopped (time limit, abort) while such a call was running left the call's late request to mark the agent's next script, whose result then ended the turn. A background script's request could likewise end a foreground script's turn. Each foreground exec now gets its own mark, captured by the inner calls it makes; a late result marks only the stopped script, which nothing reads. Background scripts' inner calls carry no mark. Refs #235. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Collaborator
Author
|
@greptileai please re-review at the current head (8fd9523). |
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 8, 2026
…onto current main GitHub reported the anima-research#247 → anima-research#252 → anima-research#256 → anima-research#257 stack DIRTY against main 02adf27 (June-1784 and Basil-1786, room-203 #51425): the conflict sits at the bottom, in anima-research#247. Integrated once here, by merge so every reviewed commit keeps its hash, and propagated upward the same way (Tessa-974 #51438; Basil-1007 reviews the resolutions, #51449). main since 64c480b brings anima-research#236 (reject visibly-empty push and channel content), anima-research#238 (zero-match pattern and feature-set diagnostics), anima-research#240 and anima-research#242 (code-execution deadlines and inner results), anima-research#241 (silent heartbeat turn rows) and anima-research#237 (lifecycle tool calls from code_execution scripts). Two textual conflicts, both additive: - src/mcpl/push-handler.ts imports: main's EmptyContentError and visible-content helpers beside anima-research#247's InboundSource type. - src/framework.ts: main's new silentTurnRowMetadata() sits before recordProseSuppression(); that method keeps anima-research#247's doc comment ("suppressed without drafts (proseRouting=disabled)"), which is what its body does since anima-research#247 holds explicit-send-suppressed prose as drafts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 12, 2026
anima-research#240, anima-research#242, anima-research#237, anima-research#169, anima-research#221) into fix/workspace-truthful-listings Brings the branch up to main 956c6d2. GitHub reported the PR as conflicting after anima-research#169's merge (6c07b6a), so CI didn't run on 2ae3554; git's merge is clean, with no file conflicts.
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 13, 2026
Brings in main through c2f9bae: anima-research#236, anima-research#241, anima-research#238, anima-research#240, anima-research#242, anima-research#237, anima-research#169, anima-research#221, anima-research#248, anima-research#308, anima-research#263 and anima-research#312. One conflict, in src/framework.ts's four MCPL handlers (push/event, inference/request, channels/incoming and host/command). anima-research#263 wraps each handler in answerOnce and refuseServerItem, and refuses the item after the barrier wait once the queue has closed. This branch replaced each handler's single-barrier wait with awaitDiscordAwarenessBarriers(), called only while a generation is current. Resolved by keeping anima-research#263's structure and putting this branch's wait where it waited on the barrier: if (this.discordAwarenessBarrier) await this.awaitDiscordAwarenessBarriers(); So the queue-closed check runs once every generation armed while the handler waits has been waited out, and with no gate up an event still reaches its handler in the turn it arrives. Everything else merged without conflict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Open
1 task done
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.
Why
A
code_executionscript could receive a tool result that belonged to a different script. A stopped script's late call could also end the next script's turn.Inner tool-call ids (
t1,t2, …) and wake ids restart in every interpreter (runtime-py.ts).PyRunnersends each answer withthis.send, which writes to whichever interpreter is current when the answer arrives (py-runner.ts).A script can stop while one of its calls is still running: it is aborted (
abortAgentScriptwhen a stream errors or a turn is abandoned), killed at its time limit, or its interpreter crashes. The call keeps running, as documented: "stopping a script does not stop tools it already called." Suppose the agent then starts another script, which gets a fresh interpreter. The old call's late result arrives astool_result t1and resolves the new script's first call. That script goes on with the wrong data, and its own result is later dropped as a duplicate.The new tests reproduce this on unmodified
mainfor both the abort path and the crash path. The second script prints the stopped script's result.The same thing happened with a request to end the turn.
scriptDeferredEndTurnrecorded an end-turn request per agent and applied it to whichever foreground script finished next. If a script was stopped while a call that ends the turn was still running, the call's late result marked the agent's next script, and that script's result ended the turn. This path doesn't need a new interpreter: a script cancelled at its time limit keeps its interpreter. A background script's request could likewise end a foreground script's turn.What this does
py-runner.ts: each child's line reader passes that child tohandleLine.tool_callandwakeare answered through a newreply(child, …)that writes only if that child is still the current interpreter. An answer whose interpreter is gone is dropped and logged as[pytc:<label>] dropped late result of <tool> call <id>: the interpreter that asked for it is gone. Before this change, such an answer went to the next interpreter, or nowhere if there was none.framework.ts: each foreground exec gets its own end-turn mark, unless the runner is busy and will refuse the exec. The runner'sonToolCallpasses the mark of the script that is running when the call arrives, andhandleScriptToolCallsets that mark. A stopped script's late result therefore marks only that script, which nothing reads any more. Background scripts' calls carry no mark, so their end-turn requests stay with them.changelog.d/fix-code-exec-stale-inner-results.fixed.md.Tests
New:
test/code-execution.test.tshas a new describe block, "code_execution inner-call results after the interpreter is replaced", with 2 tests. Each test holds the first script's tool call open, stops that script, starts a second script and lets it make its own first call. It then resolves the stale call first and the new one after it. The second script must print its own result, and the drop must be logged.abort().os._exitwhile the call is pending).Both fail on unmodified
main: the second script printsstale result of the stopped script.New: a framework test in the same file, "a stopped script's late end-turn request does not end the next script's turn". A script is stopped at a 1s limit while its
finishcall (which ends the turn) is still running. The next script runs, and the call finishes during it. On unmodifiedmain, the next script's result carriesendTurn; here it doesn't.Full suite:
npm run typecheckandnpm run buildare clean. On the final commit,npm testreported 1232 tests: 1223 passed, 5 failed, 4 skipped. All five failures aremcpl-awareness-barrierstartup-gating tests. That file doesn't usecode_execution, and it passes 25/25 when run alone. It's the known full-suite-load timing flake that also shows up onmain.This PR doesn't depend on #240 (blocking code and the time limit). The two branches merge cleanly.
This PR overlaps #237 in
framework.ts. Both change the foreground exec block inrunCodeExecution, the runner'sonToolCallclosure andhandleScriptToolCall's parameters, using the same busy-guarded per-agent pattern as #237'sforegroundScriptOrigins. Whichever merges second gets a small, mechanical conflict. Once #237 claimsbusysynchronously, the busy guard here also covers twocode_executioncalls in one response.Refs #235, #203.
🤖 Generated with Claude Code