# Get started with Ukai 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: `ukai-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.

**Choose your host:** Linux x86_64/ARM64 runs `ukai-jail` directly. Apple Silicon
Macs can use the separate [macOS VM preview](#macos-vm-preview), which runs
offline Linux commands in disposable VMs. Both are developer-preview software;
native macOS programs and online agents are outside the Mac preview.

Already have the binary? Jump to [your first command](#run-your-first-command).
Writing an integration? Start with [agents and scripts](#for-agents-and-scripts).

## Install the developer preview

The same installer detects your platform. The following commands are for Linux;
Mac users should follow the [Mac quickstart](#macos-vm-preview).

Ukai was called Ouroboros until 15 October 2026. The current previews were
published under that name (`ouro-jail`, `ouro-vm`) and keep those command names.
The installation examples below use the published commands. Later sections
use the current `ukai-jail` source build; for the published Linux binary's
complete guide, use the [release guide](https://github.com/monocursive/ukai/blob/ouro-jail-v0.1.0-rc.2/docs/guide.md).

[Linux preview 0.1.0-rc.2](https://github.com/monocursive/ukai/releases/tag/ouro-jail-v0.1.0-rc.2)
ships Linux x86_64 and ARM64 packages. Install your distribution's
`bubblewrap` package first. The binaries require glibc 2.39 or
newer; Alpine/musl and macOS execution are unavailable in this preview.
The installer needs Bash, curl, tar, and sha256sum or shasum; no Minisign,
Rust compiler or sudo is needed.

```sh
curl --proto '=https' --tlsv1.2 -fsSL \
  https://ukai.sh/install.sh | bash
export PATH="$HOME/.local/bin:$PATH"
ouro-jail version --json
ouro-jail doctor --profile tool --json
```

The installer embeds this preview's SHA-256 hashes for both architectures. It
checks the downloaded archive before extracting or executing it, then installs
outside your project in `~/.local/bin`. The HTTPS-served installer is the source
of the trusted hashes; a downloaded checksum manifest cannot override them.
Repeat to upgrade after a new installer is published. Pass `--version` through
`bash -s -- --version VERSION` to select a release already pinned in the script;
an unpinned version refuses before any download.
Downgrades require `--allow-downgrade`. Replacing a source-built `0.1.0` without
a release record also needs this explicit flag to install an RC version.

The supported-host notes remain in the release: Ubuntu 26.04 is the x86_64
reference host; stock Ubuntu 24 containment is refused. The tested Pi lacks
memory cgroups and Landlock, so its default `build` profile remains unavailable.
Run `doctor` before relying on a profile. Archived runtime releases are separate;
use the linked preview release; future releases use `ukai-jail-v...` tags.
The ledger and fleet are not included in these packages.

## macOS VM preview

[Ukai VM preview 0.1.0-preview.1](https://github.com/monocursive/ukai/releases/tag/ouro-vm-v0.1.0-preview.1)
is a signed, notarized download for **Apple Silicon and macOS 27+**. Execution
was tested on macOS 27.0.1; installation and CLI launch were also checked in a
fresh macOS VM. Allow 8 GiB free APFS storage, two virtual CPUs and 2 GiB RAM per
running attempt. Intel Macs and nested VM execution are unsupported.

```sh
curl --proto '=https' --tlsv1.2 -fsSL \
  https://ukai.sh/install.sh | bash
export PATH="$HOME/.local/bin:$PATH"
ouro-vm version
ouro-vm doctor
```

The installer checks an embedded SHA-256 pin before mounting the 512 MiB DMG.
It installs the bundled runtime and guest image under
`~/.local/share/ouro-vm/0.1.0-preview.1`, with a command in `~/.local/bin`.
Python, Rust, Swift, Homebrew, sudo and Endpoint Security approval are not needed.
Use `bash -s -- --vm-prefix /absolute/path` to change the root containing `bin`
and `share`. An already installed version is preserved and repeat installation
refuses; use a fresh prefix to evaluate another copy. `--bin-dir`, its Linux
alias `--prefix`, and `--allow-downgrade` apply only to Linux.

Run a small offline Linux command:

```sh
vm_demo="$(mktemp -d "$HOME/ukai-vm-demo.XXXXXX")"
mkdir "$vm_demo/input"
printf 'hello\n' > "$vm_demo/input/input.txt"
ouro-vm run --workspace "$vm_demo/input" --out "$vm_demo/result" \
  --profile build --wall 30 -- \
  /bin/sh -c 'cat input.txt; printf "created in Linux\n" > output.txt'
cat "$vm_demo/result/workspace/output.txt"
```

Use a new output directory for every attempt. Inputs are copied into a private
guest disk; review the returned files before adopting them. Both `tool` and
`build` allow writes to that disposable workspace copy. Protected system paths
remain read-only. The guest includes a shell and Python, rather than a full
build SDK. Each attempt boots a VM; measured startup was about 22 seconds.

This preview has no network adapter, host directory shares, credential
forwarding, interactive stdin, background/resumable VM or agent/provider
integration. It runs Linux ARM64 commands, not macOS binaries, Xcode or Apple
SDKs. Input/output limits are 5,000 regular files, 512 KiB per file and 32 MiB
total. Symlinks, devices and FIFOs are refused; `.git`, `target`, `node_modules`,
`.venv`, `__pycache__` and `.DS_Store` are excluded. Empty directories are not
preserved. Commands have 512 MiB memory, zero swap, 64 processes and a 1–300
second wall limit.

A completed attempt requires a verified Linux Jail receipt and acknowledged
VM stop. Incomplete evidence returns 125. Files and Linux receipts remain in
`<out>/workspace/` and `<out>/evidence/`; `attempt.json` records Mac supervision.
The [package qualification](https://github.com/monocursive/ukai/blob/dev/docs/benchmarks/jail/macos-vm-preview-package-2026-10-11.md)
passed all 14 measured teardown cases. Simultaneous suspension of all three
host controllers, host sleep/wake, kernel failure and arbitrary guest compromise
are outside qualified coverage. The native macOS cleanup blocker remains open.

For offline installation, save this installer and the DMG from the release,
then run `bash install.sh --from-dir /path/to/artifacts`. The same embedded hash
is required. The DMG also contains a standalone installer and build-time notes;
its notes describe the candidate before publication. The public release notes
record publication of those exact tested bytes.

To uninstall the default installation, remove `~/.local/bin/ouro-vm` and
`~/.local/share/ouro-vm/0.1.0-preview.1`. Keep or delete attempt directories as
needed. A crashed launcher can leave a disk there; never boot or reuse it.

## Build and check your host

The remaining Linux examples use the current Ukai source build. The published
previews retain their original commands, data paths and receipt schemas;
renaming an executable does not migrate those contracts.

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.

```sh
git clone --branch dev https://github.com/monocursive/ukai.git
cd ukai
cargo +1.98.1 build --release -p ukai-jail
mkdir -p "$HOME/.local/bin"
install -m 0755 target/release/ukai-jail "$HOME/.local/bin/ukai-jail"
export PATH="$HOME/.local/bin:$PATH"

ukai-jail version
ukai-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, swap, and
CPU limits require a delegated cgroup v2 scope. The
[host setup reference](https://github.com/monocursive/ukai/blob/dev/docs/specs/jail-v1/operating.md#lingering-and-the-scope-step)
explains that setup and its effect on process cleanup.

For a source-built native `ukai-jail` on macOS, `version` and `explain` work; `doctor` reports why execution is
unavailable. Other Linux architectures are not yet supported for execution.
Signed Jail packages are linked above; a Homebrew tap remains unconfigured.
Older `v0.1.x` releases belong to the archived agent runtime.

The separate [VM preview source](https://github.com/monocursive/ukai/tree/dev/crates/ukai-jail/vm)
documents its build process and lifetime design. Use the Mac quickstart above
for the packaged release.

## Verify a supplied package

For offline installation, save the [Bash installer](https://ukai.sh/install.sh)
and the archive for your architecture from the [release](https://github.com/monocursive/ukai/releases/tag/ouro-jail-v0.1.0-rc.2).
The installer verifies its embedded SHA-256 pin even when the archive comes from
a local directory. No checksum manifest or signature tool is needed:

```sh
bash /path/to/install.sh --from-dir /path/to/artifacts
ouro-jail version --json
ouro-jail doctor --profile tool --json
```

Use `--bin-dir /absolute/path` for a different destination, or
`--base-url https://mirror.example/release` for an HTTPS mirror. Both paths must
provide exactly the archive pinned in the installer. Re-running replaces an
existing installation; downgrades require `--allow-downgrade`. A failed checksum
leaves the installed binary, license notices and release record unchanged.
`doctor` still decides whether the host can enforce the requested policy.

## Run your first command

Let's write a file in a fresh directory. This example needs no network or
agent credentials.

```sh
jail_demo="$(mktemp -d "$HOME/ukai-jail-demo.XXXXXX")"
cd "$jail_demo"

ukai-jail run --profile tool --workspace "$PWD" --limit wall=30s -- \
  /bin/sh -c 'printf "hello from the jail\n" > greeting.txt'

cat greeting.txt
ukai-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 `.ukai` 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 stays writable except the hooks and configs that exist at launch. That does not make git metadata safe to use after a run; see [Git metadata under `agent`](#git-metadata-under-agent). |
| `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. |

### Git metadata under `agent`

Do not treat git metadata as safe to use after an `agent` run. The
protection below raises the cost of accidental damage and of naive planting,
such as a package's install script adding a hook; it does not stop a run
that sets out to make your next `git` command run its code.

`agent` keeps `.git` writable, so commits, fetches, checkouts,
`git submodule update`, moving and removing linked worktrees and `git rm` of
a submodule work inside the run. Where they exist at launch, it binds
read-only:

- `.git/hooks` and `.git/config` of the workspace's repository and of nested
  repositories at any depth, and of a git directory granted with
  `--rw <repo>/.git`;
- the hooks directory of each submodule's git directory under
  `.git/modules/`.

What stays open:

- **Submodule configs.** `.git/modules/<name>/config` stays writable, since
  `git submodule update` rewrites it on every run. One `core.fsmonitor` line
  there runs a command on your plain `git status` in the superproject, and
  `core.hooksPath` there makes the read-only hooks directory beside it
  moot.
- **A git directory reached through a gitlink.** A linked worktree or
  `git init --separate-git-dir` keeps its git directory elsewhere, behind a
  file-form `.git`; when that directory lies in a writable path under
  another name, its hooks and config are writable. A gitlink itself is not
  protected: binding it read-only broke `git worktree remove` and `git rm`
  of a submodule, and left half-removed worktrees behind.
- **`commondir` and `config.worktree`.** A run can plant a `commondir` file
  in a writable git directory, or rewrite the one a linked worktree's
  administrative directory holds, naming a copy with its own config and
  hooks; git then reads the copy. Where `extensions.worktreeConfig` is
  enabled, it can write `config.worktree`.
- **Included files.** A protected config that includes another file
  (`include.path`, `includeIf`) is only as protected as that file.
- **`core.hooksPath` into the worktree.** When your config points hooks at
  a directory in the checkout, as husky does with `.husky/_`, the run can
  write the hooks there.
- **A repository without a hooks directory at launch.** Nothing existed to
  bind, so the run can create `.git/hooks` and fill it.
- **Replacement.** A run can move a `.git`, or a nested repository's whole
  directory, aside and create another in its place. Repositories created
  during the run, such as a fresh `git clone`, and bare repositories are not
  protected.

When the workspace is a linked worktree of a repository outside it, the run
can commit only with a grant of that repository (`--rw <repo>`) or of its
git directory (`--rw <repo>/.git`); either way that repository's hooks and
config stay read-only.

The protection has costs inside the run. Commands that write a protected
config fail, `git config`, `git remote add` and `git submodule init` among
them; `git submodule deinit` empties the submodule but cannot unregister it,
and reports the error yet exits 0. Deleting a repository that existed at
launch fails partway and leaves its hooks and config behind. Each nested repository takes two rows of the
receipt's mount table, whose bound of 1024 rows refuses a workspace of more
than about 500 nested repositories; the largest real tree measured held 122.
The table is also bounded by size, to 1 MiB of the receipt: repositories
nested under very long paths, or paths with control characters or bytes
that are not UTF-8 (which a receipt must escape or encode), refuse sooner.
A symlinked `.git`, hooks directory or config refuses the run and names the
path, since no coverage can be claimed through it.

After an `agent` run, before you run `git` in the workspace, check:

- hooks you did not install, in `.git/hooks`, in nested repositories, in
  `.git/modules/*/hooks`, and in the directory `core.hooksPath` names;
- `.git/modules/*/config` and every file a config includes;
- a `commondir` or `config.worktree` file in any git directory, including
  `.git/worktrees/*/`;
- whether `.git` (or a nested repository) is still the directory it was, and
  whether a file-form `.git` still names the git directory it did.

Contained profiles also expose system runtime files needed to execute
programs. Inspect the resolved policy before running a new command:

```sh
ukai-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 PATH` makes a file or directory visible for reading.
- `--rw PATH` permits changes to that path.
- `--allow-host HOST[:PORT]` allows a proxied destination with the `agent`
  profile. Without a port, the grant is TLS only: it admits a tunnel to port
  443, and the proxy refuses plaintext HTTP to it (except inside a tunnel to
  a bare IP address, which it does not inspect). Plaintext HTTP needs the
  port written out (`HOST:80`, or `HOST:443` for plaintext on 443); the
  receipt lists TLS-only grants in `applied.network.tls_only`.
- `--limit wall=5m` sets a deadline. Resource limits use forms such as
  `--limit mem=512MiB`, `--limit pids=64`, or `--limit cpu=100` for 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.

### Bound RAM, swap and writable storage

RAM and swap have separate ceilings. Combine `--limit mem=512MiB` with
`--limit swap=0` to cap charged RAM and disable swap, or use a positive swap
budget such as `--limit swap=128MiB`. An omitted swap limit is not implied by
the memory limit.

`--limit storage=64MiB --limit inodes=4096` requires operator-provisioned
bounded tmpfs volumes or ext4/XFS filesystems with enforced **user hard
quotas**. Their combined capacity must fit those ceilings. The workspace,
explicit `--scratch PATH`, extra writable grants and agent state all count;
aliases of the same filesystem count once. Existing files and concurrent
users of the same budget consume capacity too. The jail does not provision
volumes or copy the workspace automatically.

For disk storage, use a dedicated non-root worker account and a filesystem
with user quota accounting and enforcement already enabled. For example,
on a prepared quota filesystem, an operator with `quota-tools` can set a
64 MiB block hard limit and 4096-inode hard limit with
`sudo setquota -u WORKER 0 65536 0 4096 /WORKER_VOLUME` (block limits are KiB).
Put both workspace and scratch there, owned by `WORKER`. Ukai queries
the kernel as that user before releasing the target. Soft limits alone,
disabled enforcement, foreign-owned writable files, idmapped writable mounts
and XFS realtime volumes refuse. This budget covers that uid across the filesystem, including
files outside the granted directories; it is not a per-run reservation.

For storage-bounded runs, `/dev/shm` is read-only and new hard links return
`EPERM`. The hard-link restriction prevents importing another user's inode
into the writable tree; these seccomp denials have no observer event. Existing
hard links within the admitted ownership rules remain usable. The storage ceiling covers
allocated file data; use `mem` to bound charged RAM as well. A storage
receipt's `hit=null` means saturation was not established, not that the limit
was never reached. The [limit specification](https://github.com/monocursive/ukai/blob/dev/docs/specs/jail-v1.md#64-limits-and-errors)
defines the scope and remaining backend limitations. Project quotas and
automatic volume provisioning are not implemented.

## 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`, your distribution's `ripgrep` package supplies `rg` on
`PATH`, and you're still in the disposable directory above:

```sh
git init
rg --version
ukai-jail doctor --launch opencode
ukai-jail explain --launch opencode --workspace "$PWD" \
  --ro "$HOME/.opencode/bin" --json

ukai-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](https://github.com/monocursive/ukai/blob/dev/docs/specs/jail-v1/operating.md#launch-profiles-and-credentials)
for the configuration format.

OpenCode 1.18.32 uses `rg` for file search. Without it, OpenCode attempts a
GitHub download that the starter profile refuses. Provision the tool before
launch instead of widening the network policy. For a private tool installation,
grant its directory with `--ro` and set the child's path explicitly, for example
`-- /usr/bin/env PATH=/private/tools:/usr/local/bin:/usr/bin:/bin /path/to/opencode run …`.
Contained profiles replace the host's `PATH`, so changing it only outside the
jail does not expose a private tool directory to the agent.
`doctor --launch` checks Jail capabilities and profile inputs; it does not
verify every vendor tool dependency or provider availability.

OpenCode has recorded live runs. The
[compatibility table](https://github.com/monocursive/ukai/blob/dev/docs/specs/jail-v1/agent-compatibility.md)
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/ukai/attempts/`.
`UKAI_DATA_DIR` can select a different private data directory.

```sh
ukai-jail tail
ukai-jail tail --follow
ukai-jail tail --json
```

For a particular attempt, use `ukai-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:

```sh
ukai-jail learn --launch opencode --workspace "$PWD" \
  --ro "$HOME/.opencode/bin" --out "$HOME/ukai-proposal.toml" -- \
  "$HOME/.opencode/bin/opencode" run "Run this project's tests."
```

The proposal can include exact read-only paths and denied network
destinations. A refused tunnel to port 443 is proposed as the bare host,
which grants TLS only; a destination is proposed with its port only when
the run sent plaintext HTTP there. 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. For native `ukai-jail` 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.

**A policy, workspace or environment is too large to record.** Every
receipt and resolved policy is held to 4 MiB, and a receipt lists at most
1024 grants, credentials, allowed hosts or environment names. A policy
whose records could not fit, such as one with thousands of `--ro` paths,
refuses with `invalid_config` before anything runs, in `explain` and
`--label-only` as in `run`; so does a `--profile none` run started from an
environment with more than 1024 variables, or with names too long to
record, since its receipt lists every name it inherits. A workspace whose
mount table would not fit refuses with `invalid_config` while it is
prepared. Narrow the grants or the workspace, or start the run with fewer
variables. A refused run whose error messages alone would not fit still
writes its receipt: messages are cut and say so, and every error is printed
whole on stderr.

## 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:

1. Read `ukai-jail version --json` to identify the build and its schemas.
2. Run `doctor --profile NAME --json` or `doctor --launch NAME --json` and
   inspect the result. `doctor` does not accept run overrides such as
   `--workspace` or `--limit`; `run` checks the final requested policy.
3. Use `explain --json` with the same policy flags you will pass to `run`.
   Keep the requested access within the user's authorized task.
4. Run the command with an explicit `--receipt PATH`. Use a unique path
   outside every child-writable grant so concurrent runs cannot overwrite it.
5. Read that receipt even when the process exits nonzero. Use its `attempt_id`
   with `tail --attempt ID --json` for the matching journal.

For example, from the disposable workspace:

```sh
jail_records="$(mktemp -d "$HOME/ukai-jail-records.XXXXXX")"
ukai-jail version --json > "$jail_records/version.json"
ukai-jail doctor --profile tool --json > "$jail_records/doctor.json"
ukai-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:

```sh
ukai-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](https://github.com/monocursive/ukai/blob/dev/docs/specs/jail-v1/jail-receipt.schema.json)
and [exit code reference](https://github.com/monocursive/ukai/blob/dev/docs/specs/jail-v1/operating.md#exit-codes)
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](https://github.com/monocursive/ukai/blob/dev/docs/benchmarks/jail/followup-2026-09-29.md#k17-results)
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](https://github.com/monocursive/ukai/blob/dev/docs/specs/jail-v1/operating.md).
See the [roadmap](https://github.com/monocursive/ukai/blob/dev/docs/roadmap.md)
for what we're working on next.
