SandboxSession#

Run commands and agent-written programs inside an operating-system boundary. See the guide.

class zett_agent.sandbox.session.SandboxSession(workspace=None, *, memory=None, cpus=None, cpu_seconds=None, processes=None, max_file_size=None, open_files=None, network=False, writable=(), deny_write=(), deny_read=(), env=None, timeout=60.0, backend=SandboxBackend.AUTO, require_isolation=False, memory_strategy=MemoryStrategy.AUTO)[source]#

Run commands inside a sandbox with the workspace and budget you configure.

Say where the process may write and how much it may use; then use it like ordinary Python. Leaving the with block cancels anything still running and removes everything the session created, whether the block ended normally or with an exception.

  • Workspace. Pass a directory to let commands write it, and it is never deleted. Pass nothing and the session creates a private one and deletes it on exit, which is what you want for running a program someone else wrote.

  • Everything else is read-only, apart from private scratch space for temporary files, which is also deleted.

  • The network is off unless you turn it on.

  • Secrets stay out. Common credential files are unreadable and the environment is an allowlist, so a token in yours does not reach the command.

Parameters:
  • workspace (str | os.PathLike[str] | None) – Directory commands may write. None creates a private one that is deleted when the session ends.

  • memory (int | str | None) – Memory the process and everything it starts may use, as bytes or a string such as "512MB".

  • cpus (float | None) – Share of processors, in cores: 0.5 is half of one core, 2 is two. Needs cgroup v2, so it is Linux only; elsewhere it is reported as not enforced.

  • cpu_seconds (int | None) – Total processor time before the kernel kills the process. Sleeping costs none of it.

  • processes (int | None) – Processes a command may add (at least 8). See processes for what is counted on each platform.

  • max_file_size (int | str | None) – Largest file the process may create, as bytes or a string.

  • open_files (int | None) – File descriptors the process may hold open (at least 16).

  • network (bool | NetworkMode) – True for unrestricted network. The default is none at all.

  • writable (Iterable[str | os.PathLike[str]]) – Further directories commands may write.

  • deny_write (Iterable[str | os.PathLike[str]]) – Paths that stay unwritable even inside a writable directory.

  • deny_read (Iterable[str | os.PathLike[str]]) – Paths commands cannot read.

  • env (Mapping[str, str] | None) – Environment variables every command receives, in addition to the allowlisted host ones.

  • timeout (float | None) – Seconds each command may run before it is killed; None for no limit.

  • backend (SandboxBackend | str) – "auto" for this platform’s sandbox, or "seatbelt", "bubblewrap" or "passthrough" to force one.

  • require_isolation (bool) – Raise rather than run unisolated on a platform with no sandbox (Windows).

  • memory_strategy (MemoryStrategy | str) – How to enforce memory; see MemoryStrategy.

Examples

Let a build write one directory, with a budget:

with SandboxSession("/work/project", memory="512MB", cpu_seconds=60) as sb:
    sb.run("python build.py", check=True)
    print(sb.run("ls dist").stdout)

Run a program an agent wrote, in a directory that disappears afterwards:

with SandboxSession(memory="256MB", timeout=30) as sb:
    (sb.workspace / "job.py").write_text(code)
    result = sb.run(["python3", "job.py"])
# the workspace and its files are gone here

From async code:

async with SandboxSession(memory="256MB") as sb:
    result = await sb.arun(["python3", "job.py"])

Note

Limits apply to each command, not to the session as a whole: two commands run one after the other each get the full memory budget.

Warning

A path you pass as workspace or writable is yours and is never removed. Only directories the session creates itself are deleted. On macOS a program that writes a hard-coded path under /tmp instead of using $TMPDIR leaves that file behind, because the sandbox lets it write there; Linux gives each command a private /tmp that disappears.

property is_open: bool#

Whether commands can be run right now.

property workspace: Path#

The directory commands may write, which is also where they start by default.

Raises:

SandboxClosedError – Before the session is opened or after it is closed.

property sandbox: Sandbox#

The underlying Sandbox, for the lower-level API.

capabilities()[source]#

Return how each limit is enforced on this machine; see Sandbox.capabilities().

Return type:

ResourceReport

open()[source]#

Create the workspace and the sandbox. with calls this for you.

Raises:
Return type:

SandboxSession

close()[source]#

Stop everything the session started and remove what it created.

Commands still running are cancelled and their process groups and descendants are killed before anything is removed, so nothing keeps writing into a directory that is being deleted. A process that detached itself from its parent earlier and outlived it cannot be found on macOS; Linux’s PID namespace ends it with the sandbox. Closing twice is harmless. A directory you supplied is left alone.

Return type:

None

async arun(command, *, cwd=None, env=None, input=None, timeout=<object object>, max_output_bytes=1048576, check=False)[source]#

Run a command in the sandbox and return what it produced. The async form of run().

Raises:
  • SandboxClosedError – If the session is not open.

  • SandboxCommandError – If check is true and the command did not succeed.

  • asyncio.CancelledError – If the session was closed while this ran.

Parameters:
  • command (str | Sequence[str])

  • cwd (str | PathLike[str] | None)

  • env (Mapping[str, str] | None)

  • input (str | bytes | None)

  • timeout (float | None)

  • max_output_bytes (int)

  • check (bool)

Return type:

SandboxResult

run(command, *, cwd=None, env=None, input=None, timeout=<object object>, max_output_bytes=1048576, check=False)[source]#

Run a command in the sandbox, wait for it, and return what it produced.

It behaves like subprocess.run(). A string runs through /bin/sh -c so pipes work; a list runs the program directly with nothing parsed, which is safer for anything built from untrusted text. It may be called from several threads, and from code that is itself inside an event loop.

Parameters:
  • command (str | Sequence[str]) – A shell string, or the program followed by its arguments.

  • cwd (str | PathLike[str] | None) – Where to start; relative paths are relative to the workspace, which is the default.

  • env (Mapping[str, str] | None) – Variables added for this command only.

  • input (str | bytes | None) – Text or bytes to feed to its standard input.

  • timeout (float | None) – Seconds before it is killed; defaults to the session’s.

  • max_output_bytes (int) – Bytes kept of each of standard output and error.

  • check (bool) – Raise SandboxCommandError unless it exits with status 0 inside its time limit.

Raises:
Return type:

SandboxResult