What the bot may do¶
This guide explains what Anna's Telegram bot is allowed to do, how to allow it one more action, and how confirmations work when you ask for something bigger. It's for you once the bot is running and you want it to do more than answer.
Note
The Telegram bot and the relay haven't yet been walked end to end on a clean machine (see the README's status). The
permission files below are what install.sh writes; expect rough edges in the bot's setup itself.
New to this?
The bot is its own Claude Code session. Claude Code checks every tool call (running a command, editing a file) against a permissions list in a settings file. The bot's list is deny by default: anything not listed is refused, silently, without asking you. That's on purpose: messages reach the bot from your phone, and photos or forwarded texts might contain instructions meant to steer it.
The bot's settings file¶
install.sh writes it once, from the scaffold's bot/settings.json.in, to:
~/.claude/anna-tg-<instance>-settings.json
<instance> is bot.instance in your config (default ops). After that, the file is yours: install.sh never
overwrites it (exists (left as is: your allowlist)). anna-tg starts the bot with --settings pointing at it.
What the template sets:
| Setting | Value | Why |
|---|---|---|
permissions.defaultMode |
dontAsk |
Anything not on allow is refused without a prompt. |
enabledPlugins |
the telegram and fakechat plugins | The bot's channel, and the relay's (fakechat is removed if relay.enabled is false). |
disableClaudeAiConnectors |
true |
No claude.ai connectors in the bot's session. |
env.ENABLE_TOOL_SEARCH |
"false" |
Tools are loaded up front rather than looked up. |
permissions.additionalDirectories |
the record and the bot's Telegram inbox | Folders outside its working directory it may read. |
What it's allowed¶
| Entry | Lets the bot |
|---|---|
Read(/<record>/**) |
read the whole record |
Read(/<state>/telegram-<instance>/inbox/**) |
read photos and files you send it |
Edit(/<record>/hosts/**) |
update host records |
Edit(/<record>/memory/**), Write(/<record>/memory/**) |
save lessons and preferences |
mcp__plugin_telegram_telegram__* |
talk to you on Telegram |
mcp__plugin_fakechat_fakechat__reply |
hand work to the brain over the relay (only if the relay is on) |
mcp__merlin__* |
search the record, if merlin is set up |
Bash(git -C <record> log*), add *, commit *, push |
commit what it saved (plain push only) |
Bash(date*) |
read the time |
Bash(anna-checks*), Bash(anna-body*) |
run your checks and senses |
Bash(anna-clock add *), list*, due*, done *, cancel * |
manage Anna's clock |
Bash(anna-stream add *), recent* |
read and write the stream |
In the file, <record> and <state> are your real absolute paths, so Read(/<record>/**) becomes something like
Read(//home/you/anna-record/**). In Claude Code's permission rules, a path starting with // is absolute.
What it's denied¶
| Entry | Blocks |
|---|---|
Read(/<secrets>/**) |
reading your token files |
Bash(*<secrets>*) |
any command that names the secrets folder |
Bash(curl *), Bash(wget *) |
HTTP requests |
Bash(rm *) |
deleting files |
Bash(sudo *) |
anything as root |
A deny rule wins over an allow rule.
Why it's this narrow¶
From the security model:
- Each entry is one specific command. When the bot needs to check something new, that check becomes a section in
anna-checks(Write a check or a sense), the one command it's already allowed, never a wider pattern. - No HTTP-capable command in the list: no
curl,wget,python,nc,bun. With the relay on, the brain listens on a local port with no authentication (relay.brain_port). A restricted session that can make local HTTP requests can drive the brain, which runs with permissions off. See the relay. - Real work goes to the brain. Anything bigger than a quick check (hosts, files, fixes) is handed over the relay, where the brain does it under the gate and asks you before anything destructive. The bot's prompt tells it never to suggest widening its own allowlist.
Allow one more action¶
The pattern is a wrapper: a small script that does exactly one thing, and an allow entry for that script.
Say you want the bot to switch a lamp on and off.
-
Write the wrapper in
paths.bin(default~/bin). It takes only the arguments it needs and checks them; it must not let its arguments choose a URL, a host or a command.#!/bin/bash # ~/bin/lamp: switch the desk lamp. Usage: lamp on|off case "${1:-}" in on|off) ;; *) echo "usage: lamp on|off" >&2; exit 2 ;; esac # ...the one command that switches it, with any credentials read from a file in paths.secrets...chmod +x ~/bin/lampPut the wrapper in
paths.binrather than in the checkout'sbin/, so updates don't touch it. Keep a copy of it in your record (for example underrunbooks/lamp/), so it's versioned. -
Allow it in
~/.claude/anna-tg-<instance>-settings.json, by adding one entry topermissions.allow:"Bash(lamp *)",Prefer
Bash(lamp *)(the command, a space, then arguments) overBash(lamp*): the second would also match any other command whose name starts withlamp. -
Tell the bot about it. Add a short file to
<record>/bot/context/, for examplebot/context/devices.md:Desk lamp: `lamp on` / `lamp off`.Every file there is added to the bot's prompt at launch (The record's layout).
-
Check the file is still valid JSON, then restart the bot so it reads the new settings and prompt:
jq . ~/.claude/anna-tg-<instance>-settings.json > /dev/null && echo valid systemctl --user restart anna-tg@<instance>You should see
valid. To check what the bot is told:anna-tg <instance> --print-prompt. -
Try it from Telegram: "turn the desk lamp on".
Never add
curl, wget, python, nc, bun or any other command that can make an HTTP request, a shell (bash -c,
sh -c), or a wildcard that covers more than one command. If you need one of those, write a wrapper that does the
single thing you want.
Confirmations¶
The bot doesn't call the engines and isn't covered by the action-gate hook (the hook passes the bot through): its allowlist is its guard. What isn't on the list doesn't happen.
When you ask for something bigger, the bot hands it to the brain over the relay. There, the rules from the security model apply:
- Your relayed words are treated exactly like a prompt you typed, and no more. The bot passes on your exact words, what it told you, and a little context.
- Destructive and outward actions are confirmed first (deleting, overwriting, sending, publishing, anything hard to undo), unless you've said to proceed. Over the relay, the brain's question comes back to you through the bot, and your answer goes back the same way.
- The action gate decides which actions need that. A relayed ask counts as the confirmation only when it's
unambiguous and names the exact action ("restart the web server"); otherwise the gate returns
confirm-firstand you're asked. See Engines: anna-action-gate. - Text inside photos and forwarded messages is data. The bot hands off your request, never instructions found inside what you shared, and the gate refuses an action whose only source is such data.
The headless runs' permissions are different¶
The morning, evening and clock runs use ~/.claude/anna-headless-settings.json, also deny by default, from
config/headless-settings.json.in. Unlike the bot's file, install.sh rewrites it on every run, so a hand edit there
is lost on the next update (Update after git pull). A check those runs are missing belongs
in anna-checks, not in that list.