RLM loop
In-tree verify/act loop: castellan run --rlm.
Sandbox host (RlmSandboxHost)
Hardened process-isolated code eval ships behind the sandbox feature on castellan-rlm (enabled by default on castellan-cli via rlm-sandbox).
| Backend | Type | When |
|---|---|---|
StubSandboxHost | Default without sandbox feature | Returns stub message |
ProcessSandboxHost | python3 -c subprocess + timeout | sandbox_backend = "process" |
HardenedProcessSandboxHost | Temp workspace, no network, platform hardening | Default (sandbox_backend = "hardened") |
BubblewrapSandboxHost | bwrap on PATH with ro-bind + unshare-net | sandbox_backend = "bubblewrap" (falls back to hardened) |
WasmSandboxHost | wasmtime echo guest (minimal eval scope) | sandbox_backend = "wasm" with --features wasm-sandbox (experimental; not default) |
Configure via CASTELLAN_SANDBOX_BACKEND=process|hardened|bubblewrap or [rlm] sandbox_backend in castellan.toml. WASM requires the optional wasm-sandbox feature — not enabled by default.
Hardened process remains the default hot path. WASM ships a minimal echo guest for REPL eval wiring only — not Python parity.
REPL eval path
When an observation includes an rlm_eval block, the RLM loop executes code through the sandbox host before verify:
{
"rlm_eval": { "code": "print(2 + 2)", "timeout_ms": 5000 },
"rlm_eval_result": { "stdout": "4\n", "stderr": "", "exit_code": 0 }
}
Wiring:
castellan-rlm:repl_eval_observation,run_until_satisfied(..., sandbox_host),select_sandbox_host()castellan run --rlm: mux RLM path usesselect_sandbox_host()(hardened default)
Build / test
cargo test -p castellan-rlm --features sandbox
CASTELLAN_SANDBOX_BACKEND=bubblewrap cargo test -p castellan-rlm --features sandbox
CASTELLAN_SANDBOX_BACKEND=wasm cargo test -p castellan-rlm --features sandbox,wasm-sandbox
cargo test -p castellan-cli rlm