Magi Agent — Terminal AI Assistant in Rust
Magi Agent (magi-rs) is a terminal AI assistant in Rust, modeled on Claude Code. It drives an LLM provider through a multi-turn tool loop with sandboxed filesystem and shell access, and persists every conversation to a locally-encrypted SQLite store. Nothing leaves your machine except the model API calls you explicitly authorize.
Why Magi?
Most AI coding agents are SaaS-bound and tied to a single vendor. Magi is built for the opposite constraint: environments where source code cannot leave the machine.
| Principle | What it means in Magi |
|---|---|
| Local-first | A single static Rust binary. Conversation history and project knowledge live in an encrypted SQLite file on disk — never a third-party vault. |
| Encrypted at rest | Every stored record is sealed with Argon2id → AES-256-GCM-SIV → Reed-Solomon FEC. The DB master key lives in the OS keyring, separate from the LLM API key. |
| Sandboxed by default | Every filesystem tool is confined to the workspace via PathGuard; the shell tool runs a strict command allowlist with a hard ban on shell metacharacters. |
| Human-in-the-loop | Each tool call must be approved inline in the TUI before it runs. |
| Fails safe | If the OS keyring is unavailable, the session degrades to ephemeral (no persistence) rather than ever encrypting with a constant key. |
Features
| Area | Capability |
|---|---|
| TUI front-end | ratatui app with Normal / Selection / Visual modes, UTF-8-safe cursor handling, system clipboard integration |
| Agent loop | Multi-turn orchestration, bounded tool calls (max 15), repetitive-call detection, ANSI/control-char sanitization, interactive approval gate |
| Provider | Anthropic Messages API with SSE streaming + tool_use assembly and 429 retry; StaticProvider fallback when no key is configured |
| Sandboxed tools | ls, view, edit, grep, bash (strict allowlist), project_knowledge (persistent facts), consult (MAGI multi-perspective consensus) |
| MAGI consult | The agent can escalate hard, trade-off-heavy decisions to a 3-perspective consensus (Melchior / Balthasar / Caspar) via the magi-core crate — invoked autonomously through the tool loop (each call passes the approval gate) or forced with /consult |
| Encrypted memory | SQLite (WAL) with one cached key derivation per session (Argon2id + per-DB salt), AES-256-GCM-SIV, Reed-Solomon FEC, salt-integrity self-heal |
| OAuth login | PKCE flow against the Anthropic Console (/login in the TUI) |
| OS keyring | API key in magi-rs, DB master key in magi-rs-internal (kept separate); magi-rust legacy names auto-migrated |
Installation
Prerequisites
- A Rust toolchain (stable, edition 2021) — install via rustup.
ripgrep(rg) onPATHfor thegreptool.- An Anthropic API key from console.anthropic.com (recommended), set via
ANTHROPIC_API_KEYorkey.txt./login(OAuth) also works but is best-effort — see Configuration.
Build from source
Run
Usage
Launch the TUI and type a prompt. The agent streams its reply, and any tool it wants to run pauses for your approval.
TUI modes
| Mode | Enter with | Keys |
|---|---|---|
| Normal | (default) | type to chat · Ctrl+S → Selection · Ctrl+V paste · Ctrl+C copy selection / quit if none |
| Selection | Ctrl+S |
↑/↓ pick a message · y copy whole message · Enter → Visual |
| Visual | Enter (in Selection) |
Shift+←/→ character-level selection · y copy |
Tool approval
When the agent requests a tool, an inline prompt appears: y approves, c / Esc denies. The agent blocks up to 5 minutes waiting for your response.
Slash commands (handled by the UI, never sent to the model)
| Command | Effect |
|---|---|
/login |
Start the OAuth (PKCE) login flow — best-effort, may be rate-limited (see Configuration); prefer an API key |
/logout |
Clear stored API keys |
/clear |
Clear the on-screen conversation |
/consult <question> |
Force a MAGI 3-perspective consensus on the question (≈ 3 model calls). Blocks the session while it runs, like a normal turn. Requires a configured LLM provider. |
/help |
Show available commands |
/exit, /quit |
Leave the app |
Tools
| Tool | Purpose |
|---|---|
ls |
List directory contents (sandboxed) |
view |
Read file contents (sandboxed) |
edit |
Create / overwrite a file (sandboxed) |
grep |
RipGrep-backed search (sandboxed) |
bash |
Run a shell command — strict allowlist |
project_knowledge |
Persist project facts to encrypted memory |
consult |
Run a MAGI 3-perspective consensus on a hard decision (via magi-core) |
The bash allowlist is: ls git npm cargo rg cat echo pwd grep mkdir touch find rm diff node python pytest (with cargo restricted to test / build / check), plus a hard ban on the shell metacharacters | & ; > < ` $ ( ) { } \.
MAGI consult (multi-perspective consensus)
For decisions with genuine trade-offs — architecture choices, "should we X vs Y given these constraints?", risk calls — Magi can run a three-perspective consensus built on the magi-core crate: three independent analyst agents (Melchior the scientist, Balthasar the pragmatist, Caspar the critic) evaluate the question and a consensus is synthesized.
- Automatic (transparent). The agent decides on its own when a question warrants multi-perspective analysis and invokes the
consulttool through the normal tool loop. Like any tool call it passes the inline approval gate (y/n) — which is also your cost control, since a consult is ≈ 3 model calls. Deny it to keep the single-LLM answer. - Forced. Type
/consult <question>to run the consensus directly, bypassing the router and the approval gate. The session showsMAGI deliberating — 3 model calls…and then renders the verbatim report (the three perspectives + the consensus verdict)./consultblocks the session while it runs, like a normal turn. - Backend. The consult reuses the same provider and credentials already resolved for the agent (Anthropic or any OpenAI-compatible endpoint) — no second config. It is unavailable in
StaticProvidermode (no API key):/consultthen reports that a provider is required, and the tool is not registered. - Model capability. Weak / small local models (e.g. Ollama
phi4-mini) may fail to emit the strict per-agent JSON the consensus requires; the result is then marked[DEGRADED: …](fewer than three agents responded). A capable model is recommended for reliable consensus.
Configuration
API key & model discovery
Resolved in order, first hit wins:
ANTHROPIC_API_KEYenvironment variable- OS keyring, service
magi-rs, keyANTHROPIC_API_KEY - OS keyring, service
magi-rust(legacy) — auto-migrated tomagi-rson read key.txtin the working directory: line 1 = API key, line 2 = optional model
The model is read from ANTHROPIC_MODEL, then key.txt line 2, defaulting to claude-sonnet-4-6. With no key found, the agent falls back to StaticProvider.
A standard API key is the recommended, supported path. Create one at console.anthropic.com (with billing enabled) and set ANTHROPIC_API_KEY or put it in key.txt.
/login(OAuth) is best-effort. It reuses Anthropic's Claude Code OAuth client to mint a key on your account — a flow intended for Anthropic's own clients — so it may be rate-limited, throttled, or blocked at any time and is not guaranteed. Prefer a standard API key.
key.txtand its variants are gitignored. Never commit a real key.
magi.toml (optional, multi-backend)
Magi can talk to any OpenAI-compatible Chat Completions endpoint — OpenAI itself, a local Ollama instance, Groq, OpenRouter — in addition to the default Anthropic Messages API. The backend and its non-secret settings live in a workspace-local magi.toml. A reference is committed as magi.toml.example; copy it to magi.toml and edit. magi.toml is gitignored.
= "openai" # "anthropic" (default) | "openai"
[]
= "http://localhost:11434/v1" # Ollama; or https://api.openai.com/v1, Groq, OpenRouter, …
= "phi4-mini" # REQUIRED when provider = "openai"
[]
= "claude-sonnet-4-6" # optional override of the Anthropic default
Precedence (per setting): environment variable > magi.toml > built-in default.
| Setting | Env var | magi.toml |
Default |
|---|---|---|---|
| Provider backend | MAGI_PROVIDER |
provider |
anthropic |
| OpenAI base URL | OPENAI_BASE_URL |
[openai].base_url |
https://api.openai.com/v1 |
| OpenAI model | OPENAI_MODEL |
[openai].model |
(none — required when provider = "openai") |
| Anthropic model | ANTHROPIC_MODEL |
[anthropic].model |
see API key & model discovery |
API keys never live in magi.toml. Keys come from env / OS keyring / key.txt only — magi.toml is non-secret runtime config and is the wrong place for credentials. Specifically:
- Anthropic key —
ANTHROPIC_API_KEYenv var, OS keyring (magi-rs), orkey.txt(see above). - OpenAI-compatible key —
OPENAI_API_KEYenv var. For a local Ollama instance, magi-rs falls back to a dummy value ("ollama") so you can run without setting anything. - Placing
api_key/OPENAI_API_KEY(or any other unknown field) insidemagi.tomlis rejected at parse time underdeny_unknown_fields, not silently dropped — the file is treated as invalid and magi-rs falls back to defaults with a startup warning. - A malformed
magi.tomldoes not crash magi-rs: it falls back to defaults and surfaces a notice in the TUI on startup.
Per-agent MAGI models — [magi] (optional)
The three MAGI perspectives (Melchior / Balthasar / Caspar) used by /consult and the consult tool can each run a different model, giving the consensus genuine lineage diversity — different model families reason and fail differently, so agreement across them carries more signal. This is opt-in: with no [magi] section and no MAGI_MODEL_* env vars, all three share the principal model (identical to v0.4.0).
[]
= "qwen3:8b" # Scientist — theoretical analysis
= "gpt-oss:20b" # Pragmatist — practical trade-offs
= "deepseek-r1:8b" # Critic — adversarial review
| Setting | Env var | magi.toml |
Default |
|---|---|---|---|
| Melchior model | MAGI_MODEL_MELCHIOR |
[magi].melchior_model |
principal model |
| Balthasar model | MAGI_MODEL_BALTHASAR |
[magi].balthasar_model |
principal model |
| Caspar model | MAGI_MODEL_CASPAR |
[magi].caspar_model |
principal model |
Per-agent overrides reuse the principal provider's base_url + API key and change only the model name. So real cross-family diversity (e.g. GLM + GPT + DeepSeek) requires the principal backend to be an Ollama-style endpoint serving all three families; with an Anthropic principal you can still vary across Anthropic models (tier diversity). See magi.toml.example for the Light / Balanced / Maximum tier suggestions. A blank value is treated as unset. In StaticProvider mode (no API key) the overrides are ignored with a startup notice.
Quick start — local Ollama (no cost, no rate limits)
# 1. Install and start Ollama, then pull a model:
# 2. Drop a magi.toml in the workspace:
# (the defaults already point at http://localhost:11434/v1 and phi4-mini)
# 3. Run magi-rs — the startup banner reports the selected provider.
To use OpenAI instead, edit magi.toml (base_url = "https://api.openai.com/v1", pick a model) and set OPENAI_API_KEY in the environment.
How It Works
┌───────────────────────────────┐
│ main.rs │
│ config discovery · keyring │
│ master-key · tool registration│
└───────────────┬───────────────┘
│
┌───────────────┴───────────────┐
▼ channels ▼
┌──────────────────┐ ◄───────────► ┌──────────────────┐
│ TUI (ratatui) │ │ Agent │
│ Normal/Sel/Vis │ │ history · loop │
└──────────────────┘ │ approval gate │
└────────┬─────────┘
┌──────────────────────────────────┼──────────────────────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Provider │ │ Tools │ │ EncryptedSqlite │
│ Anthropic (SSE) │ │ ls/view/edit │ │ CryptoVault │
│ Static fallback │ │ grep/bash/know. │ │ Argon2→GCM-SIV→RS│
└──────────────────┘ └──────────────────┘ └──────────────────┘
The agent loop
- The user message is appended to history (and persisted to encrypted memory if attached).
provider.stream_messages(...)streamsTextDeltachunks to the TUI; each chunk is sanitized of ANSI / control characters.- The assembled assistant
Messageis collected onMessageDone. - If it contains
ToolUseblocks, each tool is approved, executed, and its result pushed back as aUser-roleToolResult— then the loop repeats. - With no tool requested, the final assistant text is returned.
Invariants: at most 15 tool calls per query; three consecutive identical tool calls abort ("repetitive tool call detected"); each tool call needs UI approval within 5 minutes or it is denied.
Security Model
- Encrypted memory. Argon2id (OWASP 2025 parameters: 64 MiB, t=3, p=4) derives a 32-byte key from the DB master password and a per-database random salt. The key is derived once per session and cached (
Zeroizing) — no longer per record. AES-256-GCM-SIV (nonce-misuse resistant) provides authenticated encryption with a freshOsRngnonce per record; Reed-Solomon FEC wrapsnonce‖ciphertextagainst bit-rot. Blob layout:[u8 version][u32 LE len][RS(nonce‖ciphertext)]. - Salt integrity & self-heal. The per-DB salt is stored RS-encoded with a SHA-256 checksum. An absent / corrupt / unrecoverable salt is treated as absent and the store resets to a fresh start (no on-disk migration) rather than bricking; minor bit-rot is corrected and prior history survives.
- Secrets separation. The LLM API key (keyring
magi-rs) and the DB master password (keyringmagi-rs-internal) are kept in separate keyring services. Rotating the API key never invalidates the local conversation DB. - Filesystem sandbox. Every file-touching tool canonicalizes its target and validates it against the workspace root via
PathGuard(handling Windows\\?\verbatim prefixes, null-byte attacks, and lexical normalization). - Shell sandbox. The
bashtool enforces a per-binary argument allowlist and bans shell metacharacters to prevent subshell injection on both PowerShell and bash.
Project Structure
src/
main.rs -- config discovery, master-key bootstrap, tool registration, entry
config.rs -- MagiConfig (magi.toml load + provider/model resolution)
agent/
mod.rs -- Agent orchestrator: multi-turn tool loop + approval gate
provider.rs -- Provider trait; AnthropicProvider (SSE) + OpenAiCompatibleProvider + StaticProvider
tools/
mod.rs -- Tool trait + ToolError
ls.rs read.rs write.rs grep.rs bash.rs knowledge.rs
system/
database.rs -- EncryptedSqliteMemory (MemoryStore) over SQLite + CryptoVault
fs.rs grep.rs secrets.rs path_guard.rs
utils/
crypto.rs -- CryptoVault: Argon2id → AES-256-GCM-SIV → Reed-Solomon FEC
services/
oauth.rs -- OAuth PKCE login (callback on 127.0.0.1:54545)
tui/
mod.rs -- ratatui app (Normal / Selection / Visual)
docs/ -- public overview & philosophy (OVERVIEW.md)
Cargo.toml -- edition 2021, MIT OR Apache-2.0
Testing
Full verification (matches the project's per-phase gate):
Some tests use real OS resources:
system::secretstests write to the real OS keyring under servicemagi-rs-test-suite, and the OAuth callback server binds port54545— avoid two interactive/loginflows at once. DB/crypto tests are isolated viatempfile.
Requirements
| Component | Required | Notes |
|---|---|---|
| Rust toolchain (stable, edition 2021) | Yes | via rustup |
ripgrep (rg) |
For the grep tool |
on PATH |
| OS keyring | For persistence | Windows Credential Manager / macOS Keychain / Secret Service. Without it the session runs ephemeral |
| Anthropic API key | For live replies | env var, keyring, key.txt, or /login |
Documentation
docs/OVERVIEW.md— what Magi is, themagi-corefoundation, and the multi-perspective philosophy behind the name.
License
Dual-licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Credits
The Magi name comes from Neon Genesis Evangelion (1995, Hideaki Anno / Gainax), where three supercomputers — Melchior, Balthasar, and Caspar — govern critical decisions through structured consensus. The same multi-perspective philosophy backs the sibling magi-core crate, which Magi Agent integrates for its planned multi-perspective consult workflow.