Get started with Ouroboros Jail
Start with a small command, see what the jail recorded, then use the same
workflow for your own tools. You don’t need to change agents or adopt a new
way of working: ouro-jail wraps the command you already run.
You choose the files it can read or change, the services it can contact, and how long it can run. A receipt tells you which controls were applied and how the run ended.
You’ll need a Linux x86_64 machine to execute commands. This is pre-release software, currently installed from source. On a Mac, you can build the CLI and inspect policies; sandboxed execution is still unavailable.
Already have the binary? Jump to your first command. Writing an integration? Start with agents and scripts.
Build and check your host
You need Git, Rust 1.98.1, and your distribution’s bubblewrap package. The reference host runs Ubuntu 26.04.1 and bubblewrap 0.11.1. Run as your normal user; the jail does not install a backend or change host security settings.
git clone --branch dev https://github.com/monocursive/ouroboros.git
cd ouroboros
cargo +1.98.1 build --release -p ouro-jail
mkdir -p "$HOME/.local/bin"
install -m 0755 target/release/ouro-jail "$HOME/.local/bin/ouro-jail"
export PATH="$HOME/.local/bin:$PATH"
ouro-jail version
ouro-jail doctor --profile tool
If you already have the checkout, start at the build command. Install the binary outside any workspace you’ll make writable: the jail refuses a grant that would let a child modify its executable.
doctor checks whether your host can run the selected profile. Read its
report before continuing. A required control must be available; a preferred
limit may be left unapplied and reported as such. Explicit PID, memory, and
CPU limits require a delegated cgroup v2 scope. The
host setup reference
explains that setup and its effect on process cleanup.
On macOS, version and explain work; doctor reports why execution is
unavailable. Other Linux architectures are not yet supported for execution.
Public jail packages and a Homebrew tap are still being prepared. Older
Ouroboros releases belong to the archived agent runtime.
Run your first command
Let’s write a file in a fresh directory. This example needs no network or agent credentials.
jail_demo="$(mktemp -d "$HOME/ouro-jail-demo.XXXXXX")"
cd "$jail_demo"
ouro-jail run --profile tool --workspace "$PWD" --limit wall=30s -- \
/bin/sh -c 'printf "hello from the jail\n" > greeting.txt'
cat greeting.txt
ouro-jail tail
You should see hello from the jail when you read the file. tail then shows
the journal for the most recently updated attempt. The command had a writable
workspace, no network access, and a 30-second deadline.
Everything after -- is the program and its literal arguments. This example
names /bin/sh because it needs shell redirection. For an ordinary program,
pass its executable and arguments directly.
The workspace is yours after the command exits. Changes persist, so use a disposable checkout when trying an unfamiliar tool.
Choose a profile
A profile is a starting policy. Pick the one closest to your task, then add only the paths and services it needs.
| Profile | Good starting point for | Default access |
|---|---|---|
tool |
Tests and local commands | Writable workspace and scratch; no network. Existing .git and .ouroboros trees are protected from writes. |
agent |
Coding agents that call a provider | Writable workspace, scratch, and temporary agent state. Network destinations need explicit grants. Git metadata is writable. |
build |
Builds from read-only inputs | Writable scratch; add read-only inputs and writable outputs. No network. Requires an explicit memory limit and host support. |
none |
Deliberately uncontained execution | Host access. Filesystem and network containment are disabled. |
Contained profiles also expose system runtime files needed to execute programs. Inspect the resolved policy before running a new command:
ouro-jail explain --profile tool --workspace "$PWD" --limit wall=30s --json
explain resolves configuration without probing the host or executing the
command. It shows what you are requesting; the receipt later records what
was actually applied.
Add access as the task needs it
--ro PATHmakes a file or directory visible for reading.--rw PATHpermits changes to that path.--allow-host HOST:PORTallows a proxied destination with theagentprofile. Without a port, the grant means port 443.--limit wall=5msets a deadline. Resource limits use forms such as--limit mem=512MiB,--limit pids=64, or--limit cpu=100for one core’s worth of CPU bandwidth. Every explicit limit is required.
For tests, start with tool, a disposable checkout, and read-only access to
installed dependencies. For a build, keep source inputs read-only and give
the compiler a separate writable output directory. Toolchains installed in
your home directory may need explicit paths; your usual home and environment
are not automatically inherited.
Run an existing agent
Launch profiles supply starter file, environment, credential, and network settings for an agent. They don’t install it or choose its command line. Fourteen profiles are embedded, including OpenCode, Codex, and Claude Code. All remain experimental until a compatibility record covers the specific agent version and jail build.
Here’s an OpenCode example. It assumes OpenCode is already installed at
$HOME/.opencode/bin and you’re still in the disposable directory above:
git init
ouro-jail doctor --launch opencode
ouro-jail explain --launch opencode --workspace "$PWD" \
--ro "$HOME/.opencode/bin" --json
ouro-jail run --launch opencode --workspace "$PWD" \
--ro "$HOME/.opencode/bin" -- \
"$HOME/.opencode/bin/opencode" run "Create greeting.txt containing hello."
Review the resolved file and network grants before the run. Change the executable path and read-only grant to match your installation. A provider that needs authentication also needs explicit credential configuration in your launch profile; normal login state is not exposed automatically. See the launch profile reference for the configuration format.
OpenCode has recorded live runs. The compatibility table names the tested versions, jail builds, and remaining gaps. Check it before assuming a profile will work with your setup.
Inspect a run
Each attempt saves a jail.json receipt, its resolved policy, and an event
journal. By default, these live under ~/.local/share/ouro/attempts/.
OURO_DATA_DIR can select a different private data directory.
ouro-jail tail
ouro-jail tail --follow
ouro-jail tail --json
For a particular attempt, use ouro-jail tail --attempt ID --json, replacing
ID with its receipt’s attempt_id. This matters when several commands run
at once: the default follows the most recently updated attempt.
When reading the receipt, start with these fields:
| Field | What to check |
|---|---|
phase |
settled means the process tree was verified dead. refused means execution was refused or the command could not be started. An unfinished phase needs investigation. |
outcome |
The child’s result and the reason a run stopped. |
containment and applied |
The boundary, mounts, network mode, and limits actually applied. |
lifetime |
Whether the tree is empty and whether its integrity was verified. |
coverage and errors |
Which operation classes were observed and where evidence is missing. |
Observation is on by default and covers a defined set of operations. It does not record every read or every action. Strict evidence is also the default: loss of required evidence stops the run. A zero event count only means no observed events when that class had active coverage.
Learn missing permissions
If a command hits a permission error, learn can help identify the access it
needs. It runs the command inside containment and writes a proposal from
that run’s evidence:
ouro-jail learn --launch opencode --workspace "$PWD" \
--ro "$HOME/.opencode/bin" --out "$HOME/ouro-proposal.toml" -- \
"$HOME/.opencode/bin/opencode" run "Run this project's tests."
The proposal can include exact read-only paths and denied network
destinations. Denied writes are reported without becoming write grants.
Read the proposal, check that the access fits the task, and then apply only
what you intend. --adopt shows the change and requires confirmation in an
interactive terminal.
Learning executes real work and can change writable files. Its proposal covers one observed run; it is not a complete list of everything that program might ever need. Nothing is adopted automatically.
When something doesn’t work
The host check refuses the profile. Read doctor’s missing requirements.
Explicit resource limits need host support. On macOS, refusal is the current
expected behavior. Changing to none would remove containment.
The command cannot find a file, executable, or library. Check explain
and the receipt. The program, its interpreter, and its dependencies must be
visible. Add the specific read-only path the task needs, then try again.
The agent cannot connect or authenticate. Check the launch profile’s
provider hosts and credential configuration. --allow-host grants a
network destination; it does not supply a login or an API key.
A short command reports exec_unconfirmed. With observation off, the
command may finish before the jail independently confirms execution. Keep
the default observation mode for the first run, and inspect the receipt
before repeating work that might have side effects.
The command returned a nonzero status. It may be a child failure, a refusal, or a problem with evidence or cleanup. Read the receipt alongside the exit code. A child’s own exit code can overlap the jail’s codes.
For agents and scripts
The website provides /guide.md and /roadmap.md from these exact source
documents, plus /llms.txt as a short discovery index. Give an agent the
Markdown guide URL directly; automatic discovery depends on the client.
Roadmap entries describe future work, not commands available in the CLI.
Use this sequence when integrating the jail:
- Read
ouro-jail version --jsonto identify the build and its schemas. - Run
doctor --profile NAME --jsonordoctor --launch NAME --jsonand inspect the result.doctordoes not accept run overrides such as--workspaceor--limit;runchecks the final requested policy. - Use
explain --jsonwith the same policy flags you will pass torun. Keep the requested access within the user’s authorized task. - Run the command with an explicit
--receipt PATH. Use a unique path outside every child-writable grant so concurrent runs cannot overwrite it. - Read that receipt even when the process exits nonzero. Use its
attempt_idwithtail --attempt ID --jsonfor the matching journal.
For example, from the disposable workspace:
jail_records="$(mktemp -d "$HOME/ouro-jail-records.XXXXXX")"
ouro-jail version --json > "$jail_records/version.json"
ouro-jail doctor --profile tool --json > "$jail_records/doctor.json"
ouro-jail explain --profile tool --workspace "$PWD" --limit wall=30s \
--json > "$jail_records/policy.json"
After inspecting those reports, run the command and save its receipt:
ouro-jail run --profile tool --workspace "$PWD" --limit wall=30s \
--receipt "$jail_records/receipt.json" -- /usr/bin/true
run has no --json flag. The child’s output is passed through, and jail
diagnostics may also appear on stderr.
tail --json emits one JSON event per line (NDJSON), not a single JSON array.
A setup error may happen before a receipt is created, so handle a missing
file and retain stderr. Don’t fabricate a successful result from the exit
code alone.
On failure, report the requested policy, missing capability or evidence, and
receipt path. Do not silently widen grants, switch to none, disable
observation, or weaken evidence requirements to make a run pass. A permission
proposal needs the same review as any other access change. Review output
files and test results separately: containment does not establish that the
agent completed its task correctly.
The receipt schema and exit code reference cover the machine interfaces in detail.
Performance
Containment adds startup time. Observation also adds work while the command runs, especially when it creates, renames, or deletes many files.
In the recorded Linux benchmark from 29 September 2026, a plain-session fixture of 5,000 file-operation rounds had these median total runtimes:
| Execution mode | Median runtime |
|---|---|
| Direct, without the jail | 190 ms |
| Jailed, observation off | 375 ms |
| Jailed, observation on (default) | 1,167 ms |
Each mode had 30 measured samples. This was an earlier development build on the Ubuntu reference host, using synthetic workloads. It does not predict the speed of your agent.
Across workloads and sessions, the highest p95 added startup time with
observation off was 131 ms. The full benchmark includes 540 measured launches;
all 60 observation-off no-op runs returned exec_unconfirmed and exit 1.
Their timings are included, but they are not successful run receipts. The
full report
includes the method, build, host, raw data, and limitations.
Understand the boundary
An agent can change files you make writable and send readable data to services you allow. Keep grants small and review the resulting changes. Containment relies on the host kernel, and security review remains part of the work before release.
The network proxy supports HTTP and SOCKS5 TCP connections; UDP is not supported. Optional HTTP(S) credential vaulting supplies placeholders to the child and substitutes secrets at the proxy. An authorized upstream could still return a secret in its response. Command deny-rules help catch mistakes; another program can perform the same operation under a different name.
For more detail, use the operator reference. See the roadmap for what we’re working on next.