RayClaw is a multi-channel agentic AI runtime written in Rust. It connects to Telegram, Discord, Slack, Feishu/Lark, and a built-in Web UI through a unified agent engine. Every channel adapter routes conversations through the same tool-calling loop, LLM abstraction, and persistence layer. The project focuses on reliable tool execution, long-running session continuity, and cross-platform parity.
Rust 2021 edition. Tokio async runtime. teloxide 0.17 (Telegram), serenity 0.12 (Discord), axum 0.7 (Web API + embedded React UI). LLM calls via reqwest — native Anthropic Messages API plus OpenAI-compatible endpoints. SQLite (rusqlite, bundled) with WAL mode. cron crate for scheduling.
src/— Rust source for the single binaryweb/— Embedded Web UI (React + Vite). Built toweb/dist/and included in the binary viainclude_dir!. Serves the chat interface and settings panel at runtime.
| File | Role |
|---|---|
src/main.rs |
CLI entry: start, setup, doctor, gateway, version |
src/runtime.rs |
AppState wiring, channel boot, signal handling |
src/agent_engine.rs |
Shared agent loop, system prompt builder, context compaction |
src/llm.rs |
Provider abstraction: Anthropic native + OpenAI-compatible |
src/llm_types.rs |
Message, tool, and content-block DTOs |
src/config.rs |
YAML config loading and defaults |
src/error.rs |
Error enum (thiserror) |
src/db.rs |
SQLite schema, migrations, all persistence |
src/memory.rs |
File-based memory (AGENTS.md per chat / global) |
src/memory_quality.rs |
Remember parser, quality rules, dedup heuristics |
src/scheduler.rs |
Background task runner (60s poll) + memory reflector |
src/channels/telegram.rs |
Telegram adapter (teloxide dispatcher) |
src/channels/discord.rs |
Discord adapter (serenity gateway) |
src/channels/slack.rs |
Slack adapter (Socket Mode WebSocket) |
src/channels/feishu.rs |
Feishu/Lark adapter (WebSocket or webhook) |
src/channels/delivery.rs |
Cross-channel outbound helpers |
src/web.rs |
Web API routes, SSE stream, embedded React UI |
src/acp.rs |
ACP manager — external coding agents via JSON-RPC/stdio |
src/skills.rs |
Skill discovery and activation |
src/mcp.rs |
MCP server/tool federation |
src/tools/mod.rs |
Tool trait, ToolRegistry, sub-agent variant |
src/tools/bash.rs |
Shell execution |
src/tools/read_file.rs / write_file.rs / edit_file.rs |
File operations (path-guarded) |
src/tools/glob.rs / grep.rs |
File and content search (path-guarded) |
src/tools/memory.rs |
read_memory / write_memory |
src/tools/structured_memory.rs |
SQLite-backed structured memory |
src/tools/web_search.rs |
DuckDuckGo search |
src/tools/web_fetch.rs |
URL fetching with HTML→text |
src/tools/browser.rs |
Headless browser (agent-browser wrapper) |
src/tools/send_message.rs |
Mid-conversation messaging (all channels) |
src/tools/schedule.rs |
5 scheduling tools |
src/tools/sub_agent.rs |
Sub-agent with restricted tool set |
src/tools/todo.rs |
Task plan tracking (todo_read / todo_write) |
src/tools/path_guard.rs |
Sensitive path blocklist |
- Agent loop (
agent_engine.rs:process_with_agent): call LLM → if tool_use → execute → loop (up tomax_tool_iterations). On end_turn → persist session → return text. - Session resume: full
Vec<Message>(including tool_use/tool_result) persisted insessionstable. Next message loads the session and appends./resetclears it. - Context compaction: when messages exceed
max_session_messages, older messages are summarized by the LLM, replaced with a compact summary, and recent messages are kept verbatim. - Sub-agent: spawns a parallel agent loop with a restricted tool set (no send_message, write_memory, schedule, or recursive sub_agent).
- Tool trait:
name(),definition()(JSON Schema),execute(Value) -> ToolResult. - Shared state:
AppStatebehindArc, tools hold references toDatabase, channel adapters, etc. - Group catch-up:
db.get_messages_since_last_bot_response()loads all messages since the bot's last reply in a group. - Scheduler:
tokio::spawnloop polls DB every 60s for due tasks, runs them through the agent loop. - Typing indicator: spawned task sends typing action every 4s, aborted when the response is ready.
- Path guard: file tools block access to sensitive paths (.ssh, .aws, .env, credentials, etc.).
- SOUL.md: optional personality file injected as
<soul>XML in the system prompt. Load order:soul_pathconfig →<data_dir>/SOUL.md→./SOUL.md. Per-chat overrides at<data_dir>/runtime/groups/<chat_id>/SOUL.md. - ACP:
src/acp.rsmanages external coding agents (Claude Code, etc.) over JSON-RPC/stdio. Users control sessions via#new <agent>,#end,#agents,#sessions,#help.
cargo build
cargo run -- start # requires rayclaw.config.yaml with credentials + at least one channel
cargo run -- setup # interactive wizard to create the config
cargo run -- helpRayClaw reads rayclaw.config.yaml (or .yml). Override with RAYCLAW_CONFIG env var. See rayclaw.config.example.yaml for all fields.
- Create
src/tools/my_tool.rsimplementing theTooltrait - Add
pub mod my_tool;tosrc/tools/mod.rs - Register in
ToolRegistry::new()withBox::new(MyTool::new(...))
Core tables: chats, messages, scheduled_tasks, sessions, memories, llm_usage_logs. SQLite with WAL mode. Schema versioned via db_meta + schema_migrations. Access through Mutex<Connection> in Database, shared as Arc<Database>.
- Timestamps: ISO 8601 / RFC 3339 strings throughout
- Cron: 6-field format (sec min hour dom month dow)
- All chat messages are stored regardless of whether the bot responds
- In groups, the bot only responds to @mentions
- Consecutive same-role messages are merged before LLM calls
- Long responses are split at newline boundaries: 4096 chars (Telegram), 2000 (Discord), 4000 (Slack/Feishu)