recursive-agent 0.8.2

A Rust coding-agent platform: kernel, tools, multi-agent orchestration, HTTP API, MCP, and TUI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# 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.

[![CI](https://github.com/jeffkit/recursive/actions/workflows/ci.yml/badge.svg)](https://github.com/jeffkit/recursive/actions/workflows/ci.yml)
[![Crates.io](https://img.shields.io/crates/v/recursive-agent.svg)](https://crates.io/crates/recursive-agent)
[![Docs.rs](https://docs.rs/recursive-agent/badge.svg)](https://docs.rs/recursive-agent)
[![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)

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-only `team_*` / `task_*` tools and a deferred-tool loader)
- a **transcript** plus a `StepEvent` stream 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 loop` for 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-agent` because the name `recursive`
> was taken on crates.io. The installed binary is still called `recursive`,
> and the library is imported as `use 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.

```rust
use recursive::llm::OpenAiProvider;
use recursive::runtime::AgentRuntime;
use recursive::tools::{ReadFile, RunShell, ToolRegistry, WriteFile};
use std::sync::Arc;

# async fn run() -> anyhow::Result<()> {
let llm = OpenAiProvider::new(
    "https://api.openai.com/v1",
    std::env::var("OPENAI_API_KEY")?,
    "gpt-4o-mini",
)?;

let tools = ToolRegistry::local()
    .register(Arc::new(ReadFile::new(".")))
    .register(Arc::new(WriteFile::new(".")))
    .register(Arc::new(RunShell::new(".")));

let mut runtime = AgentRuntime::builder()
    .llm(Arc::new(llm))
    .tools(tools)
    .max_steps(20)
    .system_prompt("You are a helpful coding assistant.")
    .build()?;

let outcome = runtime
    .run("list the files in src and summarise them")
    .await?;

println!("{}", outcome.final_text.unwrap_or_default());
# Ok(()) }
```

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`](src/run_core.rs). 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`](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 `AgentEvent` stream via `EventSink`. 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_shell` enforces a configurable timeout and caps captured output.
- Agent loop respects a step budget (`max_steps`) and emits
  `FinishReason::BudgetExceeded` rather than looping forever.

## Installation

### Homebrew (macOS Apple Silicon)

```bash
brew install jeffkit/tap/recursive
```

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

```bash
cargo install --path .   # or, once published: cargo install recursive-cli
```

```bash
# one-off goal
recursive run "list files in src and summarise the kernel"

# interactive REPL (one goal per line, :q to exit)
recursive repl

# loop mode — agent self-schedules wakeups
recursive loop "monitor src/ for changes and report"

# HTTP API server
recursive http --addr 127.0.0.1:3000

# Terminal UI (connects to HTTP server)
cargo run -p recursive-tui

# inspect what tools are registered (no API key needed)
recursive tools
```

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

```bash
export RECURSIVE_API_BASE="https://open.bigmodel.cn/api/paas/v4"
export RECURSIVE_API_KEY="$GLM_API_KEY"
export RECURSIVE_MODEL="glm-4-flash"
recursive run "create hello.txt and read it back"
```

Example with a local Ollama:

```bash
export RECURSIVE_API_BASE="http://localhost:11434/v1"
export RECURSIVE_API_KEY="ollama"   # ollama ignores it but the field is required
export RECURSIVE_MODEL="qwen2.5-coder"
recursive run "explain the repo layout"
```

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

```toml
# ~/.recursive/providers.d/myvendor.toml

[[providers]]
id = "myvendor"                      # the id you'll pass to --provider
name = "My Vendor"                   # shown in `recursive init` / `providers list`
provider_type = "openai"             # "openai" or "anthropic"
api_base = "https://api.myvendor.com/v1"
default_model = "myvendor-1"
key_env = "MYVENDOR_API_KEY"        # the env var the runtime reads at start
key_url = "https://myvendor.com/keys"
models = [
  { name = "myvendor-1", context_window = 32_000 },
  { name = "myvendor-2-mini", context_window = 16_000,
    pricing = { input_per_million = 0.10, output_per_million = 0.30 } },
]
mainland_accessible = 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)

```bash
docker build -t recursive:dev --target runtime .
docker run -p 3000:3000 \
  -e RECURSIVE_API_KEY="$OPENAI_API_KEY" \
  -e RECURSIVE_API_BASE="https://api.openai.com/v1" \
  -e RECURSIVE_MODEL="gpt-4o-mini" \
  recursive:dev
```

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_KEYS` or `RECURSIVE_HTTP_AUTH_JWT_SECRET` is
> configured. For local dev, set `RECURSIVE_HTTP_AUTH_INSECURE_OK=1` as 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:

```bash
cp .env.example .env          # fill in RECURSIVE_API_KEY
docker compose up
```

Then talk to the agent over HTTP:

```bash
# create a session
SESSION=$(curl -sX POST http://localhost:3000/sessions \
  -H 'Content-Type: application/json' \
  -d '{"system_prompt":"You are a helpful assistant."}' | jq -r .session_id)

# send a message
curl -X POST http://localhost:3000/sessions/$SESSION/run \
  -H 'Content-Type: application/json' \
  -d '{"message":"List the files in /workspace"}'
```

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

```bash
cargo test --workspace
```

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`): scripted `MockProvider` driving real
  filesystem tools.

## Python SDK

```bash
cd sdk/python && pip install -e .
```

```python
from recursive_client import RecursiveClient

client = RecursiveClient("http://127.0.0.1:3000")
print(client.health())  # "ok"
result = client.run("list files in src/")
print(result.finish_reason)
```

## 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](docs/tui-fake-cc-gap.md).

## License

MIT — see [LICENSE](LICENSE).