Agentix is built around a small execution loop:
Host Python
  RuntimeClient.remote(fn, ...)
        |
        v
Sandbox runtime server
  Socket.IO endpoint
        |
        v
Worker subprocess
  import module
  unpickle args
  call function
  pickle result
Everything else supports that loop or rides side channels on the same Socket.IO connection.

Core Namespaces

Agentix core owns three reserved Socket.IO namespaces:
NamespaceSystemPublic API
/rpcRPCRuntimeClient.remote(fn, *args, **kwargs)
/logloggingstandard logging records plus captured stdout/stderr (agentix.sandbox.stdout / agentix.sandbox.stderr) forwarded sandbox -> host; also written to a durable $AGENTIX_LOG_DIR/sandbox-<id>.log
/tracetracingagentix.trace.trace(...), agentix.trace.span(...), trace.Processor
Plugin-specific protocols use their own namespace, conventionally /<package-name>. See Plugins and Side Channels for the extension model.

Systems

SystemResponsibility
agentix.runtime.sharedWire models, msgpack codec, framing, RemoteCallable, CallId
agentix.runtime.clientRuntimeClient.remote(fn, ...) and host-side namespace handlers
agentix.runtime.serverFastAPI and Socket.IO server, worker process management, namespace forwarding
agentix.providerHost-side sandbox lifecycle protocol and backend plugin lookup
agentix.cliagentix build
agentix.sioSandbox-side namespace API bridged over the worker pipe

Remote Target

The caller passes a normal imported function:
from app import run

result = await client.remote(run, input="hello")
The client encodes the callable as an import-path string:
callable_ref = RemoteCallable._resolve(run)  # "app::run"
arguments = pickle.dumps((args, kwargs))
The runtime worker imports the module, resolves the function, unpickles the arguments, and invokes it. Return values travel back as pickle blobs inside call:result. Because a sandbox may run less-trusted code, the host decodes those blobs through a restricted allowlist loader (agentix.runtime.shared.safepickle) rather than plain pickle.loads, refusing any non-allowlisted global with agentix.RestrictedUnpickleError.

Call Flow

1. Host imports `app.run`.
2. Host calls `client.remote(run, input="hello")`.
3. Client builds `RemoteCallable("app::run")` and pickles `((), {"input": "hello"})`.
4. Client emits `call` on Socket.IO `/rpc`.
5. Runtime server forwards the request to the worker subprocess.
6. Worker imports `app`, resolves `run`, unpickles args, and calls it.
7. Worker pickles the return value.
8. Server emits `call:result`; client decodes the value through the
   restricted allowlist loader and returns it.

Transports

PathCarriesWire
GET /healthhealth probeHTTP JSON
POST /callinternal short-call fast pathHTTP msgpack
Socket.IO /rpcc.remote() RPCcall / call:result / call:error / cancel
Socket.IO /trace, /log, /<plugin>side channelsplugin-defined events (msgpack payloads)
worker private piperuntime ↔ workerlength-prefixed msgpack frames

Bundle Layout

agentix build [path] installs one Python project into the runtime venv with uv (uv owns Python; Nix owns system binaries — there is no pip):
uv venv /nix/runtime/venv
VIRTUAL_ENV=/nix/runtime/venv uv sync --active --frozen --no-dev --no-editable
The sync brings in user code, direct dependencies, transitive dependencies, and integration packages declared in pyproject.toml. The runtime tree is the uv venv plus a symlinkJoin of the Nix closures (interpreter, uv, and any system deps):
/nix/runtime/
├── bootstrap.sh           # container entry point (provider backends exec this)
├── venv/                  # the uv venv — all Python deps live here
│   └── lib/python3.11/site-packages/
│       ├── agentix/
│       ├── agentix/bash/
│       ├── agentix/agents/claude_code/
│       ├── agentix/plugins/datasets/swe/
│       └── app.py
└── {bin,lib,...}/         # symlinkJoin of every Nix closure
If the project includes default.nix, the build adds a Nix builder stage and links binaries into /nix/runtime/bin.

Worker Model

The runtime server owns one worker subprocess. The worker handles remote function invocation and keeps the runtime server isolated from user code. For each call, the worker:
  1. resolves the RemoteCallable import path
  2. unpickles (args, kwargs)
  3. calls the callable (awaiting when the return value is awaitable)
  4. pickles the return value
The worker also hosts the sandbox-side agentix.sio bridge. Plugin namespaces register inside the worker and forward events through sio_emit / sio_open / sio_inbound frames on the worker pipe. The runtime server does not implement plugin business logic. It accepts namespace connections, dynamically registers generic forwarders, and relays events between host-side AsyncClientNamespace handlers and worker-side Namespace handlers. The worker uses the same /nix/runtime environment as the runtime server, so anything installed into the bundle can be imported on demand.

SandboxProvider Boundary

A SandboxProvider is a host-side plugin that stages a bundle and starts the sandbox. provider.create(...) returns a live Sandbox handle; call await sandbox.remote(fn, ...) directly. The runtime protocol is the same regardless of where the sandbox is running.
SandboxProvider.create(SandboxConfig(image="python:3.13-slim", bundle="/home/me/.cache/agentix/bundles/sha256-..."))
        |
        v
Sandbox(sandbox_id=..., runtime_url="http://...")   # live handle
        |
        v
await sandbox.remote(fn, ...)
The scoped form constructs the provider, opens a session, and tears down both the runtime connection and the container on exit:
from agentix.provider.docker import DockerProvider, DockerProviderConfig

provider = DockerProvider(DockerProviderConfig())
async with provider.session(SandboxConfig(image=..., bundle=...)) as sandbox:
    result = await sandbox.remote(run, input="hello")
RuntimeClient(runtime_url) still exists as the underlying transport for advanced, direct use against a known runtime_url (for example, an in-process server). The sandbox path uses sandbox.remote rather than a nested RuntimeClient.

Mental Model

Bundle = what code and dependencies exist in the sandbox
Remote call = which importable function to call
Worker = where user code executes
agentix.sio = sandbox ↔ host side channels (trace, log, plugins)
SandboxProvider = where the bundle is staged and run