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

3.9 KiB

Maintenance Agent (semprini-maintainer)

Autonomous, always-on agent that keeps the container stack healthy and current. Implementation: this repository — see 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).