Initial commit: autonomous maintenance agent for semprini stack

systemd-deployed daemon that drives the semprini-core stack-support agent
headless via Claude Code, monitors health via Uptime Kuma, applies safe
upgrades, and escalates to the operator over Matrix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 17:20:06 +12:00
co-authored by Claude Opus 4.8
commit e91b129841
9 changed files with 1090 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
# 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.
- **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: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` 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
```