- expose importable Python functions that callers run with
sandbox.remote - open side-channel Socket.IO namespaces for events between host and sandbox
The Three Core Systems
Agentix core owns three reserved Socket.IO namespaces:| Namespace | System | User-facing API | Extension point |
|---|---|---|---|
/rpc | RPC | await sandbox.remote(fn, *args, **kwargs) | expose a normal importable Python callable |
/log | logging | stdlib logging in sandbox code | configure host logging handlers, levels, and formatters |
/trace | tracing | agentix.trace.trace(...), agentix.trace.span(...) | register agentix.trace.Processor implementations |
/<package-name>.
RPC: Extend With Callables
The/rpc namespace is the remote-call protocol. Users extend it by
installing functions into the bundle, not by subclassing a base class:
fn.__module__ + "::" + fn.__qualname__, pickles args and kwargs, and the worker imports the same
callable inside the sandbox.
Logging: Extend With stdlib logging
The/log namespace is the logging bridge. Sandbox code uses standard Python
logging:
logging.Handler that forwards
LogRecord data over /log. The sandbox automatically registers the host
consumer and replays those records into the host logging tree.
Beyond stdlib records, the worker also captures the process’s raw stdout and
stderr — print(), subprocess output, C-extension writes — and replays each
line over the same /log namespace under the logger names
agentix.sandbox.stdout and agentix.sandbox.stderr. The identical output
is also appended to a durable $AGENTIX_LOG_DIR/sandbox-<worker-id>.log
(default /tmp/agentix; set the variable empty to disable), so output that
outlives a dropped connection is still recoverable inside the sandbox.
Users customize logging with normal logging configuration on the host:
/log namespace. If a plugin needs structured events
that are not log records, give the plugin its own namespace.
Tracing: Extend With Processors
The/trace namespace is the trace bridge. User code creates traces and spans:
trace.Processor that forwards
trace and span lifecycle events over /trace. The sandbox automatically
registers the host consumer, which replays those events into the host trace
provider.
Users extend tracing by registering processors:
/trace events. A processor registered inside sandbox
code receives sandbox-local spans.
Do not register your own /trace namespace. If a plugin needs a different
event model, use a plugin namespace and optionally convert those events into
spans through a trace.Processor.
Choosing: Callables vs. Namespaces
Two ways to expose sandbox behavior — pick by data-flow shape:- Callable (
sandbox.remote(fn, ...)) — one call in, one return value out, after the work finishes. No data flows back mid-execution. This is the default; bashrun, fileupload/download, and agent/scorer wrappers are all callables. - Callable returning
AsyncIterator[T]— progressive results streamed to the host as they happen, host → sandbox only.bash.run_streamyieldsBashStdout/BashStderr/BashExitevents this way. - Namespace — host and sandbox exchange data during execution (bidirectional), or the plugin bridges an external service the sandbox must reach through the host. abridge uses a namespace: sandbox code forwards an LLM request to the host, the host calls the real upstream, and the reply comes back on the same namespace — all while the agent keeps running in the sandbox.
Plugin Namespaces
A plugin namespace has two optional halves:- a sandbox-side
agentix.Namespace, registered inside the worker - a host-side
agentix.AsyncClientNamespace, registered on the live sandbox
sandbox.register_namespace(...) must run before the first remote call,
because the Socket.IO connection plan is fixed at connect time.
Round-trip Requests
When the sandbox needs an answer from the host mid-run, the sandbox side callsawait self.request("fetch", body). It emits the request with a
generated request_id and waits for the host to reply on fetch:result
(success) or fetch:error (failure).
On the host, wrap the handler with request_handler so the envelope, the
reply event name, and error replies are handled for you:
fetch:error reply carrying
{"type", "message"} — the shape Namespace.request re-raises as
RemoteSioError. Without the decorator you must pull request_id out of
the envelope yourself and emit the exact fetch:result / fetch:error
event by hand; a typo or a forgotten reply hangs the sandbox until its
request timeout.
What the Server Does
The runtime server is namespace-opaque: When the worker registers a namespace, it sendssio_open to the server. The
server then registers a matching Socket.IO forwarder. This is why plugins do
not need runtime-server patches for new event protocols.
Packaging a Plugin
A plugin package can contribute any combination of:- importable functions that callers invoke with
sandbox.remote - sandbox-side namespaces registered by code running in the worker
- host-side namespace handlers registered on the live sandbox
- sandbox providers through the
agentix.providerentry-point group - system dependency closures through the
agentix.nixentry-point group
/log
and /trace are core side channels; plugin-specific protocols belong on the
plugin’s own namespace.