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

OpenCrustOpenClaw (Node.js)ZeroClaw (Rust)
Binary size17 MB~1.2 GB (with node_modules)~25 MB
Memory at idle13 MB~388 MB~20 MB
Cold start3 ms13.9 s~50 ms
Credential storageAES-256-GCM encrypted vaultPlaintext config filePlaintext config file
Auth defaultEnabled (WebSocket pairing)Disabled by defaultDisabled by default
SchedulingCron, interval, one-shotYesNo
Multi-agent routingPlanned (#108)Yes (agentId)No
Session orchestrationPlanned (#108)YesNo
MCP supportStdioStdio + HTTPStdio
Channels66+4
LLM providers1510+22+
Pre-compiled binariesYesN/A (Node.js)Build from source
Config hot-reloadYesNoNo
WASM plugin systemYes (sandboxed)NoNo

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 doctor checks 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:

  1. What should I call you?
  2. What should I call myself?
  3. How do you prefer I communicate - casual, professional, or something else?
  4. 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.

ToolDescription
bashExecute shell commands (30s timeout, 32 KB max output)
file_readRead file contents (1 MB max, path traversal prevention)
file_writeWrite file contents (1 MB max, path traversal prevention)
web_fetchFetch web pages (30s timeout, 1 MB max response)
web_searchSearch via Brave Search API (requires BRAVE_API_KEY)
schedule_heartbeatSchedule 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

  1. A Slack workspace where you have permission to install apps.
  2. A Slack app created at api.slack.com/apps.

Step-by-step Setup

1. Create a Slack App

  1. Go to api.slack.com/apps and click Create New App.
  2. Choose From scratch.
  3. Name your app (e.g. "OpenCrust") and select your workspace.

2. Enable Socket Mode

  1. In the left sidebar, go to Socket Mode.
  2. Toggle Enable Socket Mode on.
  3. You will be prompted to create an app-level token. Name it anything (e.g. "opencrust-socket") and add the connections:write scope.
  4. Copy the token - it starts with xapp-. This is your App Token.

3. Subscribe to Events

  1. In the left sidebar, go to Event Subscriptions.
  2. Toggle Enable Events on.
  3. Under Subscribe to bot events, add:
    • message.im - messages in direct messages
    • message.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

  1. In the left sidebar, go to OAuth & Permissions.
  2. Under Bot Token Scopes, add:
    • chat:write - send messages
    • files:read - download shared files (needed for document ingestion)
    • users:read - look up user info (optional, for display names)

5. Install to Workspace

  1. In the left sidebar, go to Install App.
  2. Click Install to Workspace and authorize.
  3. 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:

  1. Share a file in a message - the bot will download it and prompt you.
  2. Send !ingest to ingest the pending file.
  3. Or share a file with "ingest" in the caption for immediate ingestion.

Files are capped at 10 MiB.

Security

  • Allowlist/Pairing: Configure dm_policy and group_policy per 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.

  1. Open System Settings (or System Preferences on older macOS)
  2. Navigate to Privacy & Security > Full Disk Access
  3. Click the + button (you may need to unlock with your password)
  4. Add the OpenCrust binary (/usr/local/bin/opencrust or wherever you installed it)
  5. If running from a terminal (e.g. during development), also add your Terminal app (Terminal.app, iTerm2, etc.)
  6. 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

A launchd plist template is provided at deploy/macos/com.opencrust.gateway.plist.

  1. Create the log directory:

    mkdir -p ~/Library/Logs/opencrust
    
  2. Copy and edit the plist (update the binary path if needed):

    cp deploy/macos/com.opencrust.gateway.plist ~/Library/LaunchAgents/
    
  3. Load the service:

    launchctl load ~/Library/LaunchAgents/com.opencrust.gateway.plist
    
  4. Verify it's running:

    launchctl list | grep opencrust
    
  5. 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_secs is reasonable (1-5 seconds)
  • Verify chat.db is 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_roomnames value in chat.db

LINE Channel Setup

OpenCrust supports the LINE Messaging API for both 1-on-1 chats and group/room conversations.

Prerequisites

  1. A LINE Official Account. Create one at the LINE Business ID portal.
  2. A Channel in the LINE Developers Console.
  3. 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:

SettingEnvironment variable
channel_access_tokenLINE_CHANNEL_ACCESS_TOKEN
channel_secretLINE_CHANNEL_SECRET

Access Control

DM policy (dm_policy)

Controls who can send the bot direct messages.

ValueBehaviour
openAnyone can message the bot (no auth required).
pairingNew users must enter a 6-digit pairing code (default).
allowlistOnly 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.

ValueBehaviour
openRespond to every message in the group (default).
mentionRespond only when the bot is @mentioned in the group.
disabledIgnore 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.mentionees from the LINE webhook payload. The bot's own user ID is resolved automatically from GET /v2/bot/info at startup — no manual configuration required.

Webhook Setup

  1. In the LINE Developers Console, go to Messaging API settings.
  2. Set the Webhook URL to: https://your-domain.com/webhooks/line
  3. Enable Use webhook.
  4. (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

  1. Gateway API key - All Google integration endpoints require authentication. Set one of:

    • OPENCRUST_GATEWAY_API_KEY environment variable, or
    • gateway.api_key in ~/.opencrust/config.yml

    Without this, all /api/integrations/google/* endpoints return 403 Forbidden.

  2. 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:

  1. Credential vault - ~/.opencrust/credentials/vault.json (requires OPENCRUST_VAULT_PASSPHRASE)
  2. Config file - api_key field under the llm: section in config.yml
  3. 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:// or https://)
  • Trailing slashes are automatically handled
  • For OpenAI-compatible providers, the /v1/chat/completions path is appended automatically
  • For Anthropic, the /v1/messages path is appended automatically
  • For Ollama, the /api/chat path 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.

FieldValue
Config typeanthropic
Default modelclaude-sonnet-4-5-20250929
Base URLhttps://api.anthropic.com
Env varANTHROPIC_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.

FieldValue
Config typeopenai
Default modelgpt-4o
Base URLhttps://api.openai.com
Env varOPENAI_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.

FieldValue
Config typeollama
Default modelllama3.1
Base URLhttp://localhost:11434
Env varNone
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.

FieldValue
Config typesansa
Default modelsansa-auto
Base URLhttps://api.sansaml.com
Env varSANSA_API_KEY
llm:
  sansa:
    provider: sansa
    model: sansa-auto

DeepSeek

FieldValue
Config typedeepseek
Default modeldeepseek-chat
Base URLhttps://api.deepseek.com
Env varDEEPSEEK_API_KEY
llm:
  deepseek:
    provider: deepseek
    model: deepseek-chat

Mistral

FieldValue
Config typemistral
Default modelmistral-large-latest
Base URLhttps://api.mistral.ai
Env varMISTRAL_API_KEY
llm:
  mistral:
    provider: mistral
    model: mistral-large-latest

Gemini

Google Gemini via the OpenAI-compatible endpoint.

FieldValue
Config typegemini
Default modelgemini-2.5-flash
Base URLhttps://generativelanguage.googleapis.com/v1beta/openai/
Env varGEMINI_API_KEY
llm:
  gemini:
    provider: gemini
    model: gemini-2.5-flash

Falcon

TII Falcon 180B via AI71.

FieldValue
Config typefalcon
Default modeltiiuae/falcon-180b-chat
Base URLhttps://api.ai71.ai/v1
Env varFALCON_API_KEY
llm:
  falcon:
    provider: falcon
    model: tiiuae/falcon-180b-chat

Jais

Core42 Jais 70B.

FieldValue
Config typejais
Default modeljais-adapted-70b-chat
Base URLhttps://api.core42.ai/v1
Env varJAIS_API_KEY
llm:
  jais:
    provider: jais
    model: jais-adapted-70b-chat

Qwen

Alibaba Qwen via DashScope international.

FieldValue
Config typeqwen
Default modelqwen-plus
Base URLhttps://dashscope-intl.aliyuncs.com/compatible-mode/v1
Env varQWEN_API_KEY
llm:
  qwen:
    provider: qwen
    model: qwen-plus

Yi

01.AI Yi Large.

FieldValue
Config typeyi
Default modelyi-large
Base URLhttps://api.lingyiwanwu.com/v1
Env varYI_API_KEY
llm:
  yi:
    provider: yi
    model: yi-large

Cohere

Cohere Command R Plus via the compatibility endpoint.

FieldValue
Config typecohere
Default modelcommand-r-plus
Base URLhttps://api.cohere.com/compatibility/v1
Env varCOHERE_API_KEY
llm:
  cohere:
    provider: cohere
    model: command-r-plus

MiniMax

FieldValue
Config typeminimax
Default modelMiniMax-Text-01
Base URLhttps://api.minimaxi.chat/v1
Env varMINIMAX_API_KEY
llm:
  minimax:
    provider: minimax
    model: MiniMax-Text-01

Moonshot

Kimi models from Moonshot AI.

FieldValue
Config typemoonshot
Default modelkimi-k2-0711-preview
Base URLhttps://api.moonshot.cn/v1
Env varMOONSHOT_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.

FieldValue
Config typevllm
Default model(none — must be specified)
Base URLhttp://localhost:8000
Env varVLLM_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:

  1. The LLM generates a response, optionally including tool calls
  2. If tool calls are present, the runtime executes them
  3. Tool results are appended to the conversation and sent back to the LLM
  4. 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.

PropertyValue
Timeout30 seconds
Max output32 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.

PropertyValue
Max file size1 MB
Path traversalRejected (.. 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.

PropertyValue
Max content size1 MB
Path traversalRejected (.. 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).

PropertyValue
Timeout30 seconds
Max response size1 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.

Search the web using the Brave Search API. Only available when a Brave API key is configured.

PropertyValue
Timeout15 seconds
Default results5
Max results10
RequiresBRAVE_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.

PropertyValue
Max delay30 days (2,592,000 seconds)
Max pending per session5

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

  1. You configure MCP servers in config.yml or ~/.opencrust/mcp.json
  2. At startup, OpenCrust connects to each enabled server and discovers its tools
  3. MCP tools appear alongside built-in tools with namespaced names: server.tool_name
  4. 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

FieldTypeDefaultDescription
commandstring(required)Executable to spawn
argsstring[][]Command-line arguments
envmap{}Environment variables passed to the process
transportstring"stdio"Transport type (stdio or http)
urlstring(none)URL for HTTP transport
enabledbooltrueWhether to connect at startup
timeoutinteger30Connection 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 mcp feature in opencrust-agents (enabled by default)
  • Uses the rmcp crate (official Rust MCP SDK)
  • McpManager in crates/opencrust-agents/src/mcp/manager.rs handles connections
  • McpTool in crates/opencrust-agents/src/mcp/tool_bridge.rs bridges 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 (rand crate)
  • 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.1 by default, not 0.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:

#RiskAgent Relevance
LLM01Prompt InjectionCritical - agents execute tools based on LLM output; injected instructions can trigger unintended actions
LLM02Insecure Output HandlingHigh - agent responses may be rendered in chat UIs or forwarded to other systems
LLM03Training Data PoisoningMedium - primarily a provider-side risk, but affects agent behavior
LLM04Model Denial of ServiceHigh - large inputs can exhaust context windows and provider budgets
LLM05Supply Chain VulnerabilitiesHigh - MCP servers, plugins, and skills are all supply chain vectors
LLM06Sensitive Information DisclosureCritical - agents access API keys, user data, and internal systems
LLM07Insecure Plugin DesignHigh - plugins with ambient authority can be exploited via prompt injection
LLM08Excessive AgencyCritical - agents with tool access can take real-world actions (shell commands, file writes, API calls)
LLM09OverrelianceMedium - users may trust agent output without verification
LLM10Model TheftLow - 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)