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:
@@ -0,0 +1,63 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user