Files
paul 00195ee6d5 Add Gitea integration for agent to push branches and open pull requests
- Implemented `register-gitea-bot.sh` to provision Gitea access token.
- Updated `maintainer.py` to support Gitea API for push and PR operations.
- Modified `install.sh` to warn if Gitea token is not provisioned.
- Enhanced documentation in `README.md`, `CLAUDE.md`, and `architecture.md` to reflect new Gitea functionality.
- Added Gitea configuration to `config.example.json`.
2026-06-29 20:46:33 +12:00

76 lines
3.9 KiB
Markdown

# 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`.
- **Code changes go through pull requests.** When the agent edits tracked files
it branches, pushes, and opens a PR on Gitea (`git.semprini.me`) as the
`claude-code` user rather than committing to `main`, giving the operator a
review gate. Live remediation (restart/redeploy) is *not* gated — only the git
change is. claude-code is an OIDC-linked, push-capable collaborator; since
git.semprini.me disables password login + HTTP basic auth, `register-gitea-bot.sh`
mints a personal access token via the gitea admin CLI inside the `user-gitea`
container (mirroring the Matrix bot's shared-secret pattern). A PAT still works
for git-over-HTTPS and the API, so no SSH exposure or core-stack change is
required. The daemon injects the bot's git identity + credential (`GIT_CONFIG_*`,
scoped to the Gitea host so it overrides the repo's own `user.*`) and the API
coordinates into the headless agent; the agent opens the PR itself.
## 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).