feat(delegation): per-subagent terminal sandbox isolation for delegate_task - #83430
feat(delegation): per-subagent terminal sandbox isolation for delegate_task#83430DavidMetcalfe wants to merge 2 commits into
Conversation
feat(delegation): per-subagent terminal sandbox isolation for delegate_task — no blocking issues found. A few minor observations:
|
…e_task (NousResearch#4271) Add an opt-in sandbox flag to delegate_task: each sandboxed child gets its own container via the existing per-task env-override mechanism (register_task_env_overrides), so parallel workstreams no longer collide on a shared filesystem/env/workspace. Non-sandboxed children keep the documented shared-parent-container contract. - delegate_task(sandbox=True) (top-level or per-task in a batch) registers env_type + image overrides for the child's task_id, which makes _resolve_container_task_id return the child's own container key instead of the parent's; the parent-alias registration is skipped for sandboxed children. A per-task image registered on the parent session (RL rollouts, ACP workspaces) is inherited so the fresh container mirrors the parent's actual environment. - Fails loudly on backends that cannot isolate (local/ssh/vercel_sandbox): tool_error at delegate_task entry before any child spawns, ValueError at spawn as a backstop. An explicit sandbox=True must never silently degrade to the shared parent sandbox. - Overrides are cleared at child teardown (clear_task_env_overrides) so the per-task registry cannot leak; the container itself is removed by the child's existing close() -> cleanup_vm path. - Per-task sandbox values are coerced with the shared truthy parser (is_truthy_value), so model-emitted string 'false'/'true' behave like the top-level flag instead of bool('false') == True. - Schema: sandbox (boolean, default false) on the top-level properties and per-task items; forwarded through _dispatch_delegate_task and the registry handler. - Docs: per-subagent sandbox isolation section in configuration.md. Tests: 21 new cases (schema, override registration per backend incl. parent image inheritance, isolation keying, spawn wiring, entry validation incl. string coercion, teardown cleanup, dispatch forwarding); 197 existing delegate/docker/terminal tests pass unchanged.
…erage, cwd-workdir doc Review feedback (Enough1122): fold the three is_truthy_value sandbox sites into _normalize_sandbox(value, default); pin singularity image override registration (test gap found by the review); document that the seeded child cwd feeds the fresh container's workdir.
2e98eaf to
94e0c92
Compare
|
@Enough1122 — addressed all four points. Also rebased onto current 1. Singularity override — confirmed honored, and now pinned by a test. Both container-creation sites resolve 2. Orphan containers — the existing reapers already cover the sandboxed child (all three predate this PR, which touches none of them):
3. cwd record vs container cwd — they can't diverge at creation: the record is the source of the workdir. The env build derives the container's working directory from the child's session-cwd record ( 4. Normalization drift — agreed, folded into one helper. There were three Verification: |
Summary
Adds an opt-in
sandboxflag todelegate_task(top-level, and per-task inside a batch). Sandboxed subagents get their own fresh container — separate filesystem, isolated environment variables, and an independent working directory — instead of sharing the parent's terminal backend, so parallel workstreams can no longer clobber each other's files, env mutations, or installed packages. Implements feature request #4271.Why
delegate_task(tasks=[...])children share the parent's single container (one bash, one/workspace, one set of installed packages). Concurrentcds, env mutations, and writes to the same path collide across parallel workstreams (previously documented as a known limitation inconfiguration.md). The infrastructure for per-task isolation already exists —register_task_env_overrides()gives any task_id its own container key (used by RL/benchmark harnesses like TerminalBench2) — but it was never wired to delegation and is not model-facing.Changes
tools/delegate_tool.pysandboxboolean param ondelegate_task(top-level + per-task; per-task wins; defaultfalse)._register_child_sandbox_overrides()registersenv_type+ the backend's configured image under the child's task_id, so_resolve_container_task_idkeys the child to its own container; the parent-alias registration (register_container_alias) is skipped for sandboxed children.local/ssh/vercel_sandbox):tool_erroratdelegate_taskentry before any child spawns,ValueErrorat spawn as a backstop. An explicitsandbox=Truenever silently degrades to the shared sandbox.clear_task_env_overrides) — no registry leaks; the container itself is removed by the child's existingclose()→cleanup_vmpath.is_truthy_value), matching the top-level flag, so a model-emitted"false"string behaves correctly.run_agent.py— forwardssandboxthrough_dispatch_delegate_task.website/docs/user-guide/configuration.md— documents per-subagent sandbox isolation and updates the known-limitations caveat.tests/tools/test_delegate_sandbox_isolation.py— 21 new tests.Behavior
delegate_task(goal=..., sandbox=True)sandboxsandbox=Trueonlocal/ssh/vercel_sandboxbackendValidation
"false"coercion), teardown cleanup, dispatch forwarding.Notes
Open question: per-subagent resource limits (issue concern #4 — per-child CPU/memory/disk) are intentionally deferred. The container knobs exist (
container_cpu/container_memory/container_disk) but wiring them per child is a follow-up.Open question: this deliberately uses a boolean
sandboxflag rather than abackend="..."string, so it doesn't pre-empt the named-backend-pool syntax proposed in #32141. Per-backend selection can layer on top later.Closes #4271