From 45ff793bd9c61286444eb0b31ea2b3b522cfa7c2 Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 2 Sep 2026 23:27:51 +0800 Subject: [PATCH 1/4] docs: tool descriptions state the contract the code implements The MCP catalog and the LangChain toolkit told the model one line per tool and nothing about timeouts, ring buffers, atomicity, or when to pick exec over spawn; twelve sandbox_id parameters had no description at all. Every description now carries what the SDK documents, the docs/mcp.md table follows. Prompt-audit items F3 and F4. --- docs/mcp.md | 26 ++++---- mcp/tools.go | 64 +++++++++---------- .../cocoonsandbox_langchain/toolkit.py | 17 +++-- 3 files changed, 57 insertions(+), 50 deletions(-) diff --git a/docs/mcp.md b/docs/mcp.md index b19cc6ea..9a89eb9b 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -24,21 +24,21 @@ Build: `cd mcp && go build -o sandbox-mcp .` | tool | what it does | |---|---| -| `create_sandbox` | claim a microVM (warm claims are milliseconds); returns `sandbox_id` | -| `exec` | run a shell command; returns stdout/stderr/exit code; a hibernated sandbox wakes transparently | -| `spawn` | start a command detached; returns its pid immediately | -| `ps` | list tracked processes with state and exit codes | -| `logs` | replay a process's buffered stdout/stderr (+ exit code once ended) | -| `kill` | signal a tracked process (default SIGKILL) | -| `write_file` / `read_file` / `list_dir` | text file operations | -| `fork` | clone into N children carrying exact memory + disk state | -| `checkpoint` | capture full state without stopping; returns `checkpoint_id` | +| `create_sandbox` | claim a microVM and return its id plus deadline; warm claims take milliseconds, nothing renews the deadline | +| `exec` | run a shell command to completion (5-minute cap); returns stdout, stderr and the exit code; a hibernated sandbox wakes transparently | +| `spawn` | start a command detached and return its pid; output goes to a 256 KiB ring buffer that `logs` replays | +| `ps` | list tracked processes (exec, spawn, pty) with state, exit code and start time | +| `logs` | replay a tracked process's last 256 KiB of stdout/stderr (+ exit code once ended) | +| `kill` | signal a tracked process (0 = SIGKILL); an exited process is a no-op success | +| `write_file` / `read_file` / `list_dir` | atomic whole-file write (parent must exist), whole-file text read, one-level directory listing | +| `fork` | clone into N children (1-16) carrying exact memory + disk state, all-or-nothing; the parent keeps running | +| `checkpoint` | capture full state without stopping; returns a `checkpoint_id` that can be branched repeatedly | | `branch_checkpoint` | claim a fresh sandbox from a checkpoint's captured moment | | `list_checkpoints` / `delete_checkpoint` | checkpoint lifecycle | -| `hibernate` | snapshot + stop, freeing memory; wakes on the next tool call | -| `promote` | publish the sandbox as a named template | -| `release` | destroy the sandbox | -| `node_info` | pool, claim, drain, and capacity state | +| `hibernate` | snapshot + stop, freeing memory while keeping id, files and processes; wakes on the next tool call | +| `promote` | publish the sandbox as a named template on its node; re-promoting replaces it | +| `release` | destroy the sandbox and its files; releasing twice succeeds | +| `node_info` | warm pools, live claims, drain state, capacity and mesh peers | Sandbox handles (and their tokens) are held by the server process for the session. Checkpoints outlive sessions: `branch_checkpoint` accepts any known diff --git a/mcp/tools.go b/mcp/tools.go index 36e09840..cdd00b6f 100644 --- a/mcp/tools.go +++ b/mcp/tools.go @@ -13,65 +13,65 @@ import ( var tools = []tool{ { - "create_sandbox", "Claim a fresh microVM sandbox; returns its id. Warm claims are milliseconds.", - schema(props{"template": str("template image ref; empty uses the server default"), "net": str("network lane: none (default) or egress"), "size": str("resource tier: small (default), medium, large, xlarge"), "ttl_seconds": integer("sandbox lifetime in seconds; 0 means one hour, and nothing renews it")}), toolCreateSandbox, + "create_sandbox", "Claim a fresh microVM sandbox and return its id plus deadline. Warm claims take milliseconds; a cold template boots in well under a second. Every other tool takes the returned sandbox_id. The sandbox is destroyed at its deadline unless released earlier; nothing renews it.", + schema(props{"template": str("template image ref, or a name published by promote; empty uses the server default"), "net": str("network lane: none (default, no NIC, vsock-only I/O) or egress (bridge NIC, outbound network)"), "size": str("resource tier: small (default), medium, large, xlarge"), "ttl_seconds": integer("sandbox lifetime in seconds; 0 means one hour, and nothing renews it")}), toolCreateSandbox, }, { - "exec", "Run a command in a sandbox and return stdout/stderr/exit code. A hibernated sandbox wakes transparently.", - schema(props{"sandbox_id": str("sandbox to run in"), "command": str("shell command, run via sh -c"), "cwd": str("working directory (optional)")}, "sandbox_id", "command"), toolExec, + "exec", "Run a shell command in a sandbox, wait for it to exit, and return stdout, stderr, and the exit code as JSON. The call is cut off after 5 minutes; for servers or long jobs use spawn instead. A hibernated sandbox wakes transparently on this call.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "command": str("shell command, run via sh -c"), "cwd": str("working directory; empty runs in the guest's default")}, "sandbox_id", "command"), toolExec, }, { - "spawn", "Start a command detached in a sandbox; returns its pid. Read output later with logs.", - schema(props{"sandbox_id": str(""), "command": str("shell command, run via sh -c"), "cwd": str("working directory (optional)")}, "sandbox_id", "command"), toolSpawn, + "spawn", "Start a shell command detached in a sandbox and return its guest pid immediately. The process keeps running across later tool calls; its output goes to a per-process ring buffer (most recent 256 KiB) that logs replays. Use exec instead when you need the result now.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "command": str("shell command, run via sh -c"), "cwd": str("working directory; empty runs in the guest's default")}, "sandbox_id", "command"), toolSpawn, }, { - "ps", "List a sandbox's tracked processes with state and exit codes.", - schema(props{"sandbox_id": str("")}, "sandbox_id"), toolPs, + "ps", "List the sandbox's tracked processes (exec, spawn, and pty commands) as JSON: pid, argv, detached, state (running or exited), exit_code once exited, and start time. Processes started inside the guest by other means are not listed.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint")}, "sandbox_id"), toolPs, }, { - "kill", "Signal a tracked process (default SIGKILL).", - schema(props{"sandbox_id": str(""), "pid": integer("guest pid from spawn/ps"), "signal": integer("signal number; 0 = SIGKILL")}, "sandbox_id", "pid"), toolKill, + "kill", "Send a signal to a tracked process. Killing a process that already exited is a no-op success; the guest never re-signals a reaped pid.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "pid": integer("guest pid from spawn or ps"), "signal": integer("signal number, e.g. 15 for SIGTERM; 0 sends SIGKILL")}, "sandbox_id", "pid"), toolKill, }, { - "logs", "Replay a process's buffered output (stdout/stderr, exit code when ended).", - schema(props{"sandbox_id": str(""), "pid": integer("guest pid from spawn/ps")}, "sandbox_id", "pid"), toolLogs, + "logs", "Return a tracked process's buffered stdout and stderr, plus exit_code once it has ended. The buffer keeps only the most recent 256 KiB per process, so redirect long output to a file when it must be complete.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "pid": integer("guest pid from spawn or ps")}, "sandbox_id", "pid"), toolLogs, }, { - "write_file", "Write text to a file in a sandbox (atomic on the guest).", - schema(props{"sandbox_id": str(""), "path": str("absolute path"), "content": str("file content")}, "sandbox_id", "path", "content"), toolWriteFile, + "write_file", "Write text content to a file in a sandbox, replacing any existing file. The write is atomic (temp file plus rename); an existing file keeps its permission bits. The parent directory must already exist.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "path": str("absolute path of the file"), "content": str("full file content; written verbatim")}, "sandbox_id", "path", "content"), toolWriteFile, }, { - "read_file", "Read a text file from a sandbox.", - schema(props{"sandbox_id": str(""), "path": str("absolute path")}, "sandbox_id", "path"), toolReadFile, + "read_file", "Return the whole content of a file in a sandbox as text. Meant for text files; for large or binary files use exec with head, tail, or a checksum instead. A missing path is an error.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "path": str("absolute path of the file")}, "sandbox_id", "path"), toolReadFile, }, { - "list_dir", "List a directory in a sandbox.", - schema(props{"sandbox_id": str(""), "path": str("absolute path")}, "sandbox_id", "path"), toolListDir, + "list_dir", "List one directory in a sandbox (not recursive) as JSON entries with name, kind (file, dir, symlink, or other), and size in bytes.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "path": str("absolute path of the directory")}, "sandbox_id", "path"), toolListDir, }, { - "fork", "Clone a sandbox into N independent children carrying its exact memory and disk state; children live one hour.", - schema(props{"sandbox_id": str(""), "count": integer("children, 1-16")}, "sandbox_id", "count"), toolFork, + "fork", "Clone a sandbox into N independent children that start from its exact memory and disk state, including running processes; each child gets its own id and lives one hour. All-or-nothing: on failure no child survives. The parent keeps running.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "count": integer("number of children, 1-16")}, "sandbox_id", "count"), toolFork, }, { - "checkpoint", "Capture a sandbox's full state without stopping it; returns a checkpoint id to branch from.", - schema(props{"sandbox_id": str(""), "name": str("optional label")}, "sandbox_id"), toolCheckpoint, + "checkpoint", "Capture a sandbox's full state (memory, disk, running processes) without stopping it and return a checkpoint id. branch_checkpoint claims new sandboxes from that moment any number of times; checkpoints outlive this session until deleted.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "name": str("optional human label")}, "sandbox_id"), toolCheckpoint, }, { - "branch_checkpoint", "Claim a fresh sandbox branched from a checkpoint's exact captured moment; it lives one hour.", - schema(props{"checkpoint_id": str("")}, "checkpoint_id"), toolBranchCheckpoint, + "branch_checkpoint", "Claim a fresh sandbox that resumes from a checkpoint's exact captured moment and return its id; it lives one hour. The checkpoint is unchanged and can be branched again.", + schema(props{"checkpoint_id": str("id returned by checkpoint or list_checkpoints")}, "checkpoint_id"), toolBranchCheckpoint, }, - {"list_checkpoints", "List checkpoints on the node, newest first.", schema(props{}), toolListCheckpoints}, - {"delete_checkpoint", "Delete a checkpoint.", schema(props{"checkpoint_id": str("")}, "checkpoint_id"), toolDeleteCheckpoint}, + {"list_checkpoints", "List the node's checkpoints newest first: checkpoint_id, name, source sandbox_id, created_at.", schema(props{}), toolListCheckpoints}, + {"delete_checkpoint", "Delete a checkpoint permanently. Sandboxes already branched from it are unaffected.", schema(props{"checkpoint_id": str("id returned by checkpoint or list_checkpoints")}, "checkpoint_id"), toolDeleteCheckpoint}, { - "hibernate", "Snapshot and stop a sandbox, freeing its memory; any later tool call wakes it transparently.", - schema(props{"sandbox_id": str("")}, "sandbox_id"), toolHibernate, + "hibernate", "Snapshot a sandbox and stop its VM, freeing memory while keeping its id, files, processes, and shell state. Any later tool call on the id wakes it transparently (tens of milliseconds).", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint")}, "sandbox_id"), toolHibernate, }, { - "promote", "Publish a sandbox's state as a named template; later create_sandbox calls can claim it by name.", - schema(props{"sandbox_id": str(""), "template_name": str("template name to publish as")}, "sandbox_id", "template_name"), toolPromote, + "promote", "Publish the sandbox's current state as a named template on its node; later create_sandbox calls with that name (and the same net and size) start from it. Re-promoting to the same name replaces the template.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "template_name": str("template name to publish as")}, "sandbox_id", "template_name"), toolPromote, }, - {"release", "Release a sandbox; its VM is destroyed.", schema(props{"sandbox_id": str("")}, "sandbox_id"), toolRelease}, - {"node_info", "The node's pool, claim, drain, and capacity state.", schema(props{}), toolNodeInfo}, + {"release", "Destroy a sandbox and free its resources; files and processes inside it are lost. Releasing an already-released id succeeds.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint")}, "sandbox_id"), toolRelease}, + {"node_info", "Report the connected node's warm pools, live claims, drain state, capacity, and mesh peers as JSON.", schema(props{}), toolNodeInfo}, } // tool binds one MCP tool's spec to its handler, so a tool can never exist diff --git a/sdk/langchain/cocoonsandbox_langchain/toolkit.py b/sdk/langchain/cocoonsandbox_langchain/toolkit.py index 49398a77..14761245 100644 --- a/sdk/langchain/cocoonsandbox_langchain/toolkit.py +++ b/sdk/langchain/cocoonsandbox_langchain/toolkit.py @@ -57,17 +57,24 @@ def get_tools(self) -> list[StructuredTool]: """The sandbox tool set; sync-native (_run), async via to_thread.""" return [ self._tool("sandbox_exec", - "Run a shell command in the sandbox; returns stdout, stderr, " - "and the exit code. State on disk persists across calls.", + "Run a shell command in the sandbox and wait for it to exit. " + "Returns stdout; a non-empty stderr is appended as a 'stderr:' " + "line and a non-zero status as an 'exit code: N' line. Files " + "and installed packages persist across calls; environment " + "variables and the working directory do not.", ExecInput, self._exec), self._tool("sandbox_write_file", - "Write a text file in the sandbox (atomic).", + "Write text to a file in the sandbox, replacing any existing " + "file atomically. The parent directory must already exist.", WriteFileInput, self._write_file), self._tool("sandbox_read_file", - "Read a text file from the sandbox.", + "Return the whole content of a file in the sandbox as text " + "(undecodable bytes are replaced). Prefer sandbox_exec with " + "head or tail for large files.", PathInput, self._read_file), self._tool("sandbox_list_dir", - "List a sandbox directory as JSON entries.", + "List one directory (not recursive) as a JSON array of " + "{name, kind, size}; kind is file, dir, symlink, or other.", PathInput, self._list_dir), ] From 9178b269997dfc53bd32ad04693f166adfc861cc Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 2 Sep 2026 23:27:51 +0800 Subject: [PATCH 2/4] docs: 2026-09-02 bare-metal benchmark row --- docs/benchmarks.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 9cdcf570..442e6fff 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -89,6 +89,38 @@ labeled and must only be compared against other nested runs. +### 2026-09-02 — bare metal (4604b69, Batch 0 close-out round) + +| environment | | +|---|---| +| host | bare metal, AMD Ryzen 7 9700X 8-Core Processor, 16 cores, 60 GiB | +| kernel | 7.0.0-30-generic | +| cpufreq | powersave/balance_performance | +| cocoon | master-8962579 | +| cloud-hypervisor | dev 5633d38 (cocoonstack fork, v54.0.0) | +| template | ghcr.io/cocoonstack/sandbox/rt:24.04 @ sha256:804a596ee808 | + +| claim tier | p50 | p90 | max | n | +|---|---|---|---|---| +| warm pool hit | 0.3 ms | 0.3 ms | 0.6 ms | 6 | +| clone from golden | 0.3 ms | 0.4 ms | 0.5 ms | 10 | +| cold boot (unpooled ghcr.io/cocoonstack/sandbox/python:3.12) | 216.5 ms | 216.5 ms | 218.3 ms | 3 | +| burst: 16 concurrent clones | 208.5 ms | 247.2 ms | 395.0 ms | 16 | + +The clone-tier row still measures warm hits (see 2026-07-22); the burst +row is the clone path. Burst reads slower than 2026-07-22 (102 → 208 ms +p50) on a newer host kernel (7.0.0-30 vs -28); the same sandboxd A/B'd +against the pre-round main on this host and kernel read within noise, so +the delta is not the round's code. + +| data plane | measured | +|---|---| +| exec RTT (dial per RPC) | n=200 p50=0.14ms p90=0.22ms p99=0.34ms | +| fs_pull throughput (128 MiB) | 659.4 MiB/s best of 3 | +| burst wall (16 concurrent clones) | 458 ms | +| warm refill recovery (0 → 6) | 3 ms | + + ### 2026-07-22 — bare metal (3e54866, CH-only round: both lanes on Cloud Hypervisor) | environment | | From aa994509e56e0be128e69b584179d795c96cd558 Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 2 Sep 2026 23:46:58 +0800 Subject: [PATCH 3/4] docs: descriptions promise only what the code does The catalog said every tool takes sandbox_id, an exact 256 KiB tail, a fixed 1-16 fork range, checkpoints kept until deleted, permanent fleet-wide deletion, wake on any call, and idempotent release; the code scopes sandbox_id to sandbox tools, keeps whole chunks up to 256 KiB, caps forks at max_fork_count, expires checkpoints under the TTL, deletes the node's copy, wakes on guest access, and forgets a released id. The benchmark note now cites the A/B rows it rests on. --- docs/benchmarks.md | 10 ++++++---- docs/mcp.md | 10 +++++----- mcp/tools.go | 20 +++++++++---------- .../cocoonsandbox_langchain/toolkit.py | 9 +++++---- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 442e6fff..a6680f62 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -109,9 +109,12 @@ labeled and must only be compared against other nested runs. The clone-tier row still measures warm hits (see 2026-07-22); the burst row is the clone path. Burst reads slower than 2026-07-22 (102 → 208 ms -p50) on a newer host kernel (7.0.0-30 vs -28); the same sandboxd A/B'd -against the pre-round main on this host and kernel read within noise, so -the delta is not the round's code. +p50) on a newer host kernel (7.0.0-30 vs -28). The same-day A/B of this +sandboxd (4604b69) against the pre-round main (d5ba35d), three interleaved +reps each on this host and kernel, read burst p50 104 / 75 / 51 ms vs +85 / 134 / 159 ms, warm 0.3 ms on both, cold 216–221 vs 213–219 ms, so the +delta is the host, not the round's code (cocoon-specs +`tests/2026-09-02-batch0-hardware-regression.md`, B0-05). | data plane | measured | |---|---| @@ -120,7 +123,6 @@ the delta is not the round's code. | burst wall (16 concurrent clones) | 458 ms | | warm refill recovery (0 → 6) | 3 ms | - ### 2026-07-22 — bare metal (3e54866, CH-only round: both lanes on Cloud Hypervisor) | environment | | diff --git a/docs/mcp.md b/docs/mcp.md index 9a89eb9b..7c6c8b33 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -28,16 +28,16 @@ Build: `cd mcp && go build -o sandbox-mcp .` | `exec` | run a shell command to completion (5-minute cap); returns stdout, stderr and the exit code; a hibernated sandbox wakes transparently | | `spawn` | start a command detached and return its pid; output goes to a 256 KiB ring buffer that `logs` replays | | `ps` | list tracked processes (exec, spawn, pty) with state, exit code and start time | -| `logs` | replay a tracked process's last 256 KiB of stdout/stderr (+ exit code once ended) | +| `logs` | replay up to 256 KiB of a tracked process's newest whole stdout/stderr chunks (+ exit code once ended) | | `kill` | signal a tracked process (0 = SIGKILL); an exited process is a no-op success | -| `write_file` / `read_file` / `list_dir` | atomic whole-file write (parent must exist), whole-file text read, one-level directory listing | -| `fork` | clone into N children (1-16) carrying exact memory + disk state, all-or-nothing; the parent keeps running | +| `write_file` / `read_file` / `list_dir` | atomic whole-file write (parent must exist); whole-file text read (invalid UTF-8 replaced, missing path is an error); one-level listing of `{name, kind, size}` entries | +| `fork` | clone into N children (1 to the node's `max_fork_count`, default 16) carrying exact memory + disk state, all-or-nothing; the parent keeps running | | `checkpoint` | capture full state without stopping; returns a `checkpoint_id` that can be branched repeatedly | | `branch_checkpoint` | claim a fresh sandbox from a checkpoint's captured moment | | `list_checkpoints` / `delete_checkpoint` | checkpoint lifecycle | -| `hibernate` | snapshot + stop, freeing memory while keeping id, files and processes; wakes on the next tool call | +| `hibernate` | snapshot + stop, freeing memory while keeping id, files and processes; the next call that reaches the guest wakes it | | `promote` | publish the sandbox as a named template on its node; re-promoting replaces it | -| `release` | destroy the sandbox and its files; releasing twice succeeds | +| `release` | destroy the sandbox and its files; the session forgets the id, so a second release is rejected as unknown | | `node_info` | warm pools, live claims, drain state, capacity and mesh peers | Sandbox handles (and their tokens) are held by the server process for the diff --git a/mcp/tools.go b/mcp/tools.go index cdd00b6f..f6c9c5f2 100644 --- a/mcp/tools.go +++ b/mcp/tools.go @@ -13,7 +13,7 @@ import ( var tools = []tool{ { - "create_sandbox", "Claim a fresh microVM sandbox and return its id plus deadline. Warm claims take milliseconds; a cold template boots in well under a second. Every other tool takes the returned sandbox_id. The sandbox is destroyed at its deadline unless released earlier; nothing renews it.", + "create_sandbox", "Claim a fresh microVM sandbox and return its id plus deadline. Warm claims take milliseconds; a cold template boots in well under a second. Every sandbox-scoped tool takes the returned sandbox_id. The sandbox is destroyed at its deadline unless released earlier; nothing renews it.", schema(props{"template": str("template image ref, or a name published by promote; empty uses the server default"), "net": str("network lane: none (default, no NIC, vsock-only I/O) or egress (bridge NIC, outbound network)"), "size": str("resource tier: small (default), medium, large, xlarge"), "ttl_seconds": integer("sandbox lifetime in seconds; 0 means one hour, and nothing renews it")}), toolCreateSandbox, }, { @@ -21,7 +21,7 @@ var tools = []tool{ schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "command": str("shell command, run via sh -c"), "cwd": str("working directory; empty runs in the guest's default")}, "sandbox_id", "command"), toolExec, }, { - "spawn", "Start a shell command detached in a sandbox and return its guest pid immediately. The process keeps running across later tool calls; its output goes to a per-process ring buffer (most recent 256 KiB) that logs replays. Use exec instead when you need the result now.", + "spawn", "Start a shell command detached in a sandbox and return its guest pid immediately. The process keeps running across later tool calls; its output goes to a per-process ring buffer that keeps up to 256 KiB of the newest whole output chunks, which logs replays. Use exec instead when you need the result now.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "command": str("shell command, run via sh -c"), "cwd": str("working directory; empty runs in the guest's default")}, "sandbox_id", "command"), toolSpawn, }, { @@ -33,7 +33,7 @@ var tools = []tool{ schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "pid": integer("guest pid from spawn or ps"), "signal": integer("signal number, e.g. 15 for SIGTERM; 0 sends SIGKILL")}, "sandbox_id", "pid"), toolKill, }, { - "logs", "Return a tracked process's buffered stdout and stderr, plus exit_code once it has ended. The buffer keeps only the most recent 256 KiB per process, so redirect long output to a file when it must be complete.", + "logs", "Return a tracked process's buffered stdout and stderr, plus exit_code once it has ended. The buffer keeps up to 256 KiB of the newest whole output chunks per process, so redirect long output to a file when it must be complete.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "pid": integer("guest pid from spawn or ps")}, "sandbox_id", "pid"), toolLogs, }, { @@ -41,7 +41,7 @@ var tools = []tool{ schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "path": str("absolute path of the file"), "content": str("full file content; written verbatim")}, "sandbox_id", "path", "content"), toolWriteFile, }, { - "read_file", "Return the whole content of a file in a sandbox as text. Meant for text files; for large or binary files use exec with head, tail, or a checksum instead. A missing path is an error.", + "read_file", "Return the whole content of a file in a sandbox as text; bytes that are not valid UTF-8 are replaced, so binary content is lossy. For large or binary files use exec with head, tail, or a checksum instead. A missing path is an error.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "path": str("absolute path of the file")}, "sandbox_id", "path"), toolReadFile, }, { @@ -49,11 +49,11 @@ var tools = []tool{ schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "path": str("absolute path of the directory")}, "sandbox_id", "path"), toolListDir, }, { - "fork", "Clone a sandbox into N independent children that start from its exact memory and disk state, including running processes; each child gets its own id and lives one hour. All-or-nothing: on failure no child survives. The parent keeps running.", - schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "count": integer("number of children, 1-16")}, "sandbox_id", "count"), toolFork, + "fork", "Clone a sandbox into N independent children that start from its exact memory and disk state, including running processes; each child gets its own id and lives one hour. N is capped by the node's max_fork_count (default 16). All-or-nothing: on failure no child survives. The parent keeps running.", + schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "count": integer("number of children, 1 to the node's max_fork_count (default 16)")}, "sandbox_id", "count"), toolFork, }, { - "checkpoint", "Capture a sandbox's full state (memory, disk, running processes) without stopping it and return a checkpoint id. branch_checkpoint claims new sandboxes from that moment any number of times; checkpoints outlive this session until deleted.", + "checkpoint", "Capture a sandbox's full state (memory, disk, running processes) without stopping it and return a checkpoint id. branch_checkpoint claims new sandboxes from that moment any number of times; a checkpoint outlives this session until it is deleted or expires under the node's checkpoint TTL.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "name": str("optional human label")}, "sandbox_id"), toolCheckpoint, }, { @@ -61,16 +61,16 @@ var tools = []tool{ schema(props{"checkpoint_id": str("id returned by checkpoint or list_checkpoints")}, "checkpoint_id"), toolBranchCheckpoint, }, {"list_checkpoints", "List the node's checkpoints newest first: checkpoint_id, name, source sandbox_id, created_at.", schema(props{}), toolListCheckpoints}, - {"delete_checkpoint", "Delete a checkpoint permanently. Sandboxes already branched from it are unaffected.", schema(props{"checkpoint_id": str("id returned by checkpoint or list_checkpoints")}, "checkpoint_id"), toolDeleteCheckpoint}, + {"delete_checkpoint", "Delete the node's copy of a checkpoint; a replica a peer node healed stays branchable until its own TTL sweep. Sandboxes already branched from it are unaffected.", schema(props{"checkpoint_id": str("id returned by checkpoint or list_checkpoints")}, "checkpoint_id"), toolDeleteCheckpoint}, { - "hibernate", "Snapshot a sandbox and stop its VM, freeing memory while keeping its id, files, processes, and shell state. Any later tool call on the id wakes it transparently (tens of milliseconds).", + "hibernate", "Snapshot a sandbox and stop its VM, freeing memory while keeping its id, files, processes, and shell state. The next call that reaches the guest (exec, spawn, ps, kill, logs, the file tools) wakes it transparently in tens of milliseconds; fork, checkpoint, promote and release act on the snapshot without waking it.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint")}, "sandbox_id"), toolHibernate, }, { "promote", "Publish the sandbox's current state as a named template on its node; later create_sandbox calls with that name (and the same net and size) start from it. Re-promoting to the same name replaces the template.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint"), "template_name": str("template name to publish as")}, "sandbox_id", "template_name"), toolPromote, }, - {"release", "Destroy a sandbox and free its resources; files and processes inside it are lost. Releasing an already-released id succeeds.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint")}, "sandbox_id"), toolRelease}, + {"release", "Destroy a sandbox and free its resources; files and processes inside it are lost. This session forgets the id, so a second release of it is rejected as unknown.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint")}, "sandbox_id"), toolRelease}, {"node_info", "Report the connected node's warm pools, live claims, drain state, capacity, and mesh peers as JSON.", schema(props{}), toolNodeInfo}, } diff --git a/sdk/langchain/cocoonsandbox_langchain/toolkit.py b/sdk/langchain/cocoonsandbox_langchain/toolkit.py index 14761245..565cda92 100644 --- a/sdk/langchain/cocoonsandbox_langchain/toolkit.py +++ b/sdk/langchain/cocoonsandbox_langchain/toolkit.py @@ -59,8 +59,9 @@ def get_tools(self) -> list[StructuredTool]: self._tool("sandbox_exec", "Run a shell command in the sandbox and wait for it to exit. " "Returns stdout; a non-empty stderr is appended as a 'stderr:' " - "line and a non-zero status as an 'exit code: N' line. Files " - "and installed packages persist across calls; environment " + "line and a non-zero status as an 'exit code: N' line; a " + "command that prints nothing and exits 0 returns '(no output)'. " + "Files and installed packages persist across calls; environment " "variables and the working directory do not.", ExecInput, self._exec), self._tool("sandbox_write_file", @@ -69,8 +70,8 @@ def get_tools(self) -> list[StructuredTool]: WriteFileInput, self._write_file), self._tool("sandbox_read_file", "Return the whole content of a file in the sandbox as text " - "(undecodable bytes are replaced). Prefer sandbox_exec with " - "head or tail for large files.", + "(undecodable bytes are replaced); a missing path is a tool " + "error. Prefer sandbox_exec with head or tail for large files.", PathInput, self._read_file), self._tool("sandbox_list_dir", "List one directory (not recursive) as a JSON array of " From fa3c7fcf11c5e67d24255c606b6f4c55ce23f0ed Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 2 Sep 2026 23:51:39 +0800 Subject: [PATCH 4/4] docs: delete_checkpoint names the peer broadcast --- mcp/tools.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp/tools.go b/mcp/tools.go index f6c9c5f2..cba2e9ce 100644 --- a/mcp/tools.go +++ b/mcp/tools.go @@ -61,7 +61,7 @@ var tools = []tool{ schema(props{"checkpoint_id": str("id returned by checkpoint or list_checkpoints")}, "checkpoint_id"), toolBranchCheckpoint, }, {"list_checkpoints", "List the node's checkpoints newest first: checkpoint_id, name, source sandbox_id, created_at.", schema(props{}), toolListCheckpoints}, - {"delete_checkpoint", "Delete the node's copy of a checkpoint; a replica a peer node healed stays branchable until its own TTL sweep. Sandboxes already branched from it are unaffected.", schema(props{"checkpoint_id": str("id returned by checkpoint or list_checkpoints")}, "checkpoint_id"), toolDeleteCheckpoint}, + {"delete_checkpoint", "Delete the node's copy of a checkpoint and tell peers to drop theirs; a peer replica that misses that broadcast stays branchable until its own TTL sweep. Sandboxes already branched from it are unaffected.", schema(props{"checkpoint_id": str("id returned by checkpoint or list_checkpoints")}, "checkpoint_id"), toolDeleteCheckpoint}, { "hibernate", "Snapshot a sandbox and stop its VM, freeing memory while keeping its id, files, processes, and shell state. The next call that reaches the guest (exec, spawn, ps, kill, logs, the file tools) wakes it transparently in tens of milliseconds; fork, checkpoint, promote and release act on the snapshot without waking it.", schema(props{"sandbox_id": str("id returned by create_sandbox, fork, or branch_checkpoint")}, "sandbox_id"), toolHibernate,