fix(harness): bind sandbox to per-call RuntimeContext to prevent concurrent-call corruption - #2491
Open
xseruer wants to merge 2 commits into
Open
fix(harness): bind sandbox to per-call RuntimeContext to prevent concurrent-call corruption#2491xseruer wants to merge 2 commits into
xseruer wants to merge 2 commits into
Conversation
…urrent-call corruption SandboxBackedFilesystem and SandboxLifecycleMiddleware stored per-call sandbox state in agent-level single slots (a volatile Sandbox field and an AtomicReference<SandboxAcquireResult>), assuming at most one in-flight call per agent instance. Concurrent calls on one agent (different sessions run in parallel by design) corrupted both slots: - a later call overwrote the earlier call's sandbox, routing its filesystem/tool operations into the wrong sandbox; - a finishing call nulled the slot while another call was mid-run, failing it with "No active sandbox"; - release could stop the other call's sandbox session and leak its own. Fix: bind the acquired Sandbox and SandboxAcquireResult to the per-call RuntimeContext (ctx.put) at acquire time, resolve the sandbox from the caller's RuntimeContext in the filesystem proxy, and pair release strictly through the same context. The agent-level SandboxAware slot is kept only as a fallback for context-less internal paths (e.g. RuntimeContext.empty() maintenance writes) and is now CAS-cleared so a finishing call never wipes a newer call's binding.
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
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.
AgentScope-Java Version
2.0.1-SNAPSHOT (main branch); bug also present in 2.0.0
Description
Background
SandboxBackedFilesystemandSandboxLifecycleMiddlewarestored per-call sandbox state in agent-level single slots (avolatile Sandboxfield on the filesystem proxy and anAtomicReference<SandboxAcquireResult>on the middleware), implicitly assuming at most one in-flight call per agent instance.However, a single shared agent bean serving many users/conversations is a common setup, and the SDK's
serializeOnKeyonly serializes calls of the same(userId, sessionId)— cross-session calls run in parallel by design. When two calls run concurrently on one agent, both slots get corrupted in three ways:SandboxConfigurationException: No active sandbox;releaseForCall(getAndSet(null)) could stop the other call's sandbox session and leak its own (never released).We hit the second failure mode in production at scale: an agent dispatching IM group messages across many conversations had a large fraction of runs fail with
No active sandboxwhenever two conversations were active at once. The only workaround was a customSandboxExecutionGuardserializing all calls, degrading cross-session parallelism to FIFO.Changes
SandboxLifecycleMiddleware: removed the agent-levelcurrentAcquireResultslot.acquireForCall(ctx)now binds the acquiredSandboxandSandboxAcquireResultto the per-callRuntimeContext(ctx.put);releaseForCall(ctx)retrieves the acquire result from its own context, so persist/release/lease-close are strictly paired per call.SandboxBackedFilesystem:requireSandbox(ctx)resolves the sandbox from the caller'sRuntimeContextfirst, so concurrent calls each see their own sandbox. The agent-levelSandboxAwareslot is kept only as a fallback for context-less internal paths (e.g.RuntimeContext.empty()maintenance writes inSkillUsageStore/SkillCurator/SkillAuditLog), backed by anAtomicReferencewith a new CAS-basedclearSandbox(expected)so a finishing call never wipes a newer call's binding.SandboxAware#setSandbox/getSandboxbehave as before for existing callers. A follow-up could thread a per-call context through the remaining context-less paths and then deprecate the fallback slot.How to test
SandboxLifecycleMiddlewarePerCallBindingTestcovers: concurrent calls resolving their own sandbox, a finishing call not disturbing (nor releasing the sandbox of) a still-running call, and idempotent release per context.SandboxBackedFilesystemTestwith context-priority resolution and CAS-clear semantics.agentscope-harnesssuite: 679 tests, 0 failures.Checklist
Please check the following items before code is ready to be reviewed.
mvn spotless:applymvn test)