
An AI personal assistant with a focus on security.

Stavrobot is a personal AI assistant built with the principle of "all the access an AI assistant needs, and no more".
It has all the nice features of an AI assistant, but focuses on sandboxing, isolation, and minimal permissions. It's lightweight and is deployed with only a docker compose up.
uv, isolated from the host environment.AI-assisted install: Tell your coding AI (Claude Code, Cursor, Windsurf, etc.) to fetch and follow https://stavrobot.stavros.io/INSTALL.md — it will walk you through the whole setup interactively.
Manual install:
Message @BotFather on Telegram to create a bot and copy the token. Message @userinfobot to get your Telegram user ID (this is used as the chat ID).
Copy env.example to .env and change POSTGRES_PASSWORD to something secure and TZ to your timezone.
Copy config.example.toml to data/main/config.toml and fill in the required fields: apiKey, password, publicHostname, [owner].name, [owner].telegram (your chat ID), and [telegram].botToken. Everything else in the file is optional.
docker compose up --build
That's it. Message your bot on Telegram and it will respond. See the detailed setup sections below for Signal, WhatsApp, email, and other options.
config.example.toml to data/main/config.toml.authFile (or apiKey) and publicHostname. Everything else is optional.env.example to .env and set your timezone (TZ). Postgres credentials and other environment settings can also be overridden there. Always set POSTGRES_PASSWORD to something secure — the default is a weak placeholder and should not be used in production.Stavrobot can be pointed at any OpenAI-compatible endpoint (Ollama, LiteLLM, vLLM, etc.) or a custom Anthropic-compatible proxy by setting baseUrl in config.toml. See config.example.toml for the required fields and example configurations.
The app supports two authentication modes: API key or OAuth.
apiKey in config.toml. No login or logout needed.authFile in config.toml (a path where credentials will be stored). The login page works with any OAuth provider supported by Pi.
<your-hostname>/login in a browser. Follow the prompts on the page, and credentials are saved to the auth file. If auth expires while the bot is running, it sends a message with the login URL to you via your messaging platform.authFile path. The bot will detect missing credentials on the next message and prompt you to log in again.The coder container is optional (needed only for the self-programming feature). It uses Claude Code with subscription auth (OAuth), separate from the main app's API key.
Docker Compose profiles are comma-separated, so you can combine them (e.g. COMPOSE_PROFILES=signal,coder).
COMPOSE_PROFILES in your .env file to include coder (e.g. COMPOSE_PROFILES=coder, or COMPOSE_PROFILES=signal,coder if you are also using Signal).docker compose --profile coder up --builddocker compose exec -u coder coder claude (it will prompt you to log in if you haven't).[coder].model in your config to a Claude Code model alias (sonnet, opus, or haiku).Signal requires a separate phone number — not your personal one. A prepaid SIM or VoIP number works.
COMPOSE_PROFILES=signal in your .env file to enable the signal-bridge container.docker compose --profile signal builddocker compose --profile signal run --rm --entrypoint bash signal-bridge -c 'signal-cli link -n "Stavrobot" | tee >(xargs -L 1 qrencode -t utf8)' — scan the QR code with your phone (Signal > Settings > Linked devices).docker compose --profile signal run --rm --entrypoint bash signal-bridge -c 'signal-cli -u +YOUR_NUMBER register', then verify with docker compose --profile signal run --rm --entrypoint bash signal-bridge -c 'signal-cli -u +YOUR_NUMBER verify CODE'.[signal].account in your config.docker compose up --build/settings web UI.docker compose exec app node -e "fetch('http://localhost:3001/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:'Send the message \"Hello from Stavrobot\" to my Signal number.'})}).then(r=>r.text()).then(console.log)"
[telegram].botToken in your config.[owner].telegram in your config./settings web UI.WhatsApp requires a separate phone number, as otherwise you'd be messaging yourself, which doesn't really work.
WhatsApp uses Baileys, an unofficial WhatsApp Web library that links as a companion device (like WhatsApp Web). No separate phone number is needed — it links to your existing WhatsApp account.
Risk: Baileys uses an unofficial API. WhatsApp may ban accounts that use it. Use at your own risk.
[whatsapp] section to your config.toml (see config.example.toml for the format).docker compose up --builddocker compose logs -f app)../data/whatsapp./settings web UI.Email uses a Cloudflare Email Worker for inbound delivery and SMTP for outbound. See
config.example.toml for the complete worker code and detailed setup instructions.
[email] section to your config.toml with SMTP credentials and a random webhookSecret.config.example.toml) and set the WEBHOOK_URL and WEBHOOK_SECRET environment variables on the worker./settings web UI.docker compose up --build
The API is available at http://localhost:10567/chat.
Note: Docker Compose only exposes the app on localhost:10567. To make it accessible externally (required for Telegram/Signal webhooks and the publicHostname setting), set up a reverse proxy (e.g. Nginx, Caddy) pointing to localhost:10567. You can also expose the port directly, but this is not recommended as traffic will be unencrypted.
Requires Node.js >= 20 and a running PostgreSQL instance.
npm install && npm run build && npm start
Note: Python execution and Signal integration only work inside the Docker containers.
Stavrobot has a three-tier knowledge system: memories, a scratchpad, and the database. It will manage these fairly well on its own, but they're important for you to know because you will want to help the bot use them properly.
Memories are always injected into the system prompt wholesale. You should be frugal with these, as they get included in the context every time, and having many of them can increase the context. An example of a good memory would be "the user prefers chatting in an informal style.", something that the bot should know about every time, even if that costs in additional context length.
The scratchpad is where the bot keeps less frequently-accessed knowledge, but knowledge that pertains to a topic. Scratchpad entries have a title and a body, and the title gets injected into the context automatically. Use this for important, but less-frequently needed things, things that usually pertain to a specific topic.
For example, a good scratchpad entry would be "dietary preferences", with details about what you like to eat, when, etc. The bot will see that there's a topic "dietary preferences", but not load the actual text itself into the context automatically, only accessing it on-demand.
When you're talking to the bot about food, it will know there's a "dietary preferences" scratchpad entry it can look at, and usually do that on its own.
The database is the third tier, for structured or bulk data that doesn't belong in memories or the scratchpad. The bot has full read/write access to PostgreSQL via unrestricted SQL, so it can create tables, run queries, and store anything. Use this for things like lists, logs, structured records, or any data that's better queried than read as prose.
The bot will usually know well enough what to use when, but sometimes you will want to tell it explicitly what information to put where.
Stavrobot can message people on your behalf over Signal, Telegram, WhatsApp, or email. Need to schedule a dinner with a friend? Tell the bot to find a time that works for both of you, and it will message them, negotiate a date, and put it on your calendar. Want to arrange an appointment, coordinate a group outing, or ask someone a question while you're busy? Just tell the bot what you need and who to talk to.
The bot spins up a dedicated subagent for each conversation, with its own instructions and context, so it can handle back-and-forth with the other person without cluttering your main chat. When the task is done, it disables the contact and reports back to you.
To keep things safe, messaging requires two things before the bot can talk to someone:
/settings and add their phone number
(Signal or WhatsApp) or chat ID (Telegram). This is a one-time step per person. The
bot cannot modify this list or message anyone not on it, no matter what.A typical flow: you add your friend's phone number to the allowlist via /settings once,
then tell the bot "find a time for dinner with Alex next week". The bot creates a contact
record for Alex, spins up a subagent, messages Alex on Signal, goes back and forth to
find a date, and reports the result to you.
Skills are plain-text instruction files that teach the bot new capabilities — things like creating tables, storing knowledge, making pages, or following specific workflows. They are not code; they are natural-language instructions the bot reads and follows.
To install the skill system, tell the bot to fetch
https://stavrobot.stavros.io/skills/bootstrap.md and follow its instructions.
A full list of available skills is at
https://stavrobot.stavros.io/skills/index.md. To install a skill, just tell the bot to
fetch and install it from that URL. The bot will show you what the skill does and ask for
confirmation before proceeding.
Skill files live on the pages branch of this repo (served via Cloudflare Pages). To
contribute a skill, add a markdown file to skills/ on that branch. Each skill file has
YAML front matter with title, description, version, and an optional author,
followed by the plain-language instructions in the body. See
SKILL-AUTHORING.md for the full authoring guide. Submit a PR
against the pages branch.
See PLUGIN.md for everything you need to know to create a Stavrobot plugin. The same document works as a reference for both humans and LLMs.
Plugins are fairly necessary to enable core functionality for the bot, such as the ability to "hear" voice notes, send voice notes back, be aware of places around you, search the web, etc.
These capabilities are not provided out-of-the-box to give you the ability to customize your bot however you want. You should install plugins, though, otherwise you will be missing crucial functionality.
You can find some first-party plugins here:
Three core Docker containers: app (TypeScript server, exposes POST /chat, handles Telegram webhooks at POST /telegram/webhook, handles inbound email webhooks at POST /email/webhook, and runs WhatsApp in-process via Baileys), postgres (PostgreSQL 17 for persistent state), and plugin-runner (Node.js server — lists, inspects, and executes plugins, both locally created and git-installed). An optional coder container (Claude Code headless agent for creating and modifying editable plugins) is enabled via the coder Docker Compose profile. The main agent can create subagents, each with their own conversation history, system prompt, and tool whitelist. Interlocutors are contact records assigned to agents for inbound message routing.