ResourceLimits#

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

class zett_agent.sandbox.policy.ResourceLimits(memory_bytes=None, cpu_seconds=None, cpu_percent=None, processes=None, file_size_bytes=None, open_files=None, core_dumps=False, memory_sample_seconds=0.25)[source]#

What a sandboxed process may use, and how much.

Every field is optional; None means the machine’s own default, which is usually no limit. A value is a request, not a promise: how much of it the kernel can enforce depends on the platform, and capabilities() says what a given machine will actually do with each one. What a backend cannot enforce is reported in the run’s warnings rather than silently dropped. See Run commands in a sandbox for the table of what each platform enforces.

Parameters:
  • memory_bytes (int | None) – Memory the process and everything it starts may use. The kernel accounts for the whole tree where cgroups are available; a per-process address-space limit is used where they are not, which also counts memory that is mapped but never touched. On macOS no kernel limit exists and a sampling watchdog stops a runaway process instead; see MemoryStrategy.

  • cpu_seconds (int | None) – Total processor time each process may consume. The kernel ends it with SIGXCPU once the time is spent, which is different from the wall-clock timeout of a run: a program that sleeps uses no processor time at all. The limit is per process and inherited, so a command that starts helpers gives each of them the same budget; the hard limit is a second higher, to stop a process that handles the signal.

  • cpu_percent (int | None) – Share of one processor the process tree may use, where the kernel can limit it (cgroups). 100 is one core, 250 is two and a half. Sleeps and waiting do not count against it.

  • processes (int | None) – How many processes the command may add. Where a cgroup is available the kernel counts the tree, threads included. Elsewhere the POSIX limit is used, which the kernel compares with every process the user owns, so it is applied as the number already running plus this one; other processes the same user starts meanwhile use the same allowance. Linux counts threads there as well, and macOS does not. The smallest accepted value leaves room for the sandbox’s own launcher, which is itself a process in the tree, and a small value stops a program from starting helpers at all.

  • file_size_bytes (int | None) – Largest file the process may create or extend. Writing past it fails; a file already open can be truncated by it.

  • open_files (int | None) – Number of file descriptors the process may hold open. The smallest accepted value again leaves room for the launcher, which needs descriptors of its own to set the sandbox up.

  • core_dumps (bool) – Whether a crash may write a core file. Off by default: a core file of a large process is large, and it holds whatever the process had read.

  • memory_sample_seconds (float) – How often the watchdog measures memory where one is used. Smaller values catch a runaway sooner and cost a little more.

Examples

Give a command half a processor, 30 seconds of processor time, 512 MiB and no more than 64 processes:

limits = ResourceLimits(
    memory_bytes=512 * 1024**2, cpu_seconds=30, cpu_percent=50, processes=64
)
policy = SandboxPolicy(writable_paths=["/work"], limits=limits)

Warning

A resource limit protects the machine from a process that runs away. It is not a defense against a process that respects no limits: it can still use what it is allowed as fast as it likes, and the watchdog used on macOS can be overshot between two samples.

requested()[source]#

Names of the limits that were asked for, in field order.

Return type:

tuple[str, …]