Configuration¶
This page lists every setting in Anna's configuration file: what it does, its default, and which part of Anna reads it.
It's for you when you edit anna.toml by hand, and for developers who want to know exactly where a value ends up.
Where the file lives¶
Anna reads one TOML file:
| What | Where |
|---|---|
| Your config | ~/.config/anna/anna.toml, or the path in $ANNA_CONFIG if you set it |
| The defaults | config/anna.example.toml in the scaffold checkout (installed copy: ~/.local/share/anna/config/anna.example.toml) |
You don't need to copy every key. Anna loads the defaults first, then lays your file over them, section by section. A key you leave out keeps its default.
New to this?
TOML is a plain text format for settings. A line in [brackets] starts a section; each line below it is
key = value. Text goes in quotes ("04:15"), numbers and true/false don't. A # starts a comment.
The easiest way to write the file is the /anna-setup skill in Claude Code: it interviews you and writes it. By hand:
cd ~/projects/anna
./install.sh # first run: writes ~/.config/anna/anna.toml from the example, then stops
nano ~/.config/anna/anna.toml
./install.sh # second run: installs using your values
The first run prints Wrote /home/you/.config/anna/anna.toml from the example: edit it (or run /anna-setup in Claude Code),
then run install.sh again.
How values are read¶
Every script reads the config through anna-config (see Commands). Keys are dotted:
section, dot, key.
anna-config day.open
04:15
What it does with a value:
- A path that starts with
~comes back with~expanded to your home directory. true/falsecome back astrue/false.- A list or table comes back as JSON.
- An unknown key is an error (exit 2,
anna-config: no such key: …), so a typo never reads as an empty value.
Two more forms:
anna-config --path # which file is in use; "(missing: defaults only)" if yours doesn't exist
anna-config --dump # the merged config (defaults + yours) as JSON
The Python scripts and hooks use bin/anna_cfg.py, which calls anna-config --dump once and reads from the result. If
that fails (no config, a broken file), they fall back to built-in defaults instead of failing: a missing config must never
break a hook.
Where the defaults come from¶
anna-config looks for the defaults file in this order and uses the first that exists:
$ANNA_HOME/config/anna.example.toml, ifANNA_HOMEis set../config/anna.example.tomlnext to theanna-configscript (running from the checkout)~/.local/share/anna/config/anna.example.toml(the copyinstall.shmakes)
Environment variables¶
These override or add to the config file. Most are for tests or are set by Anna's own scripts; you rarely set them by hand.
| Variable | Read by | What it does |
|---|---|---|
ANNA_CONFIG |
anna-config, install.sh |
path of your config file instead of ~/.config/anna/anna.toml |
ANNA_HOME |
anna-config |
a scaffold checkout whose config/anna.example.toml holds the defaults |
MERLIN_PATH |
anna_cfg.py |
a merlin checkout: its redaction patterns are used instead of the built-in ones |
ANNA_TG_MODEL |
anna-tg |
the bot's model, instead of models.bot |
ANNA_ADD_DIR |
anna-run |
one extra directory the headless run may read (anna-evening sets it to the state directory) |
ANNA_CLOCK_BY |
anna-clock |
who added an item (default session; the bot sets bot, the tick sets tick) |
ANNA_NOTIFY_FROM |
anna-notify |
the sender name written to the stream (clock #a3f2, anna-run morning, …) |
ANNA_ROLE |
anna-stream, hooks |
bot (set by anna-tg) or relay (set by anna-relay.service); the stream source and hook behaviour follow it |
ANNA_HEADLESS |
hooks, anna-stream |
set to 1 by anna-run: the session hooks stay quiet |
ANNA_RELAY_DIR |
anna-relay |
the relay queue directory instead of <paths.state>/relay (tests) |
RELAY_BRAIN_PORT, RELAY_BOT_PORT |
anna-relay |
override relay.brain_port / relay.bot_port (tests) |
RELAY_SETTLE_MS, RELAY_TICK_MS |
anna-relay |
how long a side must be connected before it counts as online (default 5000 ms), and the bridge's tick (default 2000 ms) |
ANNA_RELAY_NOSTREAM |
anna-relay |
1 = don't write stream lines (tests) |
ANNA_IDLE_WAIT, ANNA_IDLE_TEST |
anna-idle-checkpoint hook |
idle wait before a checkpoint (default 1200 s) and a test switch; see Hooks |
What install.sh does with it¶
install.sh reads your config and uses it to place and fill everything:
- It creates
paths.bin,paths.stateandpaths.secrets(the last with mode 700), copies the scripts and hooks intopaths.bin, and creates the record atpaths.recordfromrecord-template/if nothing is there yet. - It fills placeholders in templates with your values:
@NAME@(anna.name),@USER@(anna.user),@TZ@(anna.timezone),@RECORD@,@STATE@,@SECRETS@(the paths),@INSTANCE@(bot.instance) and@HOME@. That covers the engines in~/.claude/agents/, the record'sCLAUDE.mdandmemory/MEMORY.md,~/CLAUDE.md, the headless settings and the bot settings. - It renders the timers from your times:
day.close,day.open,runs.morning,runs.eveningandanna.timezone(see Timers). relay.enabled = truewrites~/.claude/anna-brain-settings.jsonand enablesanna-relay.service;bot.enabled = truewrites~/.claude/anna-tg-<instance>-settings.json(only if it doesn't exist yet). Either one makesbuna requirement.
It never overwrites your config file. After you change a value, run ./install.sh again so the timers and settings pick
it up. Scripts that read the config at run time (most of them) see the change straight away.
Changing a time needs install.sh
The timer times are written into the unit files at install time. Editing day.open or runs.morning changes nothing
until you run ./install.sh again.
[anna]: who she is¶
| Key | Default | What it does | Who reads it |
|---|---|---|---|
name |
"Anna" |
What she's called. The bot speaks as this name; the brain's daily session is named <name>-YYYYMMDD. |
install.sh (@NAME@), anna-day, anna-tg (bot prompt), anna_cfg.py |
user |
"you" |
The person she works with, as she should address them. Also the prefix of your lines in the stream (<user>: …), which anna-rhythm counts as your activity. |
install.sh (@USER@), anna-stream, anna-rhythm, anna-clock, anna-relay, anna-tg, the hooks |
timezone |
"UTC" |
Every time she writes or reads: journal, clock, stream day files, the nightly window, and the timers' OnCalendar zone. An IANA name like "Europe/Berlin". |
every script; install.sh renders it into the timers |
Note
The user value is matched literally against stream lines. If you change it later, anna-rhythm won't count lines
written under the old name.
[paths]: where things live¶
| Key | Default | What it does | Who reads it |
|---|---|---|---|
record |
"~/anna-record" |
Your record: journal, hosts, runbooks, planning, memory. A git repo, created from record-template/ by install.sh. Your checks (checks/), senses (senses/), clock (clock/) and bot context (bot/context/) live here too. |
install.sh, anna-run, anna-clock, anna-checks, anna-body, anna-evening, anna-tg, backlog-age, hooks |
state |
"~/.local/state/anna" |
Runtime state, outside the record: the stream, the clock lock, the relay queue, session drafts, the Telegram chat id, anna-run.log. |
anna-stream, anna-clock, anna-relay, anna-day, anna-brain, anna-evening, anna-notify, anna-tg, anna-rhythm, hooks |
bin |
"~/bin" |
Where install.sh puts the scripts and hooks. Scripts add it to their PATH; install.sh rewrites the hook commands in ~/.claude/settings.json to point here. |
install.sh, most scripts (PATH), anna-clock (the --run command), anna-gate-hook |
secrets |
"~/.config/anna/secrets" |
Secret files, directory mode 700. claude-oauth (optional: a long-lived token for headless runs) and telegram-<instance> (the bot token, one line). |
install.sh, anna-run, anna-notify, anna-tg |
install.sh writes paths.bin into the systemd units as it installs them, so the timers find the scripts wherever
you put them. Change bin, then run ./install.sh again.
[models]: which model each part runs¶
Empty means "the harness default" (whatever claude picks with no --model). Any model name claude --model accepts works.
| Key | Default | What it does | Who reads it |
|---|---|---|---|
brain |
"" |
The brain, the long-running interactive session. Passed as --model when set. |
anna-brain |
bot |
"sonnet" |
The Telegram bot. Always passed as --model; ANNA_TG_MODEL overrides it. |
anna-tg |
headless |
"" |
Scheduled headless runs (morning, evening, clock --run items). Passed as --model when set. |
anna-run |
Note
anna-tg always passes --model "$(anna-config models.bot)". If you set bot = "", that becomes --model "".
[day]: the brain's daily rollover¶
anna-day closes the brain's session overnight and opens a fresh one in the morning. See
Commands and Timers.
| Key | Default | What it does | Who reads it |
|---|---|---|---|
screen |
"anna" |
Name of the screen session the brain lives in. Attach with screen -r anna. |
anna-day |
close |
"00:30" |
When the day's session is wrapped up and closed (if idle). HH:MM, in anna.timezone. Also the start of the nightly window, when the brain is expected to be away. |
install.sh (anna-day-close.timer), anna-checks (relay check), anna-relay (the "wrapping up" message), anna-idle-checkpoint |
open |
"04:15" |
When the new day's session starts. HH:MM, in anna.timezone. The nightly window ends 5 min after it (anna-checks). |
install.sh (anna-day-open.timer), anna-checks, anna-relay, anna-idle-checkpoint |
idle_min |
30 |
Minutes without activity before close may wrap up. A session active more recently is left open, and the stream says so. |
anna-day |
wrap |
"/anna-wrapup overnight" |
What close types into the brain before /exit. anna-rhythm ignores stream lines with this text so it doesn't count them as you. |
anna-day, anna-rhythm |
remote_control |
false |
true = after open, type /remote-control into the new session (and resume with --remote-control after a crash). Needs a claude.ai login; lets you reach the brain from the Claude app. |
anna-day, anna-brain |
[runs]: scheduled headless runs¶
Each run is one claude -p session through anna-run, with deny-by-default settings
(~/.claude/anna-headless-settings.json).
| Key | Default | What it does | Who reads it |
|---|---|---|---|
morning |
"04:00" |
Time of the morning run, /anna-morning: a read-only check, written to journal/handoffs/<date>-morning.md in the record. ⛔ lines under ## Needs action notify you. |
install.sh (anna-morning.timer) |
evening |
"01:00" |
Time of the evening run, anna-evening: folds the day's session drafts and yesterday's stream into the journal. |
install.sh (anna-evening.timer) |
[drafts]: session drafts¶
When an interactive session ends, the anna-session-draft hook writes a raw, redacted draft of it into
<paths.state>/drafts/ for the evening run to fold. Drafts stay outside the record.
| Key | Default | What it does | Who reads it |
|---|---|---|---|
secret_commands |
["bw", "bw-session", "op", "pass", "vault", "secret-tool", "secret-put"] |
Commands whose output is never drafted. Add your own password tools here. If the key is missing entirely, only bw is used. |
anna-session-draft hook |
[relay]: bot to brain¶
The relay moves requests from the Telegram bot to the brain and results back, over two local fakechat channels. See The relay.
| Key | Default | What it does | Who reads it |
|---|---|---|---|
enabled |
false |
Turn the relay on. install.sh then writes the brain's fakechat settings and enables anna-relay.service; anna-brain and anna-tg load the fakechat channel; anna-checks checks it. |
install.sh, anna-brain, anna-tg, anna-checks |
brain_port |
8791 |
Local port of the brain's fakechat channel. | anna-brain (FAKECHAT_PORT), anna-relay |
bot_port |
8792 |
Local port of the bot's fakechat channel. | anna-tg (FAKECHAT_PORT), anna-relay |
alarm_min |
30 |
Minutes a hand-off may wait for a result before a ⚠ line goes to the stream (anna-relay) and the relay check warns (anna-checks). |
anna-relay, anna-checks |
Note
The relay and the Telegram bot have not yet been walked through on a clean machine. Expect rough edges.
[bot]: Telegram¶
| Key | Default | What it does | Who reads it |
|---|---|---|---|
enabled |
false |
Turn the Telegram bot on. install.sh writes the bot's settings file and tells you how to start anna-tg@<instance>. With false, anna-notify writes to the stream only, and anna-day doesn't notify on failures. |
install.sh, anna-notify, anna-day |
instance |
"ops" |
The bot instance name: lowercase letters and digits. It names the token file <paths.secrets>/telegram-<instance>, the state directory <paths.state>/telegram-<instance>, the settings ~/.claude/anna-tg-<instance>-settings.json, the working directory ~/anna-<instance> and the unit anna-tg@<instance>. |
install.sh, anna-tg, anna-notify |
A complete example¶
[anna]
name = "Anna"
user = "Sam"
timezone = "America/Chicago"
[paths]
record = "~/anna-record"
[day]
close = "23:30"
open = "06:00"
[runs]
morning = "05:45"
evening = "00:15"
[bot]
enabled = true
instance = "home"
Every key not listed keeps its default. After saving, run ./install.sh again and check the timers:
systemctl --user list-timers 'anna-*'