diff --git a/agentscope-harness/src/main/java/io/agentscope/harness/agent/sandbox/SandboxManager.java b/agentscope-harness/src/main/java/io/agentscope/harness/agent/sandbox/SandboxManager.java index 7c0e04ceb..2dcb0a52e 100644 --- a/agentscope-harness/src/main/java/io/agentscope/harness/agent/sandbox/SandboxManager.java +++ b/agentscope-harness/src/main/java/io/agentscope/harness/agent/sandbox/SandboxManager.java @@ -16,6 +16,8 @@ package io.agentscope.harness.agent.sandbox; import io.agentscope.core.agent.RuntimeContext; +import io.agentscope.harness.agent.sandbox.snapshot.RemoteSandboxSnapshot; +import io.agentscope.harness.agent.sandbox.snapshot.SandboxSnapshot; import java.util.Objects; import java.util.Optional; import org.slf4j.Logger; @@ -103,6 +105,7 @@ public SandboxAcquireResult acquire( "[sandbox] Priority 3: resuming from persisted state (scope={})", scopeKey.get()); SandboxState state = client.deserializeState(stateJson.get()); + reInjectRemoteSnapshot(state, sandboxContext); Sandbox sandbox = client.resume(state); return SandboxAcquireResult.selfManaged(sandbox, lease); } @@ -225,4 +228,24 @@ public void clearState(SandboxContext sandboxContext, RuntimeContext runtimeCont log.warn("[sandbox] Failed to clear sandbox state: {}", e.getMessage(), e); } } + + /** + * Re-wires a deserialized {@link RemoteSandboxSnapshot} with a live + * {@link io.agentscope.harness.agent.sandbox.snapshot.RemoteSnapshotClient} from the + * {@link SandboxContext#getSnapshotSpec()}. + * + *

After JSON deserialization, {@code RemoteSandboxSnapshot} has only the {@code id} + * — the {@code RemoteSnapshotClient} must be re-injected before any I/O operations + * (snapshot restore during sandbox start). + */ + private void reInjectRemoteSnapshot(SandboxState state, SandboxContext sandboxContext) { + SandboxSnapshot snapshot = state.getSnapshot(); + if (snapshot instanceof RemoteSandboxSnapshot && sandboxContext.getSnapshotSpec() != null) { + SandboxSnapshot rewired = sandboxContext.getSnapshotSpec().build(snapshot.getId()); + state.setSnapshot(rewired); + log.debug( + "[sandbox] Re-wired RemoteSandboxSnapshot client for snapshot id={}", + snapshot.getId()); + } + } } diff --git a/agentscope-harness/src/main/java/io/agentscope/harness/agent/sandbox/snapshot/RemoteSandboxSnapshot.java b/agentscope-harness/src/main/java/io/agentscope/harness/agent/sandbox/snapshot/RemoteSandboxSnapshot.java index bace902fc..393fc76c6 100644 --- a/agentscope-harness/src/main/java/io/agentscope/harness/agent/sandbox/snapshot/RemoteSandboxSnapshot.java +++ b/agentscope-harness/src/main/java/io/agentscope/harness/agent/sandbox/snapshot/RemoteSandboxSnapshot.java @@ -15,6 +15,10 @@ */ package io.agentscope.harness.agent.sandbox.snapshot; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import io.agentscope.harness.agent.sandbox.SandboxException; import java.io.InputStream; @@ -28,16 +32,22 @@ * {@link RemoteSnapshotClient} cannot be serialized. When persisting session state, * only the {@code id} is needed — the client is re-injected from the builder at resume time. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class RemoteSandboxSnapshot implements SandboxSnapshot { - - private final RemoteSnapshotClient client; + @JsonIgnore private final RemoteSnapshotClient client; private final String id; + @JsonCreator + private RemoteSandboxSnapshot(@JsonProperty("id") String id) { + this.id = id; + this.client = null; + } + /** * Creates a remote snapshot. * * @param client the remote storage client to delegate operations to - * @param id unique identifier for this snapshot + * @param id unique identifier for this snapshot */ public RemoteSandboxSnapshot(RemoteSnapshotClient client, String id) { this.client = client; @@ -78,6 +88,7 @@ public InputStream restore() throws Exception { *

Checks existence via {@link RemoteSnapshotClient#exists}. */ @Override + @JsonIgnore public boolean isRestorable() throws Exception { try { return client.exists(id); @@ -92,6 +103,7 @@ public String getId() { } @Override + @JsonIgnore public String getType() { return "remote"; }