What is RustFox?
An open-source, self-hosted Telegram AI assistant written in Rust. It solves a simple problem: most AI assistants are locked inside proprietary chat UIs with no access to your files, tools, or schedule. RustFox lives in Telegram — your everyday messaging app — and acts as a full agentic AI teammate.
Why RustFox?
Drop a file, ask a question, schedule a task — RustFox handles it. The default path is OpenRouter. Ollama is the local option, and an Ollama model can be pulled from the Ollama library. It receives your message, calls sandboxed tools (file I/O, command execution, web search via MCP), and loops until done. It remembers context via SQLite + vector RAG, runs skills and sub-agents, and even verifies its own work.
Single binary. Setup wizard. Runs as systemd/launchd service. cargo install and you're running in 2 minutes.
Star the repo ⭐, fork to contribute, or open an issue for feedback.
docs: User docs · GUIDE.md · ARCHITECTURE.md · CHANGELOG.md
| 🤖 AI Agent | Multi-provider LLM (OpenRouter, local Ollama, any OpenAI-compatible endpoint), agentic loop with tool calling, configurable max iterations, request-layer model fallback chain |
| 🔧 Built-in Tools | File read/write, command execution, file sending, task scheduling — all sandboxed |
| 🧩 MCP Servers | Connect any MCP-compatible server (Git, Brave Search, GitHub, Filesystem, Threads…) |
| 🧠 Persistent Memory | SQLite-backed conversation history, vector embedding search (hybrid + FTS5), RAG |
| 🧬 Skills & Agents | Folder-based skill instructions auto-loaded at startup; subagent skills with own model and tool whitelist |
| 🤝 Agent Layer | Isolated agentic mini-loops in agents/ with own model/tools; invoke_agent, spawn_agents, zero-trust verifier |
| 🪪 Multi-Bot / Multi-Persona | Run several Telegram bots in one process, each bound to its own persona, model, and tool whitelist via [[bots]] |
| 🔄 Task Scheduling | Cron and one-shot task scheduler with SQLite persistence, plus a dead-letter rerun queue |
| 🌐 Web Portal | Built-in browser UI (chat, agents, memory, tasks, settings) served from the same binary — token-auth, loopback by default |
| 📦 Self-Hosting | Single binary, 2-min setup wizard, background service (systemd/launchd/Windows Service) |
→ Full feature reference: docs/GUIDE.md
| Capability | Description |
|---|---|
| Self-Upgrade | In-place upgrade (ADR-0030): Telegram bare /selfupgrade = auto (Cargo.toml near exe → source/git; else → latest GitHub release); /selfupgrade main = tip-of-main (requires source tree — refuse clearly if missing, no silent clone); /selfupgrade release = latest GitHub binary. Portal Settings: auto | main | release (same mapping). Auto-restarts after upgrade. |
| Model Switching | Switch models at runtime via /models. Interactive picker lets you choose the best model per task: fast/cheap for simple queries, powerful for complex reasoning. |
| Model Fallback | Ordered [fallback] chain of provider-prefixed model IDs. When the primary model fails (e.g. HTTP 429), RustFox retries down the chain before surfacing an error. |
| Soul Files | SOUL.md (persona), AGENTS.md (behaviour), USER.md (preferences) — persistent identity files auto-injected into every system prompt. Session-end self-reflection with .bak backups. |
Run N Telegram bots inside a single process, each bound to its own persona, model, and tool whitelist. Configure them with a [[bots]] array; the legacy single-bot [telegram] section is still supported (it synthesizes one bot and is treated as a deprecated migration alias when [[bots]] is present).
[[bots]]
id = "main"
bot_token = "..." # written by the wizard / secret store
allowed_user_ids = [123456789]
persona = "main" # agents/<persona>/AGENT.md (+ optional SOUL.md overlay)
# model = "moonshotai/kimi-k2.6"
# tools = ["read_file", "write_file", "execute_command", "invoke_agent"]
[[bots]]
id = "researcher"
bot_token = "..."
allowed_user_ids = [123456789]
persona = "researcher" # read-heavier defaults; no execute_command / write_file
# tools = ["read_file", "list_files", "remember", "recall",
# "search_memory", "invoke_agent", "spawn_agents"]Precedence for a bot's model and tools: [[bots]] fields → agents/<persona>/AGENT.md → install defaults.
Routing & isolation — when more than one bot is configured, the first non-default bot owns Telegram claim/routing; each bot keeps its own conversation context. invoke_agent / spawn_agents can target a persona by id (bot= synonym), with nested peer depth capped at 2 and cycle detection. USER.md stays shared under RUSTFOX_HOME (not per-bot); the portal uses the default/shim persona.
An empty
allowed_user_idson any bot is a hard error at config load — the process will not start.
See docs/multi-bot-e2e.md and ADR-0010 (decision layer) for design details.
Option A — Download a release (recommended)
Download from the Releases page:
tar xzf rustfox-*.tar.gz
# Windows: expand the .zip from the Releases page instead- Unsigned by default. GitHub Releases
Windows zips are unsigned today. Treat every published
rustfox.exelike any other unsigned build under SAC (below). - Smart App Control (SAC). Unsigned
rustfox.exebuilds are blocked on Windows with SAC turned on, and there is no "Run anyway" button. If you build from source and hitos error 4551, build on a machine that does not enforce SAC. We do not recommend turning Smart App Control off — turning it back on can require a Windows reset when optional diagnostic data is off. - SmartScreen. An unsigned (or future signed) download may still show "Windows protected your PC". Click More info, then Run anyway. This is separate from SAC.
- Code signing (planned, deferred). ADR-0027 locks Option A (SignPath
Foundation) as the plan. Implementation is deferred: CI does not run SignPath
or
REQUIRE_SIGNINGyet. Windows release binaries stay unsigned until the maintainer resumes that work. Planned credit when live: free code signing provided by SignPath.io, certificate by SignPath Foundation. - Privacy. RustFox is self-hosted. Depending on your config it may talk to Telegram (bot API) and an LLM provider such as OpenRouter (or a local Ollama / OpenAI-compatible endpoint you choose). SignPath is not used at runtime; it would only sign the Windows release binary in CI if signing is later enabled.
Option B — Build from source (recommended script)
git clone https://github.com/chinkan/RustFox && cd RustFox
./scripts/build-all.sh --installbuild-all.sh builds the web portal frontend first (npm ci + vite build), then
the Rust binary — the portal UI is compiled into the binary via include_dir!, so
build order matters (see Building & Verifying). If you don't
need the portal, plain cargo install --path . --locked still works.
# Browser wizard
./rustfox --setup
# Or terminal wizard
./rustfox --setup --cliThe wizard guides you through provider (OpenRouter or Ollama), API key, model (shortcut list or any typed provider/model id), bot token, and system prompt.
rustfox
# or with a custom config:
rustfox --config /path/to/config.tomlrustfox install # Linux (systemd), macOS (launchd), or Windows
rustfox statusSelf-upgrade (ADR-0030):
| Surface | Label | Behaviour |
|---|---|---|
| Telegram | bare /selfupgrade |
auto — Cargo.toml near the exe → source/git; else → latest GitHub release binary |
| Telegram | /selfupgrade main |
tip-of-main source build — requires a source tree (Cargo.toml near the exe); refuse clearly if missing (no silent clone) |
| Telegram | /selfupgrade release |
download latest GitHub release binary |
| Portal Settings | auto | main | release |
same mapping as Telegram |
Preference: if you run from a git clone under systemd/launchd, prefer /selfupgrade main (or Portal main). Discourage a manual tmp clone → build → cargo install unless there is no self-upgrade path.
Ops — safe upgrade (before any upgrade / cargo install):
rustfox stop(orsystemctl --user stop rustfox)- Confirm no stray
rustfoxprocesses share the same fox-home (RUSTFOX_HOME/~/.rustfox) - Upgrade (prefer self-upgrade above)
- Start once (
rustfox start/systemctl --user start rustfox, or a single bare start)
Safe probe: rustfox --version / rustfox -V prints the package version and exits 0 (no bot/scheduler start). Prefer rustfox status / systemctl --user status rustfox for service health. Avoid bare rustfox as a probe — that starts the bot.
#289 / ADR-0046: one fox-home → exclusive flock on rustfox.lock (single bot+scheduler). Second start with the same home fail-fast; different RUSTFOX_HOME values may run side by side. Service verbs and --setup stay unlock-friendly. Stop + kill-all strays before upgrade remains required — flock is a belt, not a replacement. See ADR-0046.
→ Operator detail: docs/GUIDE.md.
| Setting | Description |
|---|---|
[[bots]] |
Array of Telegram bots, each with id, bot_token, allowed_user_ids, persona, and optional model / tools overrides |
telegram.* |
Legacy single-bot section (deprecated alias when [[bots]] is present) |
[voice] |
Telegram voice notes: local SenseVoice STT (Cantonese), opt-in OpenRouter STT, opt-in text+voice TTS. See docs/telegram-cantonese-voice.md |
openrouter.api_key |
OpenRouter API key (openrouter.ai/keys) |
openrouter.model |
Default LLM model ID (provider/model; wizard shortcuts or any typed id; default: moonshotai/kimi-k2.6) |
[[provider]] |
Additional LLM providers (Ollama, LM Studio, any OpenAI-compatible endpoint); model strings use provider/model_id |
[fallback] chain |
Ordered model fallback list, e.g. ["openrouter/…", "ollama/llama3.1"] |
[portal] |
Web portal settings (enabled, port, bind, token_sha256) |
sandbox.allowed_directory |
Directory for sandboxed file/command operations |
mcp_servers |
List of MCP servers to connect (see GUIDE.md) |
→ Full configuration reference: docs/GUIDE.md
RustFox can serve a built-in web UI from the same binary — no separate frontend
deploy. Open http://127.0.0.1:8090 in your browser to chat with the agent (live
SSE token streaming), browse memory, manage scheduled tasks and sub-agents, and
inspect your config.
Enable it in config.toml:
[portal]
enabled = true
port = 8090
bind = "127.0.0.1" # keep loopback/private-network only
token_sha256 = "<sha256>" # hash of your login token (preferred over plaintext)Generate a token + hash:
python3 -c 'import hashlib,secrets; t=secrets.token_hex(32); print("token:", t); print("token_sha256:", hashlib.sha256(t.encode()).hexdigest())'| Page | What you can do |
|---|---|
| 💬 Chat | Talk to the agent in the browser, watch tool calls stream live; history persisted and isolated from Telegram |
| 🧬 Agents | Browse configured sub-agents, filter by name |
| 🧠 Memory | Search conversation/knowledge store (FTS5 hybrid), browse recent entries |
| 🔄 Tasks | List cron/one-shot tasks, view run history, pause/resume |
| ⚙️ Settings | Inspect active config (secrets masked) |
🔒 The portal is off by default and intended for loopback/private networks (e.g. a Tailscale IP). Public exposure/TLS is out of scope for now.
| Tool | Description |
|---|---|
read_file / write_file / list_files |
Read, write, and list files within the sandbox |
send_file |
Send a file from the sandbox to the current chat |
execute_command |
Run shell commands within the sandbox |
self_upgrade |
Self-upgrade (ADR-0030): auto / main / release — same mapping as /selfupgrade; auto-restart |
read_soul_file / update_soul_file / revert_soul_file |
Read, update (append/replace, .bak backup), or restore SOUL.md / AGENTS.md / USER.md |
remember / recall / search_memory |
Store, look up, and search long-term user knowledge |
plan_create / plan_update / plan_view |
Create and track a structured execution plan |
read_skill_file / write_skill_file / reload_skills |
Read (full file, or a 1-based offset / limit line slice), write, and hot-reload skills |
read_agent_file / write_agent_file / reload_agents |
Read, write, and hot-reload sub-agent definitions |
invoke_agent |
Run a predefined agent from agents/ (or a [[bots]] persona via bot=) |
spawn_agents |
Spawn ad-hoc subagents with inline system prompts (parallel batch supported) |
try_new_tech |
Sandboxed experiment — run Rust/JS code and check results |
schedule_task / list_scheduled_tasks / get_scheduled_task_history / cancel_scheduled_task |
Create, list, inspect, and cancel cron/one-shot tasks |
task_reruns |
Inspect and resolve the dead-letter rerun queue |
→ Full tool reference: docs/GUIDE.md
RustFox runs an agentic loop: user message → LLM (OpenRouter or a local provider) → tool calls → execute → loop until final response. Tools dispatch to built-in functions, MCP servers, or skill/agent directories.
→ Full architecture with source tree and data flow: docs/ARCHITECTURE.md
./scripts/build-all.sh # web (vite) → dist → cargo build --release
./scripts/build-all.sh --install # ...then install to ~/.cargo/bin/rustfox
./scripts/build-all.sh --skip-web # reuse existing web/dist (Rust only)
./scripts/build-all.sh --profile dev # debug build (faster compile)Why a script? The portal frontend is embedded into the binary at compile time
(include_dir!("web/dist")). Two traps this guards against:
- Build order — cargo before
vite buildembeds a stale/empty dist → the portal renders a white screen.build-all.shenforces web-first and fails fast ifweb/dist/index.htmlis missing. - Stale embedding —
include_dir!has no cargo change-fingerprint, so updating dist alone won't trigger a rebuild. The script touches the embedding module to force re-embedding.
cargo run --release --example portal_preview # serves the real embedded UI + seeded demo datanode e2e-verify.mjs # Playwright: login → chat SSE → agents → memory → tasks → settingsCovers every portal page against the real embedded-dist binary (same code path as production), including a stub-dist control test that catches white-screen regressions. Zero console errors is part of the gate. CI runs the web build before cargo tests, and the same sequence applies to release builds.
MIT License. See CONTRIBUTING.md for how to open issues and submit PRs.
