| .env.example | ||
| .gitignore | ||
| _lib.sh | ||
| check-tokens.sh | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| entrypoint.sh | ||
| health-server.js | ||
| job-cleanup.sh | ||
| job-prepare.sh | ||
| mcp-config.json | ||
| package.json | ||
| README.md | ||
| rotate-logs.sh | ||
| run-agent.sh | ||
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
# 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.
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, geen ~/.gitconfig met push-credentials,
en geen toegang tot andere shares dan /share/Agent/*. Commits worden
lokaal in de per-job clone gemaakt; pushen gebeurt door jou op je
werkstation na review (CLAUDE.md regel: "git push is altijd expliciet").
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.