No description
Find a file
Madhura68 3f10d92ece chore: bump to v0.2.0 — rebuild voor entity-codes-required
scrum4me-mcp main bevat nu auto-generatie van PBI/Story/Task codes
(PBI-N, ST-001, T-N). Rebuild triggert zodat de NAS-runner de
bijgewerkte MCP-server oppikt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 16:20:47 +02:00
bin feat(ST-mmuwreer): add check_queue_empty stap + allowedTools 2026-05-03 19:37:43 +02:00
etc fix: lokale Docker build werkend krijgen 2026-05-02 19:18:35 +02:00
.env.example fix: cross-host compat — idempotent groupadd, quoted env value, ignore tarballs 2026-05-03 20:28:16 +02:00
.gitattributes fix: lokale Docker build werkend krijgen 2026-05-02 19:18:35 +02:00
.gitignore fix: cross-host compat — idempotent groupadd, quoted env value, ignore tarballs 2026-05-03 20:28:16 +02:00
CLAUDE.md feat(ST-mmuwreer): add check_queue_empty stap + allowedTools 2026-05-03 19:37:43 +02:00
docker-compose.yml initial: NAS agent runner setup 2026-05-02 15:43:59 +02:00
Dockerfile fix: cross-host compat — idempotent groupadd, quoted env value, ignore tarballs 2026-05-03 20:28:16 +02:00
mcp-config.json initial: NAS agent runner setup 2026-05-02 15:43:59 +02:00
package.json chore: bump to v0.2.0 — rebuild voor entity-codes-required 2026-05-04 16:20:47 +02:00
README.md docs(ST-mmuwreer): README deploy-procedure force-recreate na compose-wijziging 2026-05-03 18:23:06 +02:00

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/Agent waaronder 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 restart herstart 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:

  1. Configureert git's credential-helper met GH_TOKEN zodat git clone/push naar https://github.com/... zonder prompt werkt.
  2. Voor elke repo in GH_PRECLONE_REPOS (komma-gescheiden owner/name):
    • Bestaat ~/Projects/<name>/.git al? → git fetch origin --prune
    • Anders → fresh git clone

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_TOKEN kunnen draaien — wait_for_job gebruikt FOR UPDATE SKIP LOCKED dus dat is veilig op DB-niveau, maar dan moet je je node_modules-cache per container scheiden.
  • OAuth-token: 1 jaar geldig. Bij verloop schrijft de wrapper een TOKEN_EXPIRED-marker en wordt de container unhealthy. Geen auto-rotatie.
  • npm install per job kost op een N5095 ~3060 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_modules per repo als dat een knelpunt wordt.