jeevesagent.security.sandbox.subprocess

Subprocess sandbox: runs each tool call in a child Python process.

What you get:

  • Process isolation — a tool that crashes (segfault, OOM, etc.) takes down its own subprocess, not the agent.

  • Hard timeout — the parent process kills the child if it exceeds timeout_seconds; the call returns ToolResult.error_(...) with a clear timeout message.

  • Memory boundary — the child’s heap is independent; large intermediate values get GC’d by process exit even if the tool leaks them.

What you don’t get (yet):

  • Filesystem isolation, network restrictions, or syscall sandboxing. For real OS-level isolation, layer this with a Bubblewrap / Seatbelt / Docker / gVisor wrapper as Phase 6 follow-up.

Constraints:

  • The wrapped tool host must be an InProcessToolHost because we need access to the registered Tool.fn callable to ship it to the child process. MCP / external hosts can’t be sandboxed this way (they’re already a process boundary themselves — re-process-isolating them adds nothing).

  • The tool function and its arguments must be picklable. That means module-level functions (top-level def in a module); closures and locally-defined functions can’t cross the process boundary. The @tool-decorated functions in your application modules are usually fine.

Cost:

  • Spawning a Python subprocess takes ~100-300ms on most platforms (macOS uses spawn start method which is slower than fork). Don’t use this for fast tools — the spawn dwarfs the work. It pays off for tools that take seconds, can crash, or use a lot of memory.

Classes

SubprocessSandbox

Run each tool call in a fresh child Python process.

Module Contents

class jeevesagent.security.sandbox.subprocess_.SubprocessSandbox(inner: jeevesagent.core.protocols.ToolHost, *, timeout_seconds: float = 30.0)[source]

Run each tool call in a fresh child Python process.

async call(tool: str, args: collections.abc.Mapping[str, Any], *, call_id: str = '') jeevesagent.core.types.ToolResult[source]
async list_tools(*, query: str | None = None) list[jeevesagent.core.types.ToolDef][source]
async watch() collections.abc.AsyncIterator[jeevesagent.core.types.ToolEvent][source]
property inner: jeevesagent.core.protocols.ToolHost
property timeout_seconds: float