# Maintenance Agent (semprini-maintainer) Autonomous, always-on agent that keeps the container stack healthy and current. Implementation: this repository — see [`README.md`](../README.md). It maintains the **semprini-core** stack (a separate repo) referenced via `repo_dir`. ## Purpose Continuously maintain the running stack without an operator at the keyboard: 1. Detect and **remediate** service outages. 2. Periodically **check for and apply container upgrades** (with test + rollback). 3. **Communicate with the operator over Matrix** (chat.semprini.me), including asking questions when an upgrade needs a human decision. It is the unattended driver for the canonical stack-support agent (`agents/stack-support.agent.md`) — it does not re-implement that logic, it feeds the agent state and relays its results. ## Key decisions - **Scope = Uptime Kuma's live monitor list.** Read every health cycle, never hard-coded. Anything Kuma tracks is in scope — including services owned by other projects (e.g. `semprini-blog`) that register Kuma monitors. This keeps the maintained set and the monitored set identical by construction. - **Health signal = Kuma heartbeat.** The daemon reads Kuma's own up/down result rather than re-probing, reusing the stack's existing monitoring. - **Kuma data access** via a throwaway `keinos/sqlite3` container mounting the `uptime-kuma-data` volume read-only (the daemon user has Docker but not host access to root-owned volume files). - **Runner = Claude Code headless** (`claude -p`), with `agents/stack-support.agent.md` injected as the system prompt. The agent returns a JSON envelope (`status/summary/question/options`) the daemon acts on. - **Runs as `paul`** (in the `docker` group), not root — Claude Code refuses `bypassPermissions` as root. - **Autonomy = full** by default: remediate and apply safe upgrades; escalate only major/breaking upgrades to Matrix and resume on reply. Configurable to `fix-only` or `notify`. - **Matrix transport**: dedicated bot `@maintainer:semprini.me` (created via the Synapse registration shared secret) in a private room with `@paul:semprini.me`. ## Cadence | Loop | Default interval | Config key | |---|---|---| | Health check + remediation | 5 min | `health_interval_seconds` | | Upgrade review | 7 days | `upgrade_interval_seconds` | | Matrix reply polling | 30 s | `matrix_poll_seconds` | | Quiet health heartbeat | daily | `heartbeat_interval_seconds` | ## Escalation flow 1. Agent returns `status=escalate` with a `question` + `options`. 2. Daemon posts a numbered question to Matrix and pauses upgrade cycles. 3. Admin replies (a number or free text). 4. Daemon feeds the decision back to the agent, which carries it out and reports. ## Operational notes - The bot DM room invite must be accepted once by `@paul` in Element. - Secrets (`config.json`, `.bot-secrets`) are gitignored. - A single unanswered escalation blocks new upgrade cycles (health remediation continues regardless).