RustFox — Telegram AI Assistant
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
Features
→ Full feature reference: docs/GUIDE.md
Agent Lifecycle & Runtime
🪪 Multi-Bot / Multi-Persona
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.
Quick Start
1. Install
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
Windows: Smart App Control and SmartScreen
- 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 --install
build-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.
2. Configure
# Browser wizard
./rustfox --setup
# Or terminal wizard
./rustfox --setup --cli
The wizard guides you through provider (OpenRouter or Ollama), API key, model (shortcut list or any typed provider/model id), bot token, and system prompt.
3. Run
rustfox
# or with a custom config:
rustfox --config /path/to/config.toml
4. (Optional) Background service
rustfox install # Linux (systemd), macOS (launchd), or Windows
rustfox status
Upgrade & ops (self-upgrade + safe upgrade)
Self-upgrade (ADR-0030):
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.
Configuration
→ Full configuration reference: docs/GUIDE.md
🌐 Web Portal
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())'
🔒 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.
Quick Tool Overview
→ Full tool reference: docs/GUIDE.md
Architecture
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
🛠 Building & Verifying
One-command build (portal-safe)
./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.
Try the portal without Telegram/LLM
cargo run --release --example portal_preview # serves the real embedded UI + seeded demo data
End-to-end regression gate (38 checks)
node e2e-verify.mjs # Playwright: login → chat SSE → agents → memory → tasks → settings
Covers 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.
Contributing
MIT License. See CONTRIBUTING.md for how to open issues and submit PRs.