Recursive
A Rust coding-agent platform: a small ReAct kernel plus the surrounding HTTP API, MCP, multi-agent orchestration, and TUI that turn it into a full development tool.
At its core Recursive is a ReAct loop that wires together:
- an LLM provider (OpenAI-compatible HTTP by default; works with OpenAI, GLM/Zhipu, DeepSeek, Moonshot, MiniMax, Together, Ollama, vLLM, …; plus a native Anthropic adapter)
- a tool registry (
Read,Write,Edit,Glob,Bash,WebFetch,WebSearch, plan-mode, checkpoints, todo, …; plus coordinator-onlyteam_*/task_*tools and a deferred-tool loader) - a transcript plus a
StepEventstream you can observe
Around that kernel the platform adds opt-in surfaces:
- HTTP API — axum-based REST + SSE server with sessions, rate-limiting,
JWT/API-key auth, OpenAPI spec (feature
http) - MCP — both as a client (consume external MCP servers) and a server
(expose Recursive's tools to other MCP-aware agents) (feature
mcp) - TUI — ratatui-based interactive client with streaming tool indicators,
plan mode, and command palette (
crates/recursive-tui) - Multi-agent — agent pool, shared memory, messaging bus, plan-mode
coordination (feature
coordinator-mode) - Cloud runtime — Redis session store, S3 transcript storage, Docker /
E2B sandboxes (features
cloud-runtime/e2b-sandbox) - Vector memory — sqlite-vec + OpenAI embeddings for episodic recall
(feature
vector-memory) - Loop mode —
recursive loopfor self-scheduling autonomous agent runs
Embedding just the kernel (no HTTP / TUI / cloud) is supported via
--no-default-features.
The crate is published as
recursive-agentbecause the namerecursivewas taken on crates.io. The installed binary is still calledrecursive, and the library is imported asuse recursive::*;.
At a glance
Wire up an OpenAI-compatible LLM, register some tools, and drive the
agent loop. This surface has been stable since v0.7 (the legacy Agent
type from v0.5 was split into AgentKernel (stateless) and AgentRuntime
(stateful wrapper) during Goal 219); it is the API shipped in v0.8.1.
use OpenAiProvider;
use AgentRuntime;
use ;
use Arc;
# async
Run it with no API key by swapping OpenAiProvider for the
scriptable MockProvider — see examples/basic.rs (requires the
test-utils feature: cargo run --example basic --features test-utils)
and examples/with_tools.rs.
Design
The kernel has five concepts, each independently testable:
| Concept | Where | Role |
|---|---|---|
Message |
src/message.rs |
The only data primitive: chat messages with optional tool calls. |
ChatProvider |
src/llm/ |
Trait for model backends. Adapters: HTTP (OpenAI-compatible), Anthropic, Mock. |
Tool + ToolRegistry |
src/tools/ |
Trait for side effects the model can request. Sandboxed to a workspace. |
AgentKernel |
src/kernel.rs |
Stateless single-turn executor. Receives a TurnContext, returns a TurnOutcome. |
AgentRuntime |
src/runtime.rs |
Stateful wrapper. Owns the transcript, message queue, compaction, and cross-turn state. |
The actual ReAct step loop lives in src/run_core.rs::RunCore::run_inner. The kernel/wrapper split was introduced after the legacy Agent / StepEvent types were removed (Goal 219). For a deeper tour, see docs/architecture/agent-loop.md.
Orthogonality
- New tool? Implement
Tool, register it. No kernel/runtime changes. - New model backend? Implement
ChatProvider. No tool/kernel changes. - New UI / observer? Subscribe to the
AgentEventstream viaEventSink. No loop changes. - New finish reason? Add a variant to
FinishReason. Callers can match if they care.
Safety primitives baked in
- Every fs / shell tool resolves paths through
tools::resolve_within, which rejects anything escaping the configured workspace root. run_shellenforces a configurable timeout and caps captured output.- Agent loop respects a step budget (
max_steps) and emitsFinishReason::BudgetExceededrather than looping forever.
Installation
Homebrew (macOS Apple Silicon)
Intel Macs: brew install will print a message pointing at
cargo install recursive-cli --locked — see the formula for context.
Pre-built binaries (Linux, macOS, Windows)
Grab the asset matching your platform from
https://github.com/jeffkit/recursive/releases/latest, extract, and put
recursive on your $PATH.
From source
# one-off goal
# interactive REPL (one goal per line, :q to exit)
# loop mode — agent self-schedules wakeups
# HTTP API server
# Terminal UI (connects to HTTP server)
# inspect what tools are registered (no API key needed)
Configuration
Anything OpenAI-compatible works. Override via env vars (or CLI flags):
| Env | Default | Purpose |
|---|---|---|
RECURSIVE_API_BASE |
https://api.openai.com/v1 |
Chat-completions endpoint |
RECURSIVE_API_KEY |
(required) | Bearer token |
RECURSIVE_MODEL |
gpt-4o-mini |
Model name |
RECURSIVE_MAX_STEPS |
0 (unlimited) |
Loop budget (0 = unlimited; set to N to cap at N steps) |
RECURSIVE_TEMPERATURE |
0.2 |
Sampling temperature |
RECURSIVE_WORKSPACE |
cwd | Root all fs/shell tools are sandboxed to |
RECURSIVE_SYSTEM_PROMPT_FILE |
(built-in) | Path to a system prompt to load |
Example with GLM (Zhipu):
Example with a local Ollama:
# ollama ignores it but the field is required
Adding a custom provider
The bundled catalog (see providers.toml) ships presets for OpenAI,
Anthropic, DeepSeek, MiniMax, GLM/Zhipu, Moonshot, Doubao, DashScope,
Hunyuan, StepFun, Gemini, Groq, Mistral, xAI, and a local Ollama. If
the vendor you want isn't there, you have two paths.
Interactive wizard — run recursive init, pick 0 for "custom API
base", then enter the URL, the model name, and the API key. The wizard
will offer to save the result as a reusable preset under
~/.recursive/providers.d/<your-id>.toml so the next run can call it
by id.
Hand-written preset — drop a file into
~/.recursive/providers.d/:
# ~/.recursive/providers.d/myvendor.toml
[[]]
= "myvendor" # the id you'll pass to --provider
= "My Vendor" # shown in `recursive init` / `providers list`
= "openai" # "openai" or "anthropic"
= "https://api.myvendor.com/v1"
= "myvendor-1"
= "MYVENDOR_API_KEY" # the env var the runtime reads at start
= "https://myvendor.com/keys"
= [
{ = "myvendor-1", = 32_000 },
{ = "myvendor-2-mini", = 16_000,
pricing = { input_per_million = 0.10, output_per_million = 0.30 } },
]
= false
Verified on the next recursive launch — recursive providers list
will show it, and recursive init will offer it in the picker. To
override a bundled preset's models or pricing (e.g. to ride out a
catalog drift until the next release), give your file the same id
as the bundled one: the bundled entry stays visible but your file's
models[] and pricing win.
Remote catalog — recursive providers update pulls the latest
catalog from the upstream JSON the project maintains
(RECURSIVE_PROVIDERS_URL overrides the URL; defaults to a GitHub
raw URL). The wizard prompts to refresh on every run; one-shot
commands read whatever is already cached (TTL 7 days). Use
recursive providers update explicitly when you want the latest
without waiting for the TTL to expire.
recursive providers list / status cover the operations side of
catalog management — see --help for the available sub-commands.
Docker / Cloud Deployment
Single-container (local mode)
The image defaults to recursive http --addr 0.0.0.0:3000 and exposes /health for probes.
⚠️ Auth required: The HTTP server now rejects requests with 503 unless
RECURSIVE_HTTP_AUTH_KEYSorRECURSIVE_HTTP_AUTH_JWT_SECRETis configured. For local dev, setRECURSIVE_HTTP_AUTH_INSECURE_OK=1as a debug escape hatch — never use this in production.
Full cloud stack (Redis + S3)
Use the bundled docker-compose.yml to spin up Redis (session hot-state) and
LocalStack S3 (transcript persistence) locally:
Then talk to the agent over HTTP:
# create a session
SESSION=
# send a message
Environment variables — full reference
LLM provider
| Env | Default | Purpose |
|---|---|---|
RECURSIVE_API_BASE |
https://api.openai.com/v1 |
Chat-completions endpoint |
RECURSIVE_API_KEY |
(required) | Bearer token |
RECURSIVE_MODEL |
gpt-4o-mini |
Model name |
RECURSIVE_PROVIDER_TYPE |
openai |
Protocol: openai or anthropic |
RECURSIVE_MAX_STEPS |
0 (unlimited) |
Max tool-call loop iterations per run (0 = unlimited) |
RECURSIVE_TEMPERATURE |
0.2 |
Sampling temperature |
RECURSIVE_SYSTEM_PROMPT_FILE |
(built-in) | Path to a custom system-prompt file |
RECURSIVE_WORKSPACE |
cwd | Filesystem sandbox root |
HTTP server
| Env | Default | Purpose |
|---|---|---|
RECURSIVE_HTTP_ADDR |
0.0.0.0:3000 |
Bind address |
RECURSIVE_HTTP_AUTH_KEYS |
(required for prod) | Comma-separated X-API-Key allowlist |
RECURSIVE_HTTP_AUTH_JWT_SECRET |
(none) | HMAC secret for JWT bearer-token auth |
RECURSIVE_HTTP_AUTH_JWT_AUDIENCE |
(none) | Optional aud claim for JWT validation |
RECURSIVE_HTTP_AUTH_INSECURE_OK |
(none) | Set to 1 to bypass auth (local dev ONLY) |
Cloud storage — Redis (session hot-state)
Requires the cloud-runtime feature flag (--features cloud-runtime).
| Env | Default | Purpose |
|---|---|---|
RECURSIVE_REDIS_URL |
(disabled) | Redis connection URL e.g. redis://host:6379 |
RECURSIVE_REDIS_KEY_PREFIX |
recursive: |
Key namespace prefix |
RECURSIVE_REDIS_SESSION_TTL_SECS |
7200 |
Session expiry (2 h) |
Cloud storage — S3 (transcript + memory)
Requires the cloud-runtime feature flag.
| Env | Default | Purpose |
|---|---|---|
RECURSIVE_S3_BUCKET |
(disabled) | S3 bucket name |
RECURSIVE_S3_PREFIX |
recursive |
Object key prefix |
RECURSIVE_S3_TENANT_ID |
default |
Tenant namespace inside the bucket |
AWS_ACCESS_KEY_ID |
(from SDK) | AWS credential |
AWS_SECRET_ACCESS_KEY |
(from SDK) | AWS credential |
AWS_DEFAULT_REGION |
us-east-1 |
AWS region |
AWS_ENDPOINT_URL |
(AWS) | Override for LocalStack / MinIO |
Sandbox
| Env | Default | Purpose |
|---|---|---|
RECURSIVE_SANDBOX_MODE |
local |
local / policy / docker / e2b |
RECURSIVE_E2B_API_KEY |
(required for e2b mode) | E2B API key |
RECURSIVE_E2B_TEMPLATE |
base |
E2B sandbox template ID |
RECURSIVE_E2B_TIMEOUT_SECS |
300 |
Sandbox timeout in seconds |
RECURSIVE_SHELL_TIMEOUT_SECS |
30 |
Per-command shell timeout |
Local vs cloud mode — cheatsheet
| Concern | Local (default) | Cloud (cloud-runtime feature) |
|---|---|---|
| Transcript persistence | Local JSONL (~/.recursive/...) |
S3 via S3StorageBackend |
| Session hot-state | In-memory (NoopSessionStore) |
Redis via RedisSessionStore |
| Tool execution | Host shell | Docker (L2) or E2B microVM (L3) |
| Horizontal scaling | Single process | Stateless HTTP pods + shared Redis/S3 |
| Resume across restarts | Via --session flag |
Automatic via restore_from_storage() |
Library API
recursive is also a library — embed the loop in your own program if the CLI
isn't the right shell for your use case. See the example above; the public
surface lives in src/lib.rs.
Testing
540+ tests covering:
- Agent loop: termination, tool dispatch, error recovery, step budget, event stream order.
- Tool registry: dispatch, unknown-tool error, path sandboxing.
- Filesystem tools: round-trip, parent-dir creation, sort order, escape rejection.
- Shell tool: success / non-zero status / timeout.
- HTTP provider: request shape (with and without tools), response parsing (plain text / tool-call), tool-call argument round-trip.
- HTTP API: health, tools, run, sessions CRUD, SSE streaming, OpenAPI spec.
- TUI: app state, key handling, message styling, scroll, plan mode.
- Multi-Agent: pool, roles, shared memory, messaging bus, pipeline, orchestrator.
- End-to-end smoke (
tests/smoke.rs): scriptedMockProviderdriving real filesystem tools.
Python SDK
&&
=
# "ok"
=
TUI
The terminal UI is in crates/recursive-tui/. For an experience-level
comparison against fake-cc (Claude Code-style baseline), see
docs/tui-fake-cc-gap.md.
License
MIT — see LICENSE.