Axern Python SDK
The Axern Python SDK is the first-class programmable interface for Axern
sandboxes. It exposes both the control plane client and a high-level
Sandbox API for lifecycle, command execution, attached processes, file
operations, directory transfer, tunnels, capability discovery, and diagnostics.
Install
Install the published package:
uv add axern-sdk
For repository development, build and run examples through the root uv
workspace:
uv build --no-sources sdk/python
uv run --package axern-sdk python sdk/python/examples/sandbox_programming.py
Connect
from axern_sdk import AxernClient
client = AxernClient.from_context("~/.config/axern/config.json")
TLS-enabled local compose setups can pass certificate paths directly:
client = AxernClient(
"127.0.0.1:25000",
tls_ca_cert=".dev/certs/ca.crt",
tls_cert=".dev/certs/client.crt",
tls_key=".dev/certs/client.key",
)
AsyncAxernClient provides the same control-plane surface for asyncio code.
Constructors are explicit and never read the user directory. from_context()
loads a named CLI context from the supplied path; from_env() is available for
environment-driven automation and reads AXERN_ENDPOINT plus the gateway TLS
and proxy variables.
Sandbox Sources
Create a sandbox from exactly one source:
image="docker.io/library/python:3.12-slim"for an OCI image.template_id="python311"for a catalog template.environment_id="..."for an existing environment.
from axern_sdk import AxernClient, Sandbox
client = AxernClient("127.0.0.1:25000")
with Sandbox(client=client, image="docker.io/library/python:3.12-slim") as sandbox:
print(sandbox.metadata.allocation_id)
client.close()
Network Policies
Omitting network_policy preserves unrestricted v0.5 behavior. Strict
policies are fail-closed; deny_dns only refuses matching traditional UDP/TCP
DNS queries and does not block direct IP traffic, DoH, DoT, or already-resolved
addresses.
from axern_sdk import NetworkPolicy, Sandbox
with Sandbox(
client=client,
image="docker.io/library/python:3.12-slim",
network_policy=NetworkPolicy.deny_dns(
"github.com",
"*.github.com",
"githubusercontent.com",
"*.githubusercontent.com",
"gitlab.com",
"*.gitlab.com",
"bitbucket.org",
"*.bitbucket.org",
),
) as sandbox:
print(sandbox.metadata.allocation_id)
NetworkPolicy.allow_domains("example.com", "*.example.com") is strict: only
HTTP/HTTPS traffic whose controlled DNS result and HTTP Host or TLS SNI match is
allowed. Use NetworkPolicy.strict(..., cidr_rules=(CIDRRule(...),)) for
explicit TCP/UDP CIDR and port grants, and NetworkPolicy.deny_all() for no
egress.
Volumes
Use VolumeMount to attach Service V1 volumes to service-backed sandboxes.
The SDK passes volume intent through the public control plane; storage
resolution, node publish, mount injection, and release remain owned by Axern's
Storage V1 runtime flow.
from axern_sdk import Sandbox, VolumeMount
with Sandbox(
client=client,
image="docker.io/library/python:3.12-slim",
volumes=[
VolumeMount("data", "/data"),
VolumeMount("cache", "/cache", readonly=True, options=("rbind",)),
],
) as sandbox:
result = sandbox.exec("ls /data /cache", text=True, check=True)
print(result.stdout)
Function Manifests
Function.from_file() loads and validates an axern/v1 Function resource.
Function.package() creates a deterministic tar bundle, and Function.deploy()
packages the source, uploads it with FunctionControl.UploadFunctionBundle,
and then calls FunctionControl.DeployFunction. The python311 runtime image
includes the SDK Function worker module used by controld-managed warm workers.
Function.invoke() calls the dedicated Function invocation API and returns a
decoded invocation result.
from axern_sdk import AxernClient, Function
client = AxernClient.from_context("~/.config/axern/config.json")
function = Function.from_file(client, "examples/function-hello/function.yaml")
deployment = function.deploy(labels={"team": "runtime"})
print(function.name)
print(function.spec.handler)
print(deployment.function.id)
Exec
Use exec() for command-result workflows. Set text=True to decode stdout and
stderr; set check=True to raise SandboxExecError on non-zero exit.
with Sandbox(client=client, image="docker.io/library/python:3.12-slim") as sandbox:
result = sandbox.exec("python -c \"print('hello')\"", text=True, check=True)
print(result.stdout)
Use exec_stream() when stdout/stderr should be consumed as events:
for event in sandbox.exec_stream(["python", "-u", "-c", "print('streamed')"]):
if event.stream == "stdout":
print(event.text(), end="")
Attached Process
Use process() when your program needs to control stdin, observe output, wait,
or terminate a running command.
with sandbox.process(["python", "-u", "-c", "import sys; print(sys.stdin.read().upper())"]) as process:
process.write("hello process\n")
process.close_stdin()
for event in process.events():
if event.stream == "stdout":
print(event.text(), end="")
result = process.wait()
print(result.exit_code)
AsyncSandbox.process() returns AsyncSandboxProcess with async equivalents of
write(), close_stdin(), events(), wait(), terminate(), and kill().
Image-Backed Processes
Use exec_image() or process_image() to run a tool from a separate image
against explicit host-backed sandbox paths. OCI and Nydus image refs use the
same image field. When mounts=None, the SDK requests /workspace -> /workspace; pass mounts=[] for no shared paths. Use Sandbox(image=...)
when the image should be the sandbox rootfs with normal files, exec, process,
tunnel, and lifecycle APIs; image-backed processes are temporary side processes
attached to an existing sandbox.
from axern_sdk import workspace_mount
result = sandbox.exec_image(
"ghcr.io/cofy-x/agent:latest",
"tool run",
mounts=[workspace_mount("/workspace")],
check=True,
text=True,
)
print(result.stdout)
Files
Single-file APIs are byte-safe. Text helpers only encode/decode at the SDK boundary.
sandbox.write_text("/tmp/message.txt", "payload\n")
print(sandbox.read_text("/tmp/message.txt"))
sandbox.write_bytes("/tmp/blob.bin", b"\x00\x01")
data = sandbox.read_bytes("/tmp/blob.bin")
Platform file operations are handled by the node/runtime file service, not by SDK-side shell fallbacks:
sandbox.copy("/tmp/message.txt", "/tmp/message-copy.txt", overwrite=True)
sandbox.move("/tmp/message-copy.txt", "/tmp/message-final.txt")
sandbox.chmod("/tmp/message-final.txt", 0o600)
sandbox.touch("/tmp/message-final.txt")
info = sandbox.stat("/tmp/message-final.txt")
entries = sandbox.list_dir("/tmp")
exists = sandbox.exists("/tmp/message-final.txt")
Directory Transfer
Directory upload/download uses archive streaming. The SDK packages local
directories with tarfile and safely extracts downloaded archives; remote file
semantics remain owned by the platform file service.
from pathlib import Path
source = Path("example-upload")
source.mkdir(exist_ok=True)
source.joinpath("data.txt").write_text("directory payload\n")
sandbox.upload_dir(source, "/tmp/example-upload", overwrite=True)
sandbox.download_dir("/tmp/example-upload", "example-download", overwrite=True)
Local symlinks are rejected during upload. Download extraction rejects absolute paths, parent traversal, symlinks, and hardlinks.
Tunnel
Pass upstream to expose a local TCP service to code running inside the
sandbox. The SDK owns the tunnel connector and renews finite tunnel TTLs while
the sandbox is active.
from axern_sdk import Sandbox
with Sandbox(
client=client,
image="docker.io/library/python:3.12-slim",
upstream="127.0.0.1:8080",
remote_port=8786,
) as sandbox:
print(sandbox.bound_addr)
Metadata
Sandbox.state is the lightweight runtime state. Sandbox.metadata is stable
for logs and diagnostics:
metadata = sandbox.metadata
print(metadata.environment_id, metadata.service_id, metadata.allocation_id)
print(metadata.node_id, metadata.runtime_class, metadata.tunnel_session_id)
Capabilities
Use capability_status() to discover baseline and optional sandboxd-backed
providers before calling desktop or browser APIs:
status = sandbox.capability_status()
print(status.ready, status.capabilities)
for provider in status.providers:
print(provider.name, provider.state, provider.available, provider.reason)
Errors
SDK exceptions expose fields for programmatic handling:
from axern_sdk import (
SandboxConnectionError,
SandboxPermissionError,
SandboxPreconditionError,
SandboxRpcError,
)
try:
sandbox.exec("python -V", check=True)
except SandboxConnectionError as exc:
if exc.retryable:
print("temporary node/control-plane connectivity issue")
raise
except SandboxPermissionError as exc:
print("credentials do not permit this operation", exc.operation)
raise
except SandboxPreconditionError as exc:
if exc.capability:
print(
exc.capability.capability,
exc.capability.provider,
exc.capability.provider_state,
exc.capability.missing_dependencies,
)
raise
except SandboxRpcError as exc:
print(exc.operation, exc.code, exc.details, exc.allocation_id)
raise
Common error classes:
SandboxNotStartedError: operation requires an active sandbox.SandboxExecError:exec(..., check=True)observed non-zero exit.SandboxConnectionError: transport or connectivity failure.SandboxPermissionError: authentication or authorization failure.SandboxRpcError: gRPC status mapped from node/runtime APIs.SandboxTimeoutError: SDK-side timeout.
Sandboxd-backed capability failures keep their normal SDK exception class and
also expose exc.capability when the node returns provider diagnostics. That
object contains capability, provider, provider_state, reason, and
missing_dependencies, so callers can branch on missing browser or
computer-use dependencies without parsing the full error string.
Async
Async APIs mirror the synchronous shape:
from axern_sdk import AsyncAxernClient, AsyncSandbox
async with AsyncAxernClient("127.0.0.1:25000") as client:
async with AsyncSandbox(client=client, template_id="python311") as sandbox:
result = await sandbox.exec("python -c \"print('hello async')\"", text=True, check=True)
print(result.stdout)
async with await sandbox.process(["python", "-u", "-c", "import sys; print(sys.stdin.read())"]) as process:
await process.write("async input\n")
await process.close_stdin()
async for event in process.events():
if event.stream == "stdout":
print(event.text(), end="")
Examples
Runnable examples live in examples:
examples/function_manifest.pyexamples/sandbox_programming.pyexamples/sandbox_volume.pyexamples/async_sandbox_programming.pyexamples/computer_use.pyexamples/service_gateway.py
Examples expect a reachable Axern gateway control edge at 127.0.0.1:25000.
service_gateway.py also expects AXERN_SERVICE_URL, for example
http://127.0.0.1:25080. It accepts AXERN_NAMESPACE, AXERN_IMAGE,
AXERN_RUNTIME_CLASS, AXERN_REQUEST_CPU, AXERN_REQUEST_MEMORY,
AXERN_LIMIT_CPU, and AXERN_LIMIT_MEMORY for service configuration.
Validation
make test-py
make lint-py
make sdk-python-verify
make local-compose-python-sdk-e2e
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file axern_sdk-0.6.2.tar.gz.
File metadata
- Download URL: axern_sdk-0.6.2.tar.gz
- Upload date:
- Size: 234.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d84b8ac5dbb3709bf949b0aead4d02877c1de01e8a1754671b11d2d0b0517532
|
|
| MD5 |
1eb8d69f7a9d554891344008a9ea74d5
|
|
| BLAKE2b-256 |
84812fb37dadf1a52efce768229b7f32cb6cd9cdc4ee4f3a4b087478c57e66fc
|
File details
Details for the file axern_sdk-0.6.2-py3-none-any.whl.
File metadata
- Download URL: axern_sdk-0.6.2-py3-none-any.whl
- Upload date:
- Size: 299.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d62feead1c889958241772ea92a41454eae3310e4798f3e21e8564f6bee94f89
|
|
| MD5 |
5b1a448e41814e472798eefb2d13a31a
|
|
| BLAKE2b-256 |
e4024410a3c1eb8bd6a295b3e52a5247b79a3e952d5469169dec095d8e653ffb
|