fix(sandbox): persist lazily-acquired sandbox state via Command (#3464)

* fix(sandbox): persist lazily-acquired sandbox state via Command

ensure_sandbox_initialized mutates runtime.state in place, which is local
to the current tool invocation and is not picked up by LangGraph's channel
reducer. Subsequent graph steps and downstream consumers (such as
ToolOutputBudgetMiddleware and the sub-agent task_tool) therefore cannot
observe the sandbox id from state.

Wrap tool calls in SandboxMiddleware (wrap_tool_call / awrap_tool_call) to
detect fresh lazy initialization by diffing runtime.state before and after
the handler, and emit a proper state update via Command(update=...):

- ToolMessage results are wrapped into Command(update={sandbox, messages})
- Command results with a dict update are merged on the sandbox key while
  preserving messages / goto / graph / resume
- Command results with non-dict updates are left untouched to avoid silent
  data loss on unknown update shapes

Tests:
- 7 new unit tests cover lazy-init emit, passthrough, dict-update merge,
  non-dict-update passthrough (sync and async)
- Refresh replay golden write_read_file.ultra.events.json: SSE 'values'
  events now correctly carry the 'sandbox' key in their keys list, which
  is the direct evidence that the fix is effective

Closes #3463

* refactor(sandbox): use dataclasses.replace to preserve Command fields

Address Copilot review on #3464: replace manual field-copy with
dataclasses.replace so any current or future Command fields are
preserved automatically when merging sandbox_update.

Also add a regression test that constructs a Command with non-None
graph/goto/resume to lock this behavior in.
This commit is contained in:
Huixin615
2026-06-11 17:50:36 +08:00
committed by GitHub
parent 2d5f0787de
commit 919d8bc279
3 changed files with 278 additions and 0 deletions
@@ -69,6 +69,7 @@
"keys": [
"artifacts",
"messages",
"sandbox",
"thread_data",
"title",
"viewed_images"
@@ -79,6 +80,7 @@
"keys": [
"artifacts",
"messages",
"sandbox",
"thread_data",
"title",
"viewed_images"
@@ -89,6 +91,7 @@
"keys": [
"artifacts",
"messages",
"sandbox",
"thread_data",
"title",
"viewed_images"
@@ -99,6 +102,7 @@
"keys": [
"artifacts",
"messages",
"sandbox",
"thread_data",
"title",
"viewed_images"
@@ -109,6 +113,7 @@
"keys": [
"artifacts",
"messages",
"sandbox",
"thread_data",
"title",
"viewed_images"
@@ -119,6 +124,7 @@
"keys": [
"artifacts",
"messages",
"sandbox",
"thread_data",
"title",
"viewed_images"