A composable AI agent platform in the terminal — one binary that adapts to whatever you need it to be.
Rustain is a standalone Rust terminal application. By default it's an AI coding agent — streaming responses, tool execution, collapsible content blocks, vim-style navigation, all terminal-native. Underneath, a hexagonal port architecture lets the same binary act as a personal assistant, a DevOps monitor, or any custom role — by switching a profile.
No vendor lock-in. Four LLM providers (Anthropic, OpenAI, Google, OpenRouter). Three open interop standards (Agent Skills, MCP, A2A). 27 port traits. 137k lines of Rust. One binary.
- Why Rustain exists
- The big idea
- Architecture at a glance
- Pick your door
- Repository layout
- Feature highlights
- Profile system
- Four-layer extensibility
- Project status & roadmap
- Contributing
- License
The 2026 agent landscape is a fragmented archipelago. Every tool picks a lane and stays there:
| Approach | Example | What it costs you |
|---|---|---|
| Vendor-locked | Claude Code, Cursor | One model provider, one role (coding). Rich UX but no composability. |
| Multi-provider, thin UI | Aider | Model freedom, but a minimal interface and no plugin architecture. |
| Plugin-rich, loose contracts | OpenClaw | Extensible, but TypeScript-only with runtime type confusion. |
| IDE-embedded | Claudian (Obsidian), Copilot | Locked to the host app. Can't run headless, can't serve as a network agent. |
The shared failure: identity is hardcoded. A coding agent is always a coding agent. A personal assistant is always a personal assistant. If you want both, you install two tools.
Rustain answers differently: identity is composed from ports. The same binary with a different profile file becomes a different agent — different persona, different memory strategy, different tools, different channels — without recompiling.
Rustain separates what an agent is (ports and profiles) from what an agent can do (tools, skills, protocols).
- 27 port traits define the behavioral dimensions: persona, memory, session, tools, channels, scheduler, context, security, approval, clipboard, and more. Each port is a Rust trait. Adapters implement ports. Profiles compose adapters into agent roles.
- Three open standards define external reach: Agent Skills (knowledge), MCP (tools), A2A (agent-to-agent delegation). All governed by one unified permission system.
- Profiles are TOML files, not code.
codingis the default. Switch topersonal-assistantand the same binary gains daily-log memory, a Telegram channel, and a cron scheduler. Create your own profile — no compilation required. - Runtime swapping lets you change identity mid-session. Hot swap (instant: persona, tools, context), warm swap (brief pause: memory, session), cold swap (agent loop restart: channels, scheduler). The TUI stays responsive throughout.
Hexagonal architecture with strict dependency rules:
┌──────────────────────────────────────────────────────────────┐
│ main.rs │
│ (composition root — only file │
│ naming concrete adapter types) │
└──────────────┬───────────────────────────────┬───────────────┘
│ │
┌──────────▼──────────┐ ┌──────────▼──────────┐
│ adapters/ │ │ infrastructure/ │
│ │ │ │
│ tui/ (ratatui) │ │ runtime/ │
│ cli/ (clap) │ │ event_loop.rs │
│ anthropic/ │ │ app_state.rs │
│ openai/ │ │ agent_core.rs │
│ ollama/ │ │ startup.rs │
│ mcp/ │ │ config.rs │
│ channel/ │ │ logging.rs │
│ daemon/ │ │ signals.rs │
│ vector_search/ │ │ paths.rs │
│ scheduler/ │ └─────────────────────┘
│ ...40+ adapters │
└──────────┬──────────┘
│ imports from
┌──────────▼──────────┐
│ domain/ │
│ │
│ models/ │ ← Pure types, enums, structs
│ ports/ │ ← 27 trait definitions
│ services/ │ ← Pure functions (tool FSM, frontmatter)
│ events.rs │ ← AppEvent, DomainInputEvent
│ errors.rs │ ← DomainError hierarchy
│ │
│ NO I/O imports │
└─────────────────────┘
Dependency rule: domain/ imports nothing from adapters/ or
infrastructure/. Adapters and infrastructure import from domain/ only.
main.rs is the sole file that names concrete types.
The runtime is a four-branch tokio::select! event loop processing a unified
AppEvent channel. Startup targets < 20ms to first frame render.
Prerequisites: Rust toolchain (edition 2024), an LLM API key.
# Build and run
cargo build --release
cargo run --release
# Or with a specific provider
ANTHROPIC_API_KEY=sk-... cargo run --release
OPENROUTER_API_KEY=sk-... cargo run --releaseFirst-run creates ~/.rustain/ for configuration, logs, and session data. No
config files required for the basic flow — just an API key in the environment
or stored via rustain auth login <provider>.
As a daemon (personal assistant mode):
rustain daemon start # start background daemon
rustain daemon attach # connect TUI to running daemon
rustain daemon status # check daemon state
rustain daemon stop # shut downDaemon mode enables always-on channels (Telegram, scheduled tasks) that persist
across TUI sessions. Systemd and launchd service templates are in dist/.
Authentication (credential management):
rustain auth login anthropic # store API key (masked entry + validation)
rustain auth login openai # store OpenAI key
rustain auth status # show configured providers, source, validation time
rustain auth status --json # scriptable status envelope
rustain auth list # list all providers, auth methods, signup URLs
rustain auth list --json # structured provider catalogEnv vars still win over ~/.rustain/auth.json; auth status reports that
winning source without probing the network. auth list shows the full provider
catalog including unconfigured and keyless providers.
Sessions (conversation history):
rustain session list # show saved sessions in current workspace
rustain session list --json # structured output for scripts
rustain session list --all # merge sessions across registered workspaces
rustain session list --all --json # same, with absolute workspace addresses
rustain session delete <id> # delete by exact id or unique prefix
rustain session delete <id> --force --dry-run
rustain session delete --all # delete all sessions in this workspace
rustain session delete --all-workspaces --force --dry-runsession delete is the first destructive CLI operation: it removes sessions
permanently with no trash/undo. It deletes by stable id only; the positional
# index from list is not accepted. The command is interactive by default;
use --force for scripts and --dry-run to preview what would be removed. An
in-use guard refuses to delete a session held by a running daemon (name and pid
are shown); --force skips prompts but does not bypass a confirmed holder.
For a guaranteed deletion, stop the daemon first with rustain daemon stop.
Sessions are stored per workspace under .claude/sessions/. session list --all
uses a hint registry at ~/.rustain/workspaces.json, populated on the first
successful save per workspace. The * marker in the human table indicates the
session that a bare rustain command resumes by default (most recent in the
current workspace). The id column is the stable address; use it with
rustain session delete <id> instead of positional indexes. In JSON, every row
carries an always-present absolute workspace field.
For full semantics, guards, exit codes, and bulk-delete behavior see
docs/sessions.md.
cargo build # debug build
cargo check # type check (fastest feedback)
cargo test # run the test suite (~180 test files)
cargo clippy # lint
cargo fmt # format
# Feature-gated builds
cargo build --no-default-features --features anthropic # minimal
cargo build --features telegram # with Telegram channel
cargo build --features vector-search # with local embeddingsDefault features: anthropic, openai, ollama, clipboard, mcp,
meta-search.
The test suite includes 47 conformance tests that enforce architectural invariants (dependency rules, lock policies, namespace conventions, port contracts). These run on every change and are the primary guard against architectural drift.
Tracing: logs route to ~/.rustain/rustain.log (10MB rolling rotation)
because stdout is owned by the ratatui terminal. Set RUST_LOG=debug for
verbose output.
- Architecture — hexagonal ports, adapter composition, event loop:
docs/andCLAUDE.md - Profiles — named adapter compositions, built-in profiles, schema:
docs/profiles.mdandprofiles/ - Adapter composition — per-port factory dispatch pattern:
docs/adapter-composition.md - MCP integration — client adapter, server management:
docs/mcp.md - Daemon mode — lifecycle, supervision, crash recovery:
docs/daemon.md - Configuration — layered config, provider setup:
docs/configuration.md - Session management — listing, deleting, in-use guard, exit codes:
docs/sessions.md - Testing — strategy, conformance tests, E2E harness:
TESTING.md - Planning artifacts — PRD, architecture, epics, sprint status:
prd.md(local copy)
rustain/
├── src/
│ ├── main.rs # composition root
│ ├── lib.rs # lib re-export for integration tests
│ ├── domain/ # pure domain — NO I/O crate imports
│ │ ├── models/ # types, enums, structs
│ │ ├── ports/ # 27 port trait definitions
│ │ ├── services/ # pure functions (tool FSM, frontmatter parser)
│ │ ├── events.rs # unified event types
│ │ └── errors.rs # domain error hierarchy
│ ├── adapters/ # 40+ adapter implementations
│ │ ├── tui/ # ratatui + crossterm terminal UI
│ │ ├── anthropic/ # Anthropic SSE streaming provider
│ │ ├── openai/ # OpenAI-compatible provider
│ │ ├── ollama/ # Local model provider
│ │ ├── mcp/ # MCP client adapter
│ │ ├── channel/ # Telegram, socket channels
│ │ ├── daemon/ # daemon lifecycle, crash recovery
│ │ ├── vector_search/ # fastembed + cosine similarity
│ │ ├── scheduler/ # cron task scheduling
│ │ └── ... # memory, skills, security, sandbox, etc.
│ └── infrastructure/ # OS/runtime concerns
│ ├── runtime/ # tokio event loop, app state, agent core
│ ├── startup.rs # ordered startup (< 20ms to first frame)
│ ├── config.rs # layered configuration loader
│ └── signals.rs # panic hook + signal handlers
├── tests/ # 180 test files inc. 47 conformance tests
├── profiles/ # built-in profile definitions (TOML)
│ ├── base.toml
│ ├── coding.toml
│ └── personal-assistant.toml
├── docs/ # architecture, adapter composition, MCP, profiles
├── dist/ # systemd + launchd service templates
└── Cargo.toml # edition 2024, MIT license
Terminal UI — ratatui 0.30 + crossterm 0.29. Multi-tab streaming chat,
collapsible tool blocks, vim-style navigation (j/k/i), command palette,
@-mention autocomplete, context meter with cache breakdown, plan mode overlay.
Tool execution — seven-state FSM per tool call (Validating → Scheduled → AwaitingApproval → Executing → Success/Error/Cancelled). Parallel batching for
safe tools, sequential fallback otherwise. Per-call cancellation tokens.
Capability Provider Architecture — protocol-agnostic extensibility. All
interop protocols implement one trait (CapabilityProvider: discover, register,
invoke, render). Adding a new protocol = implement one trait, call
registry.register().
Persistent memory — project-scoped memory, daily logs, long-term memory with hybrid retrieval (BM25 + vector cosine via fastembed, RRF fusion with time-decay). Context assembly with windowing (turn grouping + relevance-weighted LRU trim).
Daemon mode — background operation with systemd/launchd supervision, crash recovery (latest-only crash journal + capped logs), multi-client attach via Unix socket, foreground detection with boot-id veto and process nonce verification.
Session management — list sessions per workspace or across all registered
workspaces; delete by stable id with an in-use daemon guard, typed-count
confirmation for bulk deletes, --dry-run, and a versioned --json envelope.
Profiles are named TOML files that compose one adapter per port dimension:
# profiles/personal-assistant.toml
extends = "base"
[persona]
adapter = "custom"
system_prompt_file = "prompts/assistant.md"
[memory]
adapter = "daily-log"
[session]
adapter = "daemon"
[channel]
adapter = "telegram"
[scheduler]
adapter = "cron"Three built-in profiles ship with the binary:
| Profile | Persona | Memory | Session | Channel | Scheduler |
|---|---|---|---|---|---|
| base | minimal | project-scoped | local | TUI only | none |
| coding | coding agent | project-scoped + long-term | local | TUI only | none |
| personal-assistant | assistant | daily-log + long-term | daemon | Telegram | cron |
Switch at runtime via the TUI or CLI. Custom profiles are TOML files in
~/.rustain/profiles/ or .rustain/profiles/.
Subagent delegation (task tool, @Agents/<name>, plan delegation) requires
the composite tools adapter, which only coding selects by default. base
and custom profiles that extend it compose a plain builtin tools adapter and will
not expose the task tool — rustain doctor reports this on the Profiles
line. To enable delegation in any profile, set [tools] adapter = "composite".
Layer -1: Ports What the agent IS (27 port traits, composed by profiles)
Layer 0: Built-in tools What the agent ships with (bash, read, write, edit, glob, grep, web fetch)
Layer 1: Agent Skills Markdown-based procedural knowledge (agentskills.io standard, 30+ tools)
Layer 2: MCP Tool-level interop via external servers (stdio/SSE/HTTP)
Layer 3: A2A Agent-to-agent delegation (localhost/LAN/internet)
- Agent Skills follow the open standard at agentskills.io — any skill from
Claude Code, Codex, Gemini CLI, or Cursor works in Rustain without
modification. Discovery paths:
.agents/skills/,.rustain/skills/,.claude/skills/,~/.agents/skills/. - MCP extends the tool set via sandboxed server processes. Supports stdio, SSE, and HTTP transports.
- A2A enables agent-to-agent delegation. Rustain acts as both client (discover and delegate to remote agents) and server (accept tasks from external agents).
All layers are governed by one unified permission system with three modes: Normal (prompt for each tool), YOLO (auto-approve safe tools), and Plan (propose-then-execute).
Status: v0.1-alpha, actively developed. Currently in Epic 12 — daemon mode, multi-channel TUI, and cron scheduler. 12 epics completed to date covering the full agent lifecycle from streaming chat through memory, skills, MCP, sub-agents, context assembly, and daemon supervision.
| Phase | Milestone | Status |
|---|---|---|
| v0.1 | Streaming chat, tool execution, permission system, session persistence | Shipped (Epics 1–4) |
| v0.5 | Agent Skills, MCP, sub-agents, plan mode, multi-provider | Shipped (Epics 5–10) |
| v0.75 | Memory tiers, context assembly, windowing, vector search | Shipped (Epic 11) |
| v1.0 | Daemon mode, Telegram channel, cron scheduler, multi-client attach | In progress (Epic 12) |
| v1.5 | A2A client, multi-agent orchestration | Planned (Epic 14) |
| v2.0 | A2A server, dynamic adapter loading, community hub | Planned |
Detailed sprint tracking in
sprint-status.yaml.
Rustain is built under the BMad Method — planning artifacts (PRD, architecture, epics, stories) drive implementation, and conformance tests enforce architectural invariants on every change.
-
Clone and build:
git clone https://github.com/lunarpulse/rustain.git cd rustain cargo build -
Run the test suite:
cargo test cargo clippy --all-targets cargo fmt --check -
Read the architectural docs in
docs/and the conformance test README intests/CONFORMANCE_README.md.
- Hexagonal discipline: domain imports nothing external. Adapters import
only from domain.
main.rsis the sole composition root. - Conformance tests: 47 tests enforce architectural invariants (dependency rules, async lock policy, namespace conventions). They must pass before merge.
- Async lock policy:
tokio::sync::RwLock/Mutexonly in adapters and infrastructure. Release write guards before.awaitpoints. Exceptions require explicitCONFORMANCE_EXCEPTION_STD_SYNC_LOCKtags. - Port-first design: new capabilities start as a port trait in
domain/ports/, with a noop stub and a conformance test, before any adapter is written.
Licensed under the MIT License.