Telegram: the bot and the relay¶
This page puts Anna on your phone: create a Telegram bot, give Anna its token, let only you talk to it, start it, and turn on the relay so the bot can hand real work to the brain. Do Install first.
Telegram is the default, not a requirement
The bot is a Claude Code session listening on a channel plugin. Telegram is the one the scaffold ships with and is tested on, but another chat app works the same way: swap in a different channel plugin. Today that swap means editing a few files by hand (Using another chat app below); making the channel a setting is on the roadmap.
Not yet walked on a clean machine
The bot and the relay run daily in the deployment Anna was extracted from, and each piece has tests, but nobody has yet set them up from scratch on a fresh machine by following these steps. If a step doesn't behave as described, Troubleshooting lists what the scripts check, and the notes marked "Untested" say what is unverified.
How the pieces fit¶
- The bot (
anna-tg) is a Claude Code session with the Telegram channel. It answers you quickly, runs on a cheaper model (models.bot, defaultsonnet) and can only do what its allowlist permits. - The relay (
anna-relay bridge) carries requests from the bot to the brain and results back. You talk to one Anna; the bot says the brain's result in its own voice. anna-notifysends one-way alerts from scripts (a failed night run, a ⛔ in the morning report, clock pings) to your chat.
The bot needs bun: check with bun --version (bun.sh if it's missing; the installer adds
~/.bun/bin to your shell profile, so open a new terminal afterwards).
1. Create a bot with BotFather¶
In Telegram, open a chat with @BotFather and send /newbot. It asks for:
- a name, shown in chat headers (anything, spaces allowed), and
- a username, unique and ending in
bot(for examplemy_anna_bot).
BotFather replies with a token like 123456789:AAH…. Copy all of it, including the number and the colon. Anyone with
this token can act as your bot: don't paste it into a chat, the config or the record.
2. The Claude Code plugins¶
The bot uses two plugins from Anthropic's official marketplace: telegram (the channel) and, for the relay, fakechat.
You don't install them yourself: once bot.enabled or relay.enabled is true (step 3), ./install.sh installs the
ones that are missing (at user scope) and prints plugin telegram installed (user scope). If it prints ⚠ couldn't
install the … plugin, run the command it shows, then ./install.sh again.
Both stay off for your normal Claude Code sessions: only the bot and the brain start them, with their own settings
files. install.sh sets both to false in ~/.claude/settings.json on every run. After step 3, check:
jq .enabledPlugins ~/.claude/settings.json
You want false for both telegram@claude-plugins-official and fakechat@claude-plugins-official. If either says
true, run ./install.sh again.
3. Turn the bot on in the config and reinstall¶
In ~/.config/anna/anna.toml:
[bot]
enabled = true
instance = "ops" # the default; names the token file, state folder and settings file
[relay]
enabled = true # on if you want the bot to hand work to the brain (step 7)
Then, from the clone:
cd ~/projects/anna && ./install.sh
Among its lines you should now see:
~/.claude/anna-brain-settings.json (fakechat for the brain only)
/home/you/.claude/anna-tg-ops-settings.json (deny by default: add your own wrappers to allow)
bot: start it when the token is in place: systemctl --user enable --now anna-tg@ops
The first line appears only with the relay on. With the relay on, the installer also enables anna-relay.service.
You'll also see a plugin … installed line for each plugin (step 2).
4. Save the token¶
The token goes in a file named telegram-<instance> in your secrets folder (paths.secrets, default
~/.config/anna/secrets). The scripts read it from there; it never appears on a command line.
umask 077
read -rs TOKEN # paste the token, press Enter (nothing is shown)
printf '%s\n' "$TOKEN" > ~/.config/anna/secrets/telegram-ops
unset TOKEN
Check that Telegram accepts it:
anna-notify --me
You should see {"ok":true,"bot":"my_anna_bot","name":"…"}. anna-notify: no bot token at … means the file is missing
or empty.
5. Find your chat, and allow only you¶
Anna needs your chat ID twice: anna-notify sends alerts to it, and the bot's allowlist lets only you in.
Learn your chat ID. Open your bot in Telegram (search for its username) and send it any message, for example
hello. Nothing answers yet: the bot isn't running. Then:
anna-notify --learn
You should see stored chat for ops: <your first name> (@<your username>). The ID is saved in
<paths.state>/telegram-ops.chat (default ~/.local/state/anna/telegram-ops.chat; not secret). Run this before you
start the bot: Telegram lets only one program at a time read a bot's incoming messages, and once the bot runs, it's the
one reading.
Send yourself a test:
anna-notify "hello from Anna"
The message arrives in your chat.
Allow only your account. The bot's channel (the Telegram plugin) answers only accounts on its allowlist. It keeps
that list in access.json inside the bot's state folder, <paths.state>/telegram-<instance>/ (anna-tg points the
plugin there). There are two ways to get yourself on it.
By hand (recommended). In a private chat, your chat ID is your user ID, so the file anna-notify --learn wrote has what you need:
mkdir -p -m 700 ~/.local/state/anna/telegram-ops
printf '{"dmPolicy":"allowlist","allowFrom":["%s"],"groups":{},"pending":{}}\n' \
"$(cat ~/.local/state/anna/telegram-ops.chat)" > ~/.local/state/anna/telegram-ops/access.json
The plugin rereads this file on every incoming message, so it applies right away. dmPolicy: allowlist means anyone
else is dropped silently.
Pairing (the plugin's own flow; not yet tested with the bot's settings). Start the bot (step 6), DM it, and it replies with a 6-character pairing code. Attach to the bot's session and approve the code there:
screen -r anna-tg-ops
/telegram:access pair <code>
/telegram:access policy allowlist
The second command stops the bot from answering strangers with pairing codes. Detach with Ctrl-A, then D. Approving only works typed in the session itself, never from a Telegram message.
Untested
The bot runs deny by default, and its settings may not let /telegram:access write access.json (it lives
outside the folders the bot may write). If pairing doesn't stick, use the by-hand way above.
6. Start the bot¶
systemctl --user enable --now anna-tg@ops
This runs anna-tg ops inside a detached screen session named anna-tg-ops, and restarts it 30 seconds after a crash.
Check it:
systemctl --user status anna-tg@ops
Active: active (running). To watch the bot's session, screen -r anna-tg-ops; detach with Ctrl-A, then D.
The first time, look at the session: Claude Code may be waiting on a question about the bot's working folder
(~/anna-ops). Answer it there.
Now DM the bot hi. It answers in a few lines, in Anna's voice.
What the bot sees: its prompt is bot/prompt.md filled from your config, plus every file in <record>/bot/context/
(room names, device IDs, the wrappers it may call). Print it with anna-tg ops --print-prompt. After changing a context
file, systemctl --user restart anna-tg@ops. What the bot may run, and how to add to it:
Bot permissions.
7. Turn on the relay¶
With relay.enabled = true and ./install.sh rerun (step 3), the bridge runs as anna-relay.service. Check:
systemctl --user status anna-relay
Active: active (running).
Both sessions must be restarted to load the relay's channel: they only pick up the setting at launch.
- The bot:
systemctl --user restart anna-tg@ops. - The brain: attach (
screen -r anna) and type/exit. The window showsClaude exited (rc 0). Resuming Anna-… in 10 s. [Enter] now · [s] shell · [q] close window: press Enter (or wait). It reads the config again as it resumes, so the same conversation comes back with the relay's channel.
Check that both sides are connected:
anna-relay status
{"brain":"online","bot":"online","paused":false,"new":0,"brain_open":0,"out":0,"done":0,"oldest_open":null}
anna-checks relay says the same in one line. Then try it: ask the bot for something bigger than a quick check, the kind
of work it hands off ("look through my server's logs for errors since yesterday"). The bot says it's on it; a minute or so later the result
comes back in its voice. Between day.close and day.open (default 00:30 to 04:15) the brain is wrapping up or asleep:
requests wait in the queue, and the bot tells you once that it'll get to it in the morning.
How it works, the guards and the queue: The relay.
The relay port has no password
The relay uses fakechat, Anthropic's demo channel, which has no authentication. Anything on the machine that can
reach 127.0.0.1:8791 (relay.brain_port) can put a prompt into the brain, and the brain runs with permissions off.
So:
- Run Anna on a machine that's hers alone: no other users, no services that fetch URLs for others.
- Never add an HTTP-capable command (
curl,wget,python,nc,bun) to the bot's or the headless runs' allowlist. - A relayed message is treated exactly like a prompt you typed, and no more: destructive or outward actions still come back to you for a yes, through the bot. Text inside photos and forwards is data, never instructions.
The full reasoning: The relay § Trust and Security model.
Next¶
Your first day: the daily rhythm with the bot in it.
Using another chat app¶
Anna's bot isn't tied to Telegram. It's a Claude Code session started with --channels plugin:<channel>, so any Claude
Code channel plugin (one for another chat service, or one you write) can carry the conversation. The rest of Anna (the
brain, the relay, the stream, the record) doesn't care which app you talk through.
Telegram is wired into these places today, so a different channel means changing each of them:
| Where | What's Telegram-specific |
|---|---|
bin/anna-tg |
the channel (plugin:telegram@claude-plugins-official), the token it reads from <paths.secrets>/telegram-<instance>, and TELEGRAM_STATE_DIR |
bin/anna-notify |
scripts' alerts and clock pings call Telegram's sendMessage API directly, not through the plugin |
bot/settings.json.in |
enables the telegram plugin and allows its reply tool |
install.sh |
installs the telegram plugin and keeps it off at user scope |
The relay is separate: it uses the fakechat channel between the bot and the brain, whichever app you chat in.