|
|
||
|---|---|---|
| bin | ||
| etc | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| mcp-config.json | ||
| package.json | ||
| README.md | ||
scrum4me-agent-runner
Headless Claude Code worker die de Scrum4Me job-queue (M13) leegtrekt vanaf
een QNAP NAS via Container Station. Geen Vercel, geen browser, geen
toetsenbord — Claude Code draait als daemon, claimt jobs uit
mcp__scrum4me__wait_for_job, voert ze uit in een per-job clone, en pusht
nooit zelf.
Architectuur in één plaatje
┌─ QNAP TS-664 (Container Station) ─────────────────────────────┐
│ │
│ ┌─ container: agent-runner ────────────────────────────────┐ │
│ │ PID 1: tini → run-agent.sh (daemon-loop) │ │
│ │ ├─ health-server.js (8080 → host) │ │
│ │ └─ claude -p (per-batch, met MCP via stdio) │ │
│ │ └─ scrum4me-mcp → Neon Postgres │ │
│ │ │ │
│ │ /tmp/job-<id> ephemeral working trees │ │
│ │ /var/cache/repos bare git mirrors (volume) │ │
│ │ /var/cache/npm npm cache (volume) │ │
│ │ /var/log/agent run + job logs (volume) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ /share/Agent/cache /share/Agent/logs /share/Agent/state │
└────────────────────────────────────────────────────────────────┘
│
▼ HTTPS
Neon Postgres (Scrum4Me DB)
▲
│
Vercel ─── Scrum4Me UI (gebruikers enqueueen jobs)
Eén claude -p-invocation roept intern wait_for_job aan totdat de
queue leeg is (≈600 s lege block-time → afsluiten). De wrapper start
claude -p opnieuw zodra hij eindigt, met exponentiële backoff bij
fouten.
Wat zit waar
| Bestand | Doel |
|---|---|
Dockerfile |
Ubuntu 22.04 + Node 22 + Claude Code + scrum4me-mcp + scripts |
docker-compose.yml |
Service-definitie, volumes, env-file, restart-policy, limits |
package.json |
Npm-dependencies van de runner zelf (alleen scrum4me-mcp pin) |
mcp-config.json |
Claude Code MCP-config (verwijst stdio naar scrum4me-mcp) |
CLAUDE.md |
Agent-rol-instructies, auto-geladen door claude -p |
bin/entrypoint.sh |
Container-startup: dirs, health-server, daemon-loop |
bin/run-agent.sh |
Daemon-loop met backoff, exit-code-routing en state-writes |
bin/check-tokens.sh |
Pre-flight: API-token, OAuth-token, DB-bereikbaarheid |
bin/job-prepare.sh |
Per-job: bare-fetch + clone-via-reference naar /tmp/job-<id> |
bin/job-cleanup.sh |
Per-job: logs naar /var/log, working tree weg |
bin/health-server.js |
HTTP-endpoint op 8080 dat state.json en marker-files leest |
bin/rotate-logs.sh |
Compress/cleanup van oude .log-bestanden |
.env.example |
Alle env-vars met uitleg |
Vereisten op de NAS
- Container Station 2+ (Docker compose v2)
- Drie shares aangemaakt:
/share/Agent/cache,/share/Agent/logs,/share/Agent/state - Of één share
/share/Agentwaaronder de drie subdirs vallen - Internet-uitgang naar
api.anthropic.com,github.com, je Neon-host,registry.npmjs.org
Deploy
# 1. Op je werkstation: token's regelen
# a. CLAUDE_CODE_OAUTH_TOKEN → draai `claude setup-token` (browser-flow)
# b. SCRUM4ME_TOKEN → log in als de dedicated agent-user in
# Scrum4Me, /settings/tokens, label "NAS-runner"
# c. DATABASE_URL/DIRECT_URL → Neon dashboard
# d. GH_TOKEN → github.com → Settings → Developer settings →
# Personal access tokens → Fine-grained.
# Repository access op madhura68/Scrum4Me +
# madhura68/scrum4me-mcp; Permissions:
# Contents (RW), Pull requests (RW),
# Metadata (R). Wordt gebruikt voor clone,
# push en `gh pr create` (auto_pr).
# 2. Repo op de NAS plaatsen
ssh admin@nas
cd /share/Agent
git clone https://github.com/<jij>/scrum4me-agent-runner.git
cd scrum4me-agent-runner
# 3. Env aanmaken
cp .env.example .env
chmod 600 .env
vi .env # vul alle waarden in
# 4. Build + start
docker compose build
docker compose up -d
# 5. Verifiëren
curl http://nas.local:8080/health
docker compose logs -f
Updaten (handmatig, bewust)
SCRUM4ME_TOKEN of CLAUDE_CODE_OAUTH_TOKEN rouleer je via een rebuild:
cd /share/Agent/scrum4me-agent-runner
git pull
vi .env # nieuwe waarden
docker compose build # nieuwe scrum4me-mcp-versie als dat veranderd is
docker compose up -d
Dezelfde flow voor schema-drift in scrum4me-mcp: pin een nieuwe
MCP_GIT_REF in .env of in docker-compose.yml, rebuild.
Wijzigingen in docker-compose.yml (volumes, tmpfs, env_file, ports)
Let op:
docker compose restartherstart alleen het proces in de bestaande container met de oude config. Wijzigingen in volumes, tmpfs-mounts, env_file of ports worden daarmee niet doorgevoerd.
Gebruik altijd --force-recreate als je docker-compose.yml is veranderd:
docker compose up -d --force-recreate agent
Verifieer daarna dat /var/cache op de NAS-overlay staat en niet op tmpfs:
docker exec scrum4me-agent df -h /var/cache
# Verwacht: Filesystem op /dev/mapper/cachedev* of een NAS-share
# Fout: tmpfs 16M ... (dan is force-recreate niet uitgevoerd)
Health-endpoint
GET http://<nas>:8080/health retourneert:
{
"status": "running", // running | idle | unhealthy | token-expired
"lastBatchAt": "2026-05-01T12:34:56Z",
"lastBatchExit": 0,
"consecutiveFailures": 0,
"tokenStatus": { "anthropic": "ok", "scrum4me": "ok", "db": "ok" }
}
HTTP-status: 200 als running/idle, 503 bij token-expired of als de
laatste heartbeat ouder is dan 5 minuten.
Filesystem-grenzen
De agent-user heeft geen SSH-keys en geen toegang tot andere shares dan
/share/Agent/*. Wel een ~/.git-credentials met de GH_TOKEN voor
HTTPS-clone/push (zie volgende sectie) — die token is scoped tot de twee
configured repos en mag worden gerouleerd door rebuild + redeploy.
Repo bootstrap (clone-on-start)
Bij elke container-start runt bin/repo-bootstrap.sh (als de
agent-user, ná drop-privileges) en zet zo'n setup neer:
- Configureert git's credential-helper met
GH_TOKENzodatgit clone/pushnaarhttps://github.com/...zonder prompt werkt. - Voor elke repo in
GH_PRECLONE_REPOS(komma-gescheiden owner/name):- Bestaat
~/Projects/<name>/.gital? →git fetch origin --prune - Anders → fresh
git clone
- Bestaat
Daarna vindt scrum4me-mcp's resolveRepoRoot (in wait_for_job) de
clone via z'n convention-fallback ~/Projects/<name>/.git. Worktrees
voor jobs landen vervolgens onder ~/.scrum4me-agent-worktrees/<jobId>/
zodat de hoofd-clone niet wordt aangeraakt.
Push gaat over dezelfde token: git push -u origin feat/story-<id>
slaagt zonder prompt. gh pr create (voor producten met auto_pr=true)
gebruikt dezelfde GH_TOKEN via de gh CLI's standaard env-detect.
Bekende grenzen
- Eén actieve job tegelijk. De wrapper-loop is sequentieel. Voor
parallellisme zou je meerdere containers met dezelfde
SCRUM4ME_TOKENkunnen draaien —wait_for_jobgebruiktFOR UPDATE SKIP LOCKEDdus dat is veilig op DB-niveau, maar dan moet je jenode_modules-cache per container scheiden. - OAuth-token: 1 jaar geldig. Bij verloop schrijft de wrapper een
TOKEN_EXPIRED-marker en wordt de containerunhealthy. Geen auto-rotatie. npm installper job kost op een N5095 ~30–60 s per Next.js-clone, óók met de pnpm-store. Voor zeer kleine fixes is dat de dominante factor. Kan later vervangen worden door een persistente warm-node_modulesper repo als dat een knelpunt wordt.