# semprini-maintainer — Claude Instructions ## What this is A standalone, always-on **autonomous maintenance agent** for the semprini.me container stack, deployed as a systemd service (`semprini-maintainer`). It keeps the stack healthy and current and talks to the operator over Matrix (chat.semprini.me). It is the *unattended driver* for the canonical **stack-support agent**, which lives in the separate **semprini-core** repo. This project does not reimplement that logic — it gathers state and invokes the stack-support agent headless via Claude Code, then relays/acts on the agent's JSON result. ## Relationship to other projects - **semprini-core** (`../semprini-core`) — the Enterprise Landing Zone this agent maintains. At runtime the daemon reads, from the path in `config.json` `repo_dir`: - `agents/stack-support.agent.md` (injected as system prompt) - `docs/stack-support-runbook.md` - `core_stack/compose*.yml` Keep the stack-support agent definition canonical **in semprini-core** — do not fork a copy here. - **Other projects** (e.g. `../semprini-blog`) that register Uptime Kuma monitors are also maintained; their repos are exposed to the agent via `extra_dirs`. ## How it works (one screen) - **Scope** = whatever **Uptime Kuma** tracks at check time, re-read every cycle (never hard-coded). Kuma's heartbeat is the health signal — the daemon does not re-probe. Kuma's root-owned `kuma.db` is read via a throwaway `keinos/sqlite3` container mounting the `uptime-kuma-data` volume read-only. - **Health loop** (5 min): down services confirmed over `down_confirmations` cycles → invoke stack-support agent to remediate → report to Matrix. Also reads Kuma's `important=1` heartbeat transitions since the last cycle (tracked by heartbeat id) to catch services that blipped DOWN→UP on their own, and hands those self-recovered windows to the agent for a log post-mortem. - **Upgrade loop** (7 days): resolve containers behind tracked services → agent researches + applies safe upgrades (test, pin, rollback); escalates major/breaking ones to Matrix. - **Matrix**: bot `@maintainer-bot:semprini.me` DMs `@paul:semprini.me`. Escalations are numbered questions; the reply is fed back to the agent to carry out. - **Autonomy** (`config.json` → `autonomy`): `full` (default) | `fix-only` | `notify`. ## Conventions / guardrails - Runs as host user **`paul`** (in `docker` group), **not root** — Claude Code refuses `--permission-mode bypassPermissions` under root. - `maintainer.py` is **stdlib-only** — keep it dependency-free so it deploys without a venv. - Secrets live in `config.json` and `.bot-secrets` — **never commit** (gitignored). - Prefer editing existing files over adding new ones. ## Files | File | Purpose | |---|---| | `maintainer.py` | The daemon (stdlib only). | | `config.example.json` | Template — copy to `config.json`. | | `register-matrix-bot.sh` | Creates `@maintainer-bot` account + DM room; writes token to config. | | `install.sh` | Syntax-check, install unit, enable + start. | | `semprini-maintainer.service` | systemd unit (runs as `paul`). | | `docs/architecture.md` | Architecture decision record. | ## Test without the service ```bash python3 maintainer.py --config config.json --once health python3 maintainer.py --config config.json --once upgrade python3 maintainer.py --config config.json --once replies ```