daimon-sdk v0.4.1
Python SDK for DAIMON sandbox manager. Create manager-backed sandboxes, connect to MCP endpoints, and call typed file, exec, web, and raw APIs.
Installation
pip install daimon-sdkRequires Python 3.11+. The SDK uses httpx for HTTP transport and supports both manager-backed and direct MCP connections.
Start Manager Locally
The fastest local path is the published manager Docker image plus the release compose file. This starts processd-sandbox-manager,nsjail, and sandbox MCP workers on demand.
# Replace LAN_IP with your host machine's actual LAN IP (e.g. 192.168.1.100)
# Do NOT use 0.0.0.0 — the MCP workers bind inside nsjail and won't route correctly
# Alternatively, uncomment network_mode: host in the compose file to bypass Docker NAT
PROCESSD_MANAGER_PUBLIC_MCP_HOST=LAN_IP docker compose -f compose.manager.release.yaml up -d
curl -i http://127.0.0.1:18080/healthThe release compose defaults to cgroup_required. It needs cgroup v2 mounted writable plus SYS_ADMIN, SETUID, SETGID, SETFCAP, and DAC_OVERRIDE so nsjail can create per-sandbox namespaces and cgroups. If your local Docker environment cannot provide those permissions, use a best-effort manager compose from processd-standalone or run the manager with adjusted limits.
Quickstart
Use DaimonManagerClient when your application owns sandbox lifecycle. The context manager creates a sandbox, connects to its MCP server, and deletes it on exit by default.
import asyncio
from daimon_sdk import DaimonManagerClient
async def main() -> None:
async with DaimonManagerClient("http://127.0.0.1:18080") as manager:
async with manager.sandbox() as sandbox:
runtime = await sandbox.runtime.get_context()
print(runtime.base_workdir)
result = await sandbox.exec.bash("python3 --version")
print(result.display_text)
uploaded = await sandbox.files.upload_bytes(
"artifacts/hello.bin",
b"hello from daimon-sdk",
)
print(uploaded.file_path, uploaded.bytes_written)
asyncio.run(main())DaimonClient
Low-level client that connects directly to a sandbox MCP endpoint. Use this when you already have a sandbox URL and token. For lifecycle management, prefer DaimonManagerClient.
DaimonClient(base_url, *, access_token=None, timeout_s=30.0)Construct a client targeting a sandbox MCP endpoint.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| base_url | str | — | Sandbox MCP endpoint URL (e.g. http://127.0.0.1:18080/mcp). |
| access_token | str | None | None | Optional access token sent as X-Access-Token header. |
| timeout_s | float | 30.0 | HTTP request timeout in seconds. |
Use async context manager (async with DaimonClient(...) as client:) or call await client.connect() / await client.close() manually.
RawAPI
Escape hatch for calling arbitrary MCP tools when the typed surface doesn't cover your use case.
async raw.call_tool(name, arguments=None, *, raise_on_error=True) -> dict[str, Any]Call any MCP tool by name with raw arguments.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| name | str | — | MCP tool name (e.g. 'GetRuntimeContext'). |
| arguments | dict | None | None | Tool arguments as a dictionary. |
| raise_on_error | bool | True | Raise DaimonToolError on tool failure if True. |
Returns: dict[str, Any]
payload = await client.raw.call_tool(
"GetRuntimeContext",
{},
)
print(payload["baseWorkdir"])RuntimeAPI
Access sandbox runtime context including base workdir, filesystem policy, network policy, and capabilities.
async runtime.get_context() -> RuntimeContextResultFetch the sandbox runtime context.
Returns: RuntimeContextResult
runtime = await client.runtime.get_context()
print(runtime.base_workdir)
print(runtime.capabilities)DaimonManagerClient
High-level client that talks to the processd-sandbox-manager HTTP API. Manages sandbox lifecycle — create, find-or-create, start, stop, delete.
DaimonManagerClient(base_url, *, access_token=None, timeout_s=30.0)Construct a manager client targeting the sandbox manager HTTP endpoint.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| base_url | str | — | Manager HTTP endpoint (e.g. http://127.0.0.1:18080). |
| access_token | str | None | None | Access token for X-Access-Token header. |
| timeout_s | float | 30.0 | HTTP request timeout. |
async manager.health() -> boolCheck if the manager is reachable and healthy.
Returns: bool
async manager.capacity() -> ManagerCapacityResultGet current capacity info — memory, PID, CPU limits and usage.
Returns: ManagerCapacityResult
capacity = await manager.capacity()
print(capacity.mode, capacity.memory_bytes.available)async manager.create_sandbox() -> DaimonSandboxCreate a fresh sandbox workspace.
Returns: DaimonSandbox
async manager.find_or_create_sandbox(*, labels, ttl_seconds=None) -> DaimonSandboxFind a sandbox by labels or create one if none matches. Useful for thread-scoped sandbox reuse.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| labels | dict[str, str] | — | Labels to match against existing sandboxes. |
| ttl_seconds | int | None | None | Time-to-live in seconds for the sandbox. |
Returns: DaimonSandbox
sandbox = await manager.find_or_create_sandbox(
labels={"thread_id": "chat-123"},
ttl_seconds=3600,
)async manager.get_sandbox(sandbox_id) -> SandboxInfoGet sandbox info by ID.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| sandbox_id | str | — | Sandbox unique identifier. |
Returns: SandboxInfo
async manager.start_sandbox(sandbox_id) -> SandboxInfoStart (resume) a stopped sandbox.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| sandbox_id | str | — | Sandbox unique identifier. |
Returns: SandboxInfo
async manager.stop_sandbox(sandbox_id) -> SandboxInfoStop (pause) a running sandbox.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| sandbox_id | str | — | Sandbox unique identifier. |
Returns: SandboxInfo
async manager.delete_sandbox(sandbox_id) -> NoneDelete a sandbox and its workspace permanently.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| sandbox_id | str | — | Sandbox unique identifier. |
Returns: None
manager.sandbox(*, delete_on_exit=True) -> SandboxContextReturn an async context manager that creates, connects, and optionally deletes a sandbox.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| delete_on_exit | bool | True | Delete the sandbox when exiting the context. |
Returns: SandboxContext
async with manager.sandbox() as sandbox:
result = await sandbox.exec.bash("pwd")
print(result.display_text)DaimonSandbox
Wraps a sandbox info and its connected MCP client. Exposes the same namespaces as DaimonClient.
SandboxContext
Async context manager returned by manager.sandbox(). Creates a sandbox on enter, connects the client, and optionally deletes on exit.
FilesAPI
Read, write, edit, glob, and grep files inside the sandbox. All methods return typed result objects.
async files.read(file_path, *, offset=None, limit=None, pages=None) -> ReadResultRead a file from the sandbox filesystem. Supports text, image, and multi-page content.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_path | str | — | Path to the file. |
| offset | int | None | None | Starting line for text files. |
| limit | int | None | None | Max lines to read. |
| pages | str | None | None | Page range for PDFs (e.g. '1-5'). |
Returns: ReadResult
read = await client.files.read("/tmp/demo.txt")
print(read.file.content)
print(read.file.num_lines)async files.write(file_path, content) -> WriteResultWrite content to a file. Creates the file if it doesn't exist.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_path | str | — | Path to write to. |
| content | str | — | Text content to write. |
Returns: WriteResult
written = await client.files.write("/tmp/demo.txt", "hello\n")
print(written.file_path)async files.edit(file_path, *, old_string, new_string, replace_all=False) -> EditResultEdit a file by replacing text. Returns a structured patch.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_path | str | — | Path to the file. |
| old_string | str | — | Text to replace. |
| new_string | str | — | Replacement text. |
| replace_all | bool | False | Replace all occurrences. |
Returns: EditResult
async files.glob(pattern, *, path=None) -> GlobResultSearch for files matching a glob pattern.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| pattern | str | — | Glob pattern (e.g. '**/*.py'). |
| path | str | None | None | Root directory for the search. |
Returns: GlobResult
glob = await client.files.glob("**/*.rs", path=runtime.base_workdir)
print(glob.search_path, glob.num_files)async files.grep(pattern, *, path=None, glob=None, output_mode=None, ...) -> GrepResultSearch file contents with regex. Supports the full Grep tool parameter set.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| pattern | str | — | Regex pattern to search for. |
| path | str | None | None | Root directory to search. |
| glob | str | None | None | File filter glob (e.g. '*.py'). |
| output_mode | str | None | None | 'content', 'files_with_matches', or 'count'. |
Returns: GrepResult
grep = await client.files.grep("TODO", path=runtime.base_workdir)
print(grep.num_matches)File Transfer
Upload and download raw bytes through the SDK-only HTTP endpoint (/sdk/file). Meant for SDK and GUI integrations.
async files.upload_bytes(file_path, data) -> FileTransferResultUpload raw bytes to a file in the sandbox.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_path | str | — | Remote path in the sandbox. |
| data | bytes | — | Raw byte content to upload. |
Returns: FileTransferResult
blob = await client.files.upload_bytes(
"artifacts/report.pdf",
pdf_bytes,
)
print(blob.file_path, blob.bytes_written, blob.created)async files.download_bytes(file_path) -> bytesDownload raw bytes from a file in the sandbox.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_path | str | — | Remote path in the sandbox. |
Returns: bytes
data = await client.files.download_bytes("artifacts/report.pdf")
print(len(data))async files.upload_file(local_path, remote_path) -> FileTransferResultUpload a local file to the sandbox.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| local_path | str | Path | — | Local filesystem path. |
| remote_path | str | — | Remote path in the sandbox. |
Returns: FileTransferResult
async files.download_file(remote_path, local_path) -> PathDownload a remote file to the local filesystem.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| remote_path | str | — | Remote path in the sandbox. |
| local_path | str | Path | — | Local destination path. |
Returns: Path
ExecAPI
Run commands, background tasks, and interactive sessions inside the sandbox.
async exec.bash(command, *, timeout_ms=None, description=None, run_in_background=False, dangerously_disable_sandbox=False) -> BashResultExecute a bash command in the sandbox.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| command | str | — | Bash command to execute. |
| timeout_ms | int | None | None | Command timeout in milliseconds. |
| description | str | None | None | Human-readable description of the command. |
| run_in_background | bool | False | Run the command as a background task. |
| dangerously_disable_sandbox | bool | False | Disable sandbox for this command. |
Returns: BashResult
result = await client.exec.bash("printf 'hello\n'")
print(result.stdout)
background = await client.exec.bash(
"sleep 1; echo done",
run_in_background=True,
)
print(background.background_task_id)Sessions
Start a persistent TTY session, then write input, poll output, and wait for the process to exit.
async exec.start_session(cmd, *, workdir=None, tty=False, ...) -> SessionHandleStart an interactive session in the sandbox.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| cmd | str | — | Command to start (e.g. '/bin/cat'). |
| workdir | str | None | None | Working directory for the session. |
| tty | bool | False | Allocate a TTY for the session. |
Returns: SessionHandle
session = await client.exec.start_session("/bin/cat", tty=True)
echoed = await session.write("hello session\n")
print(echoed.output)
final = await session.wait_for_exit()
print(final.exit_code)async session.write(chars='', *, yield_time_ms=None, max_output_tokens=None) -> ExecResultWrite characters to the session's stdin.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| chars | str | '' | Characters to write to stdin. |
| yield_time_ms | int | None | None | Time to wait for output. |
Returns: ExecResult
async session.poll(*, yield_time_ms=None, max_output_tokens=None) -> ExecResultPoll the session for new output without writing any input.
Returns: ExecResult
async session.wait_for_exit(*, timeout_s=10.0, yield_time_ms=5000, ...) -> ExecResultPoll until the session process exits or timeout is reached.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| timeout_s | float | 10.0 | Max wait time in seconds. |
| yield_time_ms | int | 5000 | Time to yield for output per poll. |
Returns: ExecResult
async session.close(*, exit_payload='__EXIT__\\n', yield_time_ms=500, ...) -> ExecResultSend exit signal to the session.
Returns: ExecResult
WebAPI
Fetch web pages through the sandbox. Returns a typed result with content, status code, and metadata.
async web.fetch(url, *, timeout_ms=None, max_bytes=None, follow_same_host_redirects=None) -> WebFetchResultFetch a URL through the sandbox's controlled HTTP client.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| url | str | — | URL to fetch. |
| timeout_ms | int | None | None | Request timeout in milliseconds. |
| max_bytes | int | None | None | Maximum response body size. |
| follow_same_host_redirects | bool | None | None | Follow same-host redirects. |
Returns: WebFetchResult
page = await client.web.fetch("https://example.com")
print(page.status_code)
print(page.content[:200])Authentication
Manager access tokens protect the control plane. Sandbox MCP tokens are returned by the manager and wired into DaimonSandbox automatically.
from daimon_sdk import DaimonManagerClient
async with DaimonManagerClient(
"http://127.0.0.1:18080",
access_token="your-token",
) as manager:
sandbox = await manager.create_sandbox()
await sandbox.connect()
runtime = await sandbox.runtime.get_context()
print(runtime.capabilities)DaimonManagerClient sends X-Access-Token to manager APIs. Direct DaimonClient also sends X-Access-Token to MCP and /sdk/file when you pass access_token.
Exceptions
| Exception | Description |
|---|---|
| DaimonError | Base exception for all SDK errors. |
| DaimonHttpError | HTTP-level errors from manager or file transfer APIs. Includes status_code and payload. |
| DaimonToolError | MCP tool execution failures. Raised when a tool returns an error result. |
| DaimonConnectionError | Connection failures (network unreachable, DNS, timeout). |
| DaimonProtocolError | Protocol-level errors from the MCP transport. |
Models
All tool results expose content_blocks (list of ContentBlock) and display_text (str) for agent/UI output. Use typed fields for logic, raw_payload for debugging.