Architecture Decision Record · 0030

Worktree-basierte parallele Loop-Pipeline — Dual-Mode (in-session + detached)

proposed

TL;DR. Multi-Branch-Arbeit am SOLCOM-Repo geht ab sofort über git worktree mit eigener DDEV-Site pro Worktree (DDEV-Blog-Pattern: name:-Key raus aus committed Config, Auto-Name nach Verzeichnis, DB via ddev snapshot restore). Zwei Run-Modes nebeneinander: (1) In-Session via Claude-Code built-in Agent(isolation: "worktree", run_in_background: true) für kurze Sub-Agent-Loops aus einer Master-Session; (2) Detached via neuem Skill solcom-worktree mit git worktree add + nohup claude --print für langlebige autonome Loops, die das Ende der Master-Session überleben. Shared zwischen beiden Modes: Mini-Skill solcom-ddev-bootstrap kapselt Snapshot-Restore + ddev start im aktuellen Verzeichnis.

Kontext

Bisherige Annahme (aufgezeichnet in Auto-Memory project_sandcastle_host_only_architecture, datiert auf einen geplanten, aber nicht realisierten .sandcastle/-Orchestrator): Branches werden direkt im Hauptverzeichnis ausgecheckt, AFK-Pipeline läuft sequenziell ein Issue zur Zeit, kein git worktree. Begründung damals: .ddev/config.yaml trägt name: solcom, ein paralleler Worktree würde mit demselben Project-Namen die Eltern-Instance abschießen (Port-/Container-Konflikt). Außerdem: vor MVP-Go-Live 2026-07-08 ist eine schlanke Pipeline wichtiger als Container-Isolation.

Beide Annahmen sind heute überholt:

  • Der DDEV-Blog-Eintrag „git worktree contributor training" (veröffentlicht 2025-09) zeigt: wird der name:-Key aus .ddev/config.yaml entfernt, benennt sich jede DDEV-Site automatisch nach ihrem Verzeichnis. Der Port-Konflikt verschwindet — Primary solcom/ bleibt solcom.ddev.site, Worktree ../solcom-worktrees/P2503-127/ wird solcom-worktrees-p2503-127.ddev.site.
  • Claude Code hat seit dem aktuellen Release ein eingebautes Worktree-Feature: Agent(isolation: "worktree", run_in_background: true) erzeugt automatisch einen temporären Worktree für einen Sub-Agent und räumt ihn auf, wenn der Sub-Agent keine Änderungen produziert hat. Sub-Agents können parallel zur Master-Session laufen.
  • Marc will mehrere Loops parallel im Hintergrund laufen lassen (/implement für mehrere Stories, auto-review-fix als Watcher, ralph-loop als generischer Driver). Sequenzielle Pipeline limitiert Throughput vor dem MVP-Termin.

Der Memory-Eintrag verweist zusätzlich auf zwei Files (docs/adr/0028-host-only-sandcastle-orchestrator.html, docs/plans/2026-05-12-001-sandcastle-host-only-pipeline.html), die im Repo nicht existieren. Das .sandcastle/-Verzeichnis fehlt ebenfalls. Die alte Architektur war also Skizze, nie Code — die Wende ist Greenfield, keine Migration.

Die zentrale Frage ist nicht, ob Worktrees genutzt werden (das ist gesetzt), sondern wer den Worktree-Lifecycle steuert: das Claude-Code-Built-in für kurze Sub-Agent-Loops, oder ein eigenständiger Skill, der OS-Background-Prozesse spawnt und Master-Session-Lebenszyklen überlebt? Beide Pattern haben unterschiedliche Use Cases; eine Single-Mode-Wahl bricht den jeweils anderen.

Entscheidung

Dual-Mode-Architektur. In-Session via Claude-Code Agent(isolation: "worktree", run_in_background: true); Detached via neuem Skill solcom-worktree mit git worktree add + nohup claude --print. Geteilte Infrastruktur: name:-Key raus aus committed .ddev/config.yaml und Mini-Skill solcom-ddev-bootstrap (Snapshot-Restore + ddev start).

1 — Geteilte Infrastruktur

  • .ddev/config.yaml: Zeile name: solcom wird entfernt. Committed-State. Primary solcom/ bekommt Auto-Name solcom (Verzeichnisname identisch), URL bleibt solcom.ddev.site. Jeder zukünftige Worktree-Ordner trägt seinen eigenen Auto-Name.
  • Worktree-Layout: peer zum Repo unter ../solcom-worktrees/<branch-or-issue-key>/. Nicht inside des Repos — DDEV-Nesting würde sonst kollidieren.
  • Mini-Skill solcom-ddev-bootstrap: kapselt ddev snapshot restore latest + ddev start + ddev drush cr im aktuellen Arbeitsverzeichnis. Idempotent (zweiter Aufruf: no-op). Wird von beiden Modes aufgerufen, nicht vom Worktree-Lifecycle selbst.
  • Snapshot-Pflege: Primary erzeugt periodisch (manuell oder via Hook) ddev snapshot --name latest-primary. Worktrees restoren diesen Snapshot. Stale-Risk wird akzeptiert — Worktree-Loops laufen typischerweise sub-stündig, Snapshot-Refresh-Kadenz ist Marc-Workflow-Detail.

2 — Mode A: In-Session (Claude-Code-Built-in)

Use Case: Marc orchestriert aus einer Master-Session mehrere parallele Sub-Agents, die jeweils einen Loop in ihrem eigenen Worktree fahren. Lebenszyklus an die Master-Session gebunden.

  • Aufruf: Agent(subagent_type: "general-purpose" | ..., isolation: "worktree", run_in_background: true, prompt: "/implement P2503-127") innerhalb der Master-Session.
  • Claude Code erstellt den Worktree, der Sub-Agent läuft drin. Sub-Agent ruft als ersten Step solcom-ddev-bootstrap auf, wenn der Loop DDEV braucht.
  • Cleanup: automatisch durch Claude Code, wenn der Sub-Agent keine Änderungen produziert hat. Bei Änderungen: Worktree + Branch werden zurückgegeben, Marc entscheidet manuell (Cleanup oder PR-Pickup).
  • Konsumenten (Skills, die diesen Mode nutzen sollen): /implement, auto-review-fix, solcom-review in ihrer typischen sub-stündigen Variante. Skill-Anpassung erfolgt in separater Story.

3 — Mode B: Detached (Skill solcom-worktree)

Use Case: autonome Hintergrund-Loops, die die Master-Session überleben sollen — Sandcastle-AFK-Pipeline parallelisiert, Multi-Stunden-ralph-loop, persistente Watcher.

  • Skill solcom-worktree mit drei Sub-Workflows:
    • spawn <key> <skill-or-prompt>: git worktree add ../solcom-worktrees/<key>solcom-ddev-bootstrap im Worktree → nohup claude --print --output-format stream-json --dangerously-skip-permissions "<prompt>" > .worktree-logs/<key>.log 2>&1 & → PID festhalten in .worktree-logs/<key>.pid.
    • list: alle Worktrees aus git worktree list kreuzen mit den PID-Files; pro Eintrag: PID alive? letzte Log-Zeile? Branch-Status?
    • cleanup <key>: Prozess via PID killen → ddev delete -O im Worktree → git worktree remove --force ../solcom-worktrees/<key> → Log-Datei archivieren.
  • Logs landen in .worktree-logs/ im Primary-Repo (gitignored, dort sammelt Marc Telemetrie).
  • Master-Session-Ende killt den Prozess nicht (nohup + Detachment). Marc muss cleanup explizit aufrufen — bewusste Mehrarbeit gegen versehentliche Hintergrund-Zombies.

4 — Mode-Wahl

KriteriumIn-Session (A)Detached (B)
Loop-Dauer≤ ~60 minMulti-Stunden, langfristig
Master-Session-Bindungendet mit Masterüberlebt Master
Cleanupautomatisch bei No-Changeexplizit via Skill-cleanup
Mechanik-Aufwandbuilt-in, kein Custom-CodeCustom-Skill
TelemetrieMaster-Session-OutputLog-Dateien
Defaultjaopt-in

5 — Story-Gate ready-for-agent

Die bisherige Beschränkung „Stories mit DB-/Drupal-Runtime-Bedarf dürfen nicht ready-for-agent" (Memory) fällt weg. Mit eigener DDEV-Site pro Worktree kann der Agent drush-Calls, Kernel-/Functional-Tests und Migrations laufen lassen. Die Memory-Repair-Task aktualisiert diese Regel.

Rejected alternatives

Topology

  • Pure In-Session-Mode (kein Detached-Skill): Sub-Agents sterben mit der Master-Session. Langlebige Loops (Sandcastle-AFK-Watcher, Multi-Stunden-Bauten) sind damit nicht möglich. Außerdem teilen sich alle Sub-Agents die Master-Session-API-Quota und das Master-Session-Context-Limit indirekt über die Tool-Result-Pipes.
  • Pure Detached-Mode (kein Built-in): Verzichtet auf Claude-Code-Gratis-Infrastruktur. Jeder Skill, der kurz mal in einem Worktree Sub-Agent-Logik laufen lassen will, müsste den Custom-Skill aufrufen statt einfach Agent(isolation: "worktree"). Mehr Code, mehr Wartung, kein klarer Vorteil für den Sub-Stunden-Use-Case.
  • Mehrere Repo-Clones statt Worktrees: Disk-Multiplikator (volle .git-Kopie pro Clone), kein gemeinsamer Object-Store, Branch-Synchronisation manuell. Ist genau das, was Worktrees lösen.
  • Worktrees inside Repo (./worktrees/<key>/): DDEV-Config-Nesting (Worktree sieht das Primary-.ddev/ als „Parent"-Config), Risiko von versehentlicher Verschachtelung in Composer/Drush-Pfaden. Peer-Layout ist Blog-konform und semantisch sauber.

DDEV-Naming-Strategie

  • Per-Worktree .ddev/config.local.yaml-Override (committed-Config behält name: solcom): Vorteil: kein Touch der committed Config. Nachteil: jeder Worktree braucht einen manuellen Setup-Step, der die lokale Override schreibt — und vergisst Marc das, kollidiert die Worktree-Site mit der Primary-Site. Auto-Naming via Blog-Pattern ist robuster.
  • Hardcoded name: solcom-${SLUG} in .ddev/config.local.yaml: Variante des obigen, gleiche Schwäche. Wird zudem nicht-deterministisch beim Verzeichnis-Rename.

DB-Initialisierung

  • Empty-DB + drupal:recipe-unpack + Recipe-Apply pro Worktree: 3–5 min Setup-Zeit, plus kein editorial Content (kein Pages-, Vacancy-, sf_publication-Stand). Reproduzierbar, aber für Build-/Review-Loops praktisch unbrauchbar — Stories erwarten realistische Daten.
  • Shared remote DB (Mittwald-Dev oder dedizierter shared Container): Disk-Vorteil, aber Race-Conditions zwischen parallelen Worktrees (zwei Loops drush cr auf demselben Cache, zwei Migrations gegeneinander). Bricht das Worktree-Isolations-Versprechen.
  • Hybrid Snapshot-default + --empty-Flag: später ergänzbar, wenn ein konkreter Use Case auftaucht (z.B. Recipe-Reset-Tests). Im MVP nicht eingebaut, kein Verlust.

Background-Prozess-Spawn (Detached-Mode)

  • tmux-Session-Pattern: jeder Loop läuft in einem benannten tmux-Pane. Vorteil: interaktiv reattach-bar, Output-Streaming. Nachteil: zusätzliche Dependency (tmux nicht in jedem Setup), Pane-Lifecycle separat zu pflegen, Cleanup komplizierter. nohup + Log-Datei ist robuster und dependency-frei.
  • launchctl-Plist pro Worktree: macOS-native Daemon-Verwaltung. Schwergewichtig (Plist-Generierung, launchctl bootstrap), wenig portabel falls Marc später eine Linux-VM nutzt. Nicht den Aufwand wert.
  • Docker-/DDEV-Container-basierter Claude-Runner: Claude läuft in einem Container, der dann beendet wird wenn fertig. Containerisierung von Claude Code ist möglich, aber die Vorteile (Reproducibility, Cleanup) gegenüber nohup sind im single-host Marc-Setup gering, der Overhead deutlich.

Concurrency-Strategie

  • Strikte Obergrenze (z.B. max 3 Worktrees) im Skill solcom-worktree hardcoded: schützt vor RAM-Eskalation, ist aber für Marcs Workflow zu restriktiv. Concurrency-Pacing bleibt Marc-Verantwortung; list zeigt Lauf-Status, Marc reguliert manuell.
  • Job-Queue mit Worker-Pool: zentrale Queue, N Worker pollen Issues. Bricht den Skill-First-Ansatz und addiert Persistenz-Schicht (Queue-Store, Lock-Mgmt). Out-of-Scope für die MVP-Mechanik.

Consequences

Was muss man wissen

  • Committed-Config-Change: .ddev/config.yaml verliert name: solcom. Alle Devs im Team müssen nach Pull lokal ddev poweroff && ddev start ausführen — die DDEV-Site wird neu provisioniert (Auto-Name gleich „solcom" wenn das Verzeichnis so heißt). Onboarding-Doku in README.md / CLAUDE.md entsprechend ergänzen.
  • Disk-Impact: pro Worktree DB-Snapshot (~hundert MB bis >1 GB je nach Content-Volumen) + web/sites/default/files (mehrere GB bei Bedarf). 5 parallele Worktrees ≈ >25 GB. Marc muss Mac-Disk im Auge behalten; bei Ressourcenknappheit greift Cleanup zuerst.
  • RAM-Impact: jede DDEV-Site bringt ~500 MB – 1 GB DB-Container + ~500 MB Web-Container. Auf einem 32-GB-Mac sind 3–4 parallele Sites realistisch; mehr drückt das System in Swap.
  • Story-Gate-Erweiterung: ready-for-agent umfasst ab jetzt auch DB-/Drupal-Runtime-Stories. Triage-Label-Doku (docs/agents/triage-labels.html) entsprechend updaten.
  • Detached-Prozess-Hygiene: nohup-Prozesse überleben Terminal-Schließen und Reboot-Erinnerungs-Frage (macOS Login-Reload). Marc muss solcom-worktree cleanup aufrufen, sonst sammeln sich Zombies. list-Sub-Workflow ist die Reality-Check-Schnittstelle.
  • Master-Session-Lifecycle für Mode A: Agent(run_in_background: true)-Sub-Agents enden, wenn die Master-Session endet. Wer das nicht will, muss explizit Detached-Mode wählen.
  • Token-Verbrauch im Detached-Mode: claude --print --dangerously-skip-permissions läuft autonom — Bauen, Reviewen, Iterieren ohne menschliche Bremse. Token-Eskalation ist real, Kosten-Monitoring ist eine offene Folge-Story (nicht in dieser ADR adressiert).

Bewusste Restrisiken

  • DDEV-Snapshot-Drift: Snapshot ist statisch, Primary lebt weiter. Worktree-Loops sehen alten Stand, wenn Primary inzwischen migriert / Recipes appliziert / Content angelegt hat. Mitigation: Snapshot-Refresh-Routine (manuell oder Marc-Cron); Snapshot-Name-Konvention macht das Datum sichtbar.
  • Branch-Konvention-Drift: Bisher feature/<JIRA-KEY>. Worktrees brauchen einen Branch-Namen; Auto-Generierung im spawn-Sub-Workflow setzt agent/<key>-<slug> oder feature/<key>? Skill-Detail, default feature/<key> bleibt repo-konvention-konform.
  • Parallele Composer-/NPM-Installs: zwei Worktrees, die gleichzeitig composer install oder npm install laufen lassen, sind unproblematisch (jeder Worktree hat eigene vendor/ bzw. node_modules/), aber: Composer-Cache und NPM-Cache sind global. Cache-Lock-Konflikte werden in der Praxis selten ein Problem, sind aber theoretisch möglich.
  • Git-Worktree-Branch-Lock: ein Branch kann zu einer Zeit nur in einem Worktree ausgecheckt sein. spawn mit bereits-aktivem Branch schlägt fehl; Skill muss Branch-Konflikt sauber abfangen und reporten.
  • Memory-Fallout: Auto-Memory project_sandcastle_host_only_architecture wird invertiert (siehe Memory-Repair-Task). Wenn die Memory nicht repariert wird, geben zukünftige Sessions weiterhin „kein Worktree"-Empfehlungen.

Folge-Stories (nicht in diesem ADR)

  • Anpassung der Skills /implement, auto-review-fix, solcom-review auf Worktree-Sub-Agent-Aufruf (Mode A).
  • Cost-Monitoring / Token-Budget für Detached-Mode-Loops.
  • Snapshot-Refresh-Automation (z.B. ddev snapshot --name latest-primary als post-cim-Hook).
  • Telemetrie-Dashboard für laufende Background-Loops (HTML-Report aus .worktree-logs/).

Customer-Lens-Reviewer: N/A — Dev-Infrastruktur

Die solcom-adr-workflow-Konvention macht den customer-lens-reviewer-Aufruf bei Komponenten-Topologie-/Architektur-Entscheidungen mit ≥2 Optionen zur Pflicht. Die drei festgeschriebenen Stakeholder-Linsen — Figma-Designer:innen, Drupal-Site-Builder:innen, Theme-Entwickler:innen — orientieren sich an UI-/Frontend-Artefakten.

Diese Entscheidung betrifft keinen Frontend-Touchpoint: kein SDC, kein Canvas, kein Twig, keine Brand-Tokens. Es geht um Dev-Workflow-Infrastruktur (Git-Worktree-Topologie, DDEV-Site-Naming, Background-Prozess-Lifecycle). Die Stakeholder dieser Entscheidung ist Marc als Drupal-Dev, nicht Designer:innen oder Site-Builder:innen.

Konsequenz: Customer-lens-Konsultation entfällt. Begründung wird hier explizit notiert.