ri is a fast, transparent terminal coding agent — a trimmed, Linux-only fork of xi-agent with a single provider surface (any OpenAI-compatible endpoint). Every tool call streams live as it happens — you watch the agent read, edit, run commands, and reason in real time. No hidden steps, no pre-canned workflows.
Inspired by pi but more streamlined: colours, emoji, and block characters give structure without noise. The UX stays compact even during busy tool loops.
- Raw agent workflow — see everything, no black boxes
- Compact, styled output — clear block delineation with colour and emoji
- You define the instructions and skills that fit your workflows; ri provides a smooth, streamlined harness for the model to interact with your environment
- Linux-only, single provider surface — no OAuth, no per-vendor adapters
- LLM transport via
rigwith live streaming of text, reasoning, and tool-call argument deltas - Lightweight Rust binary — low memory, fast startup, single executable
- Standard
AGENTS.mdandSKILL.mdsupport - Custom tools and skills — extend without bloat
- Caveats: No safety guards on tool calls; you are in control
| Provider | Type | Auth |
|---|---|---|
| OpenAI-compatible endpoint | Any OpenAI-compatible API (OpenAI, DeepSeek, vLLM, local inference servers, …) | API key in config |
Configure named provider instances in ~/.config/ri/config.toml and select them with -P <name> or /provider <name>.
Each provider is a [[providers]] array entry (not a [providers.<id>] table) with an id field:
[[providers]]
id = "my-endpoint"
service_type = "openai-compatible"
api_type = "openai-compatible"
base_url = "https://api.openai.com/v1"
api_key = "sk-..."All three are optional; omit them to defer to the endpoint's defaults.
| Config key | Description |
|---|---|
temperature |
Sampling temperature (0.0–2.0). Higher is more creative. |
max_tokens |
Maximum output tokens the model may emit per turn. |
output_schema |
JSON Schema constraining the model's final answer (structured output). |
[[providers]]
id = "my-endpoint"
service_type = "openai-compatible"
api_type = "openai-compatible"
base_url = "https://api.openai.com/v1"
api_key = "sk-..."
temperature = 0.7
max_tokens = 4096
output_schema = { type = "object", title = "Answer", additionalProperties = false, properties = { answer = { type = "string" } }, required = ["answer"] }output_schema drives rig's structured output (response_format / text.format).
It asks the model for JSON-only answers, so only set it when the whole turn should be
machine-readable; while tools are active (and before any tool result is in history)
some endpoints suppress tool calls in favour of the schema — not recommended for
general coding work.
AGPL-3.0-only. See LICENSE.
Install from source:
cargo install --path .| Short | Long | Description |
|---|---|---|
-P |
--provider <PROVIDER> |
Configured provider instance id to use |
-m |
--model <MODEL> |
Model name to use (e.g. gpt-4o) |
-p |
--print <PROMPT>... |
Run in non-interactive mode: send PROMPT, stream the response to stdout, and exit. Accepts multiple words without shell quoting |
--serve |
Headless ACP server on stdio — and serves the web UI (browser chat over ACP v2) at http://0.0.0.0:8990 (LAN-reachable; override with --serve-ws <ADDR> or $RI_WEB_PORT) |
|
--serve-ws <ADDR> |
Serve the ACP v2 WebSocket (ws://ADDR/acp) + web UI at ADDR instead of the TUI |
|
--serve-ws-token <TOKEN> |
Require this admin token on mutating _ri/* methods |
|
--serve-ws-cert <CERT> / --serve-ws-key <KEY> |
Serve --serve-ws over TLS (wss://) |
|
--tui-acp |
Keep the interactive TUI but run the agent as a detached child ri --serve over ACP (decoupled UI ↔ agent) |
|
--print-dirs |
Print the file-system paths ri uses and exit | |
-h |
--help |
Print help |
-V |
--version |
Print version |
Agent tool subprocesses (bash, exec, custom tools) always run inside a
rootless container built from raw Linux syscalls — no OCI, no
podman/docker, no root. There is no --sandbox flag and no sandbox config
key: this is the only execution mode:
ri-sandbox(a second bin target, spawned per tool call) creates a user namespace (unshare(CLONE_NEWUSER|CLONE_NEWNS)), maps your uid to 0, binds a scrubbed view into a flat rootfs directory andchroots.- Isolation: the host's
$HOME,/root,/etcsecrets and other projects are invisible. Only the explicit binds are visible/writable:/work(the tool cwd),/tools(custom tools),/tmp(scratch), non-secret/etc/*for DNS/users, and the dynamic loader dirs for the shell. - Network stays host-shared (internet works: crates.io, APIs, curl).
- File tools come from a static musl uutils coreutils when provisioned
(
just sandbox-provision/scripts/fetch-uutils-coreutils.sh); otherwise the host's/bin,/usrare bind-mounted read-only as a fallback, so the sandbox works everywhere. Custom tools are static-musl binaries and run inside with zero dependencies. - The shell is itself a static Rust shell:
ri-sh(a ~70-lineshCLI built on the embeddableepshPOSIX shell library) is installed as/bin/sh. In the default strict image (ri-sh + static coreutils) every binary is static, so there is no host/bin/shcopy and no/lib/loader binds at all; a compatible fallback keeps the host shell when ri-sh is absent. - Resource limits are applied by default
(
cpu=30s, nproc=64, nofile=2048, as=512MiB, fsize=1GiB, core=0) and can be tuned via$RI_SANDBOX_RLIMITS(nonedisables). - Tool bootstrapping:
rustc(Rust) andmuslcc(C) agent tools compile sources inside the sandbox into static musl custom tools (~/.ri/tools), using ri's own downloaded toolchains — never your system rustup or host musl-dev. Rust needs no dev libs (-C link-self-contained=yesshipslibc.a+CRT; musl is monolithic); C uses the bundled relocatable musl.cc cross compiler. Aseccompdenylist (§7) blocks namespace/fs/kernel escapes; freshly compiled tools hot-reload into the running agent (no restart). Provision withjust sandbox-toolchain/scripts/fetch-rust-toolchain.sh(seedocs/CONTAINER-RUNTIME-SPEC.md§16–17). - Linux-only; user namespaces must be enabled in the kernel.
See docs/CONTAINER-RUNTIME-SPEC.md for the full design spec and its
implementation-status appendix. Tests: tests/container_sandbox.rs (uid
mapping, host isolation, /work persistence, static coreutils) plus an
in-process bash-tool round-trip; they skip gracefully when user namespaces
are unavailable.
scripts/fetch-uutils-coreutils.sh # optional: static file tools
scripts/fetch-rust-toolchain.sh # optional: in-sandbox rustc (bootstrap)
cargo build --bins # produces `ri` + `ri-sandbox` + `ri-sh`
ri # TUI (tools already sandboxed)
ri --serve # ACP server + web UI at http://0.0.0.0:8990ri --serve (or ri --serve-ws 0.0.0.0:8990) serves a minimal browser chat —
the web counterpart of the TUI — at http://<this-machine-ip>:8990/ (bound to
0.0.0.0, so any machine on the LAN can open it; override the port with
$RI_WEB_PORT or the whole address with --serve-ws <ADDR>). It is ACP v2
only (no v1 fallback): the page connects to ws://<host>/acp,
negotiates session/new + session/prompt, and renders the streaming
session/update notifications (assistant/thought text, tool call cards,
usage), answers session/request_permission prompts, and stops a run with
session/cancel. Set --serve-ws-token to protect the mutating _ri/*
methods.
Assets are embedded (web/index.html, web/app.js); the workspace the agent
operates in is the cwd of the ri --serve process — the browser only owns
the chat surface.
| Key | Action |
|---|---|
F1 |
Show keyboard shortcuts |
Enter |
Submit message (or queue steering message if agent loop is running) |
Shift+Enter |
Insert newline in input |
Tab / Up / Down |
Apply / navigate completion suggestions in the input line |
Page Up |
Scroll chat up one page |
Page Down |
Scroll chat to bottom |
Scroll wheel |
Scroll chat (3 lines/tick) |
Ctrl+I / Alt+S |
Toggle provider/model info bar |
Ctrl+F |
Toggle full tool output |
Ctrl+R |
Resume latest session for current folder |
Ctrl+Z |
Suspend ri only when the UI is idle and no agent/shell subprocess is running |
Ctrl+D |
Quit when input is empty (or leave shell mode if shell input is empty) |
Ctrl+E |
Edit the selected custom provider (provider picker) |
! |
Enter shell mode when input is empty |
Alt+C |
Copy the last assistant response |
Alt+Up / Alt+Down |
Step backward / forward through session history |
Ctrl+C |
Abort agent loop (1: stop after turn, 2: abort, 3: force-kill); quit in shell mode |
Esc |
Abort current agent loop; also cancel slash/selection contexts |
| Mouse drag | Drag to select text in the log; release to copy to the clipboard |
| Command | Description |
|---|---|
/new |
Start a new conversation |
/model |
Open interactive model picker |
/model <name> |
Switch to a named model |
/provider |
Open interactive provider picker |
/provider <name> |
Switch to a configured provider instance |
/thinking <level> |
Set reasoning level (off / minimal / low / medium / high / xhigh) |
/resume |
Open session picker (local + foreign sessions) |
/compact [instructions] |
Compact session context now, optionally with summary instructions |
/export [path] |
Export this session to a self-contained HTML file |
/agent [name] |
Switch to a named agent profile, or show the agent picker |
/retry |
Retry the last turn after an error |
/reload |
Reload AGENTS.md context, available skills, and agents |
/skill:<name> |
Invoke a skill by name (e.g. /skill:plan) |
/quit |
Quit |
Add custom agent capabilities and expertise by placing SKILL.md files in these directories; reference them with /skill:<name>:
~/.ri/skills~/.agents/skills./.agents/skills./.ri/skills
Add custom tools by placing executable files in these directories (in this order):
~/.ri/tools~/.agents/tools./.agents/tools./.ri/tools
Tools must respond to a --describe option and output a JSON description of the
tool's interface, including its name, description, and expected input
parameters. This allows the agent to understand how to use the tool effectively.
For example:
{
"name": "my_tool",
"description": "A tool that does something useful",
"parameters_schema": {
"type": "object",
"properties": {
"path": { "type": "string", "description": "Path to operate on" }
},
"required": ["path"]
}
}Agent profiles live in ~/.ri/agents/<name>/ (global) and .ri/agents/<name>/
(project-local, shadowing global). Each agent uses a SYSTEM.md (with YAML
frontmatter) as its system prompt and an optional AGENTS.md that replaces the
global instructions. Tool/skill availability can be restricted per agent with
include_tools / exclude_tools / include_skills / exclude_skills globs.
Switch agents with /agent [name] or the /agent picker.
Agents with mode: subagent in the frontmatter are subagents: they never
appear in the picker but can be delegated to by the orchestrator via the
invoke_subagent tool. A subagent runs with its own system prompt and tool
universe (filters applied, invoke_subagent removed to prevent recursion) and
finishes within a bounded number of steps.
Reference a file directly in your prompt with @path (tab-completable). ri
reads the file and attaches its contents (or the image itself, for image
files) to the message before the agent runs — useful for giving the model
context without a separate read_file turn.
Every conversation is stored as an append-only event log under the XDG data
directory (grouped by working folder). Press Ctrl+R to resume the latest
session for the current folder, /resume for the full picker, and /export
to write a self-contained HTML transcript. Long sessions are automatically
compacted against the active model's context window; /compact forces it
manually, optionally with custom summary instructions.
ri --serve runs the same agent loop without the TUI, speaking the
vendor-neutral Agent Client Protocol —
JSON-RPC 2.0 over stdio, one message per newline — so any ACP-capable client
(editors, desktop/web UIs, acpx, …) can drive ri as a subprocess. A WebSocket
variant is available via ri --serve-ws 0.0.0.0:8990 (route /acp, with
streamable HTTP on the same server for non-WebSocket clients):
ri --serve --provider test # stdio + web UI on all interfaces
ri --serve-ws 0.0.0.0:8990 --provider test # WebSocket + HTTP, LAN-reachableSecurity: the server binds to
0.0.0.0, so anyone who can reach the port can drive the agent (create sessions, run prompts/tools). Prefer a--serve-ws-token, run it behind a reverse-proxy/SSH tunnel, or bind only loopback with--serve-ws 127.0.0.1:8990on untrusted networks.
Implemented surface:
initialize— protocol negotiation, image prompts,session/load,session/fork(ACP v2 session capability)session/new,session/prompt(streamsagent_message_chunk,agent_thought_chunk,tool_call/tool_call_updatewith live tool output forwarded as in-progress updates,usage_update; theend_turnresponse folds the turn's token usage)session/fork— clones a session (live or persisted) into a new id so clients can branch conversationssession/load— replays a session's history as updates; sessions are persisted to disk after each prompt (~/.local/share/ri/sessions/acp/), so a later process can resume them (_ri/list_sessions→session/load→session/prompt); prompt tools run anchored at the sessioncwdsession/cancel— maps to ri's hard abortask_user— forwarded assession/request_permission(multiple-choice mapping with descriptions folded into labels; freeform asks get a trailing "Continue" escape), so headless clients can approve/deny tool operations- ri-specific
_ri/*methods:_ri/get_state(model, thinking level, sessions),_ri/set_model,_ri/set_thinking,_ri/set_provider(re-resolve a provider preset by id and hot-swap the active provider for subsequent turns),_ri/list_sessions/_ri/delete_session/_ri/prune_sessions(persisted-session management),_ri/logs(recent activity),_ri/steering(queues steering for the next prompt turn); call unit-request methods with"params": null; mutating methods accept atokenfield enforced by--serve-ws-token - Transport: stdio (
--serve) or HTTP+WebSocket at/acp(--serve-ws, optionally TLS via--serve-ws-cert/--serve-ws-key); the WS server multiplexes many clients, stdio serves one.initializeis negotiated per-connection: protocol v1 serves the full surface, and protocol v2 (unstable) is served over the shared per-turn core with the standard v2 session surface (session/new/resume/list/close/fork/delete/prompt/cancel+ask_user→session/request_permission, plus the ri-specific_ri/*methods via shared version-neutral implementations)
Current limitations: one prompt at a time per session; _ri/steering applies
at the next turn boundary (mid-turn injection is not implemented).
session/cancel maps to ri's HardAbort, which the agent loop honours
mid-stream: the current model stream is chopped on the next token (verified by
an in-process e2e). Requires Rust 1.88 (ACP dependency baseline).
ri --tui-acp keeps the familiar ratatui interface but stops owning the
agent loop: it spawns a detached child ri --serve (same provider/model) and
speaks ACP over stdio, so the UI and the agent are two separate processes
joined only by the protocol. Submitted messages become session/prompt calls,
session/update notifications are translated back into the TUI's event
vocabulary, tool-permission asks surface through the TUI ask dialog
(session/request_permission), and Ctrl-C maps to session/cancel.
ri --tui-acp --provider test # interactive TUI driving a detached --serveLimitations: steering applies at the next turn boundary (mid-turn injection is
not implemented) and provider instance (preset/API-key) changes inside the
TUI are not hot-swapped in the child — model/thinking changes are pushed
automatically via _ri/set_model/_ri/set_thinking, and _ri/get_state now
reflects live mid-turn state too. The plain in-process TUI remains the default.