Appearance
Reference deployment — an always-on personal Cicero
Setup gets you a working voice loop in a terminal you're watching. This runbook turns that into the thing the README promises: a box that's always there — browser or PWA from any device on your network, Telegram texts, a real phone call, a morning briefing — and that comes back on its own after crashes, reboots, and upgrades.
It composes the feature guides rather than repeating them: behavior and tuning live in web voice, notifications, the call sidecar, and security. Everything here is generic — swap paths for your own.
Before you start: run the first-conversation path by hand once, end to end, including one real spoken turn. Never debug a fresh install through a service manager.
1. Pin the secrets
An always-on deployment can't use the print-once web token, and its secrets need one home:
- Web token — generate once with
openssl rand -hex 16and set it asweb_voice.tokenin~/.cicero/config.yaml. Do this before running under a supervisor: the per-run token prints to startup stdout, and supervisors retain stdout in logs. - Repo
.env(git-ignored, next to the checkout) — the Telegram bot token and, if you deploy calls, the userbot API credentials and the caller allowlist (CICERO_TG_ALLOWED). The call listener fails closed without the allowlist. Names and details: notifications and the call sidecar README. - Those are the credentials you place. The runtime creates more of its own — the Telegram userbot session file, the sidecar hook token, agent settings copies — so treat
~/.cicero/as credential-bearing wholesale. What must never leak into logs or dashboards, and the storage rules, are specified in security.
2. The daemon, supervised
Use the shipped user unit — no root, survives logout via lingering:
bash
mkdir -p ~/.config/systemd/user
cp deploy/cicero.service ~/.config/systemd/user/ # edit WorkingDirectory first
systemctl --user daemon-reload
systemctl --user enable --now cicero
loginctl enable-linger $USER
journalctl --user -u cicero -fThe daemon launches and owns its model servers, so STT/TTS need no units of their own — they stay running, and for most bundled engines the daemon also revives one that dies unexpectedly (a few backends only report the death). A managed engine's port staying closed is therefore a failure or an in-progress recovery, not idleness: check journalctl --user -u cicero rather than shrugging it off.
3. Phone calls, supervised
The Telegram call bridge is a separate process and needs its own unit — running it in a terminal is the classic silent gap: everything works for weeks, then a reboot kills incoming calls with nothing to notice. Ship it the same way as the daemon:
bash
cp deploy/telegram-call.service ~/.config/systemd/user/ # edit WorkingDirectory first
systemctl --user daemon-reload
systemctl --user enable --now telegram-callProvision the sidecar (venv, userbot session, allowlist) per its README before enabling the unit. The sidecar reconnects to the daemon by itself when the daemon restarts.
4. Reaching you: texts, briefings, schedules
All configuration, no new processes — the bot, quiet hours, the morning briefing, and scheduled prompts are notify: blocks in config.yaml, documented in notifications. The "call me" intent needs no config of its own — it's built into the daemon and works once the Telegram text line and the call sidecar are up. For the always-on shape you'll typically want at minimum: the Telegram bot (two-way text line), quiet hours, and the briefing time.
5. Prove it survives a reboot
Don't declare the deployment done until you've watched it come back:
bash
sudo reboot
# after the box returns:
systemctl --user status cicero telegram-call # both active
cicero status # daemon + configured backendsThen the real acceptance tests, in order: one spoken browser turn, one text to the bot and its reply, and one "call me" that actually rings. If you only check ports, you'll miss an unauthenticated CLI or a dead userbot session.
6. Upgrades
bash
cd <your-checkout>
git pull --ff-only
bun install
systemctl --user restart ciceroRestart only the daemon by default. The call sidecar survives daemon restarts on its own — it re-dials the daemon socket with backoff and releases the bridge on Telegram's hang-up — whereas restarting telegram-call sends SIGTERM to the listener, which ends a live call. Deploy sidecar changes separately, preferably between calls, and actually apply what changed before restarting — a bare restart keeps stale dependencies and stale unit definitions:
bash
# Only when sidecars/telegram-call/ code, requirements, or .env changed:
uv pip install --python ~/.cicero/tgcalls-venv -r requirements/telegram-call.txt
systemctl --user restart telegram-call
# Only when deploy/cicero.service changed:
cp deploy/cicero.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user restart cicero
# Only when deploy/telegram-call.service changed (between calls):
cp deploy/telegram-call.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user restart telegram-callRe-run the step-5 acceptance tests after any upgrade that touched the voice path.
If you automate this (a cron that fetches, fast-forwards, and restarts), keep three properties: fast-forward only on a clean checkout, restart only when files the daemon actually runs changed, and an alert channel for the cases the script refuses to handle. A dirty tree or a diverged branch is a message to you, not something a deploy script should resolve.
7. Watch it (optional, recommended)
Restart=on-failure covers crashes; it does not cover a daemon that's up but wedged, or a unit someone disabled. A small external check — cron hitting the HTTPS endpoint and alerting through a path that does not depend on the daemon (e.g. the Telegram Bot API directly) — closes that gap. Keep the watchdog dumber than the thing it watches: probe, alert, at most restart; decisions stay with you.
8. Back up the state that matters
Everything irreplaceable lives in two places:
~/.cicero/—config.yaml,voices/(your reference WAVs), the Telegram userbot session file, health log, notification state.- The repo
.env— tokens and API credentials.
The checkout itself, models, and venvs are all re-creatable from the setup guide. Never commit .env, session files, or real voice references — see security for the storage rules.