Files
Masterarbeit/.claude/skills/run-experiment/SKILL.md
T
Christoph SchwörerandClaude Opus 5 28e927013b LM-Studio-Matrix mit gemma-4-e4b: zwoelf Laeufe und vier Befunde
Skill 12.0.0 bis 12.2.0 und Adapter 2.0.0 bis 2.2.0.

Werkzeugfreigabe: Die Shell-Rechte des OpenCode-Adapters sind jetzt eine
Denylist wie beim Claude-Adapter statt einer Allowlist. Ausloeser war der
erste Qwen-Lauf, dessen einzige beide Werkzeugaufrufe an einer Pipeline
scheiterten. Kontrolltest belegt beide Richtungen: Get-ChildItem | Format-Table
laeuft durch, rm wird verweigert, die Datei bleibt bestehen.

Lokaler Betrieb: Der Preflight entlaedt alle Modelle vor jedem Lauf und laedt
den Kontext auf das Modellmaximum. Gemessen waren zuvor beide Modelle
gleichzeitig geladen - 168 MiB frei von 16,3 GB. Qwen 27B passt auf dieser
Karte nicht und wurde durch qwen3.5-9b ersetzt, in Q4_K_M wie Gemma.

Messinstrument: analyse-anforderungen.py erkennt Feldnamen in Markdown-
Fettschrift und Modulpraefixe in IDs. Der erste lokale Lauf mit Artefakten
wurde sonst mit null Anforderungen gezaehlt statt mit neun. Regressionsprobe
an vier Claude-Laeufen unveraendert.

Befunde: Der Standard-Ausgabeblock, mit dem Claude sieben Artefakte erzeugt,
liefert bei gemma-4-e4b null von sechs Laeufen ein Ergebnis - das Modell loest
den Pfad relativ zum Arbeitsverzeichnis auf. Ein Lauf startete alle sieben
vorgesehenen Rollen und lieferte neun formkonforme Anforderungen. Ein anderer
erzeugte sieben richtig benannte Dateien ohne eine einzige formkonforme
Anforderung. Und eine Shell-Umleitung schrieb an der Denylist vorbei in den
eingefrorenen Snapshot - gefunden vom Vorher/Nachher-Vergleich, nicht von der
Regel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 11:02:04 +02:00

1451 lines
118 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: run-experiment
description: Führt einen Versuchs-Prompt aus einer Prompt-Datei als messbaren Headless-Lauf mit Claude Code, Codex CLI oder OpenCode (TensorX oder lokales LM Studio) aus und schreibt ein Messprotokoll mit Start-/Endzeit, Modell, Tokenverbrauch und weiteren Metriken. Verwenden bei "/run-experiment <Pfad-zur-Prompt-Datei>" oder wenn der User einen Versuch/ein Experiment ausführen und tracken will.
argument-hint: <Pfad zur Prompt-Datei> <Root-Verzeichnis>
version: 12.2.0
---
# RunExperiment – Versuchslauf mit Messprotokoll
Du führst einen wissenschaftlichen Versuchslauf für die Masterarbeit aus. Der Prompt aus der
übergebenen Datei wird als **separater Headless-Lauf** über den zum Modell passenden
Werkzeugadapter ausgeführt, damit Tokenverbrauch und Modell maschinenlesbar erfasst werden. Anschließend
schreibst du ein Messprotokoll neben die Prompt-Datei.
## Parameter
- **Prompt** (Pflicht): Pfad zur Prompt-Datei (Markdown). Erstes Argument in `$ARGUMENTS`.
- **Root** (Pflicht): Verzeichnis, das als Root des Versuchslaufs dient. Zweites Argument.
Es ist die Wurzel der zu analysierenden Codebasis und wird nur GELESEN. Der Codex-Adapter
erstellt daraus ein isoliertes temporäres Arbeitsabbild; andere Adapter starten
unmittelbar mit diesem Root als Arbeitsverzeichnis. Fehlt das Argument: den
User danach fragen und NICHT stillschweigend das aktuelle Verzeichnis verwenden. Vor dem
Lauf prüfen, dass das Verzeichnis existiert; sonst abbrechen und den User informieren.
- **Modell** (Pflicht, **kein Default**): Wird **vor jedem Lauf beim User erfragt** – siehe
Schritt 4 im Ablauf. Nennt der User das Modell bereits im Aufruf („… mit Opus 5"), diese
Angabe übernehmen und **nicht** erneut fragen. Ohne Angabe niemals stillschweigend ein
Modell wählen: Das Modell ist eine unabhängige Variable der Versuchsreihe.
- **Agentenmodus** (Pflicht, **kein Default**): Wird wie das Modell **vor jedem Lauf beim User
erfragt** – siehe Schritt 5 im Ablauf. Bestimmt, ob und welche Subagenten der Lauf einsetzen
darf. Drei Modi: `solo`, `builtin`, `custom` (Details in Schritt 5).
- **Effort** (Pflicht, **kein Default**): Denkaufwand des Modells. Wird wie Modell und
Agentenmodus **vor jedem Lauf beim User erfragt** – siehe Schritt 6.
Alle Ausgaben des Laufs (Ergebnisdateien, Messprotokoll, Rohdaten) landen in einem **neuen
Laufverzeichnis neben der Prompt-Datei**, niemals im Root-Verzeichnis:
```
<Verzeichnis der Prompt-Datei>\
<NN>_Prompt.md
<Iteration>\<ModellID>\<Agentenmodus>\<Effort>\
<NN>_Lauf_<yyyy-MM-dd_HHmmss>_v<skillversion>-<id4>\
Protokoll.md
RawResult.json (adapterübergreifend normalisierte Messdaten)
RawEvents.jsonl (nur Codex: unveränderter JSONL-Ereignisstrom)
Stderr.log
_meta\ (Laufsteuerung: combined_prompt.md, before.txt, after.txt, Zeitstempel,
subagenten.md/.json, anforderungen.md/.json)
Ergebnisse\ (NUR vom Prompt erzeugte Artefakte, z. B. StRS.md, SyRS.md, ...)
```
**Die unabhängigen Variablen bilden die Ordnerebenen, nicht den Dateinamen:**
- `<Iteration>` – `Iteration 1`, `Iteration 2`, … fortlaufend. Die Ebene hält Blöcke auseinander, die
unter unterschiedlichem Stand des Versuchsaufbaus **oder des Untersuchungsgegenstands**
entstanden sind. **Vor dem Anlegen die höchste vorhandene Iteration ermitteln** und entscheiden,
ob der Lauf dazugehört oder eine neue Iteration eröffnet – im Zweifel den User fragen.
Eine neue Iteration ist zu eröffnen, wenn sich etwas ändert, das Läufe **nicht mehr poolbar**
macht: der Codebasis-Snapshot, die Prompt-Version, die Werkzeugkonfiguration oder eine
MAJOR-Version dieses Skills. Reine PATCH- und MINOR-Änderungen begründen keine neue Iteration.
**Abgrenzung zur Prompt-Version.** `<Iteration>` meint die *Versuchs*iteration – eine Menge
poolbarer Läufe. Die Fassung der Prompt-Datei heißt seit Version 4.4.0 durchgängig
**Prompt-Version** (`01_Prompt.md` = Prompt-Version 01). Beides fällt nicht zusammen:
Iteration 2 und Iteration 3 laufen beide unter Prompt-Version 02 und unterscheiden sich nur
im Untersuchungsgegenstand.
Die Ebene hieß bis Version 4.3.0 `<Versuchstag>` mit Werten `Tag 1`, `Tag 2`, …. Der Name band
sie an das Kalenderdatum, obwohl sie die Vergleichbarkeit abbildet: Iteration 2 und Iteration 3
entstanden am selben Tag, unterscheiden sich aber im Untersuchungsgegenstand (Iteration 3
enthält den DB-Schema-Dump `SSMS_DB_SCHEMA.sql`, Iteration 2 nicht). Maßgeblich ist die
Vergleichbarkeit, nicht das Datum.
- `<ModellID>` – die an `--model` übergebene ID, z. B. `claude-sonnet-5`, `claude-opus-5`,
`claude-fable-5`
- `<Agentenmodus>` – `solo`, `builtin` oder `custom`
- `<Effort>` – `low`, `medium`, `high`, `xhigh` oder `max`
Der Verzeichnisname trägt nur noch, was den einzelnen Lauf identifiziert:
- `<NN>` – Nummernpräfix der Prompt-Datei (z. B. `01` bei `01_Prompt.md`)
- `<yyyy-MM-dd_HHmmss>` – Startzeit **sekundengenau**
- `<skillversion>` – Version dieses Skills zum Startzeitpunkt, z. B. `v3.10.0`
- `<id4>` – 4 Hexstellen Zufall; garantiert Eindeutigkeit auch bei gleichzeitigem Start
**Warum Ordnerebenen statt Namensbestandteile:** Der Name wuchs mit jeder neuen unabhängigen
Variable weiter und war mit fünf Bestandteilen kaum noch lesbar. Als Ordnerebenen sind die
Bedingungen navigierbar, je Zelle abzählbar (`ls "<tag>/<modell>/<modus>/<effort>" | wc -l` ergibt
unmittelbar die Anzahl der Messpunkte) und beim Hinzufügen einer weiteren Variable erweiterbar,
ohne bestehende Namen zu brechen. Die Angaben sind **redundant zum Protokoll** – bei Widerspruch
gilt das Protokoll.
**Jeder Lauf ist vollständig selbstenthalten.** Sämtliche Steuerdateien liegen in `_meta\`
**innerhalb** des Laufverzeichnisses – nicht im gemeinsamen Scratchpad. Das ist Voraussetzung
für parallele Läufe (siehe „Parallele Läufe") und archiviert nebenbei den exakt gesendeten
Prompt je Lauf. `Ergebnisse\` enthält ausschließlich die vom Prompt geforderten Artefakte.
## Aufbau dieses Skills
Der Skill ist zweigeteilt:
- **`## Prozess`** beschreibt werkzeugneutral, **was** je Lauf zu tun und zu erheben ist. Dieser
Teil gilt unabhängig davon, mit welchem LLM oder welcher CLI gearbeitet wird.
- **`## Werkzeugadapter`** beschreibt **wie** das mit einem konkreten Werkzeug umgesetzt wird.
Ausgearbeitet sind Adapter für Claude Code, Codex CLI und OpenCode über TensorX. Der frühere
direkte Python-API-Adapter bleibt nur als ausdrücklich gewählter Legacy-Fallback erhalten. Konkrete Flags und Rohfelder sind
ausschließlich dem gewählten Adapterabschnitt zu entnehmen.
**Arbeitsteilung mit der Prompt-Datei:** Der Prompt enthält ausschließlich die *Analyseanweisung*
und ist damit von jedem LLM verwendbar. Alles Werkzeug- und Ablaufbezogene – verfügbare
Werkzeuge, Ausgabeverzeichnis, Messgrößen – steuert dieser Skill bei und dokumentiert es im
Protokoll. Die Prompt-Datei wird dabei **nie verändert**; ergänzende Blöcke werden nur an den
per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen").
## Prozess
### 1. Vorbereitung
1. **Prompt-Datei bestimmen und lesen.** Nennt der User eine Datei ausdrücklich, gilt diese.
Andernfalls im Versuchsordner die **höchste Prompt-Versionsnummer** wählen (`02_Prompt.md` vor
`01_Prompt.md`) und die getroffene Wahl im Abschlussbericht nennen. Existiert die Datei
nicht: abbrechen und den User informieren.
Die gewählte Datei wird mit Pfad **und** SHA-256 ins Protokoll übernommen. Ältere
Prompt-Versionen bleiben unverändert liegen – sie sind der Beleg dafür, unter welcher Fassung
frühere Läufe entstanden sind, und dürfen nicht nachträglich angepasst werden.
2. Aus dem Metadaten-Block der Prompt-Datei (falls vorhanden) Versuch/Prompt-Version übernehmen.
3. **Werkzeugadapter bestimmen und CLI-Pfad auflösen.** Die Modell-ID entscheidet eindeutig:
`claude-*` verwendet Claude Code, OpenAI-IDs wie `gpt-*` oder `o*` verwenden Codex CLI,
`z-ai/*`, `qwen/qwen3.8-flash-next` und `moonshotai/*` verwenden OpenCode mit
`--provider tensorx`, `google/gemma-4-e4b` und `qwen/qwen3.5-9b` verwenden OpenCode mit
`--provider lmstudio` gegen den lokalen LM-Studio-Server.
Keine Modell-ID an eine CLI übergeben, die sie nicht unterstützt.
**Achtung Präfixkollision:** `qwen/qwen3.8-flash-next` läuft remote über TensorX,
`qwen/qwen3.5-9b` lokal über LM Studio. Das Präfix `qwen/` allein entscheidet **nicht** –
maßgeblich ist die vollständige Modell-ID.
Unter Windows liegt `claude` in der Regel **nicht im PATH**. Erst
`Get-Command claude -ErrorAction SilentlyContinue` versuchen; schlägt das fehl, auf die
VSCode-Extension zurückfallen und die höchste Versionsnummer wählen:
```powershell
$claude = (Get-Command claude -ErrorAction SilentlyContinue).Source
if (-not $claude) {
$claude = Get-ChildItem "$env:USERPROFILE\.vscode\extensions\anthropic.claude-code-*-win32-x64\resources\native-binary\claude.exe" |
Sort-Object FullName | Select-Object -Last 1 -ExpandProperty FullName
}
```
Für OpenAI-Modelle entsprechend `Get-Command codex -ErrorAction SilentlyContinue` verwenden;
als Fallback die höchste VS-Code-Extension unter
`$env:USERPROFILE\.vscode\extensions\openai.chatgpt-*-win32-x64\bin\windows-x86_64\codex.exe`
wählen. Findet sich die benötigte ausführbare Datei nicht: abbrechen und den User informieren.
Adapter und aufgelösten Pfad fürs Protokoll festhalten.
Für TensorX-Modelle `Get-Command opencode -ErrorAction SilentlyContinue` versuchen. Der
Adapter löst zusätzlich die globale npm-Installation unter
`$env:APPDATA\npm\node_modules\opencode-ai\bin\opencode.exe` auf. Fehlt OpenCode, mit
`npm install -g opencode-ai` installieren; fehlt die TensorX-Anmeldung, `opencode auth login`
ausführen. Niemals dafür Cline-Konfigurationsdateien oder Cline-Credentials lesen.
4. **Modell beim User erfragen.** Hat der User das Modell nicht bereits im Aufruf genannt,
**immer** per `AskUserQuestion` nachfragen – auch dann, wenn frühere Läufe derselben
Versuchsreihe ein bestimmtes Modell verwendet haben. Es gibt bewusst keinen Default.
Als Optionen die vollen Modell-IDs anbieten, nicht die Kurzformen. Nur aktuell dokumentierte
und von der installierten CLI angebotene IDs aufnehmen:
| Modell-ID | Einordnung |
|---|---|
| `claude-opus-5` | stärkstes Modell, teuerster Lauf |
| `claude-sonnet-5` | 1-Mio.-Kontextfenster, für große Codebasen meist ausreichend |
| `claude-fable-5` | schnell und günstig |
| `claude-haiku-4-5-20251001` | günstigstes Modell |
| OpenAI-Modell-ID | Einordnung |
|---|---|
| `gpt-5.6-sol` | stärkstes Modell der GPT-5.6-Familie, 1.050.000 Kontexttokens |
| `gpt-5.6-terra` | ausgewogenes Verhältnis aus Leistung und Verbrauch |
| `gpt-5.6-luna` | schnelle und günstige GPT-5.6-Variante |
| TensorX-Modell-ID | Einordnung |
|---|---|
| `z-ai/glm-5.2` | Z.AI GLM 5.2 über TensorX-Gateway |
| `z-ai/glm-5.3-flash` | Z.AI GLM 5.3 Flash über TensorX-Gateway |
| `qwen/qwen3.8-flash-next` | Qwen 3.8 Flash Next über TensorX-Gateway |
| `moonshotai/kimi-k3` | Moonshot Kimi K3 – 1-Mio.-Kontext, 2,8T Parameter |
| Lokale Modell-ID (LM Studio) | Einordnung |
|---|---|
| `google/gemma-4-e4b` | Gemma 4 E4B, 7,5B Parameter, lokal über LM Studio |
| `qwen/qwen3.5-9b` | Qwen 3.5 9B, lokal über LM Studio (Q4_K_M wie Gemma) |
Lokale IDs nur anbieten, wenn `lms ls` das Modell als heruntergeladen ausweist. Fehlt es,
den User auf `lms get <ID>` hinweisen und den Download **nicht** ungefragt starten – es
sind mehrere Gigabyte. Lokale Läufe sind eine eigene Versuchsbedingung und nicht mit
Cloud-Läufen poolbar: anderes Kontextfenster, quantisierte Gewichte, kein Effort.
In der Frage den letzten verwendeten Stand nennen, damit der User bewusst wechseln oder
bewusst wiederholen kann (z. B. „Lauf 3 lief mit `claude-sonnet-5`, 11.516.200 Tokens, 23:40").
**Niemals die Kurzformen `opus`, `sonnet`, `fable` an `--model` übergeben.** Sie lösen auf
das jeweils neueste Modell auf und verschieben sich über die Zeit – damit wäre die
Versuchsbedingung nicht reproduzierbar. Für ein 1-Mio.-Token-Fenster die Variante mit
`[1m]`-Suffix wählen (z. B. `claude-opus-5[1m]`).
Für OpenAI ebenfalls keine Familienaliase wie `gpt-5.6` verwenden. Die gewünschte Variante
vollständig binden, beispielsweise `gpt-5.6-sol`. Vor einem Lauf die Modell-ID gegen die
aktuelle offizielle OpenAI-Modelldokumentation und, soweit verfügbar, den lokalen
`codex debug models`-Katalog prüfen.
Weicht das gewählte Modell vom letzten Lauf ab, im Protokoll unter „Anmerkungen" als
geänderte Versuchsbedingung vermerken und in der Änderungshistorie des Skills ergänzen.
5. **Agentenmodus beim User erfragen.** Wie beim Modell: kein Default, immer nachfragen, wenn
der User den Modus nicht bereits im Aufruf genannt hat. Der Modus ist die zentrale
Unterscheidung zwischen den Versuchen der Arbeit.
| Modus | Versuch | Bedeutung |
|---|---|---|
| `solo` | **V1** | Ein Thread, ein Kontext. Der Lauf darf **keine** Subagenten starten. |
| `builtin` | **V1b** | Eingebaute Subagenten erlaubt (`Explore`, `general-purpose`). |
| `custom` | **V2** | Nur **vordefinierte, geprompte Agenten** aus `--agents`. |
Flags je Modus – zusätzlich zur Standardkonfiguration aus Abschnitt 2:
```powershell
switch ($modus) {
'solo' { $agentFlags = @('--disallowedTools','Task','Agent','Workflow') }
'builtin' { $agentFlags = @() }
'custom' { $agentFlags = @('--agents', (Get-Content $agentDatei -Raw)) }
}
```
**Warum `Task`, `Agent` *und* `Workflow`:** Welchen Namen das Subagenten-Werkzeug in der
jeweiligen CLI-Version trägt, ist nicht dokumentiert – im Binary von 2.1.245 kommen `Task`
und `Agent` vor; beide zu sperren ist unschädlich, ein nicht existierender Name läuft ins
Leere. `Workflow` orchestriert ebenfalls Subagenten und muss mitgesperrt werden: Im
Verifikationstest (2026-08-25, CLI 2.1.245) wich der Agent nach der Sperre von `Task`/`Agent`
**genau darauf** aus. Verifiziert wurden `spawned: 0`, die Antwort „KEINE SUBAGENTEN
MOEGLICH" und ein Denial auf `Workflow`. Kontrolle nach dem Lauf: `subagent_stats.spawned` **muss** im
Modus `solo` genau `0` sein – andernfalls hat die Sperre nicht gegriffen und der Lauf ist
als Fehlmessung zu kennzeichnen.
Im Modus `solo` sind **Permission-Denials auf `Task`/`Agent` erwartbar**, wenn der Agent zu
delegieren versucht. Sie sind im Protokoll getrennt von sonstigen Denials auszuweisen und
**nicht** als Einschränkung der Werkzeugkonfiguration zu werten – sie sind die Bedingung selbst.
Für `custom` (V2): Die Agentendefinitionen liegen als JSON-Datei **neben der Prompt-Datei**
(z. B. `Versuche/Versuch_02/02_Agents.json`), **nicht** im Codebasis-Snapshot. Sie werden wie
der Prompt per SHA-256 gehasht und im Protokoll geführt. So bleibt der eingefrorene Snapshot
unangetastet und die Agentenkonfiguration ist eine dokumentierte, versionierte
Versuchsbedingung. Format siehe `claude --help` zu `--agents`.
**Delegationstiefe – nur Modus `custom`, beim User erfragen.** Zwei Fassungen der
Agentendatei stehen bereit; sie unterscheiden sich ausschließlich darin, ob die Rollen selbst
delegieren dürfen:
| Fassung | Rollen dürfen delegieren | Wirkung |
|---|---|---|
| `verschachtelt` | ja | Rollen erben über `--agents` alle Werkzeuge einschließlich `Task` und starten eigene Subagenten. `max_depth` > 1. |
| `unverschachtelt` | nein | Jede Rolle führt ihren Auftrag selbst aus. Nur der Hauptagent delegiert; `max_depth` soll 1 sein. |
**Warum das eine eigene Option ist.** Im ersten V2-Lauf (`v9.1.0-0c39`) entfielen 26 von
86 Subagenten auf Starts durch Subagenten, `max_depth` = 3. Das war nicht beabsichtigt – der
`iso29148-orchestrator` ist ausdrücklich als nicht-delegierende Rolle entworfen – und es
verteuert den Lauf erheblich, weil jede zusätzliche Ebene ihren Kontext erneut liest. Es
verwischt außerdem die Zuständigkeit: Ein von einer Rolle gestarteter Subagent ist keiner
Teilaufgabe der Bindungstabelle mehr zuzuordnen.
Die Fassung ist **nicht** technisch erzwungen, sondern in jedem Rollenprompt als Abschnitt
*Keine Weiterdelegation* formuliert. Kontrolle nach dem Lauf: `subagent_stats.max_depth` und
`spawned_by_subagents`. Beide gehen ins Protokoll; bei `unverschachtelt` und
`spawned_by_subagents` > 0 ist die Bedingung verletzt und im Protokoll zu kennzeichnen.
Beide Fassungen liegen neben der Prompt-Datei und werden per SHA-256 geführt; die gewählte
Fassung ist über ihren Hash eindeutig belegt.
**Codex-Adapter ab Version 5.0.0:** ausschließlich `solo` ist freigegeben und wird doppelt
mit `--disable multi_agent` sowie `-c agents.enabled=false` erzwungen. Bei `builtin` oder
`custom` abbrechen und mitteilen, dass dieser Adaptermodus noch nicht verifiziert ist. Eine
stillschweigende Annäherung an Claude-Agentendefinitionen wäre keine reproduzierbare Bedingung.
6. **Effort beim User erfragen.** Kein Default, immer nachfragen, wenn der User die Stufe nicht
bereits im Aufruf genannt hat. Stufen: `low`, `medium`, `high`, `xhigh`, `max`.
Übergabe bei Claude per `--effort <stufe>`, bei Codex per
`-c model_reasoning_effort=\"<stufe>\"`.
In der Frage den bisherigen Stand nennen. **Alle Läufe bis einschließlich Lauf B liefen auf
`high`** – geerbt aus der Sitzungseinstellung, nicht bewusst gesetzt; der Wert wurde
nachträglich aus den Transkripten belegt. Wer die Stufe wechselt, ändert die
Versuchsbedingung und macht den Lauf mit den bisherigen unvergleichbar.
**Claude-spezifisch:** Der Effort ist aus dessen `RawResult.json` nicht rekonstruierbar. Das Ergebnisobjekt
enthält kein `effort`-Feld. Nur das Session-Transkript hält ihn je Nachricht fest
(`"effort": "high"`). Fehlt er im Protokoll, ist die Bedingung nachträglich allein über das
Transkript belegbar – und das nur, solange die Session persistiert ist.
7. Messgrößen VOR dem Lauf erfassen (PowerShell):
- Startzeit: `Get-Date -Format o`
- SHA-256 der Prompt-Datei: `(Get-FileHash <datei> -Algorithm SHA256).Hash`
- Adapter und CLI-Version: `& $cli --version`
- Git-Zustand des Root-Verzeichnisses: `git -C <root> rev-parse HEAD` und
`git -C <repo> status --porcelain -- <pfad-des-roots>` (dirty ja/nein).
**Immer pfadskopiert prüfen.** Liegt die Codebasis als Unterverzeichnis im Arbeitsrepo
(seit Commit `f045b99a` der Fall, davor ein eigenes Repo per Gitlink), meldet ein
unskopiertes `git -C <root> status --porcelain` den Status des **gesamten** Arbeitsrepos –
einschließlich aller Versuchsordner. Der Vorher/Nachher-Vergleich schlägt dann bei jedem
Lauf falsch an.
- Git-Commit dieses Repos (Stand der Prompt-Datei): `git rev-parse HEAD`
8. **Kollisionsfreies Laufverzeichnis anlegen.** Sekundengenau, mit Agentenmodus und
Zufalls-ID; bei Namenskollision neu würfeln:
```powershell
# Bedingungen werden Ordnerebenen, nicht Namensbestandteile
# Iteration ermitteln: hoechste vorhandene Iteration, sonst 'Iteration 1'
$iterationen = Get-ChildItem "<promptverzeichnis>" -Directory -Filter 'Iteration *' -EA SilentlyContinue |
Sort-Object { [int]($_.Name -replace '\D','') }
$iteration = if ($iterationen) { $iterationen[-1].Name } else { 'Iteration 1' }
$zelle = Join-Path (Join-Path (Join-Path $iteration $modell) $modus) $effort
$skillVer = 'v10.0.2' # entspricht version: im Frontmatter dieses Skills
do {
$id4 = '{0:x4}' -f (Get-Random -Maximum 65536)
$lauf = Join-Path "<promptverzeichnis>\$zelle" "<NN>_Lauf_$(Get-Date -Format 'yyyy-MM-dd_HHmmss')_${skillVer}-$id4"
} while (Test-Path $lauf)
New-Item -ItemType Directory -Force "$lauf\Ergebnisse" | Out-Null
New-Item -ItemType Directory -Force "$lauf\_meta" | Out-Null
```
9. Dateibestand des Roots vor dem Lauf sichern – das Root soll unverändert bleiben, jede
Änderung dort ist eine Auffälligkeit fürs Protokoll. Ziel ist `_meta\` **im Laufverzeichnis**,
nicht der gemeinsame Scratchpad:
```powershell
# Leerwertsicher: -Value erzwingt das Schreiben auch bei sauberem Root.
Set-Content -Path "$lauf\_meta\before.txt" `
-Value (git -C <repo> status --porcelain -- <pfad-des-roots> | Out-String)
```
**Nicht** `git ... | Set-Content <datei>` verwenden: Ist die Pipeline leer (sauberes Root),
schreibt `Set-Content` die Datei nicht und lässt einen alten Inhalt stehen. Der
Vorher/Nachher-Vergleich meldet dann eine Abweichung, die es nicht gibt.
(Kein Git-Repo? Dann rekursive Dateiliste mit LastWriteTime nach `_meta\` schreiben.)
**Isolation: Die Codebasis ist ein eingefrorener Snapshot ohne KI-Konfigurationen.**
Der Untersuchungsgegenstand wurde einmalig um alle KI-Assistenz-Konfigurationen bereinigt
(`.claude/`, `.agents/`, `.codex/`, `.cursor/`, `.serena/`, `.opencode/`, `.memory-mcp/`,
`CLAUDE.md`, `AGENTS.md`, `.mcp.json`, `opencode.json`, `skills-lock.json`, `.coderabbit.yaml`,
`.aiignore`, `.claudeignore`, `.cursorignore`), und das GitHub-Remote ist entkoppelt. Die
Bedingung „keine Agentendateien, keine MCP-Server" ist damit eine **Eigenschaft des Snapshots**
und muss nicht pro Lauf hergestellt werden. Damit entfällt jeder destruktive Vorbereitungsschritt.
Vor jedem Lauf den Snapshot **prüfen** – nicht bereinigen:
```powershell
$muster = 'CLAUDE\.md|AGENTS\.md|GEMINI\.md|\.mcp\.json|opencode\.json|skills-lock\.json|' +
'\.coderabbit\.yaml|\.aiignore|\.claudeignore|\.cursorignore|copilot-instructions|' +
'^\.(claude|agents|codex|cursor|serena|opencode|memory-mcp|continue|gemini|windsurf)/'
$fund = git -C <root> ls-files | Select-String -Pattern $muster
if ($fund) { "WARNUNG: KI-Konfigurationen im Snapshot:"; $fund }
```
**Den Regex nicht „vereinfachen".** Die Anker (`(^|/)`, `$`, führender Punkt) sind notwendig.
`Select-String` arbeitet in PowerShell standardmäßig **case-insensitiv**; ein gelockertes Muster
schlägt deshalb auf echte Produktdateien an. In dieser Codebasis etwa:
`src/backend/Centron.BL/ArtificialIntelligence/Chat/ClaudeCodeChatModelClient.cs` und
`GoogleGeminiChatModelClient.cs` – Bestandteile des KI-Assistenz-Features der ERP-Suite und
damit legitimer Untersuchungsgegenstand, keine Werkzeugkonfiguration. Bei einem Treffer immer
erst die konkreten Pfade ansehen, bevor Alarm ausgelöst wird.
Findet die Prüfung echte Treffer: **abbrechen und den User informieren – nicht eigenmächtig löschen.**
Der Codebasis-Snapshot ist ein Messinstrument. Eine Änderung daran ist eine Änderung der
Versuchsbedingung und macht Läufe untereinander unvergleichbar; sie gehört abgestimmt,
committet und im Protokoll dokumentiert.
**Das Remote ist entkoppelt.** Im Codebasis-Repo wird niemals gepusht, gefetcht oder ein Remote
hinzugefügt. Der Snapshot bleibt eingefroren.
### 2. Prompt zusammenstellen
Dem Prompttext werden **zwei** Blöcke angehängt. Die Prompt-Datei selbst bleibt unverändert –
ergänzt wird nur der per stdin übergebene Text, und dieser wird als `_meta\combined_prompt.md`
archiviert.
**Block 1 – Werkzeugkontext.** Der Prompt trifft bewusst keine Annahme darüber, welche Werkzeuge
zur Verfügung stehen. Diese Bedingung stellt der Versuchsaufbau bei; sie ist die untersuchte
unabhängige Variable und gehört deshalb hierher, nicht in die Analyseanweisung:
```
### Werkzeugkontext (vom Versuchsaufbau vorgegeben)
Für diesen Lauf stehen zur Verfügung: <Auflistung>.
Nicht verfügbar sind: <Auflistung>.
Triff keine Annahmen über weitere Werkzeuge und versuche nicht, nicht verfügbare
Werkzeuge zu ersetzen.
```
Der Inhalt richtet sich nach dem Agentenmodus:
| Modus | „Zur Verfügung" | „Nicht verfügbar" |
|---|---|---|
| `solo` | Lesen, Suchen und Ausführen von Kommandozeilenbefehlen im Arbeitsverzeichnis | Subagenten, spezialisierte Agentenrollen, externe Werkzeugserver |
| `builtin` | zusätzlich die werkzeugeigenen Subagenten | spezialisierte Agentenrollen aus Konfigurationsdateien, externe Werkzeugserver |
| `custom` | zusätzlich die beigestellten Agentenrollen (namentlich nennen) | externe Werkzeugserver, sofern nicht Teil des Versuchs |
**Zuständigkeitsbindung – nur Modus `custom`.** Der Prompt verlangt im Abschnitt
*Arbeitsteilung*, dass eine Teilaufgabe von dem dafür vorgesehenen Bearbeiter ausgeführt wird,
nennt aber bewusst keine Rolle – sonst wäre er nicht mehr für `solo` und `builtin` gültig. Welche
Rolle wofür zuständig ist, gehört deshalb in den Werkzeugkontext. Block 1 wird bei `custom` um
folgende Tabelle ergänzt, mit den Rollennamen aus der `--agents`-Datei:
```
Für die folgenden Teilaufgaben stehen vorgesehene Bearbeiter bereit. Führe diese
Teilaufgaben durch den jeweils genannten Bearbeiter aus, nicht selbst:
| Teilaufgabe | Vorgesehener Bearbeiter |
|---|---|
| Modulinventar (Schritt 0) | modulinventar |
| Faktenerhebung zu einem Modulausschnitt (Schritte 2 bis 4) | faktenermittler |
| Formulierung der StRS-Anforderungen | strs-autor |
| Formulierung der SyRS-Anforderungen | syrs-autor |
| Formulierung der SwRS-Anforderungen samt Konsolidierungsprüfung | swrs-autor |
| Prüfung ausgewiesener Belege gegen die Codebasis | belegpruefer |
| Prüfung des Gesamtbestands an den Nahtstellen der Ausschnitte | iso29148-orchestrator |
| Konsistenzcheck des fertigen Anforderungssatzes (Abschnitt Abschluss) | konsistenzpruefer |
Zuschnitt, Anzahl der Aufträge je Bearbeiter, deren Reihenfolge und die Tiefe
entscheidest du. Gebunden ist allein, wer eine Teilaufgabe ausführt. Die Bearbeiter
lesen nur; das Anlegen der Ergebnisdateien und die Übernahme ihrer Rückmeldungen
bleiben deine Aufgabe.
```
Die Tabelle wird aus der `--agents`-Datei abgeleitet und **nicht** freihändig formuliert: Jede
Zeile nennt eine Rolle, die dort definiert ist, und jede definierte Rolle kommt vor. Kommt eine
Rolle in der Datei vor, aber nicht in der Tabelle, ist sie im Lauf faktisch nicht vorgesehen –
das ist zulässig, aber im Protokoll zu vermerken.
**Warum die Bindung keine Strategieinjektion ist.** Sie ist vor dem Lauf statisch deklariert,
wird als Teil von `_meta\combined_prompt.md` archiviert und ist über den SHA-256 der
`--agents`-Datei versioniert. Damit unterscheidet sie sich grundlegend von den in Version 9.0.0
entfernten **laufzeitabhängigen** Eingriffen des TensorX-Adapters (Subagenten-Limit,
turnabhängige Schreib-Erinnerungen), die den Lauf abhängig von seinem eigenen Verlauf umsteuerten
und Lauf 34 unpoolbar machten. Was in `custom` weiterhin **nicht** vorgegeben wird: Anzahl der
Aufrufe, Zerlegungstiefe, Reihenfolge und Turn-Anzahl.
**Die Bindung ist nicht technisch erzwungen.** Sie wirkt über den Prompt; ob der Agent sie
einhält, ist selbst eine Messgröße. Nach dem Lauf ist sie zu prüfen – siehe Protokollfeld
*Zuständigkeitsbindung*.
**Block 2 – Ausgabeverzeichnis.** Damit die Ergebnisse im Laufverzeichnis landen und nicht in
der Codebasis:
```
### Ausgabeverzeichnis (überschreibt anderslautende Pfadangaben oben)
Schreibe ALLE zu erzeugenden Ergebnisdateien in das Verzeichnis
`<absoluter Pfad zum Laufverzeichnis>\Ergebnisse\`.
Verändere keine Dateien im Arbeitsverzeichnis (der analysierten Codebasis).
```
**Codex-Abweichung bei Block 2.** Der Codex-Adapter läuft in einer isolierten Arbeitskopie
im System-Temp-Verzeichnis mit `workspace-write`. Das originale Root liegt außerhalb dieses
Arbeitsbereichs und kann vom Agenten nicht erreicht werden. Trotz des technisch beschreibbaren
Abbilds darf der Agent keine Dateien verändern. Block 2 wird dort durch folgende strukturierte Rückgabeanweisung ersetzt;
die CLI erzwingt `codex-output-schema.json`, anschließend materialisiert
`normalise-codex-result.py` die Dateien:
```
### Ergebnisausgabe (überschreibt anderslautende Pfadangaben oben)
Verändere keine Dateien im Arbeitsverzeichnis. Gib alle geforderten Ergebnisdateien im vorgegebenen JSON-Schema zurück.
Jeder Eintrag in `files` enthält unter `path` einen relativen Pfad innerhalb von `Ergebnisse`
und unter `content` den vollständigen Dateiinhalt. `summary` enthält nur eine kurze Laufzusammenfassung.
```
### 3. Ausführung (Headless-Lauf)
Den Adapter anhand der Modell-ID wählen. Der folgende Aufruf ist **ausschließlich der
Claude-Code-Adapter**. Der Codex-Aufruf steht vollständig unter `Adapter: Codex CLI`, der
TensorX-Aufruf unter `Adapter: OpenCode`; beide dürfen nicht mit Claude-Flags vermischt werden.
Den kombinierten Prompt per stdin an `claude -p` übergeben. `--add-dir` gibt dem
Headless-Lauf Schreibrecht auf das Laufverzeichnis außerhalb seines Arbeitsverzeichnisses.
Versuchsläufe können lange dauern – **immer als Background-Task starten** (`run_in_background`),
nicht mit Foreground-Timeout arbeiten:
```powershell
$lauf = "<absoluter Pfad zum Laufverzeichnis>"
$prompt = (Get-Content "<prompt-datei>" -Raw) + "`n`n<Ausgabe-Anweisung>"
# Steuerdateien IMMER ins Laufverzeichnis - nie in den gemeinsamen Scratchpad (parallelfaehig)
Set-Content -Path "$lauf\_meta\combined_prompt.md" -Value $prompt -Encoding utf8
# Ohne diese Variable bricht der Headless-Modus nach 600 s ab, sobald noch
# Hintergrund-Subagenten laufen - und meldet dabei `is_error: false`.
$env:CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS = '0'
Set-Content -Path "$lauf\_meta\startzeit.txt" -Value (Get-Date -Format o)
# Schreibende und bauende Shell-Kommandos sperren – die Codebasis wird nur gelesen.
$denyBash = @(
'Bash(rm:*)','Bash(rmdir:*)','Bash(mv:*)','Bash(cp:*)','Bash(dd:*)',
'Bash(truncate:*)','Bash(chmod:*)','Bash(chown:*)','Bash(ln:*)','Bash(tee:*)',
'Bash(sed -i:*)','Bash(git checkout:*)','Bash(git restore:*)','Bash(git clean:*)',
'Bash(git reset:*)','Bash(git add:*)','Bash(git commit:*)','Bash(git push:*)',
'Bash(dotnet:*)','Bash(msbuild:*)','Bash(npm install:*)','Bash(nuget:*)'
)
$denyPs = @(
'PowerShell(Remove-Item:*)','PowerShell(Move-Item:*)','PowerShell(Copy-Item:*)',
'PowerShell(New-Item:*)','PowerShell(Set-Content:*)','PowerShell(Add-Content:*)',
'PowerShell(Clear-Content:*)','PowerShell(Out-File:*)','PowerShell(Set-ItemProperty:*)',
'PowerShell(dotnet:*)','PowerShell(msbuild:*)'
)
Set-Location "<root>"
Get-Content "$lauf\_meta\combined_prompt.md" -Raw |
& $claude -p `
--output-format json `
--safe-mode `
--strict-mcp-config `
--permission-mode acceptEdits `
--allowedTools "Bash" "PowerShell" `
--disallowedTools @($denyBash + $denyPs) `
--model <vom User gewaehlte Modell-ID aus Schritt 4> `
--effort <vom User gewaehlte Stufe aus Schritt 6> `
@agentFlags `
--add-dir "$lauf" `
2> "$lauf\Stderr.log" | Set-Content "$lauf\RawResult.json"
Set-Content -Path "$lauf\_meta\endzeit.txt" -Value (Get-Date -Format o)
```
Bedeutung der Flags:
| Flag | Zweck |
|---|---|
| `--safe-mode` | Isolation: CLAUDE.md, Skills, Plugins, Hooks, MCP-Server, Custom-Agenten, Commands und Output-Styles aus – ohne Dateieingriff. Auth, Modellwahl, eingebaute Tools und Permissions bleiben normal aktiv. **Nur in den Modi `solo` und `builtin` verwendbar** – siehe „Isolation je Agentenmodus". |
| `--strict-mcp-config` | zweite Absicherung gegen MCP-Server aus Projekt- oder User-Konfiguration |
| `--permission-mode acceptEdits` | Schreibrechte für die Ergebnisdateien im Laufverzeichnis |
| `--allowedTools "Bash" "PowerShell"` | **Shell-Zugriff ohne Rückfrage** – Standard seit Prompt-Version 02 |
| `--disallowedTools <Denylist>` | schreibende und bauende Kommandos gesperrt; Deny hat Vorrang vor Allow |
| `--add-dir "$lauf"` | Schreibziel außerhalb des Arbeitsverzeichnisses |
| `@agentFlags` | Agentenmodus aus Schritt 5: `solo` sperrt `Task`/`Agent`, `builtin` fügt nichts hinzu, `custom` übergibt `--agents` |
| `--effort <stufe>` | Denkaufwand aus Schritt 6; nicht aus `RawResult.json` rekonstruierbar, daher zwingend ins Protokoll |
| `--model <id>` | exakte Modellbindung aus Schritt 4; überschreibt die Modellpräferenz der User-Settings. Volle ID, keine Kurzform. |
Build-Kommandos (`dotnet`, `msbuild`, `nuget`, `npm install`) stehen bewusst auf der Denylist:
Sie würden `bin/`- und `obj/`-Artefakte im Root erzeugen und widersprechen der Methodenvorgabe
„statische Analyse, keine Ausführung".
**Grenzen der Denylist – ehrlich einordnen.** Pattern-Matching auf Shell-Kommandos ist nicht
lückenlos; Umleitungen, Pipes und verschachtelte Aufrufe lassen sich nicht vollständig abfangen.
Die belastbare Read-only-Garantie bleibt der Vorher/Nachher-Vergleich per
`git status --porcelain` aus Abschnitt 1 bzw. 3. Die Denylist senkt das Risiko, sie ersetzt die
Verifikation nicht.
**Isolation je Agentenmodus – `--safe-mode` ist nicht immer verwendbar.**
`--safe-mode` schaltet ausweislich der CLI-Hilfe „all customizations (CLAUDE.md, skills, plugins,
hooks, **MCP servers, custom commands and agents**, output styles, workflows, …)" ab. Damit
deaktiviert es genau das, was die Modi `custom` (V2) und der MCP-Einsatz (V3) untersuchen sollen.
Verifiziert am 2026-08-26 gegen CLI 2.1.246 mit identischem Aufruf, nur `--safe-mode` variiert:
| Konfiguration | `subagent_stats.spawned` | `by_type` |
|---|---:|---|
| mit `--safe-mode` | **0** | leer – der Agent meldet, die Rollen seien „nicht in der Agent-Registry registriert" |
| ohne `--safe-mode` | **2** | `{"modulinventar": 1, "konsistenzpruefer": 1}` |
Daraus folgt eine modusabhängige Isolation:
| Modus | Isolation |
|---|---|
| `solo`, `builtin` | `--safe-mode` + `--strict-mcp-config` (unverändert) |
| `custom`, sowie jeder Lauf mit `--mcp-config` | **kein** `--safe-mode`; stattdessen `--strict-mcp-config` und die Sperre `--disallowedTools Skill WebSearch WebFetch SlashCommand` |
Der gezielte Ersatz wurde am selben Tag gegengeprüft. Der Agent antwortete auf eine
Werkzeugabfrage: `VORGELADEN: NICHTS VORGELADEN` · `SKILLS: KEINE` · `WEB: KEIN WEBZUGRIFF` ·
alle acht Custom-Rollen verfügbar. Ohne die Sperre lädt die CLI **16 global installierte Skills**
(darunter `code-review`, `security-review`, `run`, `init`); `--setting-sources ''` unterdrückt sie
**nicht**.
**Was der Ersatz nicht abdeckt.** `--safe-mode` deaktiviert zusätzlich Plugins, Hooks und
Output-Styles. Die Sperre tut das nicht. Auf der Maschine, auf der die Versuchsreihe läuft, sind
davon keine konfiguriert (`~/.claude/settings.json` enthält nur `model` und
`agentPushNotifEnabled`, kein `hooks`; kein `plugins`- und kein `output-styles`-Verzeichnis) –
das ist jedoch eine Eigenschaft der Umgebung, keine Garantie. **Vor jedem Lauf im Modus `custom`
oder mit MCP ist deshalb zu prüfen:**
```powershell
$us = Join-Path $env:USERPROFILE '.claude\settings.json'
if (Test-Path $us) { (Get-Content $us -Raw | ConvertFrom-Json).PSObject.Properties.Name }
foreach ($d in 'plugins','output-styles','skills') {
$pfad = Join-Path $env:USERPROFILE ".claude\$d"
if (Test-Path $pfad) { "VORHANDEN: $d -> " + ((Get-ChildItem $pfad | Measure-Object).Count) + ' Eintraege' }
}
```
Treten Hooks, Plugins oder Output-Styles auf, ist der Lauf **nicht** isoliert und die Bedingung
im Protokoll als abweichend zu kennzeichnen.
**Unverändert bleibt:** `--safe-mode` hat die Modellwahl nie beeinflusst. Der Eintrag `model` in
den User-Settings (hier `opus[1m]`) kann Subagenten binden – das ist der bekannte Grund der zwei
dokumentierten Modellabweichungen und gilt mit wie ohne `--safe-mode`.
**`--safe-mode` bleibt als zweite Sicherung aktiv.** Die primäre Isolation leistet der
bereinigte Snapshot (Abschnitt 1); `--safe-mode` fängt zusätzlich alles ab, was von außerhalb
des Roots wirken könnte – User-Level-Settings, global installierte Skills, Plugins, Hooks.
Verifiziert am 2026-08-25 gegen Version 2.1.245, dokumentiert als Begründung für den
Snapshot-Ansatz:
- `--safe-mode` verhindert zuverlässig das **Vorladen** von `CLAUDE.md`/`AGENTS.md` als
Systemanweisung. Kontrolltest ohne Werkzeuge: mit `--safe-mode` antwortet der Agent
„NICHTS VORGELADEN", ohne das Flag zitiert er den Inhalt der `CLAUDE.md`.
- `--safe-mode` verhindert **nicht**, dass der Agent solche Dateien als gewöhnliche Dateien
**findet und liest**. Im Smoke-Test mit Shell-Zugriff hat er genau das getan und den Inhalt
von `AGENTS.md` und `CLAUDE.md` wiedergegeben.
Genau deshalb reicht `--safe-mode` allein bei gewährtem Shell-Zugriff nicht aus, und die
KI-Konfigurationen wurden aus dem Snapshot entfernt statt nur deaktiviert.
**Trust-Hinweis auf stderr.** Liegt im Root eine `.claude/settings.json` mit
`permissions.allow`-Einträgen, meldet die CLI auf stderr sinngemäß „Ignoring N permissions.allow
entries … this workspace has not been trusted". Im bereinigten Snapshot tritt das nicht mehr auf;
erscheint die Meldung dennoch, ist der Snapshot nicht sauber – Abschnitt 1 prüfen.
Regeln:
- Arbeitsverzeichnis des Befehls = **Root-Verzeichnis** des Versuchs; geschrieben wird
ausschließlich ins Laufverzeichnis.
- Treten unerwartete Permission-Denials auf, die den Lauf behindern: die Denylist prüfen und die
Abweichung im Protokoll dokumentieren – **nicht** pauschal alle Prüfungen per
`--dangerously-skip-permissions` abschalten. Eine Änderung der Werkzeugkonfiguration ist eine
Änderung der Versuchsbedingung und gehört mit dem User abgestimmt.
- Optional `--max-budget-usd <betrag>` als harte Obergrenze setzen (nur mit `-p` wirksam) und den
Wert im Protokoll notieren. Das Flag arbeitet nur in USD – die **berichtete** Messgröße bleibt
der Tokenverbrauch. Anhaltspunkt: Der bisher aufwendigste Lauf verbrauchte 55.167.397 Tokens.
- Nach Abschluss des Background-Tasks: Endzeit mit `Get-Date -Format o` erfassen.
### 4. Ergebnis auswerten
`RawResult.json` im Laufverzeichnis lesen und defensiv parsen. Beim Claude-Adapter ist dies die
unveränderte CLI-Antwort; beim Codex-Adapter erzeugt `normalise-codex-result.py` diese Datei aus
`RawEvents.jsonl` und `_meta\final_response.json`; beim OpenCode-Adapter erzeugt
`opencode-adapter.py` sie aus dem Sessionexport und `OpenCodeEvents.jsonl` gemäß der
OpenCode-Referenz. Relevante Claude-Felder:
| Feld | Bedeutung |
|---|---|
| `is_error`, `subtype` | Lauf erfolgreich oder abgebrochen |
| `duration_ms`, `duration_api_ms` | Gesamt- und reine API-Dauer |
| `num_turns` | Anzahl Agent-Turns (Proxy für Werkzeugaktivität) |
| `total_cost_usd` | Kosten in USD – **wird nicht berichtet**, bleibt aber als Rohwert in der Datei |
| `usage.input_tokens`, `usage.output_tokens` | Tokens **nur des Hauptagenten** |
| `usage.cache_creation_input_tokens`, `usage.cache_read_input_tokens` | Cache-Tokens (getrennt ausweisen!) |
| `modelUsage` bzw. `model` | tatsächlich eingesetzte Modell-ID(s), Verbrauch **inklusive aller Subagenten-Ebenen** |
| `permission_denials` | verweigerte Werkzeugaufrufe – Anzahl und Aufschlüsselung nach `tool_name` |
| `subagent_stats` | gestartete Subagenten: `spawned`, `by_type`, `completed`, `failed`, `max_depth` |
| `session_id` | für spätere Transcript-Analyse (`claude --resume <id>`) |
| `result` | Abschlusstext des Agenten |
**`modelUsage` erfasst alle Verschachtelungsebenen – empirisch verifiziert.** Kontrolltest am
2026-08-25 gegen CLI 2.1.245: Eine erzwungene Kaskade (`max_depth: 2`, `spawned: 2`,
`spawned_by_subagents: 1`), bei der ausschließlich der **Enkel-Agent auf Tiefe 2** arbeitete –
er las 15 große Quelldateien, der mittlere Agent reichte nur durch, der Hauptagent las nichts:
| | Tokens |
|---|---:|
| `usage` (nur Hauptagent, 1 Turn) | 49.103 |
| `modelUsage` (gesamt) | **1.689.288** |
| Differenz = Subagenten | 1.640.185 (Faktor 34,4) |
Da die gesamte Leseleistung auf Tiefe 2 stattfand, belegt die Differenz, dass **auch
Sub-Subagenten in `modelUsage` einfließen**. Für die berichtete Aufwandsgröße „Tokens gesamt"
ist `modelUsage` daher die vollständige und einzig richtige Quelle.
**Subagenten-Transkripte werden nicht separat persistiert.** Im Projektverzeichnis liegt je Lauf
genau **eine** `.jsonl`-Datei, und Subagenten-Nachrichten erscheinen darin nicht als Sidechain.
Ihr Verbrauch ist deshalb ausschließlich über `modelUsage` sichtbar, nicht über das Transkript –
und die Summe der Transkript-Nachrichten ist **kein** gültiges Verbrauchsmaß (sie zählt Cache-Reads
über Turns hinweg mehrfach).
Zwei Auswertungsfallen:
- **`usage` erfasst ausschließlich den Hauptagenten.** Für den abrechnungsrelevanten
Gesamtverbrauch ist `modelUsage` über alle Modell-IDs zu summieren. In Prompt-Version 01 standen
105.959 Output-Tokens in `usage` gegenüber 249.040 in `modelUsage`.
- **`duration_api_ms` kann die Wanduhrzeit übersteigen**, wenn Subagenten parallel laufen
(Prompt-Version 01: 47:05 API gegenüber 31:31 Wanduhr). Das ist kein Widerspruch, sondern die
Summe nebenläufiger Anfragen – im Protokoll entsprechend einordnen.
**Pflichtprüfung: tatsächlich eingesetzte Modelle.** Nach jedem Lauf die Schlüssel von
`modelUsage` gegen das angeforderte Modell abgleichen. Zulässig sind nur die angeforderte
Modell-ID und `claude-haiku-4-5-*` (interne Hilfsaufrufe, wenige tausend Tokens). Taucht ein
weiteres Modell auf, ist die **Versuchsbedingung verletzt** – der Lauf ist im Protokoll als
solcher zu kennzeichnen und für Modellvergleiche unbrauchbar.
**`--model` steuert nur den Hauptagenten, nicht zwingend die Subagenten.** Belegt am 2026-08-25
(CLI 2.1.245, Lauf `fable5_builtin_ehigh_v3.7.0-15db`): Mit `--model claude-fable-5` und Modus
`builtin` liefen die 13 Subagenten auf **`claude-opus-5[1m]`** – dem Standardmodell aus
`~/.claude/settings.json`. Auf sie entfielen 136,7 von 150,3 Mio. Tokens (91 %).
| Kombination | Läufe | Modelle in `modelUsage` |
|---|---:|---|
| Sonnet / Opus / Fable + `solo` | 12 | nur das angeforderte Modell (+ Haiku) |
| Sonnet + `builtin` | 11 | nur `claude-sonnet-5` (+ Haiku) |
| **Fable + `builtin`** | **1** | **Fable + `claude-opus-5[1m]`** |
Sonnet und Opus werden an die Subagenten durchgereicht, Fable nicht; Claude Code fällt dann auf
die Sitzungsvorgabe zurück. **`--safe-mode` verhindert das nicht** – es deaktiviert
Customizations, nicht die Modellwahl. Im Modus `solo` tritt das Problem nicht auf, weil es keine
Subagenten gibt.
Bei Modus `builtin` oder `custom` daher **nie** den Flag-Wert allein ins Protokoll schreiben,
sondern die Modelle aus `modelUsage` mit ihrem jeweiligen Anteil ausweisen.
**Pflichtprüfung: Hat der Lauf überhaupt etwas erzeugt?** `is_error` allein genügt **nicht**.
Ein Lauf kann `is_error: false`, `subtype: success` und `terminal_reason: completed` melden und
trotzdem **null Ergebnisdateien** hinterlassen. Belegt am 2026-08-26, Lauf
`Iteration 3/claude-opus-5/builtin/high/…160037_v4.5.0-116d`: Der Hauptagent startete zehn
Subagenten im Hintergrund, beendete seinen Turn und schrieb als Abschlusstext „Die Erhebung
läuft"; der Headless-Modus wartete 600 s und brach dann ab. `Stderr.log` enthielt
„Background tasks still running after 600s; terminating." Verbraucht waren zu diesem Zeitpunkt
**193,4 Mio. Tokens** – der teuerste Lauf der Reihe, ohne ein einziges Artefakt.
Nach jedem Lauf daher zwingend prüfen:
1. `Ergebnisse\` ist **nicht leer** und enthält die vom Prompt geforderten Dateien. Fehlen sie,
ist der Lauf **unabhängig von `is_error` als Fehlmessung zu kennzeichnen**.
2. `Stderr.log` enthält keine Abbruchmeldung. Die Datei ist im Normalfall 0 Byte groß.
Vorbeugend setzt der Ausführungsabschnitt `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0`, damit der
Lauf auf seine Hintergrund-Subagenten wartet, statt sie abzuschneiden.
**Subagenten-Ergebnisse kommen nicht als Werkzeugergebnis zurück.** Startet der Hauptagent einen
Subagenten im Hintergrund, liefert der Werkzeugaufruf sofort eine **Start-Quittung** von rund
1.093 Zeichen („Async agent launched successfully …") zurück, nicht die Befunde. Deren Länge ist
folglich **kein** Maß für den Ertrag des Subagenten. Die Quittung nennt einen Pfad
`<temp>\<session>\tasks\<agentId>.output`; diese Dateien werden zwar angelegt, bleiben aber
**leer** (geprüft an 33 Dateien aus zwei Läufen). Die Aussage, dass Subagenten-Transkripte nicht
auswertbar persistiert werden, gilt damit unverändert.
`permission_denials` ist eine **reguläre Messgröße** und immer auszuweisen, auch bei 0. Ein
hoher Wert bedeutet, dass die Werkzeugkonfiguration den Lauf eingeschränkt hat, und ist bei der
Interpretation der Ergebnisqualität zu berücksichtigen.
**Subagenten-Prompts sichern** (entfällt nur im Modus `solo`, dort gibt es keine). Die
vollständigen Prompts, mit denen der Hauptagent seine Subagenten beauftragt hat, stehen im
persistierten Session-Transkript unter
`%USERPROFILE%\.claude\projects\<projekt>\<session_id>.jsonl`. Das mitgelieferte Skript
extrahiert sie:
```powershell
python "<skillverzeichnis>\extract-subagenten.py" "<laufverzeichnis>"
```
Es schreibt `_meta\subagenten.md` (lesbar, mit vollständigem Prompt je Subagent) und
`_meta\subagenten.json` (maschinenlesbar). Erfasst werden Werkzeug (`Task`/`Agent`),
`subagent_type`, `description`, Hintergrund-Flag, Prompt-Länge und Länge des zurückgelieferten
Ergebnisses.
**Abgewiesene Starts sind keine Subagenten.** Erreicht der Hauptagent das
Nebenläufigkeitslimit (20 gleichzeitige Subagenten), erscheint der Aufruf im Transkript wie ein
regulärer Start, liefert aber nur die Absage „Concurrent subagent limit reached" zurück und zählt
**nicht** in `spawned`. Das Skript trennt beide Fälle und weist die Absagen getrennt aus; ohne
diese Trennung meldet der Abgleich eine Abweichung, die es nicht gibt (Lauf
`Iteration 3/…/125032_v4.4.0-fb24`: 21 gefundene Aufrufe gegenüber 13 erwarteten – die Differenz
waren 8 Absagen). Die Zahl der Absagen ist selbst eine Messgröße: Sie zeigt, wie viel stärker der
Agent parallelisieren wollte, als das Werkzeug zuließ.
**Plausibilitätskontrolle:** Das Skript vergleicht die Anzahl der **echten** Starts mit
`subagent_stats.spawned` minus `spawned_by_subagents`. Im Haupttranskript stehen nämlich nur die
**direkt** vom Hauptagenten gestarteten Subagenten – von Subagenten gestartete (`max_depth` > 1)
liegen in deren eigenen Transkripten. Bei Abweichung setzt das Skript eine Warnung in
`subagenten.md`; dann ist die Ursache zu klären, bevor die Prompts ausgewertet werden.
**Warum das erhoben wird:** In V1b bestimmt der Agent selbst, wie er die Analyse zerlegt – über
alle bisherigen Läufe schwankte das zwischen 0 und 14 Subagenten mit völlig unterschiedlichen
Zuschnitten. Diese selbstgewählte Zerlegung ist die dominierende Störgröße der Versuchsreihe.
Erst die protokollierten Prompts machen sie als Datum auswertbar statt nur als Zahl sichtbar.
Für V2 (`custom`) sind sie zusätzlich der Vergleichsmaßstab: Sie zeigen, wie der Agent ohne
Vorgabe delegiert – gegenüber den vorformulierten Agenten aus `--agents`.
**Anforderungen auswerten** – der inhaltliche Ertrag des Laufs, nicht nur sein Aufwand:
```powershell
python "<skillverzeichnis>\analyse-anforderungen.py" "<laufverzeichnis>"
```
Das Skript parst das im Prompt vorgegebene Blockformat (`ID:`, `Typ:`, `Belege:`, `Status:` …)
aus `Ergebnisse\StRS.md`, `SyRS.md` und `SwRS.md` und schreibt `_meta\anforderungen.md`
(fertiger Protokollabschnitt) sowie `_meta\anforderungen.json` (maschinenlesbar). Erhoben werden:
- **Verteilung** über die drei Ebenen
- **Anforderungstypen** (funktional, Sicherheit, Daten, Schnittstelle …)
- **Belegqualität**: Anzahl der Belege, Aufteilung `PRIMÄR`/`SEKUNDÄR`/`KONTEXT`, Median je
Anforderung, Anteil mit mindestens einem Primärbeleg
- **Status**: belegt, `[HYPOTHESE]`, Workaround, Konsolidierungskandidaten
**Die Ebene einer Anforderung wird nicht aus dem Dateinamen abgeleitet.** Maßgeblich ist das
Feld `Ebene:` des Blocks, hilfsweise das ID-Präfix, erst zuletzt die Datei. Agenten legen Blöcke
regelmäßig in der Datei einer anderen Ebene ab – im Lauf `Iteration 2/…/094250_v4.2.1-c69e` standen
12 StRS- und 5 SyRS-Blöcke in `SwRS.md`. Wird nach Datei gezählt, ist die Verteilungstabelle
falsch, obwohl die Gesamtzahl stimmt. Das Skript weist eine solche Fremdablage als eigene
Auffälligkeit unter der Verteilungstabelle aus; sie ist ein Befund zur **Set-Qualität**, weil die
Dreiteilung dann nicht mehr an der Dateistruktur ablesbar ist.
- **Regelkonformität** – geprüft wird gegen die Vorgaben des Prompts selbst:
Belegpflicht (jede Anforderung ≥ 1 Beleg), risikobasierte Priorisierung (Sicherheit,
Abrechnung, Berechtigungen brauchen `PRIMÄR` **oder** `[HYPOTHESE]`), Verifizierbarkeit
(Prüfidee vorhanden), Traceability (Tracelinks gesetzt)
**Zuordnung zum Evaluationsrahmen (Kap. 4.3).** Die erhobenen Kenngrößen decken die
**maschinell prüfbare** Hälfte der drei Qualitätsdimensionen ab; die Expertenbewertung nach
Likert-Skala bleibt davon unberührt und tritt daneben:
| Dimension (Kap. 4.3) | Maschinell erhoben | Bleibt der Expertenbewertung |
|---|---|---|
| **Statement-Qualität** | Belegqualität, Primärbelegquote, Anteil mit Prüfidee, Hypothesenanteil, Übernahmewürdigkeit | Eindeutigkeit der Formulierung, sachliche Korrektheit, Verständlichkeit |
| **Set-Qualität** | Verteilung über die Ebenen, Konsolidierungskandidaten, doppelte IDs, Anforderungen ohne Beleg | Vollständigkeit der Prozessabdeckung, Redundanzfreiheit im fachlichen Sinn |
| **Traceability-Qualität** | Anteil mit Tracelinks, Belegklassifikation, Tracelinks auf nicht existierende IDs | Auffindbarkeit des Belegs, Nachvollziehbarkeit der Ableitung |
**Warum das erhoben wird:** Die Anforderungsanzahl allein ist als Qualitätsmaß wertlos – sie
streute über Läufe gleicher Bedingung um Faktor 5,9. Belegdichte, Primärbelegquote und
Regelverstöße sind dagegen inhaltliche Größen und machen Läufe vergleichbar, deren Mengengerüst
weit auseinanderliegt. Die Regelkonformitätsprüfung deckt zudem auf, wo der Agent die eigenen
Vorgaben des Prompts verfehlt hat – ein Befund, der sich sonst nur durch Handlektüre ergäbe.
Der erzeugte Abschnitt wird **unverändert** als `## Gefundene Anforderungen` ins Protokoll
übernommen, zwischen `## Ergebnis` und den Vergleichs- bzw. Anmerkungsabschnitten.
Erzeugte Dateien: Inhalt von `<laufverzeichnis>\Ergebnisse\` auflisten. Zusätzlich prüfen,
ob das Root unverändert blieb: `git -C <repo> status --porcelain -- <pfad-des-roots>` – mit
**demselben Pfadfilter wie in Abschnitt 1** – gegen `before.txt`
vergleichen (Nachher-Stand nach `$lauf\_meta\after.txt`) – Abweichungen als Auffälligkeit
ins Protokoll. Beim Vergleich Zeilenenden
normalisieren (`before.txt` entsteht je nach Werkzeug mit CRLF, der Nachher-Stand mit LF),
sonst meldet ein naiver `diff` alle Zeilen als geändert.
### 5. Messprotokoll schreiben
Als `Protokoll.md` ins Laufverzeichnis, neben `RawResult.json` und `Stderr.log`.
Vorlage:
```markdown
# Messprotokoll – <Versuch> – Prompt-Version <NN>
## Lauf
- **Prompt-Datei:** <relativer Pfad>
- **Prompt-Version:** <Nummer aus dem Dateinamen; bei mehreren vorhandenen Fassungen begründen, warum diese gewählt wurde>
- **SHA-256 (Prompt):** <hash>
- **Startzeit:** <ISO 8601>
- **Endzeit:** <ISO 8601>
- **Dauer gesamt:** <hh:mm:ss> (API: <hh:mm:ss>)
- **Root-Verzeichnis:** <Pfad>
- **Codebasis-Commit:** <hash> (dirty: ja/nein)
- **Snapshot-Zustand:** <bereinigt von KI-Konfigurationen: ja/nein; Remote entkoppelt: ja/nein>
- **Prompt-Repo-Commit:** <hash>
## Werkzeugkonfiguration
- **Skill-Version:** <version aus dem Frontmatter dieses Skills>
- **Werkzeugadapter:** <Claude Code | Codex CLI>
- **CLI-Version:** <version>
- **CLI-Pfad:** <aufgelöster Pfad zur ausführbaren Datei>
- **Modell (angefordert):** <Wert von `--model`>
- **Modelle (tatsächlich eingesetzt):** <alle Schlüssel aus `modelUsage` mit Tokenanteil |
nicht erfasst: Codex-JSONL nennt das tatsächlich bediente Modell nicht>
- **Kontrolle Modell:** <bestanden | **verletzt**: nicht angefordertes Modell <ID> mit <N> Tokens |
nicht prüfbar: Codex-JSONL enthält keine tatsächliche Modell-ID>
- **Effort:** <low | medium | high | xhigh | max> (per `--effort` gesetzt; Gegenprobe im
Transkript-Feld `effort`)
- **Laufverzeichnis-ID:** `v<skillversion>-<id4>` aus dem Verzeichnisnamen
- **Ablage:** `<Iteration>/<ModellID>/<Agentenmodus>/<Effort>/`
- **Parallele Läufe:** <nein | ja: welche Laufverzeichnisse liefen zeitgleich>
- **Agentenmodus:** <solo (V1) | builtin (V1b) | custom (V2)>; bei `custom` zusätzlich
Pfad und SHA-256 der Agentendefinitionen
- **Kontextfenster:** <Tokens, z. B. aus `modelUsage.contextWindow`>
- **Sampling-Parameter:** <Temperatur u. a. | nicht steuerbar>
- **Nur bei lokalem Modellbetrieb:** Inferenz-Runtime samt Version, Quantisierungsstufe des
Modell-Builds
- **Permission-/Sandbox-Modus:** <acceptEdits | read-only / approval never | ...>
- **Toolfreigabe:** <adapterabhängige Flags wörtlich>
- **Isolationsmechanismus:** <adapterabhängige Flags wörtlich>
- **MCP-Server / Agentendateien:** <keine – aus dem Snapshot entfernt, zusätzlich --safe-mode |
Liste der Server mit Pfad und SHA-256 der `--mcp-config`-Datei; Pfad und SHA-256 der
`--agents`-Datei>
- **Delegationstiefe (nur `custom`):** <verschachtelt | unverschachtelt> gemäß gewählter
Agentendatei. Kontrolle: `subagent_stats.max_depth` und `spawned_by_subagents`. Bei
`unverschachtelt` muss `spawned_by_subagents` = 0 und `max_depth` = 1 sein; andernfalls hat
die Rollenvorgabe nicht gegriffen und der Lauf ist entsprechend zu kennzeichnen.
- **Zuständigkeitsbindung (nur `custom`):** je Rolle die Zahl der Aufrufe aus
`subagent_stats.by_type`; Rollen mit null Aufrufen namentlich nennen. Dazu der Abgleich mit
der Dokumentationspflicht aus dem `Analysebericht.md`: Welche Teilaufgabe hat der Hauptagent
entgegen der Bindung selbst ausgeführt, und hat er das dort offengelegt? Eine nicht
offengelegte Abweichung ist im Protokoll als solche zu kennzeichnen.
- **Umgebungsprüfung (nur `custom` / MCP):** <keine Hooks, Plugins, Output-Styles im
User-Profil vorgefunden | **abweichend**: welche>
- **Subagenten:** <Anzahl und Typ aus `subagent_stats`, z. B. 8 × Explore, 0 fehlgeschlagen>
- **Verschachtelung:** `spawned` = <N>, davon `spawned_by_subagents` = <M>, `max_depth` = <D>.
Bei `max_depth` > 1 ausdrücklich vermerken – die Tokens der tieferen Ebenen sind in
„Tokens gesamt" enthalten, ihre Prompts jedoch **nicht** in `_meta\subagenten.md`
## Validierungsstichprobe
- **Größe:** <vor Versuchsbeginn festgelegte Anzahl Anforderungen>
- **Ziehungsverfahren:** <Zufallsstichprobe über alle Ebenen | ...>
- **Validatoren:** <Anzahl und fachlicher Zuschnitt>
- **Stand:** <noch nicht gezogen | gezogen am <Datum> | ausgewertet>
Kapitel 4.3 verlangt, die Stichprobengröße **vor** Versuchsbeginn festzulegen und im
Versuchsprotokoll zu dokumentieren. Ist für den Lauf keine Validierung vorgesehen, hier
`entfällt` eintragen – das Feld darf nicht fehlen.
## Verbrauch
### Hauptagent (`usage`)
| Messgröße | Wert |
|---|---|
| Input-Tokens | |
| Output-Tokens | <Wert> (davon N Thinking-Tokens aus `usage.output_tokens_details`) |
| Cache-Write-Tokens | |
| Cache-Read-Tokens | |
| Agent-Turns | |
### Gesamtlauf inkl. Subagenten (`modelUsage`, abrechnungsrelevant)
| Messgröße | <Modell-ID> | <weitere Modell-ID> | Summe |
|---|---:|---:|---:|
| Input-Tokens | | | |
| Output-Tokens | | | |
| Cache-Write-Tokens | | | |
| Cache-Read-Tokens | | | |
| **Tokens gesamt** | | | |
**Tokens gesamt: <Summe>** — Input + Output + Cache-Write + Cache-Read über alle Modelle.
Das ist die **berichtete Aufwandsgröße** der Versuchsreihe. `total_cost_usd` bleibt unberührt in
`RawResult.json` erhalten, wird aber nicht ins Protokoll übernommen: Token sind modell- und
preisunabhängig und bleiben damit über Preisänderungen und Modellwechsel hinweg vergleichbar.
**Codex-Semantik:** `cached_input_tokens` ist eine Teilmenge von `input_tokens` und wird nicht
erneut addiert. Dort gilt `Tokens gesamt = input_tokens + output_tokens`; Reasoning-Tokens sind
eine Teilmenge der Output-Tokens. Cache-Write-Tokens werden von `codex exec --json` nicht geliefert.
## Gefundene Anforderungen
<unverändert aus `_meta\anforderungen.md` übernehmen – Verteilung, Typen, Belegqualität,
Status, Regelkonformität>
## Ergebnis
- **Status:** <erfolgreich | Fehler: ...>
- **Session-ID:** <id>
- **Permission-Denials:** <Anzahl> (<Aufschlüsselung nach Tool>; bei keinem Denial: 0).
Im Modus `solo` Denials auf `Task`/`Agent` **getrennt** ausweisen – sie sind die
Versuchsbedingung, keine Einschränkung.
- **Kontrolle Agentenmodus:** `subagent_stats.spawned` = <Zahl> (bei `solo` muss 0 stehen,
sonst Fehlmessung)
- **Subagenten-Prompts:** <`_meta\subagenten.md`, N Aufrufe erfasst | entfällt (Modus solo)>
- **Gültigkeit:** <gültig | **Fehlmessung**: Ergebnisverzeichnis leer / Abbruchmeldung in
Stderr.log – Wortlaut zitieren>
- **Erzeugte Dateien:** <Liste aus Ergebnisse\>
- **Root unverändert:** <ja | nein: welche Abweichungen>
- **Abschlusstext des Agenten:** siehe RawResult.json (`result`)
- **Anmerkungen/Auffälligkeiten:** <Beobachtungen, Abbrüche, manuelle Eingriffe>
```
### 6. Abschlussbericht an den User
Kurz zusammenfassen: Status, Dauer, Tokenverbrauch (inkl. Cache), Modell, Pfad des
Laufverzeichnisses und welche Dateien der Lauf erzeugt hat. Bei `is_error` oder leerem
Ergebnis: `Stderr.log` und `RawResult.json` zitieren, nicht spekulieren.
## Werkzeugadapter
Der Prozess oben ist werkzeugneutral. Dieser Abschnitt hält fest, wie er mit einem konkreten
Werkzeug umgesetzt wird und welche Messgrößen dieses Werkzeug liefert.
### Adapter: Claude Code
Alle mit `claude -p`, `--safe-mode`, `--allowedTools`, `modelUsage` und `subagent_stats`
bezeichneten Aufrufe und Felder im Prozessteil gehören zu diesem Adapter. Er wurde gegen
**CLI 2.1.245** entwickelt und verifiziert.
**Gelieferte Messgrößen** – vollständig aus `RawResult.json`:
| Messgröße | Feld |
|---|---|
| Abbruchstatus | `is_error`, `subtype`, `stop_reason`, `terminal_reason` |
| Dauer | `duration_ms`, `duration_api_ms` (Wanduhr wird selbst gemessen) |
| Tokens Hauptagent | `usage.*` inkl. `output_tokens_details.thinking_tokens` |
| Tokens gesamt inkl. aller Subagenten-Ebenen | `modelUsage` je Modell-ID |
| Tatsächlich eingesetzte Modelle | Schlüssel von `modelUsage` |
| Verweigerte Werkzeugaufrufe | `permission_denials` |
| Subagenten | `subagent_stats` inkl. `spawned_by_subagents`, `max_depth`, `failed` |
| Sitzungskennung für Transcript-Analyse | `session_id` |
**Nicht erfasst:** Sampling-Parameter wie Temperatur sind über die CLI nicht steuerbar und
werden nicht ausgewiesen; der Effort steht **nicht** in `RawResult.json` und ist nur über das
Session-Transkript belegbar (siehe Schritt 6 der Vorbereitung).
### Adapter: Codex CLI / OpenAI-Modelle
Dieser Adapter wurde für den reproduzierbaren Headless-Betrieb mit `codex exec` und exakten
OpenAI-Modell-IDs entworfen. Referenzstand bei Einführung: **Codex CLI 0.149.0-alpha.4.3**.
Vor jedem Lauf `codex --version` protokollieren; bei geändertem JSONL-Schema den Normalisierer
zuerst mit einem kleinen, ausdrücklich freigegebenen Smoke-Test prüfen.
**Unterstützter Agentenmodus:** Auch in Version 6.0.0 nur `solo`. `builtin` und `custom` müssen
abbrechen. `solo` wird mit zwei unabhängigen Einstellungen erzwungen:
`--disable multi_agent` und `-c agents.enabled=false`.
**Isolation und Ausgabe.** Unter Windows blockierte `--sandbox read-only` mit Codex CLI
0.149.0-alpha.4.3 bereits den Start rein lesender Prozesse (`pwsh`, `cmd`, `rg`). Der Adapter
erstellt deshalb vor dem Lauf eine isolierte Kopie der versionierten und nicht ignorierten
Quelldateien im System-Temp-Verzeichnis und startet Codex dort mit `--sandbox workspace-write`.
Das originale Root liegt außerhalb des Codex-Arbeitsbereichs und bleibt technisch getrennt.
`prepare-codex-workspace.py` hasht die Kopie vor und nach dem Lauf; jede Änderung macht den Lauf
ungültig. Quelle, Dateizahl, Größe und beide Manifeste werden unter `_meta` archiviert; die
temporäre Kopie wird erst nach der Integritätsprüfung entfernt. Die Ablage außerhalb des
IDE-Projektbaums verhindert automatische Design-Time-Restores, die sonst ungefragt `obj`-Dateien
in der Kopie erzeugen.
Der Agent liefert weiterhin ausschließlich ein Schemaobjekt mit vollständigen Dateiinhalten;
`--output-last-message` schreibt dieses durch die CLI nach `_meta\final_response.json`. Erst nach
Ende des Agenten materialisiert das lokale, deterministische Skript die validierten relativen
Pfade unter `Ergebnisse\`. Absolute Pfade, `..`, Laufwerkspräfixe und case-insensitive Duplikate
werden abgelehnt.
Vor dem Start `$codex`, `$skillDir`, `$lauf`, `$root`, `$modell` und `$effort` auf absolute
Pfade beziehungsweise die bestätigten Versuchsbedingungen setzen. Den Prompt mit dem
Codex-Ausgabeblock aus Abschnitt 2 nach `_meta\combined_prompt.md` schreiben. Anschließend das
isolierte Abbild anlegen; bei einem Fehler darf der API-Lauf nicht starten:
```powershell
$meta = Join-Path $lauf '_meta'
$workspace = Join-Path ([IO.Path]::GetTempPath()) "codex-experiment-<Laufverzeichnis-ID>"
python (Join-Path $skillDir 'prepare-codex-workspace.py') create $root $workspace $meta
if ($LASTEXITCODE -ne 0) { throw 'Codex-Arbeitsabbild konnte nicht erstellt werden.' }
```
Der eigentliche Aufruf lautet:
```powershell
$codexArgs = @(
'--model', $modell,
'-c', "model_reasoning_effort=`"$effort`"",
'-c', 'service_tier="default"',
'--sandbox', 'workspace-write',
'--ask-for-approval', 'never',
'--disable', 'multi_agent',
'-c', 'agents.enabled=false',
'--disable', 'plugins',
'--disable', 'apps',
'--disable', 'hooks',
'--disable', 'skill_search',
'--cd', $workspace,
'exec',
'--ignore-user-config',
'--ignore-rules',
'--strict-config',
'--json',
'--output-schema', (Join-Path $skillDir 'codex-output-schema.json'),
'--output-last-message', (Join-Path $lauf '_meta\final_response.json'),
'-'
)
Set-Content -Path "$lauf\_meta\startzeit.txt" -Value (Get-Date -Format o)
Get-Content "$lauf\_meta\combined_prompt.md" -Raw |
& $codex @codexArgs 2> "$lauf\Stderr.log" |
Set-Content "$lauf\RawEvents.jsonl" -Encoding utf8
$codexExit = $LASTEXITCODE
Set-Content -Path "$lauf\_meta\exitcode.txt" -Value $codexExit
Set-Content -Path "$lauf\_meta\endzeit.txt" -Value (Get-Date -Format o)
python (Join-Path $skillDir 'prepare-codex-workspace.py') verify $workspace $meta
$workspaceExit = $LASTEXITCODE
python (Join-Path $skillDir 'normalise-codex-result.py') $lauf `
--model $modell --effort $effort
python (Join-Path $skillDir 'prepare-codex-workspace.py') cleanup $workspace $meta
```
Auch dieser Aufruf ist als Background-Task zu starten. Das Erscheinen von `RawEvents.jsonl`
allein bedeutet noch nicht, dass der Lauf fertig ist; Ende ist der abgeschlossene Prozess plus
Integritätsprüfung und erzeugte `RawResult.json`. Ist `$workspaceExit` ungleich null, den Lauf
wegen einer veränderten Arbeitskopie als ungültig kennzeichnen. Schlägt die Normalisierung fehl,
Rohdateien unverändert lassen und den Lauf als Fehler protokollieren.
**Warum diese Flags:**
| Flag / Einstellung | Zweck |
|---|---|
| `--model <id>` | vollständige, vom User bestätigte OpenAI-Modell-ID |
| `model_reasoning_effort` | expliziter Denkaufwand |
| `service_tier="default"` | verhindert eine geerbte Fast-/Priority-Bedingung |
| `--sandbox workspace-write` | erlaubt Windows-Prozessstarts nur innerhalb der isolierten Arbeitskopie |
| `--ask-for-approval never` | keine interaktiven Unterbrechungen im Headless-Lauf |
| `--ignore-user-config`, `--ignore-rules` | keine User-Konfiguration und keine Exec-Regeln |
| `--disable plugins/apps/hooks/skill_search` | keine externen oder benutzerspezifischen Erweiterungen |
| `--disable multi_agent`, `agents.enabled=false` | keine Subagenten im Modus `solo` |
| `--json` | vollständiger maschinenlesbarer Ereignisstrom |
| `--output-schema`, `--output-last-message` | validierbare Ergebnisdateien außerhalb des Arbeitsabbilds |
`--ignore-user-config` lässt die gespeicherte Authentifizierung weiterhin nutzbar; niemals
`auth.json` kopieren oder in Laufartefakten ablegen. Live-Websuche ist nicht freigegeben, weil
`--search` fehlt. Der bereinigte Snapshot bleibt zusätzlich Pflicht, da projektlokale
Anweisungsdateien eine eigene Versuchsbedingung wären.
**Gelieferte Messgrößen** aus `RawEvents.jsonl`, normalisiert nach `RawResult.json`:
| Messgröße | Quelle |
|---|---|
| Abbruchstatus | Prozess-Exitcode, `turn.failed`, `error`, fehlerhafte JSONL-Zeilen |
| Wanduhrdauer | `_meta\startzeit.txt` bis `_meta\endzeit.txt` |
| Tokens gesamt | `turn.completed.usage.input_tokens + output_tokens` |
| Cache-Read-Tokens | `turn.completed.usage.cached_input_tokens`, Teilmenge der Input-Tokens |
| Reasoning-Tokens | `turn.completed.usage.reasoning_output_tokens`, Teilmenge der Output-Tokens |
| Agent-Turns | Anzahl `turn.completed` |
| Sitzungskennung | `thread.started.thread_id` |
| Werkzeugaktivität | Anzahl abgeschlossener `item.*` je Item-Typ |
| Erzeugte Artefakte | validierte Einträge aus `_meta\final_response.json` |
**Nicht erfasst und niemals schätzen:** reine API-Dauer, Cache-Write-Tokens, einzelne
Permission-Denials und die tatsächlich serverseitig bediente Modell-ID. `--model` dokumentiert
die angeforderte ID; da `codex exec --json` sie im Ereignisstrom nicht wiederholt, lautet die
Modellkontrolle im Protokoll `nicht prüfbar`. Temperatur und weitere Sampling-Parameter sind in
diesem CLI-Ablauf nicht steuerbar; Effort und Service-Tier werden dagegen explizit festgelegt.
### Adapter: OpenCode / TensorX-Modelle und lokale LM-Studio-Modelle
Dies ist der **primäre Adapter für alle TensorX-Modell-IDs und für den lokalen
LM-Studio-Betrieb**. Beide nutzen OpenCodes OpenAI-kompatiblen Custom Provider und dasselbe
Skript `opencode-adapter.py`; `--provider` wählt Gateway und Modellvorlage. OpenCode verwaltet
Authentifizierung, Agenten-Loop und Session; der Wrapper erzeugt eine isolierte Konfiguration,
streamt die Rohereignisse, erzwingt Berechtigungen und normalisiert das Ergebnis nach
`RawResult.json`. Es besteht keine Abhängigkeit zu Cline.
| `--provider` | Modell-IDs | Betrieb | Vorlage |
|---|---|---|---|
| `tensorx` (Standard) | `z-ai/*`, `qwen/qwen3.8-flash-next`, `moonshotai/*` | Remote `https://api.tensorx.ai/v1` | `opencode-tensorx.json` |
| `lmstudio` | `google/gemma-4-e4b`, `qwen/qwen3.5-9b` | lokal `http://localhost:1234/v1` | `opencode-lmstudio.json` |
Vor jedem Lauf die vollständige Referenz
[`references/opencode-adapter.md`](references/opencode-adapter.md) lesen und deren Aufruf,
Timeouts, Artefakte und Pflichtprüfungen anwenden. API-Keys gehören ausschließlich in OpenCodes
Credential-Store; die Vorlagen enthalten keine.
**Lokaler Betrieb ist eine eigene Versuchsbedingung.** Ein Preflight prüft Server,
Modellverfügbarkeit, Tool-Fähigkeit, geladenes Kontextfenster (`--min-context`, Standard 32768)
und dass genau eine Modellinstanz geladen ist; er bricht sonst mit Exitcode `2` und dem exakt
nötigen `lms`-Befehl ab. `RawResult.json` führt zusätzlich `local_runtime` (Quantisierung,
Architektur, Runtime, `lms`-Version, Kontextfenster) und `context_window` – damit sind die von
Kap. 4.3 geforderten Angaben für lokalen Betrieb erfasst. **Effort ist bei `lmstudio` nicht
steuerbar** (`effort_applied: false`) und im Protokoll so auszuweisen; Kosten sind
definitionsgemäß `0`, Cache-Metriken `nicht erfasst`. Lokale Läufe niemals mit Cloud-Läufen
poolen.
Referenzstand bei Einführung: **OpenCode 1.18.25**. Ein Live-Smoke-Test mit
`qwen/qwen3.8-flash-next`, Variante `low`, bestätigte Headless-Ausführung, Sessionexport,
Reasoning-/Cache-Metriken und die erwartete Textantwort. Für LM Studio bestätigte ein
Smoke-Test mit `google/gemma-4-e4b` (Q4_K_M, gguf, 32768 Kontexttokens) Preflight,
Providerauflösung, Streaming und Tool-Calling.
**Terminierung kleiner lokaler Modelle.** Im Smoke-Test lief `google/gemma-4-e4b` über 50
Schritte weiter, ohne die geforderte Datei zu schreiben. Da laufend Text erzeugt wird, greift
der Stall-Timeout nicht. Lokale Läufe deshalb immer mit absolutem `--max-runtime` starten und
einen Abbruch als Abbruch protokollieren, nicht als Ergebnis.
### Legacy-Adapter: direkte Python API / GLM-, Qwen- und Kimi-Modelle über TensorX
Dieser frühere Adapter darf nur verwendet werden, wenn der User ihn ausdrücklich verlangt oder
OpenCode nach dokumentierter Diagnose technisch nicht einsetzbar ist. Die Abweichung ist im
Messprotokoll festzuhalten; Läufe beider Adapter sind verschiedene Versuchsbedingungen.
Der Legacy-Adapter wurde für Modelle entwickelt, die über den **TensorX API-Gateway**
(`https://api.tensorx.ai/v1`) erreichbar sind. Er nutzt das Skript `glm-kimi-adapter.py`,
das einen minimalen Agent-Loop mit Tool-Calling direkt gegen die OpenAI-kompatible
REST-API implementiert. Referenzstand bei Einführung: **Python 3.13, requests 2.34**.
**Verfügbare Modell-IDs über TensorX:**
| TensorX-Modell-ID | Hersteller | Effort-Parameter |
|---|---|---|
| `z-ai/glm-5.2` | Z.AI (Zhipu AI) | `thinking` (`{"type":"enabled","level":"…"}`) |
| `z-ai/glm-5.3-flash` | Z.AI (Zhipu AI) | `thinking` (`{"type":"enabled","level":"…"}`) |
| `qwen/qwen3.8-flash-next` | Alibaba Qwen | `thinking` (`{"type":"enabled","level":"…"}`) |
| `moonshotai/kimi-k3` | Moonshot AI | `reasoning_effort` (top-level) |
Das Modell-Präfix (`z-ai/`, `qwen/` bzw. `moonshotai/`) bestimmt, welcher Effort-Parameter an die
API gesendet wird. Der Adapter wählt ihn automatisch anhand des Präfixes.
**Authentifizierung.** Der API-Key wird **automatisch aus der Cline providers.json**
gelesen (`~/.cline/data/settings/providers.json`, Provider `tensorx`). Alternativ kann
er per `--api-key` oder Umgebungsvariable `TENSORX_API_KEY` übergeben werden. Der Key
wird **nicht** in Laufartefakten gespeichert.
**Unterstützte Agentenmodi:** `solo` (V1), `builtin` (V1b) und `custom` (V2). Der Modus wird per
`--mode solo|builtin|custom` gesteuert. Im Modus `solo` steht das `spawn_subagent`-Tool nicht
zur Verfügung. Im Modus `builtin` kann der Hauptagent Subagenten mit eigenem Kontext
starten (`spawn_subagent`-Tool) – diese erhalten Read-Only-Tools (kein `write_file`) und
eine eigene, vom Hauptagenten unabhängige Konversation. Anzahl und Typen bestimmt allein
das Modell; der Adapter setzt weder ein Anzahl- noch standardmäßig ein Turn-Limit. Fordert
das Modell in einer Antwort mehrere Subagenten an, laufen sie tatsächlich parallel. Der
Subagent-Typ (`explore` oder `general-purpose`) bestimmt den System-Prompt. Subagent-Token
fließen vollständig in `usage` und `modelUsage` ein. Im Modus `custom` werden die Rollen mit
`--agents <JSON-Datei>` geladen und über dasselbe `spawn_subagent`-Werkzeug bereitgestellt.
**Lebenszeichen.** Der Adapter schreibt standardmäßig alle 60 Sekunden eine
`LIFESIGN`-Zeile nach `Stderr.log`. Sie nennt die aktiven Operationen des Hauptagenten und
jedes Subagenten, beispielsweise einen API-Turn, einen Tool-Aufruf oder das Warten auf eine
parallele Subagentengruppe. Das Intervall ist mit `--heartbeat-interval` steuerbar. Ein
Lebenszeichen während eines nicht gestreamten HTTP-Aufrufs beweist nur, dass der lokale
Adapterprozess lebt und auf TensorX wartet; es ist kein Nachweis, dass serverseitig weiterhin
Tokens erzeugt werden.
**Keine impliziten Abbrüche.** `--timeout 0`, `--max-turns 0` und
`--subagent-max-turns 0` bedeuten tatsächlich unbegrenzt. Der Adapter injiziert keine
laufzeit- oder limitbedingten Schreibaufforderungen. Damit bleibt es Teil der Messung, ob,
wie und mit wie vielen Subagenten ein Modell zum Abschluss kommt.
**Isolation.** Der Adapter ist ein eigenständiges Python-Skript, das nur die
Python-Standardbibliothek und `requests` benötigt. Es liest die Codebasis über die
implementierten Tools (`read_file`, `list_directory`, `search_files`,
`execute_command`) und schreibt Ergebnisdateien ausschließlich über das `write_file`-Tool
ins Ausgabeverzeichnis. Schreibende und bauende Shell-Kommandos sind durch eine
Denylist gesperrt (analog zum Claude-Code-Adapter). Der bereinigte Snapshot bleibt
zusätzliche Pflicht.
**Effort-Steuerung.** Der Adapter mappt die Skill-Effort-Stufen auf beide APIs:
| Skill-Effort | GLM `thinking.level` | Qwen `thinking.level` | Kimi `reasoning_effort` |
|---|---|---|---|
| `low` | `low` | `low` | `low` |
| `medium` | `medium` | `medium` | `medium` |
| `high` | `high` | `high` | `high` |
| `xhigh` | `xhigh` | `xhigh` | `high` (höchste verfügbare Stufe) |
| `max` | `xhigh` | `xhigh` | `high` |
**Der Aufruf** (als Background-Task starten):
```powershell
$skillDir = "<Verzeichnis des Skills>"
$lauf = "<absoluter Pfad zum Laufverzeichnis>"
$root = "<Root-Verzeichnis der Codebasis>"
$modell = "<vom User gewählte Modell-ID, z.B. z-ai/glm-5.2>"
$effort = "<vom User gewählte Stufe>"
$modus = "<solo|builtin|custom>"
$agents = "<Agenten-JSON; im Modus custom erforderlich>"
# Prompt zusammenstellen (wie bei den anderen Adaptern)
$prompt = (Get-Content "<prompt-datei>" -Raw) + "`n`n<Werkzeugkontext-Block>`n`n<Ausgabe-Block>"
Set-Content -Path "$lauf\_meta\combined_prompt.md" -Value $prompt -Encoding utf8
Set-Content -Path "$lauf\_meta\startzeit.txt" -Value (Get-Date -Format o)
# API-Key wird automatisch aus Cline providers.json gelesen
python "$skillDir\glm-kimi-adapter.py" `
--prompt "$lauf\_meta\combined_prompt.md" `
--root $root `
--output "$lauf\Ergebnisse" `
--model $modell `
--effort $effort `
--mode $modus `
--agents $agents `
--max-turns 0 `
--subagent-max-turns 0 `
--timeout 0 `
--heartbeat-interval 60 `
--result-dir $lauf `
2> "$lauf\Stderr.log"
Set-Content -Path "$lauf\_meta\endzeit.txt" -Value (Get-Date -Format o)
```
**Gelieferte Messgrößen** – vollständig aus `RawResult.json` (vom Adapter geschrieben):
| Messgröße | Feld |
|---|---|
| Abbruchstatus | `is_error`, `subtype`, `finish_reason` |
| Dauer | `duration_ms` (Wanduhr, selbst gemessen; keine separate API-Zeit) |
| Tokens gesamt | `usage.total_tokens` über alle Turns akkumuliert |
| Input-Tokens | `usage.prompt_tokens` |
| Output-Tokens | `usage.completion_tokens` |
| Reasoning-Tokens | `usage.reasoning_tokens` (aus `completion_tokens_details.reasoning_tokens`) |
| Cache-Read-Tokens | `usage.cached_tokens` (soweit von TensorX geliefert) |
| Cache-Write-Tokens | **nicht erfasst** – TensorX liefert keine |
| Agent-Turns | `num_turns` |
| Tatsächlich eingesetztes Modell | `model` (aus API-Antwort; mit `model_requested` abzugleichen) |
| Tool-Aufrufe | `tool_call_count`, `tool_call_types` (nach Werkzeugname) |
| Subagenten | `subagent_stats` (`spawned`, `completed`, `failed`, `by_type`) – nur in den Modi `builtin` und `custom` |
| Subagenten-Details | `subagent_details` (je Subagent: Typ, Beschreibung, Turns, Tokens, Status) |
| Erzeugte Artefakte | `written_files` (Pfad und Größe je Datei) |
| Abschlusstext | `result` |
| Lebenszeichen | periodische `LIFESIGN`-Zeilen in `Stderr.log` |
**Semantik der Token-Zählung.** `usage.total_tokens` wird über alle Turns aufsummiert.
Jeder Turn umfasst dabei den vollen Kontext (Eingabe + Ausgabe); die Token-Zahl ist
daher die Summe aller API-Aufrufe, nicht die eines einzelnen Austauschs.
Reasoning-Tokens sind eine Teilmenge von `completion_tokens` und werden nicht erneut
addiert.
**Nicht erfasst und niemals schätzen:** Cache-Write-Tokens, Permission-Denials (das
Deny-Modell ist ein hartes Blockieren, keine zählbaren Denials), Subagenten-Stats (keine
Subagenten), Session-ID (keine persistierte Session), API-Dauer (nicht von der API
geliefert). Sampling-Parameter: Temperatur ist steuerbar (`--temperature`),
Reasoning-Effort ist steuerbar (`--effort`).
**Pflichtprüfung nach dem Lauf:**
1. `Ergebnisse\` ist **nicht leer** – sonst Fehlmessung (analog zu MAJOR 6.1.0).
2. `Stderr.log` enthält keine Abbruchmeldung.
3. `model` in `RawResult.json` stimmt mit `model_requested` überein (Modellkontrolle).
4. `tool_call_count` > 0 (ein Lauf ohne Tool-Aufrufe hat die Codebasis nicht analysiert).
### Weitere Adapter (für Versuch 4 nachzurüsten)
Kapitel 4 der Arbeit sieht zusätzlich DeepSeek über die Cloud-API vor; dieser Adapter ist noch
nicht ausgearbeitet. Der lokale LM-Studio-Betrieb ist seit 10.1.0 über
`opencode-adapter.py --provider lmstudio` abgedeckt – allerdings mit OpenCode als Agenten-Loop
statt der in Kap. 4 genannten Qwen Code CLI. Diese Abweichung gehört ins Protokoll. Damit ein
Lauf als Messpunkt taugt, muss ein Adapter mindestens liefern:
| Pflichtangabe | Zweck |
|---|---|
| Modell-ID und Kontextfenster | Bedingung dokumentieren |
| Input- und Output-Tokens | Aufwandsvergleich |
| Dauer | Aufwandsvergleich |
| Abbruchstatus | Gültigkeit des Laufs |
| Erzeugte Artefakte | Ertrag |
| Sampling-Parameter, soweit steuerbar | Reproduzierbarkeit (Kap. 4.3) |
| Bei lokalem Betrieb: Runtime samt Version, Quantisierungsstufe | Reproduzierbarkeit (Kap. 4.3) |
Was ein Adapter nicht liefern kann, wird im Protokoll ausdrücklich als `nicht erfasst`
ausgewiesen – niemals geschätzt. Fehlen mehrere Pflichtangaben, ist der Lauf nur eingeschränkt
mit Claude-Code-Läufen vergleichbar; das gehört in die Anmerkungen des Protokolls.
## Randbedingungen
- Niemals Messwerte schätzen oder erfinden – fehlt ein Feld im JSON, im Protokoll als
`nicht erfasst` eintragen.
- Prompt-Datei unverändert lassen; das Protokoll ist eine neue Datei.
- Läuft der Background-Task ungewöhnlich lange, User informieren statt abbrechen.
- **Am Codebasis-Root wird nichts verändert** – weder zur Vorbereitung noch zur Isolation noch
zur Nachbereitung. Ist das Root vor dem Lauf bereits `dirty`, den Zustand im Protokoll
festhalten und den User informieren, statt ihn stillschweigend zu bereinigen.
- **Der Codebasis-Snapshot ist eingefroren.** Kein `push`, kein `fetch`, kein Hinzufügen eines
Remotes, kein Commit im Codebasis-Repo. Soll der Snapshot geändert werden (etwa weil eine
weitere KI-Konfiguration auftaucht), ist das mit dem User abzustimmen, als eigener Commit
festzuschreiben und in allen betroffenen Protokollen als geänderte Versuchsbedingung zu
vermerken.
- **Werkzeugkonfiguration ist eine unabhängige Variable.** Weicht ein Lauf von der hier
festgelegten Standardkonfiguration ab (andere Flags, anderer Permission-Mode, zusätzliche
Tools), ist das im Protokoll unter „Werkzeugkonfiguration" wörtlich zu dokumentieren –
sonst sind die Läufe nicht vergleichbar.
## Versionierung
Die aktuelle Version steht im Frontmatter (`version:`) und **muss in jedes Protokoll** unter
„Werkzeugkonfiguration" als **Skill-Version** eingetragen werden. Nur so lässt sich später
zuordnen, unter welcher Fassung des Versuchsaufbaus eine Messung entstanden ist.
Schema (Semantic Versioning):
| Stelle | Bedeutung | Folge für die Vergleichbarkeit |
|---|---|---|
| **MAJOR** | Änderung der Versuchsbedingung – Flags, Toolfreigabe, Isolationsmechanismus, Codebasis-Snapshot | Läufe verschiedener MAJOR-Versionen sind **nicht** direkt vergleichbar |
| **MINOR** | Neue Pflichtschritte oder Messgrößen ohne Änderung der Versuchsbedingung | Läufe bleiben vergleichbar; die Protokolle werden reichhaltiger |
| **PATCH** | Fehlerkorrekturen am Ablauf, Dokumentation, Klarstellungen | Ohne Einfluss auf die Messung |
**Regel:** Wer den Skill ändert, erhöht die Version im Frontmatter und ergänzt eine Zeile in
der Historie unten – im selben Arbeitsschritt.
## Änderungshistorie
| Version | Änderung | Grund | Verwendet in |
|---|---|---|---|
| **12.2.0** | **`analyse-anforderungen.py` erkennt Feldnamen in Markdown-Fettschrift und Modulpraefixe in IDs.** `**ID:** M003-StRS-01` wird wie `ID: StRS-01` gelesen; die Ebene wird aus der ID auch dann bestimmt, wenn ein Praefix vorangeht. Zwei neue Hilfsskripte: `lauf-uebersicht.py` verdichtet die `RawResult.json` einer Matrix zu einer Tabelle und zeigt mit `--details` die tatsaechlichen Schreibziele; `protokoll-geruest.py` erzeugt aus den Rohdaten eines Laufs das Protokollgeruest und fuellt nur belegbare Felder - Deutung und Gueltigkeit bleiben Handarbeit. | Der erste lokale Lauf mit Artefakten (`Iteration 7/.../v12.1.0-001c`) erzeugte vier regelkonform gefuellte Dateien, wurde vom Parser aber mit **0 Anforderungen** gezaehlt: Das Modell formatierte die Feldnamen als Markdown. Nach der Korrektur sind es **9**. Die Praefixregel behob zugleich eine falsche Auffaelligkeitsmeldung ('StRS-Block in StRS.md' als Fremdablage). Regressionsprobe an vier Claude-Laeufen: 42/82/60/73 Anforderungen vor und nach der Aenderung identisch - die Korrektur findet nur zusaetzlich, was zuvor uebersehen wurde. `lauf-uebersicht.py` entstand, weil `written_files` bei fehlgeleiteten Schreibversuchen schlicht leer bleibt und die Ursache so unsichtbar ist. MINOR: Korrektur am Messinstrument, keine Aenderung der Versuchsbedingung; rueckwirkend auf alle Laeufe anwendbar. | rueckwirkend; ab sofort |
| **12.1.0** | **Speicherhygiene und Kontextgroesse fuer lokale Laeufe; Modellwechsel `qwen/qwen3.8-27b` -> `qwen/qwen3.5-9b`.** Der Preflight entlaedt vor jedem Lauf **alle** Modelle (`lms unload --all`) und bricht ab, wenn neben dem angeforderten ein weiteres geladen ist. Neue Optionen `--lmstudio-context` (Standard `max`: laedt das Modellmaximum statt der Mindestgroesse), `--lmstudio-parallel` (Standard 4) und `--lmstudio-gpu` (Standard `max`). `local_runtime` fuehrt zusaetzlich `parallel_slots`, `gpu_offload` und `alleiniges_modell`. Adapter-Version 2.2.0. | Messungen vom 01.09.2026 auf einer RTX 5080 Laptop GPU (16.303 MiB): Gemma und Qwen 27B waren **gleichzeitig geladen** - 15.836 MiB belegt, 168 MiB frei, Durchsatz 0,028 Mio. Tokens/h. Nach `unload --all` laeuft Gemma mit 48,3 tok/s. Das Modellmaximum kostet fast nichts: 131.072 statt 32.768 Kontext bedeutet 6.854 statt 5.162 MiB und 46,8 statt 48,3 tok/s - vierfaches Fenster fuer 1,5 tok/s. `--parallel 4` ist gegenueber 1 messtechnisch neutral (47,7 vs. 48,3 tok/s). **Der Modellwechsel ist hardwarebedingt:** `qwen3.8-27b` belegt mit 17,74 GB Gewichten mehr, als die Karte hat; ein Generierungstest brach nach 10 Minuten ohne Ergebnis ab. Die 27B-Klasse ist auch mit kleinerer Quantisierung nicht messbar, weil der KV-Cache bei brauchbarem Kontext mehrere GB zusaetzlich fordert. `qwen3.5-9b` ist die groesste Qwen-Variante, die mit vollem Fenster hineinpasst, und laeuft wie Gemma in **Q4_K_M** - damit unterscheiden sich die beiden lokalen Modelle nur in der Groesse, nicht zusaetzlich in der Quantisierung. MINOR fuer die Ladeparameter; der Modellwechsel eroeffnet ohnehin eine neue Iteration, weil `qwen3.8-27b` keinen gueltigen Messpunkt geliefert hat. | ab der LM-Studio-Matrix in Iteration 13 (V1) bzw. 6 (V2) |
| **12.0.0** | **Die Shell-Rechte des OpenCode-Adapters werden eine Denylist statt einer Allowlist** – spiegelbildlich zu den Claude-Eintraegen: alles erlaubt ausser den ausdruecklich gesperrten schreibenden und bauenden Kommandos (`rm`, `mv`, `sed -i`, schreibende `git`-Kommandos inkl. `fetch`/`pull`/`remote`, `dotnet`, `msbuild`, `npm install`, `Remove-Item`, `Set-Content`, `Out-File` u. a.). Das Catch-all `"*": "allow"` steht zuerst, weil OpenCode die **zuletzt passende** Regel gewinnen laesst. Zusaetzlich lehnt der Adapter `--stall-timeout > 0` jetzt fuer **jeden** lokalen Provider ab, nicht nur fuer die delegierenden Modi. Adapter-Version 2.0.0, vier neue Regressionstests. | Der Claude-Adapter erlaubt ueber eine Denylist jedes nicht gesperrte Kommando, der OpenCode-Adapter nur explizit Gelistetes. Die Werkzeugfreiheit war zwischen beiden **nie aequivalent** – ein Confounder fuer jeden Werkzeugvergleich. Ausloeser war der erste Qwen-Lauf (`v11.1.0-45b1`): Dessen **einzige beide** Werkzeugaufrufe, `Get-ChildItem ... | Format-Table ...`, wurden verweigert, weil die Allowlist nur Praefixe trifft und an Pipelines scheitert. Bei Gemma war das ein Randfall (1 von 106 Aufrufen), bei Qwen legte es den Lauf still. Der zweite Ausloeser: Qwen 27B benoetigte fuer einen einzelnen Schritt mehr als 15 Minuten, sodass der Stall-Timeout auch in `solo` Modellgeschwindigkeit als Haenger wertete. **Kontrolltest am 01.09.2026 bestaetigte beide Richtungen:** `Get-ChildItem ... | Format-Table Name` lief durch, `rm opfer.txt` wurde verweigert, und die Datei blieb auf der Platte. MAJOR: Die Toolfreigabe ist eine unabhaengige Variable; Laeufe ab dieser Version sind mit allen frueheren OpenCode- und TensorX-Laeufen nicht poolbar. | ab dem naechsten OpenCode-Lauf; Iterationen 10 und 11 bleiben unter der Allowlist |
| **11.1.0** | **Der Adapter lehnt `--stall-timeout > 0` in den Modi `builtin` und `custom` ab.** Die Laufzeit wird dort ausschliesslich ueber `--max-runtime` begrenzt. Adapter-Version 1.3.0; Referenz und Aufrufbeschreibung entsprechend ergaenzt. | Der erste `builtin`-Lauf mit LM Studio (`v11.0.0-9ad0`) wurde nach 15:48 min abgebrochen, obwohl das Limit bei 60 min lag. Ursache war nicht das Modell: OpenCode sendet **keine Ereignisse, solange ein Subagent arbeitet**. Nach 8 Ereignissen in den ersten 39 Sekunden schwieg der Strom, waehrend der gestartete `explore`-Subagent lief; der Stall-Timeout deutete das als Haenger. Jeder Lauf mit Subagenten waere so zuverlaessig zu frueh gestorben. Die Kombination wird abgelehnt statt stillschweigend korrigiert, damit die Entscheidung bewusst faellt. MINOR: Es existierte noch **kein** gueltiger OpenCode-Lauf in `builtin` oder `custom`, dessen Bedingung sich dadurch aendern koennte; die `solo`-Laeufe waren nie betroffen, weil bei ihnen der Stall-Timeout nie griff. **Zu pruefen:** ob die als Fehler protokollierten TensorX-V2-Laeufe (Modus `custom`, `--stall-timeout 600`) dieselbe Ursache haben. | ab dem naechsten OpenCode-Lauf in `builtin` oder `custom` |
| **11.0.0** | **Read-only-Shell-Allowlist des OpenCode-Adapters erweitert.** Neben `rg` und den lesenden `git`-Kommandos sind jetzt die verbreiteten POSIX-Werkzeuge `ls`, `cat`, `head`, `tail`, `find`, `grep`, `wc`, `file`, `stat`, `tree` sowie `dir`, `type`, `Get-Item` und `Measure-Object` freigegeben; `git log` und `git show` kommen hinzu. Alles Übrige bleibt `deny`. Zwei Regressionstests sichern die Liste ab: Sie muss die lesenden Kommandos enthalten und darf kein schreibendes enthalten. Adapter-Version 1.2.0. | Der erste LM-Studio-Lauf (`v10.1.0-b00a`, Gemma/solo) erzeugte zwei Permission-Denials auf `ls src` – das Kommando fehlte auf der Allowlist. Der Agent hatte damit kein POSIX-Mittel, Verzeichnisse aufzulisten, obwohl genau das laut Werkzeugkontext zur Bedingung gehört. Die Lücke benachteiligte OpenCode-Läufe gegenüber den Claude-Läufen, die mit einer Denylist arbeiten und deshalb jedes nicht ausdrücklich gesperrte Lesekommando erlauben. **MAJOR: Die Toolfreigabe ist eine unabhängige Variable.** Läufe ab dieser Version sind mit den bisherigen OpenCode- und TensorX-Läufen nicht poolbar; der nächste Lauf eröffnet eine neue Iteration. | ab dem nächsten OpenCode-Lauf; Iteration 10 bleibt unter der alten Allowlist |
| **10.1.0** | **Lokaler LM-Studio-Adapter für `google/gemma-4-e4b` und `qwen/qwen3.8-27b`.** `opencode-tensorx-adapter.py` heißt jetzt `opencode-adapter.py` und wählt über `--provider {tensorx,lmstudio}` Gateway und Modellvorlage; die Referenz heißt entsprechend `references/opencode-adapter.md`. Neue keyfreie Vorlage `opencode-lmstudio.json` (`http://localhost:1234/v1`). Ein Preflight über `/api/v0/models` prüft Servererreichbarkeit, Modellverfügbarkeit, `tool_use`-Fähigkeit, geladenes Kontextfenster (`--min-context`, Standard 32768) und dass genau **eine** Modellinstanz geladen ist; `--lmstudio-autoload` stellt den Sollzustand per `lms unload`/`lms load` selbst her. Das geladene Fenster wird als `limit.context` in die Laufkonfiguration gepinnt. `RawResult.json` erhält `local_runtime` (Quantisierung, Architektur, Runtime, `lms`-Version, Instanzbezeichner, Kontextfenster), `context_window`, `cost_source` und providerübergreifend `effort_applied`. Neue Artefaktdatei `_meta/lmstudio-modelle.json`. Adapter-Version 1.1.0, fünf zusätzliche Unit-Tests. Zwei Korrekturen am gemeinsamen Pfad: Der Abbruchgrund wird nur noch einmal in `errors` vermerkt statt je Sekunde bis zum Prozessende, und die `lms`-Version wird aus dem ANSI-Banner der CLI sauber extrahiert. | Kapitel 4 sieht lokalen Betrieb als eigene Bedingung vor und fordert nach Kap. 4.3 Runtime samt Version und Quantisierungsstufe – beides liefert erst der Preflight. Drei Befunde aus der Inbetriebnahme sind direkt in den Adapter eingeflossen: LM Studio lädt Modelle standardmäßig mit nur 8192 Kontexttokens, was eine Codebasisanalyse stillschweigend abschneiden würde; ein erneutes `lms load` erzeugt eine **zweite** Instanz (`modell:2`), womit die `model`-Angabe der OpenAI-API nicht mehr eindeutig routet; und der lokale Endpunkt nimmt keinen Thinking-Level entgegen, weshalb Effort als nicht steuerbar auszuweisen ist statt als gesetzt. MINOR: neuer Provider und neue Messgrößen; für `--provider tensorx` bleiben Aufruf, Berechtigungen und Metriken unverändert – die vier bestehenden TensorX-Regressionstests laufen unverändert durch, sodass laufende V2-Läufe vergleichbar bleiben. Live-Smoke-Test am 31.08.2026 mit `google/gemma-4-e4b` (Q4_K_M, gguf, 32768 Tokens): Preflight bestanden, Providerauflösung, Streaming und Tool-Calling bestätigt. | ab dem ersten LM-Studio-Lauf |
| **10.0.2** | Ergebnis-Allowlist zusätzlich relativ zur per Git ermittelten Worktree-Wurzel; Adapter-Version 1.0.2. | Der erste Fix deckte den aktiven Root und den kanonischen Pfad ab. OpenCode 1.18.25 matcht ein Ziel innerhalb desselben Repositories jedoch gegen den Pfad relativ zur Worktree-Wurzel. PATCH: weitere Normalisierungsform desselben bereits autorisierten Zielverzeichnisses. | ab dem ersten OpenCode-V2-Lauf |
| **10.0.1** | Der OpenCode-Adapter autorisiert Ergebnisziele zusätzlich mit einem zum aktiven Root relativen Pfad, einschließlich notwendiger `..`-Segmente; Adapter-Version 1.0.1. Regressionstest für Root und Laufverzeichnis in verschiedenen Unterordnern desselben Windows-Git-Worktrees. | OpenCode normalisiert solche Ziele intern worktree-relativ. Die alleinige kanonische Allow-Regel griff daher nicht, obwohl der absolute Werkzeugpfad exakt im erlaubten Ergebnisordner lag. Ein Custom/max-Preflight startete den vorgesehenen Subagenten erfolgreich, konnte anschließend aber keine Ergebnisdatei schreiben. PATCH: korrigiert nur die beabsichtigte Schreibfreigabe. | ab dem ersten OpenCode-V2-Lauf |
| **10.0.0** | **OpenCode wird primärer TensorX-Adapter.** Neue keyfreie Provider-/Modellvorlage `opencode-tensorx.json`, Wrapper `opencode-tensorx-adapter.py`, Unit-Tests und Detailreferenz. Der Wrapper startet `opencode run --pure` mit einer isolierten Laufkonfiguration, streamt JSONL und stderr, exportiert die Session, normalisiert Token-, Tool- und Subagentenmetriken und beendet bei Inaktivität oder Benutzerabbruch den Prozessbaum. `solo`, `builtin` und aus `03_Agents.json` übersetztes `custom` werden unterstützt. Der direkte Python-Adapter bleibt als ausdrücklich gewählter Legacy-Fallback erhalten. | Der direkte Adapter hing bei `qwen/qwen3.8-flash-next` in einem nicht gestreamten HTTP-Aufruf ohne lokalisierbaren Fortschritt. OpenCode liefert inkrementelle Ereignisse, eine persistierte Session und einen klaren Prozesslebenszyklus; außerdem entfällt die Kopplung der TensorX-Authentifizierung an Cline. MAJOR, weil Agentenlaufzeit, Werkzeugsemantik und Metrikquelle eine neue Versuchsbedingung bilden. Live-Smoke-Test am 31.08.2026 mit Qwen/low: Exitcode 0, erwartete Antwort, Sessionexport und vollständige Tokenfelder. | ab dem nächsten TensorX-Lauf; vorherige direkte Python-Läufe bleiben Legacy-Bedingung |
| **9.3.0** | **TensorX-Modellkatalog für Versuch 2 erweitert:** `qwen/qwen3.8-flash-next` und `z-ai/glm-5.3-flash`; Qwen erhält explizit das `thinking.level`-Effort-Mapping. Der TensorX-Aufruf dokumentiert nun `--agents`. Im Adapter stellt `custom` wie `builtin` das Werkzeug `spawn_subagent` bereit; Adapter-Version 2.1.0. | Beide IDs wurden am 31.08.2026 über `GET /v1/models` des konfigurierten TensorX-Gateways bestätigt. Ein Qwen-Smoke-Test bestätigte `thinking`, Reasoning-Tokens und Tool-Calling. Bei der Konfigurationsprüfung fiel außerdem auf, dass `custom` trotz geladener Rollen das Delegationswerkzeug ausblendete und der dokumentierte Aufruf die Agentendatei nicht übergab; damit wäre Versuch 2 über TensorX ohne spezialisierte Rollen gelaufen. MINOR für die Modellauswahl, funktionale Korrektur für V2. | ab dem ersten TensorX-Lauf in Versuch 2 |
| **9.2.0** | **Delegationstiefe als Option im Modus `custom`.** Zwei gehashte Fassungen der Agentendatei: `verschachtelt` (Rollen dürfen selbst delegieren, bisheriges Verhalten) und `unverschachtelt` (Abschnitt *Keine Weiterdelegation* in jedem Rollenprompt). Die Fassung wird wie Modell, Modus und Effort vor dem Lauf beim User erfragt. Neues Protokollfeld *Delegationstiefe* mit Kontrolle über `subagent_stats.max_depth` und `spawned_by_subagents`. | Im ersten V2-Lauf (`v9.1.0-0c39`) entfielen 26 von 86 Subagenten auf Starts durch Subagenten (`max_depth` = 3). Das war nicht beabsichtigt – der `iso29148-orchestrator` ist als nicht-delegierende Rolle entworfen – und ist ein erheblicher Kostentreiber: Jede zusätzliche Ebene liest ihren Kontext erneut, und Cache-Reads stellten 46 % der Laufkosten. Es verwischt zudem die Zuständigkeit, weil ein von einer Rolle gestarteter Subagent keiner Teilaufgabe der Bindungstabelle mehr zuzuordnen ist. MINOR: neue Option und neue Messgröße; die Bedingung bestehender Läufe ändert sich nicht, sie gelten rückwirkend als `verschachtelt`. | ab dem zweiten V2-Lauf |
| **9.1.0** | **Zuständigkeitsbindung im Modus `custom`.** Block 1 (Werkzeugkontext) wird bei `custom` um eine Tabelle `Teilaufgabe → vorgesehener Bearbeiter` ergänzt, abgeleitet aus der `--agents`-Datei. Der Prompt formuliert die Bindung abstrakt und nennt weiterhin keine Rolle, damit dieselbe Prompt-Datei für `solo` und `builtin` gültig bleibt. Neues Protokollfeld *Zuständigkeitsbindung*: Aufrufe je Rolle aus `subagent_stats.by_type`, Rollen mit null Aufrufen namentlich, Abgleich gegen die Dokumentationspflicht im `Analysebericht.md`. | V2 untersucht die Wirkung rollenspezialisierter Agentendateien. Bleibt die Nutzung freigestellt, wird die Bedingung nicht hergestellt: Im Smoke-Test vom 26.08. nutzte der Agent von acht beigestellten Rollen nur zwei (`modulinventar`, `konsistenzpruefer`) — der Lauf hätte die Rollen mitgeführt, ohne sie einzusetzen. Die Bindung ist statisch deklariert, in `_meta\combined_prompt.md` archiviert und über den SHA-256 der `--agents`-Datei versioniert; sie unterscheidet sich damit von den in 9.0.0 entfernten laufzeitabhängigen Adaptereingriffen. Anzahl der Aufrufe, Zerlegungstiefe, Reihenfolge und Turn-Anzahl bleiben unvorgegeben. MINOR, weil bislang **kein** Lauf im Modus `custom` existiert und deshalb keine Läufe unpoolbar werden; der erste V2-Lauf eröffnet ohnehin eine neue Iteration. | ab dem ersten V2-Lauf |
| **9.0.0** | **Freie und tatsächlich parallele Subagenten-Orchestrierung im Python-Adapter.** Das künstliche 10er-Limit, die limitbedingte Zwangsnachricht und die laufzeitabhängigen Schreib-Erinnerungen entfallen. Mehrere `spawn_subagent`-Aufrufe desselben Turns werden parallel ausgeführt; Haupt- und Subagenten haben standardmäßig kein Turnlimit. `--timeout 0` bedeutet nun wirklich keinen HTTP-Timeout. Ein threadsicherer Heartbeat protokolliert alle 60 Sekunden den lokalen Zustand von Haupt- und Subagenten. API-Fehler setzen unabhängig von vorhandenem Freitext `is_error: true`, werden nach `Stderr.log` geschrieben und führen zu Exitcode 1. | Das 10er-Limit und die serielle Abarbeitung veränderten gerade die zu untersuchende autonome Delegationsstrategie des Modells. Außerdem wurden fünf Kimi-Timeouts wegen fehlerhafter Statuslogik als Erfolg klassifiziert. MAJOR, weil Subagentenfreiheit, Parallelität und Abbruchsemantik die Versuchsbedingung ändern; der nächste Lauf eröffnet eine neue Iteration. | ab dem nächsten TensorX-Lauf |
| **8.0.0** | **Neuer Python-API-Adapter für GLM- und Kimi-Modelle über den TensorX-Gateway.** Das Skript `glm-kimi-adapter.py` implementiert einen minimalen Agent-Loop mit Tool-Calling (read_file, list_directory, search_files, execute_command, write_file) direkt gegen die OpenAI-kompatible TensorX-API (`https://api.tensorx.ai/v1`). Modelle: `z-ai/glm-5.2` und `moonshotai/kimi-k3`. Der API-Key wird automatisch aus der Cline providers.json gelesen. Token-Verbrauch wird pro Turn aus dem API-Response `usage`-Objekt akkumuliert, inkl. Reasoning-Tokens (`completion_tokens_details.reasoning_tokens`). Effort-Mapping anhand des Modell-Präfixes: `z-ai/*` nutzt `thinking.level`, `moonshotai/*` nutzt `reasoning_effort`. Nur Modus `solo` freigegeben. Neue Modell-Tabelle für TensorX-Modell-IDs im Vorbereitungsabschnitt. | Die Cline CLI (`npm i -g cline`) versprach einen Headless-Modus mit GLM-Support, ihr natives Binary (143 MB, Bun-kompiliert) wurde jedoch durch die Application-Control-Richtlinie der Maschine blockiert (EPERM/Zugriff verweigert). Der Python-Adapter umgeht dieses Problem und bietet zusätzliche Vorteile: exakte Token-Metriken direkt aus der API (inkl. Reasoning-Tokens, die Claude Code nur als `thinking_tokens` liefert), keine externen Binary-Abhängigkeiten, API-Key aus bestehender Cline-Konfiguration. MAJOR, da neues Werkzeug mit anderer Isolationsarchitektur und anderen Metrik-Quellen eine neue Versuchsbedingung bildet; der nächste Lauf eröffnet eine neue Iteration. | ab dem ersten GLM/Kimi-Lauf |
| **7.0.0** | **Isolationsmechanismus wird modusabhängig.** In den Modi `solo` und `builtin` unverändert `--safe-mode`; im Modus `custom` und bei jedem Lauf mit `--mcp-config` **kein** `--safe-mode`, stattdessen `--strict-mcp-config` plus `--disallowedTools Skill WebSearch WebFetch SlashCommand`. Neue Pflicht-Umgebungsprüfung auf Hooks, Plugins und Output-Styles im User-Profil; neue Protokollfelder für die MCP-Konfiguration und die Umgebungsprüfung. | `--safe-mode` schaltet ausweislich der CLI-Hilfe „MCP servers, custom commands and agents" ab – also genau das, was V2 und V3 untersuchen. Smoke-Test am 2026-08-26 (CLI 2.1.246, identischer Aufruf, nur `--safe-mode` variiert): mit Flag `spawned` = 0 und die Meldung, die Rollen seien „nicht in der Agent-Registry registriert"; ohne Flag `spawned` = 2 mit `{"modulinventar": 1, "konsistenzpruefer": 1}`. Ohne Ersatz hätte der erste V2-Lauf stillschweigend als V1-Lauf gemessen. Der Ersatz wurde gegengeprüft: nichts vorgeladen, keine Skills, kein Webzugriff, alle Rollen verfügbar. **MAJOR: Läufe im Modus `custom` sind hinsichtlich der Isolation nicht unmittelbar mit `solo`- und `builtin`-Läufen vergleichbar** – Plugins, Hooks und Output-Styles sind dort nicht durch das Flag, sondern nur durch die Umgebungsprüfung ausgeschlossen. | ab dem ersten V2-Lauf |
| **6.1.0** | Zwei adapterunabhängige Pflichtprüfungen nach jedem Lauf: **(a)** `Ergebnisse\` darf nicht leer sein – sonst Fehlmessung, unabhängig von `is_error`; **(b)** `Stderr.log` auf Abbruchmeldungen prüfen. Der Ausführungsabschnitt setzt `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0`. Neues Protokollfeld „Gültigkeit". Dokumentiert, dass die Rückgabe eines Hintergrund-Subagenten eine Start-Quittung ist und ihre Länge kein Ertragsmaß. | Lauf `…160037_v4.5.0-116d` meldete `is_error: false`, `subtype: success` und lieferte **null Ergebnisdateien** bei 193,4 Mio. Tokens – der Headless-Modus hatte nach 600 s abgebrochen, während zehn Hintergrund-Subagenten noch liefen. Ohne die neue Prüfung wäre der Lauf als gültiger Messpunkt mit „0 Anforderungen" in den Modellvergleich eingegangen. MINOR: neue Prüfschritte; die Umgebungsvariable ändert die Versuchsbedingung für künftige `builtin`-Läufe und ist dort zu vermerken. | ab sofort |
| **6.0.0** | **Windows-taugliche Codex-Isolation.** Pro Lauf wird im System-Temp-Verzeichnis außerhalb des IDE-Projektbaums eine Kopie der versionierten und nicht ignorierten Dateien des Codebasis-Roots erstellt. Codex läuft ausschließlich dort mit `--sandbox workspace-write`; SHA-256-Manifeste vor und nach dem Lauf erkennen jede Änderung. Ergebnisse und Nachweise bleiben im Laufverzeichnis, die temporäre Kopie wird danach entfernt. | Der erste Codex-Lauf mit 5.0.0 konnte fachlich nicht starten, weil die Windows-Read-only-Sandbox sämtliche lesenden Kindprozesse vor dem Start blockierte. Eine Kopie im Laufverzeichnis wurde zudem vom C#-Projektservice automatisch mit ignorierten `obj`-Dateien verändert. MAJOR, weil Sandboxmodus und Arbeitsverzeichnis eine neue Versuchsbedingung bilden; der erste Lauf eröffnet deshalb eine neue Iteration. | ab dem ersten wiederholten Codex-Lauf |
| **5.0.0** | **Neuer Codex-CLI-Adapter für OpenAI-Modelle.** Exakte OpenAI-Modell-IDs, expliziter Reasoning-Effort und Service-Tier; technisch read-only ausgeführtes `codex exec`; schemaerzwungene Dateirückgabe; JSONL-Rohdaten und deterministische Normalisierung nach `RawResult.json`. Codex ist zunächst nur im Modus `solo` freigegeben. Zusätzlich fünf beschädigte Backslashes vor `analyse-anforderungen.py`, `anforderungen.*` und `after.txt` repariert. | Der bisherige Skill konnte nur Claude Code ausführen. Ein Codex-Lauf braucht andere Isolations-, Ausgabe- und Messmechanismen. MAJOR, weil Werkzeug, Rohdatenformat und Ergebnisübergabe neue Versuchsbedingungen bilden; der nächste Lauf eröffnet deshalb eine neue Iteration. | ab dem ersten Codex-Lauf |
| **1.0.0** | Ausgangsfassung: `--permission-mode acceptEdits`, kein Shell-Zugriff; Isolation durch Löschen der KI-Konfigurationsdateien im Root vor dem Lauf und `git restore` danach | Erstaufsetzung des Versuchsaufbaus | Lauf 1 (`01_Lauf_2026-08-25_1228`) |
| **2.0.0** | `--allowedTools "Bash" "PowerShell"` + 33er-Denylist für schreibende und bauende Kommandos. Isolation über **eingefrorenen Codebasis-Snapshot ohne KI-Konfigurationen** (Commit `79c1142`, Parent `89ccfd6`, GitHub-Remote entkoppelt), zusätzlich `--safe-mode` und `--strict-mcp-config`. Der destruktive Lösch-/Restore-Schritt pro Lauf entfällt. `permission_denials` und `subagent_stats` werden reguläre Messgrößen; Verbrauchstabelle trennt Hauptagent und Gesamtlauf. CLI-Pfad-Auflösung als eigener Schritt. | Lauf 1 erzeugte 36 Permission-Denials (32 × Bash, 4 × PowerShell); Shell-gestützte Verzeichnisinventuren fehlten dem Agenten. `--safe-mode` allein genügt nicht: Es unterdrückt das Vorladen von `CLAUDE.md`/`AGENTS.md`, verhindert aber nicht, dass der Agent sie mit Shell-Zugriff selbst liest – im Smoke-Test nachgewiesen. | Lauf 2 (`01_Lauf_2026-08-25_1349`, API-Abbruch) |
| **2.0.1** | `before.txt` wird leerwertsicher geschrieben (`Set-Content -Value (… \| Out-String)` statt Pipeline) | Bei sauberem Root schreibt `Set-Content` aus leerer Pipeline die Datei nicht und lässt alten Inhalt stehen; der Vorher/Nachher-Vergleich meldete dadurch eine Abweichung, die es nicht gab | Lauf 3 (`01_Lauf_2026-08-25_1429`) |
| **2.1.0** | **Modell wird vor jedem Lauf beim User erfragt** (Pflichtparameter, kein Default); Kurzformen `opus`/`sonnet`/`fable` untersagt | Das Modell ist eine unabhängige Variable und wurde bis dahin ad hoc gewählt. Kurzformen lösen auf das jeweils neueste Modell auf und verschieben die Versuchsbedingung über die Zeit. | Lauf 4 (`01_Lauf_2026-08-25_1505`) |
| **2.1.1** | Warnung zur Snapshot-Prüfung: Regex nicht vereinfachen, `Select-String` ist case-insensitiv; Beispiel-Falschtreffer dokumentiert | Ein verkürztes Muster sprach auf echte Produktdateien an (`ClaudeCodeChatModelClient.cs`, `GoogleGeminiChatModelClient.cs` – KI-Assistenz-Feature der ERP-Suite) und hätte beinahe einen Fehlabbruch ausgelöst | – |
| **3.0.0** | **Agentenmodus als Pflichtparameter** (`solo` / `builtin` / `custom`), vor jedem Lauf beim User erfragt. `solo` sperrt `Task`/`Agent`, `custom` übergibt vordefinierte Agenten per `--agents`. Neue Protokollfelder: Agentenmodus und Kontrolle `subagent_stats.spawned`. | Der Subagenten-Einsatz schwankte bei identischem Prompt zwischen 0 und 14 und war damit die dominierende Störgröße: Drei Läufe unter sonst identischer Bedingung ergaben 55 / 106 / 325 Anforderungen und 27,6 / 11,5 / 52,7 Mio. Tokens (Faktor 5,9 bzw. 4,6). Ohne Festlegung des Modus ist keine Bedingung sauber messbar. | ab Versuch 1 neu |
| **3.1.0** | **Parallelfähigkeit**: Laufverzeichnis sekundengenau plus Modus und 4-stellige Zufalls-ID; alle Steuerdateien nach `_meta\` im Laufverzeichnis statt in den gemeinsamen Scratchpad; neue Protokollfelder „Laufverzeichnis-ID" und „Parallele Läufe"; Abschnitt zu den messtechnischen Grenzen des Parallelbetriebs | Minutengenaue Namen kollidieren bei zwei Starts in derselben Minute, und geteilte Scratchpad-Dateien (`before.txt`, `combined_prompt.md`, Zeitstempel) hätten sich zwischen parallelen Läufen gegenseitig überschrieben. Nebeneffekt: Der exakt gesendete Prompt ist nun je Lauf archiviert. | – |
| **3.2.0** | Verzeichnisname enthält zusätzlich **Modell-Kurzform und Skill-Version**: `<NN>_Lauf_<yyyy-MM-dd_HHmmss>_<modell>_<modus>_v<skillversion>-<id4>`. Solo-Modus sperrt zusätzlich **`Workflow`**. | Modell, Modus und Skill-Version sind die unabhängigen Variablen der Reihe und waren bisher nur im Protokoll sichtbar – im Namen verhindern sie, dass Läufe verschiedener Bedingungen gemeinsam ausgewertet werden. Die `Workflow`-Sperre schließt eine im Verifikationstest entdeckte Lücke: Nach der Sperre von `Task`/`Agent` versuchte der Agent, über `Workflow` zu orchestrieren. | ab Lauf 7 |
| **3.3.0** | **Subagenten-Prompts werden protokolliert.** Neues Skript `extract-subagenten.py` zieht aus dem Session-Transkript alle Subagenten-Aufrufe samt vollständigem Prompt nach `_meta\subagenten.md` / `.json`; neues Protokollfeld „Subagenten-Prompts". | Die selbstgewählte Zerlegung der Analyse ist die dominierende Störgröße (0 bis 14 Subagenten bei identischem Prompt) und war bisher nur als Zahl sichtbar. Mit den Prompts wird sie inhaltlich auswertbar und für V2 zum Vergleichsmaßstab gegenüber vorformulierten Agenten. | rückwirkend für alle Läufe angewandt |
| **3.4.0** | **Effort als dritter Pflichtparameter**: `--effort` wird vor jedem Lauf erfragt und im Protokoll geführt; Thinking-Tokens sind in der Verbrauchstabelle vorgesehen. | Der Denkaufwand ist eine unabhängige Variable, wurde aber bis dahin nur aus der Sitzung geerbt und nirgends dokumentiert. `RawResult.json` enthält **kein** `effort`-Feld – nachträglich ist er nur aus dem Session-Transkript belegbar. Rekonstruktion ergab: alle Läufe 1 bis B liefen auf `high`, die Bedingung war also konstant. | ab dem nächsten Lauf |
| **3.5.0** | **Berichtete Aufwandsgröße ist der Tokenverbrauch statt der USD-Kosten.** Verbrauchstabelle und alle Vergleiche führen „Tokens gesamt" (Input + Output + Cache-Write + Cache-Read über alle Modelle); `total_cost_usd` bleibt als Rohwert in `RawResult.json`, wird aber nicht mehr ins Protokoll übernommen. | USD-Beträge hängen an Preisliste und Modellwahl und veralten. Token sind die unmittelbare Verbrauchsgröße, bleiben über Preisänderungen und Modellwechsel hinweg vergleichbar und sind in der Arbeit ohne Währungsbezug zitierbar. | rückwirkend auf alle Protokolle angewandt |
| **3.6.0** | **Verschachtelte Subagenten dokumentiert und verifiziert.** Belegt, dass `modelUsage` auch Sub-Subagenten (Tiefe ≥ 2) erfasst; neues Protokollfeld „Verschachtelung" mit `spawned`, `spawned_by_subagents` und `max_depth`. Festgehalten, dass Subagenten-Transkripte nicht separat persistiert werden. | Bis dahin war unbelegt, ob tiefere Ebenen in die berichtete Tokensumme einfließen. Kontrolltest mit erzwungener Kaskade (Arbeit ausschließlich auf Tiefe 2): 49.103 Tokens im Hauptagenten gegenüber 1.689.288 in `modelUsage` – die Differenz stammt nachweislich vom Enkel-Agenten. | ab sofort |
| **3.7.0** | **Effort im Verzeichnisnamen**: `<NN>_Lauf_<yyyy-MM-dd_HHmmss>_<modell>_<modus>_e<effort>_v<skillversion>-<id4>`, z. B. `_sonnet5_solo_ehigh_v3.7.0-a3f1`. | Das Schema entstand, als der Effort bei allen Läufen konstant `high` war. Mit dem ersten `max`-Block (Läufe P, Q) variiert er – gleichnamige Verzeichnisse hätten unterschiedliche Bedingungen bezeichnet und damit den Zweck des Schemas verfehlt. Der Effort ist zudem als möglicherweise stärkste Einflussgröße aufgefallen: Der `max`-Block erreichte die höchsten Thinking-Token-Werte der gesamten Reihe. | rückwirkend auf alle Laufverzeichnisse angewandt |
| **3.8.0** | **Pflichtprüfung der tatsächlich eingesetzten Modelle.** `modelUsage` ist nach jedem Lauf gegen das angeforderte Modell abzugleichen; neue Protokollfelder „Modell (angefordert)", „Modelle (tatsächlich eingesetzt)" und „Kontrolle Modell". | `--model` steuert nur den Hauptagenten. Im Lauf `fable5_builtin_ehigh_v3.7.0-15db` liefen die 13 Subagenten auf `claude-opus-5[1m]` statt auf Fable – 91 % des Verbrauchs entfielen auf ein nicht angefordertes Modell, und der Lauf wurde mit 150,3 Mio. Tokens der teuerste der Reihe. Ohne diese Prüfung bleibt die Bedingungsverletzung unbemerkt. | ab sofort; Gegenprüfung der 23 Vorläufe ergab keine weiteren Fälle |
| **3.9.0** | **Pflichtabschnitt „Gefundene Anforderungen" im Protokoll.** Neues Skript `analyse-anforderungen.py` wertet die erzeugten Anforderungen aus: Verteilung, Typen, Belegqualität (`PRIMÄR`/`SEKUNDÄR`/`KONTEXT`), Status, Konsolidierungskandidaten und **Regelkonformität gegen die Vorgaben des Prompts**. | Die Protokolle maßen bis dahin nur den Aufwand, nicht den Ertrag. Die reine Anforderungsanzahl taugt nicht als Qualitätsmaß (Streuung Faktor 5,9 bei gleicher Bedingung); Belegdichte und Regelverstöße sind inhaltliche Größen. Erste Anwendung deckte sofort Verstöße gegen die risikobasierte Priorisierung auf. | rückwirkend auf alle 24 Protokolle angewandt |
| **3.10.0** | **Bedingungen als Ordnerstruktur statt im Dateinamen.** Ablage unter `<ModellID>\<Agentenmodus>\<Effort>\`; der Verzeichnisname lautet wieder `<NN>_Lauf_<yyyy-MM-dd_HHmmss>_v<skillversion>-<id4>`. Neues Protokollfeld „Ablage". | Der Name wuchs mit jeder unabhängigen Variable und war mit fünf Bestandteilen kaum lesbar. Als Ordnerebenen sind die Bedingungen navigierbar, je Zelle unmittelbar abzählbar und um weitere Variablen erweiterbar, ohne bestehende Namen zu brechen. | rückwirkend auf alle 24 Läufe angewandt |
| **4.0.0** | **Ausrichtung auf Kapitel 4 der Arbeit.** Skill zweigeteilt in `## Prozess` (werkzeugneutral) und `## Werkzeugadapter` (konkrete Aufrufe, derzeit nur Claude Code). Der Prompt trägt nur noch die Analyseanweisung; Werkzeugkontext und Ausgabeverzeichnis werden zur Laufzeit angehängt. Versuchszuordnung korrigiert: **V1 = `solo`, V1b = `builtin`**. Neue Protokollfelder: Kontextfenster, Sampling-Parameter, lokale Runtime-Angaben, Abschnitt Validierungsstichprobe. Abschnitt „Gefundene Anforderungen" den drei Qualitätsdimensionen zugeordnet. | Prompt und Skill waren über 24 Läufe organisch gewachsen und stellenweise nicht mehr deckungsgleich mit Kap. 4. Der Prompt enthielt Aussagen zur Werkzeugkonfiguration, die dort nicht hingehören und ihn an Claude Code banden; der Skill verankerte weder den Evaluationsrahmen noch die von Kap. 4.3 geforderten Reproduzierbarkeitsangaben. MAJOR, weil sich die Zuordnung der Versuchsbedingungen ändert. | ab sofort |
| **4.1.0** | **Ebene `<Versuchstag>` über den Bedingungen**: `Tag 1`, `Tag 2`, …; Schritt 8 ermittelt den höchsten vorhandenen Tag. Protokollfeld „Ablage" entsprechend erweitert. | Der Versuchsaufbau änderte sich am ersten Tag über 17 Skill-Versionen hinweg. Die Tagesebene hält Blöcke auseinander, die unter unterschiedlichem Stand entstanden sind, und macht sichtbar, welche Läufe überhaupt unter vergleichbaren Rahmenbedingungen liefen. | rückwirkend: alle 24 Läufe nach `Tag 1` verschoben |
| **4.5.0** | `extract-subagenten.py` trennt am Nebenläufigkeitslimit **abgewiesene** Aufrufe von echten Subagenten-Starts und weist sie getrennt aus. | Der erste Lauf mit mehr als 20 gleichzeitigen Subagenten (`Iteration 3/…/125032_v4.4.0-fb24`, 31 gestartet, `refused.concurrency_limit` = 29) meldete eine Abweichung von 21 gefundenen gegenüber 13 erwarteten Aufrufen. Ursache waren 8 Absagen im Haupttranskript, die wie Starts aussehen, aber nur „Concurrent subagent limit reached" zurückliefern und nicht in `spawned` zählen. Ohne die Trennung wäre jeder stark parallelisierende Lauf mit einer Scheinwarnung versehen. MINOR: neue Messgröße, keine Änderung der Versuchsbedingung. | ab sofort; die drei `builtin`-Läufe der Iteration 3 wurden neu extrahiert und stimmen exakt überein |
| **4.4.0** | Zwei Umbenennungen zur Auflösung einer Begriffskollision. **(a)** Ordnerebene `<Versuchstag>` → `<Iteration>` (`Tag N` → `Iteration N`), mit ausformuliertem Kriterium: Eine neue Iteration eröffnet, was Läufe nicht mehr poolbar macht – Codebasis-Snapshot, Prompt-Version, Werkzeugkonfiguration oder eine MAJOR-Version dieses Skills. **(b)** Die Fassung der Prompt-Datei heißt nicht mehr „Iteration", sondern **Prompt-Version**; Protokollfeld entsprechend umbenannt. | „Tag" band die Ebene an das Kalenderdatum, obwohl sie die Vergleichbarkeit abbildet: Am 26.08. entstanden zwei nicht poolbare Blöcke am selben Tag – ohne und mit dem DB-Schema-Dump `SSMS_DB_SCHEMA.sql` (Commit `f349d189`). Die Umbenennung in „Iteration" kollidierte dann mit der Prompt-Iteration: Iteration 2 und Iteration 3 laufen beide unter derselben Prompt-Fassung. Erst die zweite Umbenennung macht beide Begriffe eindeutig. MINOR: reine Benennung, keine Änderung der Versuchsbedingung. | rückwirkend: `Tag 1` → `Iteration 1` (24 Läufe), `Tag 2` → `Iteration 2` (5 Läufe); die fünf Läufe mit DB-Schema nach `Iteration 3`. Die Prompt-Dateien selbst bleiben unverändert – ihre SHA-256 sind in 33 Protokollen dokumentiert. |
| **4.3.0** | `analyse-anforderungen.py` bestimmt die Ebene einer Anforderung aus dem Feld `Ebene:` beziehungsweise dem ID-Präfix statt aus dem Dateinamen und weist Fremdablage als eigene Auffälligkeit aus. | Das Skript zählte die Ebene nach der Datei, in der ein Block stand. Im Lauf `Iteration 2/…/094250_v4.2.1-c69e` lagen 12 StRS- und 5 SyRS-Blöcke in `SwRS.md`; die Verteilungstabelle meldete daraufhin 9/10/141 statt der tatsächlichen 21/15/124. Die Gesamtzahl war korrekt, die Verteilung – Kenngröße der Set-Qualität nach Kap. 4.3 – nicht. Gegenprobe an einem sauber abgelegten Lauf (`084301_v4.2.0-d6f9`): unverändert 20/120/30. MINOR, weil eine Messgröße korrigiert und eine neue Auffälligkeit ergänzt wird, ohne die Versuchsbedingung zu ändern. | ab sofort; die 24 Protokolle von Iteration 1 wurden geprüft: keine Fremdablage |
| **4.2.1** | Vorher/Nachher-Prüfung des Roots wird **pfadskopiert** ausgeführt: `git -C <repo> status --porcelain -- <pfad-des-roots>` statt `git -C <root> status --porcelain` (Schritte 7, 9 und Abschnitt 4). | Seit Commit `f045b99a` liegt die Codebasis als Dateien im Arbeitsrepo statt als Gitlink. Die unskopierte Abfrage lieferte damit den Status des gesamten Arbeitsrepos – beim Lauf `Iteration 2/…/084301_v4.2.0-d6f9` rund 55 KB Ausgabe, praktisch nur Versuchsdateien. Ohne Pfadfilter hätte die Read-only-Verifikation ab sofort bei **jedem** Lauf eine Abweichung gemeldet, die es nicht gibt. PATCH: keine Änderung der Versuchsbedingung, nur eine korrigierte Messung. | ab Lauf `084301_v4.2.0-d6f9` (dort bereits so ausgeführt und im Protokoll vermerkt) |
| **4.2.0** | **Auswahlregel bei mehreren Prompt-Versionen**: ohne ausdrückliche Angabe gilt die höchste Prompt-Versionsnummer; neues Protokollfeld „Prompt-Version". Ältere Fassungen bleiben unverändert. | Mit `02_Prompt.md` liegt erstmals mehr als eine Fassung im Versuchsordner. Ohne Regel wäre unklar, welche gilt, und der SHA-256 im Protokoll ließe sich keiner Datei mehr zuordnen. Bei der Einführung fiel auf, dass `01_Prompt.md` nachträglich verändert worden war – der in 24 Protokollen dokumentierte Hash zeigte auf eine Fassung, die es nicht mehr gab. | ab Prompt-Version 02 |
## Parallele Läufe
Mehrere Läufe dürfen gleichzeitig gestartet werden. Voraussetzung ist, dass **kein Zustand
geteilt** wird:
- **Verzeichnisname** ist sekundengenau und trägt Modus plus 4-stellige Zufalls-ID – zwei
gleichzeitige Starts können nicht kollidieren (Schritt 7 würfelt bei Kollision neu).
- **Alle Steuerdateien** (`combined_prompt.md`, `before.txt`, `after.txt`, Zeitstempel) liegen
in `_meta\` **im jeweiligen Laufverzeichnis**. Der gemeinsame Scratchpad wird für Laufdaten
**nicht** verwendet – dort würden parallele Läufe einander überschreiben.
- **Der Codebasis-Snapshot wird nur gelesen.** Beliebig viele Läufe dürfen ihn gleichzeitig
lesen. Die Vorher/Nachher-Prüfung bleibt gültig, weil kein Lauf schreibt.
- **Endeerkennung je Lauf** über das Erscheinen der eigenen `RawResult.json`, nicht über einen
gemeinsamen Marker.
**Messtechnische Einschränkung – im Protokoll zwingend vermerken.** Gleichzeitig laufende
Versuche konkurrieren um CPU, Netzwerk und API-Kontingent. Betroffen sind:
| Messgröße | Bei Parallelbetrieb |
|---|---|
| Wanduhrzeit | **verzerrt** – nicht mit seriellen Läufen vergleichbar |
| `duration_ms`, `duration_api_ms` | **verzerrt** |
| Tokenverbrauch, Anforderungsanzahl, Denials | unverzerrt |
Für Laufzeitvergleiche daher **seriell** messen. Parallelbetrieb eignet sich, um schnell
mehrere Datenpunkte für Umfang, Tokenverbrauch und Varianz zu sammeln. Das Feld
**„Parallele Läufe"** im Protokoll nennt die zeitgleich laufenden Laufverzeichnisse; steht dort
etwas anderes als `nein`, sind die Zeitangaben des Laufs nicht für Laufzeitvergleiche zu
verwenden.
## Zuordnung Versuch ↔ Agentenmodus
Nach Kapitel 4 der Arbeit (Versuchsdesign):
| Versuch | Modus | Bedeutung |
|---|---|---|
| **V1** – Baseline (Prompt-only) | `solo` | Ein Thread, ein Kontext; keine Subagenten |
| **V1b** – Baseline mit werkzeugeigenen Agenten | `builtin` | Eingebaute Subagenten des Werkzeugs zugelassen |
| **V2** – Agentengestützt | `custom` | Rollenspezialisierte Agentendateien: Stakeholder, System, Software, ISO-29148-Orchestrator |
| **V3** – Werkzeugzugriff | `custom` + MCP | Wie V2, zusätzlich externe Werkzeugserver |
**Abgrenzung V1 gegen V1b.** Die Arbeit unterscheidet zwischen *Agentendateien* (V2) und den
*werkzeugeigenen* Subagenten, die eine CLI von sich aus mitbringt. Letztere sind keine
Konfigurationsartefakte und damit nach dem Wortlaut von V1 nicht ausgeschlossen – ihr Einsatz
verändert die Ergebnisse jedoch erheblich. V1b trennt beide Fälle, sodass der Effekt der
werkzeugeigenen Delegation getrennt vom Effekt der rollenspezialisierten Agentendateien
messbar wird.
**Bisherige Läufe.** Die 24 Läufe in `Versuche/Versuch_01/` verteilen sich auf V1 (`solo`) und
V1b (`builtin`). Die Ordnerstruktur macht die Zuordnung unmittelbar sichtbar; die Zellen sind
über `ls "<Iteration>/<ModellID>/<Modus>/<Effort>" | wc -l` abzählbar. Alle 24 liegen unter `Iteration 1`.