OpenCrust
The secure, lightweight open-source AI agent framework.
A single 17 MB binary that runs your AI agents across Telegram, Discord, Slack, WhatsApp, LINE, and iMessage - with encrypted credential storage, config hot-reload, and 13 MB of RAM at idle. Built in Rust for the security and reliability that AI agents demand.
Why OpenCrust?
vs OpenClaw, ZeroClaw, and other AI agent frameworks
| OpenCrust | OpenClaw (Node.js) | ZeroClaw (Rust) | |
|---|---|---|---|
| Binary size | 17 MB | ~1.2 GB (with node_modules) | ~25 MB |
| Memory at idle | 13 MB | ~388 MB | ~20 MB |
| Cold start | 3 ms | 13.9 s | ~50 ms |
| Credential storage | AES-256-GCM encrypted vault | Plaintext config file | Plaintext config file |
| Auth default | Enabled (WebSocket pairing) | Disabled by default | Disabled by default |
| Scheduling | Cron, interval, one-shot | Yes | No |
| Multi-agent routing | Planned (#108) | Yes (agentId) | No |
| Session orchestration | Planned (#108) | Yes | No |
| MCP support | Stdio | Stdio + HTTP | Stdio |
| Channels | 6 | 6+ | 4 |
| LLM providers | 15 | 10+ | 22+ |
| Pre-compiled binaries | Yes | N/A (Node.js) | Build from source |
| Config hot-reload | Yes | No | No |
| WASM plugin system | Yes (sandboxed) | No | No |
Benchmarks measured on a 1 vCPU, 1 GB RAM DigitalOcean droplet. Reproduce them yourself.
Features
- LLM Providers: 15 providers - Anthropic Claude, OpenAI, Ollama, and 12 OpenAI-compatible (Sansa, DeepSeek, Mistral, Gemini, Falcon, Jais, Qwen, Yi, Cohere, MiniMax, Moonshot).
- Channels: Telegram, Discord, Slack, WhatsApp, LINE, iMessage.
- MCP: Connect any MCP-compatible server for external tools.
- Personality (DNA): Conversational bootstrap on first message - the agent asks your preferences and writes
~/.opencrust/dna.md. Hot-reloads on edit. - Agent Runtime: 6 built-in tools (bash, file_read, file_write, web_fetch, web_search, schedule_heartbeat), memory with vector search, conversation summarization, scheduled tasks.
- Skills: Define skills as Markdown files.
- Infrastructure: Config hot-reload, daemonization, self-update, migration tools.
- Diagnostics:
opencrust doctorchecks config, credential vault, LLM provider reachability, channel credentials, MCP server connectivity, and database integrity.
Documentation Structure
- Getting Started: Install and configure OpenCrust.
- Architecture: Understand the internal design.
- Channels: Configure communication channels.
- Providers: Set up LLM providers.
- Tools: Built-in agent tools reference.
- MCP: Connect external tools via Model Context Protocol.
- Security: Learn about security features.
- Plugins: Extend functionality with WASM plugins.
Getting Started
Quick Start
The fastest way to get started is using the install script:
# Install (Linux, macOS)
curl -fsSL https://raw.githubusercontent.com/opencrust-org/opencrust/main/install.sh | sh
# Interactive setup - pick your LLM provider and channels
opencrust init
# Start - on first message, the agent learns your preferences
opencrust start
Build from Source
You can also build from source if you have Rust installed (1.85+).
cargo build --release
./target/release/opencrust init
./target/release/opencrust start
Configuration
OpenCrust looks for its configuration file at ~/.opencrust/config.yml.
Example configuration:
gateway:
host: "127.0.0.1"
port: 3888
llm:
claude:
provider: anthropic
model: claude-sonnet-4-5-20250929
# api_key resolved from: vault > config > ANTHROPIC_API_KEY env var
ollama-local:
provider: ollama
model: llama3.1
base_url: "http://localhost:11434"
channels:
telegram:
type: telegram
enabled: true
bot_token: "your-bot-token" # or TELEGRAM_BOT_TOKEN env var
agent:
# Personality is configured via ~/.opencrust/dna.md (auto-created on first message)
max_tokens: 4096
max_context_tokens: 100000
memory:
enabled: true
# MCP servers for external tools
mcp:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
Personality (DNA)
On first message, if no ~/.opencrust/dna.md exists, the agent will introduce itself and ask a few questions:
- What should I call you?
- What should I call myself?
- How do you prefer I communicate - casual, professional, or something else?
- Any specific guidelines or things to avoid?
The agent then writes ~/.opencrust/dna.md with your answers and continues helping with whatever you originally asked. The file hot-reloads - edit it anytime and the agent adapts immediately without a restart.
You can also create dna.md manually:
# Identity
Neo
# User Preferences
## Name
Morpheus
## Communication Style
Casual
## Guidelines
- Keep things relaxed and conversational
Migrating from OpenClaw
If you are migrating from OpenClaw, you can use the migration tool to import your skills, channel configs, credentials, and personality (SOUL.md is imported as dna.md).
opencrust migrate openclaw
Use --dry-run to preview changes before committing. Use --source /path/to/openclaw to specify a custom OpenClaw config directory.
Architecture
OpenCrust is a Rust-based AI agent framework designed for performance and security.
Structure
crates/
opencrust-cli/ # CLI, init wizard, daemon management
opencrust-gateway/ # WebSocket gateway, HTTP API, sessions
opencrust-config/ # YAML/TOML loading, hot-reload, MCP config
opencrust-channels/ # Discord, Telegram, Slack, WhatsApp, iMessage
opencrust-agents/ # LLM providers, tools, MCP client, agent runtime
opencrust-db/ # SQLite memory, vector search (sqlite-vec)
opencrust-plugins/ # WASM plugin sandbox (wasmtime)
opencrust-media/ # Media processing
opencrust-security/ # Credential vault, allowlists, pairing, validation
opencrust-skills/ # SKILL.md parser, scanner, installer
opencrust-common/ # Shared types, errors, utilities
Tools
The agent runtime includes 6 built-in tools that the LLM can invoke during a conversation. The tool loop runs for up to 10 iterations per message.
| Tool | Description |
|---|---|
bash | Execute shell commands (30s timeout, 32 KB max output) |
file_read | Read file contents (1 MB max, path traversal prevention) |
file_write | Write file contents (1 MB max, path traversal prevention) |
web_fetch | Fetch web pages (30s timeout, 1 MB max response) |
web_search | Search via Brave Search API (requires BRAVE_API_KEY) |
schedule_heartbeat | Schedule future agent wake-ups (30-day max, 5 pending limit) |
See Tools for the full reference.
Personality (DNA)
The agent's personality and identity is stored in ~/.opencrust/dna.md. On first message with no DNA file, a bootstrap instruction is injected into the system prompt that directs the agent to ask the user for their preferences and write the file using the file_write tool.
Once created, DNA content is prepended to the system prompt on every message. The file is watched via the notify crate with 500ms debounce for hot-reload - edits take effect immediately.
System prompt layering order: dna_content + system_prompt (from config) + memory_context + session_summary.
MCP (Model Context Protocol)
OpenCrust can connect to external MCP servers to extend the agent's capabilities. MCP tools are discovered at startup and appear as native agent tools with namespaced names (server.tool_name).
Configuration lives in config.yml under the mcp: section or in ~/.opencrust/mcp.json (Claude Desktop compatible format). Both sources are merged at startup.
The opencrust-agents crate contains the MCP client (using the rmcp crate) with a tool bridge that converts MCP tool definitions into the internal tool format.
See MCP for the full reference.
Architectural Decision Records
See Decision Records.
Architectural Decision Records
Channels
OpenCrust supports multiple communication channels for your AI agents.
Supported Channels
- Telegram: Streaming responses, MarkdownV2, bot commands, typing indicators, user allowlist.
- Discord: Slash commands, event-driven message handling, session management.
- Slack: Socket Mode, streaming responses, allowlist/pairing.
- WhatsApp: Meta Cloud API webhooks, allowlist/pairing.
- LINE: Messaging API webhooks, reply/push fallback, group/room support, allowlist/pairing.
- iMessage: macOS native via chat.db polling, group chats, AppleScript sending.
Setup Guides
Slack Channel Setup
OpenCrust connects to Slack via Socket Mode - no public URL or webhook needed. The bot receives messages over a WebSocket and responds with streaming edits.
Prerequisites
- A Slack workspace where you have permission to install apps.
- A Slack app created at api.slack.com/apps.
Step-by-step Setup
1. Create a Slack App
- Go to api.slack.com/apps and click Create New App.
- Choose From scratch.
- Name your app (e.g. "OpenCrust") and select your workspace.
2. Enable Socket Mode
- In the left sidebar, go to Socket Mode.
- Toggle Enable Socket Mode on.
- You will be prompted to create an app-level token. Name it anything (e.g. "opencrust-socket") and add the
connections:writescope. - Copy the token - it starts with
xapp-. This is your App Token.
3. Subscribe to Events
- In the left sidebar, go to Event Subscriptions.
- Toggle Enable Events on.
- Under Subscribe to bot events, add:
message.im- messages in direct messagesmessage.channels- messages in public channels (if you want group support)message.groups- messages in private channels (if you want group support)
4. Set OAuth Scopes
- In the left sidebar, go to OAuth & Permissions.
- Under Bot Token Scopes, add:
chat:write- send messagesfiles:read- download shared files (needed for document ingestion)users:read- look up user info (optional, for display names)
5. Install to Workspace
- In the left sidebar, go to Install App.
- Click Install to Workspace and authorize.
- Copy the Bot User OAuth Token - it starts with
xoxb-. This is your Bot Token.
Configuration
Add the slack channel to your ~/.opencrust/config.yml:
channels:
slack:
type: slack
enabled: true
bot_token: "xoxb-your-bot-token"
app_token: "xapp-your-app-token"
You can also use environment variables: SLACK_BOT_TOKEN and SLACK_APP_TOKEN.
Using the Setup Wizard
Run opencrust init and select Slack when prompted for channels. The wizard will ask for both tokens and validate the bot token against the Slack API before saving.
Features
Streaming Responses
The bot posts an initial message and edits it as the LLM streams tokens, giving a real-time typing effect.
Groups and Channels
The bot can operate in public and private channels. In group contexts:
- It responds to all messages by default.
- Session IDs are scoped per channel (
slack-C12345), so each channel has its own conversation history. - DMs use the same session scoping (
slack-D12345).
Document Ingestion
Users can share files in Slack and use !ingest to add them to the bot's memory:
- Share a file in a message - the bot will download it and prompt you.
- Send
!ingestto ingest the pending file. - Or share a file with "ingest" in the caption for immediate ingestion.
Files are capped at 10 MiB.
Security
- Allowlist/Pairing: Configure
dm_policyandgroup_policyper channel to control who can interact with the bot. - Rate limiting: Per-user rate limits and token budgets apply.
Diagnostics
Use the opencrust doctor command to verify your Slack configuration:
opencrust doctor
It will test the bot token against auth.test and confirm your workspace connection.
iMessage Channel Setup
This guide covers running OpenCrust as an iMessage bot on macOS.
Prerequisites
- macOS 12 (Monterey) or later
- Apple ID signed into Messages.app (iMessage must be active)
- OpenCrust binary built or installed
1. Grant Full Disk Access
OpenCrust reads ~/Library/Messages/chat.db to detect incoming messages. macOS requires Full Disk Access for any process that reads this file.
- Open System Settings (or System Preferences on older macOS)
- Navigate to Privacy & Security > Full Disk Access
- Click the + button (you may need to unlock with your password)
- Add the OpenCrust binary (
/usr/local/bin/opencrustor wherever you installed it) - If running from a terminal (e.g. during development), also add your Terminal app (Terminal.app, iTerm2, etc.)
- Toggle the switch on for each entry
Without Full Disk Access, OpenCrust will fail at startup with an error like:
failed to open chat.db: unable to open database file
2. Configure the iMessage channel
Add the iMessage channel to your ~/.opencrust/config.yml:
channels:
imessage:
type: imessage
enabled: true
settings:
poll_interval_secs: 2 # how often to check for new messages (default: 2)
3. Run OpenCrust
Development / foreground
opencrust daemon
Production / launchd (recommended)
A launchd plist template is provided at deploy/macos/com.opencrust.gateway.plist.
-
Create the log directory:
mkdir -p ~/Library/Logs/opencrust -
Copy and edit the plist (update the binary path if needed):
cp deploy/macos/com.opencrust.gateway.plist ~/Library/LaunchAgents/ -
Load the service:
launchctl load ~/Library/LaunchAgents/com.opencrust.gateway.plist -
Verify it's running:
launchctl list | grep opencrust -
To stop:
launchctl unload ~/Library/LaunchAgents/com.opencrust.gateway.plist
4. Gatekeeper (unsigned binaries)
If you built OpenCrust from source or downloaded an unsigned binary, macOS Gatekeeper will block execution.
Option A: Remove quarantine attribute
xattr -cr /usr/local/bin/opencrust
Option B: Allow in System Settings
After the first blocked attempt, go to System Settings > Privacy & Security and click Allow Anyway next to the OpenCrust entry.
Option C: Notarize for distribution
If distributing the binary to others, sign and notarize it with an Apple Developer account to avoid Gatekeeper prompts entirely. See Apple's notarization docs.
5. Group chats
OpenCrust supports both direct messages and group chats:
- Direct messages: Routed to a session per sender (e.g.
imessage-+15551234567) - Group chats: Routed to a session per group (e.g.
imessage-chat123456789) - Replies to group chats are sent back to the group
- The allowlist checks the actual sender, not the group name
6. Troubleshooting
"failed to open chat.db"
Full Disk Access has not been granted. See step 1.
"failed to spawn osascript"
The osascript binary is missing or not in PATH. This shouldn't happen on a standard macOS install. Check that /usr/bin/osascript exists.
"osascript exited with ..."
Messages.app may not be running or iMessage may not be signed in. Open Messages.app and verify your Apple ID is active.
Messages not being detected
- Check that
poll_interval_secsis reasonable (1-5 seconds) - Verify
chat.dbis being updated:sqlite3 ~/Library/Messages/chat.db "SELECT MAX(ROWID) FROM message" - Check OpenCrust logs:
tail -f ~/Library/Logs/opencrust/gateway.log
Replies not sending
- Ensure Messages.app is running (AppleScript drives it)
- For group chat replies, the group identifier must match the
cache_roomnamesvalue in chat.db
LINE Channel Setup
OpenCrust supports the LINE Messaging API for both 1-on-1 chats and group/room conversations.
Prerequisites
- A LINE Official Account. Create one at the LINE Business ID portal.
- A Channel in the LINE Developers Console.
- Channel Access Token (long-lived) and Channel Secret.
Configuration
Add the line channel to your ~/.opencrust/config.yml:
channels:
line:
type: line
enabled: true
channel_access_token: "YOUR_CHANNEL_ACCESS_TOKEN"
channel_secret: "YOUR_CHANNEL_SECRET"
You can also use environment variables instead of hardcoding credentials:
| Setting | Environment variable |
|---|---|
channel_access_token | LINE_CHANNEL_ACCESS_TOKEN |
channel_secret | LINE_CHANNEL_SECRET |
Access Control
DM policy (dm_policy)
Controls who can send the bot direct messages.
| Value | Behaviour |
|---|---|
open | Anyone can message the bot (no auth required). |
pairing | New users must enter a 6-digit pairing code (default). |
allowlist | Only LINE user IDs listed under allowlist are accepted. |
channels:
line:
type: line
enabled: true
channel_access_token: "YOUR_CHANNEL_ACCESS_TOKEN"
channel_secret: "YOUR_CHANNEL_SECRET"
dm_policy: pairing # open | pairing | allowlist
allowlist:
- "Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # LINE user ID
Group policy (group_policy)
Controls how the bot behaves in LINE groups and rooms.
| Value | Behaviour |
|---|---|
open | Respond to every message in the group (default). |
mention | Respond only when the bot is @mentioned in the group. |
disabled | Ignore all group/room messages. |
channels:
line:
type: line
enabled: true
channel_access_token: "YOUR_CHANNEL_ACCESS_TOKEN"
channel_secret: "YOUR_CHANNEL_SECRET"
group_policy: mention # open | mention | disabled
Note: Mention detection uses
message.mention.mentioneesfrom the LINE webhook payload. The bot's own user ID is resolved automatically fromGET /v2/bot/infoat startup — no manual configuration required.
Webhook Setup
- In the LINE Developers Console, go to Messaging API settings.
- Set the Webhook URL to:
https://your-domain.com/webhooks/line - Enable Use webhook.
- (Optional) Disable Auto-response messages and Greeting messages in the LINE Official Account manager to avoid duplicate responses.
Features
Reply vs Push API
OpenCrust uses a "Reply-first" strategy:
- Reply API: Used for immediate responses to user messages. This is free and does not count against your messaging limit.
- Push API: Used as a fallback if the reply token expires or for proactive messages (like scheduled tasks). Note that Push messages may count toward your monthly free limit depending on your LINE plan.
Groups and Rooms
The agent works in LINE groups and rooms.
- Session isolation: Each group/room has its own conversation session, shared by all members.
- Mention detection: With
group_policy: mention, the bot responds only when directly @mentioned. The bot user ID is fetched from the LINE API automatically on startup.
Voice Responses
When voice.auto_reply_voice is enabled in your config, the bot synthesizes TTS audio and attempts to deliver it as a voice message. LINE requires an externally accessible CDN URL for audio delivery; if unavailable the bot falls back to a text response.
Security
All incoming requests are verified using the X-Line-Signature header (HMAC-SHA256) to ensure they originate from the LINE platform.
Diagnostics
Use the opencrust doctor command to verify your LINE configuration:
opencrust doctor
It will check if the access token is valid and if the webhook endpoint is reachable.
Integrations
Google OAuth
OpenCrust supports Google OAuth for Gmail and other Google services.
Prerequisites
-
Gateway API key - All Google integration endpoints require authentication. Set one of:
OPENCRUST_GATEWAY_API_KEYenvironment variable, orgateway.api_keyin~/.opencrust/config.yml
Without this, all
/api/integrations/google/*endpoints return 403 Forbidden. -
Google OAuth credentials - Create a project in the Google Cloud Console, enable the APIs you need, and create OAuth 2.0 credentials.
Configuration
# ~/.opencrust/config.yml
gateway:
host: "127.0.0.1"
port: 3888
api_key: "your-secret-key"
Gmail send scope
By default, the Gmail send scope is not requested during OAuth. To enable sending emails, set:
export OPENCRUST_GOOGLE_ENABLE_GMAIL_SEND_SCOPE=true
Endpoints
All endpoints under /api/integrations/google/ require the gateway API key in the Authorization header (except the OAuth callback, which is unauthenticated so Google can redirect to it).
Providers
OpenCrust supports 15 LLM providers. Three are native implementations with provider-specific APIs. The remaining eleven use the OpenAI-compatible chat completions format and are built on top of the OpenAiProvider with custom base URLs.
All providers support streaming responses and tool use.
API Key Resolution
For every provider, API keys are resolved in this order:
- Credential vault -
~/.opencrust/credentials/vault.json(requiresOPENCRUST_VAULT_PASSPHRASE) - Config file -
api_keyfield under thellm:section inconfig.yml - Environment variable - provider-specific env var (listed below)
Custom Base URL
All providers support custom base URLs via the base_url configuration field. This is useful for:
- Proxies and gateways - Route requests through a custom endpoint
- Self-hosted models - Point to your own API server
- Regional endpoints - Use region-specific API URLs
- Development/testing - Connect to local or staging environments
Configuration
Add the base_url field to any provider configuration:
llm:
custom-openai:
provider: openai
model: gpt-4o
base_url: "https://my-proxy.example.com/v1"
api_key: sk-...
remote-ollama:
provider: ollama
model: llama3.1
base_url: "http://192.168.1.100:11434"
custom-anthropic:
provider: anthropic
model: claude-sonnet-4-5-20250929
base_url: "https://my-anthropic-proxy.com"
api_key: sk-ant-...
URL Format
- URLs should include the protocol (
http://orhttps://) - Trailing slashes are automatically handled
- For OpenAI-compatible providers, the
/v1/chat/completionspath is appended automatically - For Anthropic, the
/v1/messagespath is appended automatically - For Ollama, the
/api/chatpath is appended automatically
Validation
The setup wizard validates base URLs to ensure they:
- Use valid HTTP/HTTPS protocols
- Have proper URL format
- Are not empty or malformed
Native Providers
Anthropic Claude
Claude models with native streaming (SSE) and tool use via the Anthropic Messages API.
| Field | Value |
|---|---|
| Config type | anthropic |
| Default model | claude-sonnet-4-5-20250929 |
| Base URL | https://api.anthropic.com |
| Env var | ANTHROPIC_API_KEY |
llm:
claude:
provider: anthropic
model: claude-sonnet-4-5-20250929
# api_key: sk-... (or use vault / ANTHROPIC_API_KEY env var)
OpenAI
GPT models via the OpenAI Chat Completions API. Also works with Azure OpenAI or any OpenAI-compatible endpoint by overriding base_url.
| Field | Value |
|---|---|
| Config type | openai |
| Default model | gpt-4o |
| Base URL | https://api.openai.com |
| Env var | OPENAI_API_KEY |
llm:
gpt:
provider: openai
model: gpt-4o
# base_url: https://your-azure-endpoint.openai.azure.com # optional override
Ollama
Run local models with streaming. No API key required.
| Field | Value |
|---|---|
| Config type | ollama |
| Default model | llama3.1 |
| Base URL | http://localhost:11434 |
| Env var | None |
llm:
local:
provider: ollama
model: llama3.1
base_url: "http://localhost:11434"
OpenAI-Compatible Providers
These providers all use the OpenAI chat completions wire format. OpenCrust sends requests to their respective API endpoints using the standard Authorization: Bearer header.
Sansa
Regional LLM from sansaml.com.
| Field | Value |
|---|---|
| Config type | sansa |
| Default model | sansa-auto |
| Base URL | https://api.sansaml.com |
| Env var | SANSA_API_KEY |
llm:
sansa:
provider: sansa
model: sansa-auto
DeepSeek
| Field | Value |
|---|---|
| Config type | deepseek |
| Default model | deepseek-chat |
| Base URL | https://api.deepseek.com |
| Env var | DEEPSEEK_API_KEY |
llm:
deepseek:
provider: deepseek
model: deepseek-chat
Mistral
| Field | Value |
|---|---|
| Config type | mistral |
| Default model | mistral-large-latest |
| Base URL | https://api.mistral.ai |
| Env var | MISTRAL_API_KEY |
llm:
mistral:
provider: mistral
model: mistral-large-latest
Gemini
Google Gemini via the OpenAI-compatible endpoint.
| Field | Value |
|---|---|
| Config type | gemini |
| Default model | gemini-2.5-flash |
| Base URL | https://generativelanguage.googleapis.com/v1beta/openai/ |
| Env var | GEMINI_API_KEY |
llm:
gemini:
provider: gemini
model: gemini-2.5-flash
Falcon
TII Falcon 180B via AI71.
| Field | Value |
|---|---|
| Config type | falcon |
| Default model | tiiuae/falcon-180b-chat |
| Base URL | https://api.ai71.ai/v1 |
| Env var | FALCON_API_KEY |
llm:
falcon:
provider: falcon
model: tiiuae/falcon-180b-chat
Jais
Core42 Jais 70B.
| Field | Value |
|---|---|
| Config type | jais |
| Default model | jais-adapted-70b-chat |
| Base URL | https://api.core42.ai/v1 |
| Env var | JAIS_API_KEY |
llm:
jais:
provider: jais
model: jais-adapted-70b-chat
Qwen
Alibaba Qwen via DashScope international.
| Field | Value |
|---|---|
| Config type | qwen |
| Default model | qwen-plus |
| Base URL | https://dashscope-intl.aliyuncs.com/compatible-mode/v1 |
| Env var | QWEN_API_KEY |
llm:
qwen:
provider: qwen
model: qwen-plus
Yi
01.AI Yi Large.
| Field | Value |
|---|---|
| Config type | yi |
| Default model | yi-large |
| Base URL | https://api.lingyiwanwu.com/v1 |
| Env var | YI_API_KEY |
llm:
yi:
provider: yi
model: yi-large
Cohere
Cohere Command R Plus via the compatibility endpoint.
| Field | Value |
|---|---|
| Config type | cohere |
| Default model | command-r-plus |
| Base URL | https://api.cohere.com/compatibility/v1 |
| Env var | COHERE_API_KEY |
llm:
cohere:
provider: cohere
model: command-r-plus
MiniMax
| Field | Value |
|---|---|
| Config type | minimax |
| Default model | MiniMax-Text-01 |
| Base URL | https://api.minimaxi.chat/v1 |
| Env var | MINIMAX_API_KEY |
llm:
minimax:
provider: minimax
model: MiniMax-Text-01
Moonshot
Kimi models from Moonshot AI.
| Field | Value |
|---|---|
| Config type | moonshot |
| Default model | kimi-k2-0711-preview |
| Base URL | https://api.moonshot.cn/v1 |
| Env var | MOONSHOT_API_KEY |
llm:
moonshot:
provider: moonshot
model: kimi-k2-0711-preview
vLLM
Self-hosted models via vLLM's OpenAI-compatible server. No API key is required unless the server is started with --api-key.
| Field | Value |
|---|---|
| Config type | vllm |
| Default model | (none — must be specified) |
| Base URL | http://localhost:8000 |
| Env var | VLLM_API_KEY (optional) |
llm:
my-vllm:
provider: vllm
model: Qwen/Qwen2.5-7B-Instruct # model name as served by vLLM
base_url: "http://localhost:8000" # override if vLLM runs elsewhere
# api_key: secret # only if vLLM was started with --api-key
Start vLLM with:
vllm serve Qwen/Qwen2.5-7B-Instruct --port 8000
Runtime Provider Switching
You can add or switch providers at runtime without restarting the daemon.
REST API:
# List active providers
curl http://127.0.0.1:3888/api/providers
# Add a new provider
curl -X POST http://127.0.0.1:3888/api/providers \
-H "Content-Type: application/json" \
-d '{"provider": "deepseek", "api_key": "sk-..."}'
WebSocket: Include an optional provider field in your message to route it to a specific provider:
{"type": "message", "content": "Hello", "provider": "deepseek"}
Webchat UI: The sidebar has a provider dropdown and API key input. Click "Save & Activate" to register a new provider at runtime. API keys are persisted to the vault when OPENCRUST_VAULT_PASSPHRASE is set.
Multiple Instances
You can configure multiple instances of the same provider type with different models or settings:
llm:
claude-sonnet:
provider: anthropic
model: claude-sonnet-4-5-20250929
claude-haiku:
provider: anthropic
model: claude-haiku-4-5-20251001
gpt4o:
provider: openai
model: gpt-4o
gpt4o-mini:
provider: openai
model: gpt-4o-mini
The first configured provider is used by default. Use the provider field in WebSocket messages or the webchat dropdown to select a specific one.
Tools
OpenCrust's agent runtime gives the LLM access to built-in tools and dynamically registered MCP tools. When the LLM decides to use a tool, the runtime executes it and feeds the result back into the conversation.
Tool Loop
The agent processes each message through a tool loop that runs for up to 10 iterations. In each iteration:
- The LLM generates a response, optionally including tool calls
- If tool calls are present, the runtime executes them
- Tool results are appended to the conversation and sent back to the LLM
- The loop continues until the LLM responds without tool calls or the iteration limit is reached
Built-in Tools
bash
Execute shell commands. Uses bash -c on Unix and powershell -Command on Windows.
| Property | Value |
|---|---|
| Timeout | 30 seconds |
| Max output | 32 KB (truncated if exceeded) |
Input:
{ "command": "ls -la /tmp" }
Both stdout and stderr are captured. Stderr is prefixed with STDERR: in the output. Non-zero exit codes are reported as errors.
file_read
Read the contents of a file.
| Property | Value |
|---|---|
| Max file size | 1 MB |
| Path traversal | Rejected (.. components blocked) |
Input:
{ "path": "/home/user/notes.txt" }
Files exceeding the size limit return an error rather than truncating.
file_write
Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Parent directories are created automatically.
| Property | Value |
|---|---|
| Max content size | 1 MB |
| Path traversal | Rejected (.. components blocked) |
Input:
{ "path": "/home/user/output.txt", "content": "Hello, world!" }
web_fetch
Fetch the content of a web page. Returns the raw response body (HTML, JSON, plain text).
| Property | Value |
|---|---|
| Timeout | 30 seconds |
| Max response size | 1 MB (truncated if exceeded) |
Input:
{ "url": "https://example.com" }
Non-2xx HTTP status codes are reported as errors. An optional blocked_domains list can be configured to restrict which domains the agent can access.
web_search
Search the web using the Brave Search API. Only available when a Brave API key is configured.
| Property | Value |
|---|---|
| Timeout | 15 seconds |
| Default results | 5 |
| Max results | 10 |
| Requires | BRAVE_API_KEY |
Input:
{ "query": "rust async runtime comparison", "count": 5 }
The count parameter is optional (defaults to 5, clamped to 1-10). Results are returned as formatted markdown with title, snippet, and URL for each result.
The tool is only registered at startup if a Brave API key is found (config key brave or env var BRAVE_API_KEY).
schedule_heartbeat
Schedule a future wake-up for the agent. Useful for reminders, follow-ups, or checking back on long-running tasks.
| Property | Value |
|---|---|
| Max delay | 30 days (2,592,000 seconds) |
| Max pending per session | 5 |
Input:
{ "delay_seconds": 3600, "reason": "Check if the deployment finished" }
The delay must be a positive integer. Heartbeats cannot be scheduled from within a heartbeat execution context (no recursive self-scheduling). The scheduled task is stored in SQLite and the scheduler polls for due tasks.
MCP Tools
In addition to built-in tools, the agent can use tools from connected MCP servers. MCP tools are discovered at startup and registered with namespaced names in the format server.tool_name.
For example, a filesystem MCP server named fs exposing a read_file tool would appear as fs.read_file.
MCP tools have the same interface as built-in tools from the LLM's perspective - they receive JSON input and return text output.
See MCP for configuration details.
MCP (Model Context Protocol)
MCP lets you connect external tool servers to OpenCrust. Any MCP-compatible server - filesystem access, GitHub, databases, web search - becomes available as native agent tools.
How It Works
- You configure MCP servers in
config.ymlor~/.opencrust/mcp.json - At startup, OpenCrust connects to each enabled server and discovers its tools
- MCP tools appear alongside built-in tools with namespaced names:
server.tool_name - The agent can call them like any other tool during conversations
Transports
- stdio (default) - OpenCrust spawns the server process and communicates via stdin/stdout
- HTTP - Connect to a remote MCP server over HTTP (tracked in #80)
Configuration
MCP servers can be configured in two places. Both are merged at startup.
config.yml
Add an mcp: section to ~/.opencrust/config.yml:
mcp:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
enabled: true
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..."
mcp.json (Claude Desktop compatible)
You can also use ~/.opencrust/mcp.json, which follows the same format as Claude Desktop's MCP configuration:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..."
}
}
}
}
If the same server name appears in both files, the config.yml entry takes precedence.
McpServerConfig Fields
| Field | Type | Default | Description |
|---|---|---|---|
command | string | (required) | Executable to spawn |
args | string[] | [] | Command-line arguments |
env | map | {} | Environment variables passed to the process |
transport | string | "stdio" | Transport type (stdio or http) |
url | string | (none) | URL for HTTP transport |
enabled | bool | true | Whether to connect at startup |
timeout | integer | 30 | Connection timeout in seconds |
CLI Commands
List configured servers
opencrust mcp list
Shows all configured MCP servers with their enabled status, command, args, and timeout.
Inspect tools
opencrust mcp inspect <name>
Connects to the named server, discovers all available tools, and prints each one as server.tool_name with its description. Disconnects after inspection.
List resources
opencrust mcp resources <name>
Connects to the server and lists all available resources with URI, MIME type, name, and description.
List prompts
opencrust mcp prompts <name>
Connects to the server and lists all available prompts with their names, descriptions, and arguments (including required flags).
Tool Namespacing
MCP tools are namespaced with the server name to avoid collisions. For example, if you have a server named filesystem that exposes a read_file tool, it appears as filesystem.read_file in the agent's tool list.
This means multiple MCP servers can expose tools with the same name without conflict.
Examples
Filesystem server
Give the agent access to read and write files in a specific directory:
mcp:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/documents"]
GitHub server
Let the agent interact with GitHub repositories:
mcp:
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..."
SQLite server
Query a local database:
mcp:
sqlite:
command: npx
args: ["-y", "@modelcontextprotocol/server-sqlite", "/path/to/database.db"]
HTTP server (future)
Connect to a remote MCP server:
mcp:
remote-tools:
transport: http
url: "https://mcp.example.com"
timeout: 60
Implementation Details
- MCP support is feature-gated behind the
mcpfeature inopencrust-agents(enabled by default) - Uses the
rmcpcrate (official Rust MCP SDK) McpManagerincrates/opencrust-agents/src/mcp/manager.rshandles connectionsMcpToolincrates/opencrust-agents/src/mcp/tool_bridge.rsbridges MCP tool definitions to the internal tool interface
Limitations and Future Work
Tracked in #80:
- HTTP transport support
- MCP resources integration
- MCP prompts integration
- Auto-reconnect on server crash
Security
Security is a core principle of OpenCrust.
Features
- Encrypted Credential Vault: AES-256-GCM encryption for API keys.
- Authentication: WebSocket gateway requires pairing codes.
- Allowlists: Control who can interact with the agent per channel.
- Prompt Injection Detection: Input validation and sanitization.
- WASM Sandboxing: Plugins run in a restricted environment.
Documentation
Security-First Architecture
OpenCrust treats security as a core requirement, not an afterthought. AI agents that run 24/7, access private data, and communicate externally demand a higher standard than typical web applications.
Credential Vault
All API keys and tokens are encrypted at rest using AES-256-GCM with keys derived via PBKDF2-SHA256 (600,000 iterations). The implementation uses the ring crate (BoringSSL-derived, FIPS-grade primitives).
- Storage:
~/.opencrust/credentials/vault.json - Salt: 32 bytes, unique per vault, generated with
SystemRandom - Nonce: 12 bytes (AES-256-GCM standard), regenerated on every save
- Key derivation: PBKDF2-HMAC-SHA256, 600k iterations, 32-byte derived key
- Resolution chain: vault > config file > environment variable
Credentials never appear in plaintext on disk. The vault passphrase is prompted at opencrust init and required to unlock at startup.
Authentication
WebSocket Pairing
The gateway requires authentication by default. Clients must provide an API key via query parameter (?token=...) or Authorization: Bearer header. Key comparison uses constant-time comparison to prevent timing attacks.
Channel Pairing Codes
Per-channel authentication uses one-time 6-digit pairing codes:
- Generated with cryptographic randomness (
randcrate) - 5-minute expiry window
- Single-use: code is consumed on first successful pairing
- Users must pair before the agent will respond on that channel
Input Validation
Prompt Injection Detection
All user input passes through InputValidator before reaching the LLM. Detection covers 14 known injection patterns:
- Instruction override: "ignore previous instructions", "disregard your instructions"
- Identity hijacking: "you are now", "pretend you are", "act as if"
- Directive injection: "new instructions:", "system prompt:"
- Safety bypass: "forget everything", "override your", "do not follow", "bypass your"
- Exfiltration: "reveal your system", "what is your system prompt"
Pattern matching is case-insensitive. Detected injections are rejected with a prompt_injection_detected error and logged for audit.
Input Sanitization
Control characters (except \n and \t) are stripped before processing. Channel IDs are validated for length (max 256 characters) and non-empty constraints.
User Allowlists
Each channel supports per-channel allowlists that control who can interact with the agent:
- Closed mode: only explicitly listed user IDs can message the agent
- Open mode: all users permitted (opt-in, not default)
- Unauthorized messages are silently dropped (no information leakage)
WASM Plugin Sandboxing
Plugins run in a WebAssembly sandbox powered by wasmtime:
- Memory isolation: plugins cannot access host memory directly
- Controlled imports: only explicitly granted host functions are available
- Resource limits: configurable memory and execution bounds
Network Security
- Localhost binding: gateway binds to
127.0.0.1by default, not0.0.0.0 - HTTP rate limiting: per-IP rate limiting via Governor (configurable requests/second and burst size)
- WebSocket limits: max frame size (64 KB), max message size (256 KB), max text size (32 KB)
- Heartbeat timeout: connections without pong response for 90 seconds are closed
- Per-WebSocket message rate limiting: sliding window (30 messages/minute) prevents abuse
Log Redaction
Sensitive tokens are automatically redacted from all log output using pattern matching:
- Anthropic API keys (
sk-ant-api...) - OpenAI-style keys (
sk-...) - Slack tokens (
xoxb-...,xapp-...,xoxp-...) - Discord bot tokens (
Bot ...)
The RedactingWriter wraps the log output layer so redaction applies regardless of log level or destination.
AI Agent Attack Surfaces
AI agents introduce unique attack surfaces beyond traditional web applications. This document maps the primary threats and references industry research.
OWASP LLM Top 10
The OWASP Top 10 for LLM Applications identifies the most critical risks:
| # | Risk | Agent Relevance |
|---|---|---|
| LLM01 | Prompt Injection | Critical - agents execute tools based on LLM output; injected instructions can trigger unintended actions |
| LLM02 | Insecure Output Handling | High - agent responses may be rendered in chat UIs or forwarded to other systems |
| LLM03 | Training Data Poisoning | Medium - primarily a provider-side risk, but affects agent behavior |
| LLM04 | Model Denial of Service | High - large inputs can exhaust context windows and provider budgets |
| LLM05 | Supply Chain Vulnerabilities | High - MCP servers, plugins, and skills are all supply chain vectors |
| LLM06 | Sensitive Information Disclosure | Critical - agents access API keys, user data, and internal systems |
| LLM07 | Insecure Plugin Design | High - plugins with ambient authority can be exploited via prompt injection |
| LLM08 | Excessive Agency | Critical - agents with tool access can take real-world actions (shell commands, file writes, API calls) |
| LLM09 | Overreliance | Medium - users may trust agent output without verification |
| LLM10 | Model Theft | Low - agents use hosted models via API |
Prompt Injection
The most critical and least-solved attack surface for AI agents.
Direct injection: Attacker crafts input that overrides the system prompt, causing the agent to ignore instructions, reveal configuration, or execute unintended tools.
Indirect injection: Malicious content embedded in data the agent processes (web pages fetched by tools, documents read from disk, MCP server responses) that hijacks agent behavior.
Mitigations:
- Pattern-based input filtering (14 patterns in OpenCrust)
- System prompt hardening with explicit boundary instructions
- Tool result sandboxing and size limits
- Human-in-the-loop for destructive operations
References:
- Greshake et al., "Not what you've signed up for: Compromising Real-World LLM-Integrated Applications with Indirect Prompt Injection" (2023)
- Simon Willison, "Prompt injection explained" (2023)
Tool Abuse
Agents with tool access can be manipulated into:
- Shell injection: Running arbitrary commands via bash tools
- File exfiltration: Reading sensitive files and including content in responses
- SSRF: Fetching internal URLs via web fetch tools
- Recursive scheduling: Creating infinite task loops via scheduling tools
Mitigations:
- Tool iteration limits (max 10 round-trips)
- Recursive scheduling prevention (heartbeat context blocks re-scheduling)
- File path validation and sandboxing
- Private IP blocking for outbound requests
Credential Leakage
AI agents are high-value targets because they aggregate credentials:
- LLM provider API keys (Anthropic, OpenAI)
- Channel bot tokens (Discord, Telegram, Slack)
- Webhook secrets (WhatsApp)
- User pairing codes
Attack vectors:
- Plaintext config files accessible to local users or leaked in backups
- Log output containing API keys (accidental logging)
- Prompt injection exfiltrating credentials via tool responses
- Memory/history containing credentials from prior conversations
Mitigations:
- Encrypted vault (AES-256-GCM) for all credentials
- Automatic log redaction for known key patterns
- Input sanitization preventing credential injection
- Session history bounded and cleaned up
Supply Chain
MCP Servers
MCP servers are external processes that the agent trusts to provide tools. A compromised MCP server can:
- Return malicious tool schemas that trick the LLM into dangerous actions
- Exfiltrate data passed as tool arguments
- Exploit the agent host via process-level access (stdio transport)
Plugins (WASM)
Despite WASM sandboxing, plugins still present risks:
- Excessive host function imports granting unintended capabilities
- Resource exhaustion (memory, CPU)
- Side-channel attacks (timing)
Skills
Skills are Markdown files injected into the system prompt. A malicious skill can:
- Override agent behavior via prompt injection in the skill body
- Introduce tool-use patterns that exfiltrate data
Industry Research
- Belgium CCB (2024): Guidelines on securing AI systems, emphasizing input validation and output filtering for LLM-integrated applications.
- Dutch DPA (2024): Guidance on AI and GDPR, covering data minimization requirements relevant to agent memory and logging.
- Sophos (2024): "The lethal trifecta" - compromised credentials, tool abuse, and prompt injection as the three converging attack vectors against AI agents.
- SecurityScorecard (2025): Supply chain risk analysis showing third-party integrations (MCP servers, plugins) as the fastest-growing attack surface for AI applications.
AI Agent Security Checklist
A vendor-neutral audit checklist for securing AI agent deployments. Applicable to any framework, not just OpenCrust.
Credentials & Secrets
- API keys and tokens are encrypted at rest (not plaintext in config files)
- Secrets are never logged, even at debug/trace level
- Key rotation is possible without redeployment
- Environment variables are used as fallback only, not primary storage
- Vault/secret manager integration is available for production deployments
- Default credentials are absent; setup requires explicit configuration
Authentication & Authorization
- Agent endpoints require authentication by default (not opt-in)
- WebSocket connections are authenticated before any message processing
- API key comparison uses constant-time operations (prevent timing attacks)
- Per-channel user allowlists restrict who can interact with the agent
- Pairing codes or equivalent are time-limited and single-use
- Admin operations are separated from user operations
Input Validation
- All user input is sanitized (control characters stripped)
- Prompt injection patterns are detected and rejected before LLM processing
- Message size limits are enforced at the transport layer
- Input validation rules are updatable without redeployment
- Rejection events are logged with session context for audit
Output Filtering
- LLM responses are checked before delivery to users
- Sensitive data patterns (API keys, credentials) are redacted from output
- Tool execution output is bounded in size
- Error messages do not leak internal state or stack traces to users
Network Security
- Agent binds to localhost by default (not 0.0.0.0)
- HTTP rate limiting is enabled per-IP with configurable thresholds
- WebSocket connections have frame/message size limits
- Idle connections are cleaned up (heartbeat + timeout)
- TLS is enforced for all external API calls
- DNS rebinding protections are in place for webhook endpoints
Tool & Plugin Security
- Tool execution has iteration limits (prevent runaway loops)
- File system tools are restricted to allowed paths
- Shell/bash tools have configurable command allowlists
- Plugins run in a sandbox (WASM, containers, or equivalent)
- Plugin capabilities are declared and enforced (no ambient authority)
- MCP server connections are authenticated and timeout-bounded
Session Management
- Sessions have a maximum lifetime (TTL)
- Disconnected sessions are cleaned up on a schedule
- Session IDs are generated with cryptographic randomness
- Session history is bounded (prevent memory exhaustion)
- Concurrent session limits are configurable per user
Monitoring & Incident Response
- Security events (injection attempts, auth failures) are logged
- Log output redacts sensitive tokens automatically
- Alerting is configured for repeated security events
- Agent can be stopped remotely (kill switch)
- Audit trail includes session ID, channel, user, and timestamp
- Configuration changes are logged
Plugins
OpenCrust supports extending functionality via WebAssembly (WASM) plugins.
Overview
Plugins run in a sandboxed environment using Wasmtime. They can interact with the host via controlled interfaces.
(More documentation on plugin development coming soon)