Follow Discord
Sweep 22 Oct 2026 · 04:32Z Build v2.1.308 527 read Stable v2.1.308 Latest v2.1.308 Next v2.1.308 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · api

spawn.sh: called once per claimed work item changedmanaged-agents/self-hosted-sandboxes

Nearest release: v2.1.239, published 2 hours before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Recorded here
Lines+26added
Lines−20removed
From line 241 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits17to this page, all time

The whole hunk

from line 241, old and new numbered
/
lines
from line 241
241241 `ant beta:worker poll` claims work items assigned to the environment, downloads skills, executes tool calls in the working directory, and posts results back. It reads `ANTHROPIC_ENVIRONMENT_KEY` and `ANTHROPIC_ENVIRONMENT_ID` from the environment.
242242 
243243 ```bash
244 ant beta:worker poll \
245 --workdir "/workspace"
244 ant beta:worker poll --workdir "/workspace"
246245 ```
247246 
248247 The worker exits cleanly on SIGTERM or SIGINT: it cancels any in-flight tool call, posts its error result, and releases the work item before stopping.
from line 280
281280 Start the poller pointing at the script:
282281 
283282 ```bash
284 ant beta:worker poll \
285 --on-work ./spawn.sh
283 ant beta:worker poll --on-work ./spawn.sh
286284 ```
287285 </Step>
288286 </Steps>
from line 425
427425 ```python Python
428426 import asyncio
429427 import os
430 import signal
431428 import anthropic
432429 import standardwebhooks # installed by the anthropic[webhooks] extra
433430 
from line 433
436433 client = anthropic.AsyncAnthropic(
437434 auth_token=environment_key,
438435 )
439 # Cancelled on SIGINT or SIGTERM (wired in handle) so an in-flight work item can upload
440 # changed memory files and remove its store directories before the process exits.
436 # Cancelled by shutdown() so an in-flight work item can upload changed memory files and
437 # remove its store directories before the process exits.
441438 inflight: set[asyncio.Task[None]] = set()
442439 
443440 
444 def cancel_inflight() -> None:
441 # Await this from the host's shutdown hook, such as an ASGI lifespan shutdown (the code after
442 # `yield` in a FastAPI lifespan), which uvicorn runs on SIGTERM. uvicorn lets open requests
443 # finish before that hook runs, so set --timeout-graceful-shutdown to bound the wait.
444 async def shutdown() -> None:
445445 for task in inflight:
446446 task.cancel()
447 await asyncio.gather(*inflight, return_exceptions=True)
447448 
448449 
449450 async def handle(raw: bytes, headers: dict[str, str]) -> tuple[dict[str, str], int]:
from line 454
453454 return {"error": "signature verification failed"}, 401
454455 if event.data.type != "session.status_run_started":
455456 return {"status": "ignored"}, 200
456 loop = asyncio.get_running_loop()
457 for signum in (signal.SIGINT, signal.SIGTERM):
458 loop.add_signal_handler(signum, cancel_inflight)
459457 task = asyncio.create_task(run_queued_work())
460458 inflight.add(task)
461459 task.add_done_callback(inflight.discard)
462460 try:
463 await task
461 # Shielded: a dropped or timed-out delivery must not cancel the item; shutdown() does.
462 await asyncio.shield(task)
464463 except asyncio.CancelledError:
465464 return {"status": "shutting down"}, 503
466465 return {"status": "ok"}, 200
from line 492
493492 const client = new Anthropic({
494493 authToken: environmentKey
495494 });
496 // Aborted on SIGINT or SIGTERM so an in-flight work item can upload changed memory
497 // files and remove its store directories before the process exits.
498 const shutdown = new AbortController();
499 process.once("SIGINT", () => shutdown.abort());
500 process.once("SIGTERM", () => shutdown.abort());
495 // Call shutdown.abort() from the host's SIGTERM/SIGINT handler, alongside closing the server,
496 // then wait for in-flight handle() calls before exiting: the abort lets a running work item
497 // upload changed memory files and remove its store directories first.
498 export const shutdown = new AbortController();
501499 
502500 export async function handle(req: Request): Promise<Response> {
501 // Never acknowledge a delivery whose work will not run here; a 503 makes the sender retry.
502 if (shutdown.signal.aborted) {
503 return Response.json({ status: "shutting down" }, { status: 503 });
504 }
503505 const body = await req.text();
504506 let event;
505507 try {
from line 532
530532 signal: shutdown.signal
531533 });
532534 }
535 // The poller and handleItem return quietly on abort, so a drain cut short lands here.
536 if (shutdown.signal.aborted) {
537 return Response.json({ status: "shutting down" }, { status: 503 });
538 }
533539 return Response.json({ status: "ok" });
534540 }
535541 ```
from line 691
685691 * `drain`: whether to stop polling once the queue is empty rather than waiting for new work.
686692 * `block_ms`: how long to wait for work to arrive before returning, in milliseconds. Must be between 1 and 999 (per-poll wait; the helper re-polls automatically). Pass `null` (`None` in Python, `param.Null[int64]()` in Go) for a non-blocking check; omitting the parameter uses the default 999 ms long-poll.
687693 * `reclaim_older_than_ms`: re-claim work items that were claimed but never acknowledged within this many milliseconds.
688 * `auto_stop` (`autoStop` in TypeScript, `AutoStop` in Go): whether to post a stop signal for each work item once your loop body finishes with it. Turn it off when the process you launch for the session, rather than the loop body, runs the work item to completion.
694 * `auto_stop` (`autoStop` in TypeScript, `AutoStop` in Go): whether to post a stop signal for each work item once your loop body finishes with it. Turn it off whenever whatever runs the work item posts the stop itself: `handle_item()` does, so set it to false when you hand claimed items to `handle_item()` as the webhook handlers on this page do, and so does a sandbox you launch that owns the stop call.
689695 
690696* **`client.beta.sessions.events.tool_runner()`:** runs tool calls for a single session, given the session ID and a tool list. Use when you've already claimed the work and only need the execution layer.
691697 
from line 1156
11501156Do not create the per-store directories yourself. The worker creates each store's `mount_path` directory (for example, `/mnt/memory/user-preferences`) when a session starts, refuses to start the session's work if something already exists at that path, and removes the directory when the session ends. Two operating rules follow:
11511157 
11521158* **Run one session per filesystem when sessions attach the same store.** Two sessions cannot mount the same store on one host at the same time, because both need the same path. Giving each session its own sandbox, as described in [Run one sandbox per session](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes#run-one-sandbox-per-session), satisfies this rule.
1153* **Stop workers gracefully.** When you stop a worker while a session runs, `EnvironmentWorker` uploads the session's changed memory files and removes its store directories only if it is cancelled rather than killed: a killed process runs no teardown, and the worker does not install signal handlers itself. Wire SIGTERM and SIGINT to cancellation in the process that runs it: abort the `signal` you pass to the worker in TypeScript, cancel the context in Go, and in Python cancel the task that runs `run()` or `handle_item()` from a signal handler. Then stop workers with SIGTERM and give them at least 30 seconds to exit before any hard kill, because the final upload can take that long. If a worker is killed before its teardown runs, remove the leftover store directory under `/mnt/memory/` before the next session that attaches that store; any edits in it that had not synced are lost.
1159* **Stop workers gracefully.** When you stop a worker while a session runs, `EnvironmentWorker` uploads the session's changed memory files and removes its store directories only if it is cancelled rather than killed: a killed process runs no teardown, and the worker does not install signal handlers itself. Wire SIGTERM and SIGINT to cancellation in the process that runs it: abort the `signal` you pass to the worker in TypeScript, cancel the context in Go, and in Python cancel the task that runs `run()` or `handle_item()`. Do that from a signal handler when your worker is the process, as the standalone workers on this page do, or from your server's own shutdown hook when the worker runs inside a webhook handler, which must not take over the server's signals. Then stop workers with SIGTERM and give them at least 30 seconds to exit before any hard kill, because the final upload can take that long. If a worker is killed before its teardown runs, remove the leftover store directory under `/mnt/memory/` before the next session that attaches that store; any edits in it that had not synced are lost.
11541160 
11551161### Run one sandbox per session
11561162 
from line 1348
13421348 
13431349### Read-only stores and conflicts
13441350 
1345For a store attached with `access: "read_only"`, the `write` and `edit` tools refuse to change files inside its directory, and the worker never uploads anything from it. Changes made through `bash` are not blocked locally: they are never synced to the store, and the next remote change to that memory overwrites them. If you need the local copy itself to stay unchanged during the session, disable the `bash` tool for that agent; do not mount the store path read-only, because the worker itself must create the directory and write the downloaded memories into it.
1351For a store attached with `access: "read_only"`, the `write` and `edit` tools refuse to change files inside its directory, and the worker never uploads anything from it. Changes made through `bash`, or through a custom tool or MCP server you serve from the sandbox, are not blocked locally: they are never synced to the store, and the next remote change to that memory overwrites them. If you need the local copy itself to stay unchanged during the session, disable the `bash` tool for that agent and give it no custom tool that writes to the sandbox's filesystem; do not mount the store path read-only, because the worker itself must create the directory and write the downloaded memories into it.
13461352 
13471353Conflicts resolve in favor of the store. When the agent changes a memory file that also changed in the store since the session last synced it, the worker keeps the store's version at the next sync, overwrites the local file with it, and logs a warning; the `write` and `edit` tools themselves succeed and no error reaches the agent. If the agent's change still applies, it can re-read the file after the sync and make the change again.
13481354 
Feedback