gpt skill ergünzt

This commit is contained in:
Christoph Schwörer
2026-08-26 16:54:32 +02:00
parent 844b4e5569
commit affde3a45f
3 changed files with 376 additions and 33 deletions
+191 -33
View File
@@ -1,15 +1,15 @@
--- ---
name: run-experiment name: run-experiment
description: Führt einen Versuchs-Prompt aus einer Prompt-Datei als messbaren Headless-Lauf aus (claude -p) 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. description: Führt einen Versuchs-Prompt aus einer Prompt-Datei als messbaren Headless-Lauf mit Claude Code oder Codex CLI 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> argument-hint: <Pfad zur Prompt-Datei> <Root-Verzeichnis>
version: 4.5.0 version: 5.0.0
--- ---
# RunExperiment – Versuchslauf mit Messprotokoll # RunExperiment – Versuchslauf mit Messprotokoll
Du führst einen wissenschaftlichen Versuchslauf für die Masterarbeit aus. Der Prompt aus der Du führst einen wissenschaftlichen Versuchslauf für die Masterarbeit aus. Der Prompt aus der
übergebenen Datei wird als **separater Headless-Lauf** von Claude Code ausgeführt, damit übergebenen Datei wird als **separater Headless-Lauf** über den zum Modell passenden
Tokenverbrauch und Modell exakt und maschinenlesbar erfasst werden. Anschließend Werkzeugadapter ausgeführt, damit Tokenverbrauch und Modell maschinenlesbar erfasst werden. Anschließend
schreibst du ein Messprotokoll neben die Prompt-Datei. schreibst du ein Messprotokoll neben die Prompt-Datei.
## Parameter ## Parameter
@@ -39,7 +39,8 @@ Laufverzeichnis neben der Prompt-Datei**, niemals im Root-Verzeichnis:
<Iteration>\<ModellID>\<Agentenmodus>\<Effort>\ <Iteration>\<ModellID>\<Agentenmodus>\<Effort>\
<NN>_Lauf_<yyyy-MM-dd_HHmmss>_v<skillversion>-<id4>\ <NN>_Lauf_<yyyy-MM-dd_HHmmss>_v<skillversion>-<id4>\
Protokoll.md Protokoll.md
RawResult.json RawResult.json (adapterübergreifend normalisierte Messdaten)
RawEvents.jsonl (nur Codex: unveränderter JSONL-Ereignisstrom)
Stderr.log Stderr.log
_meta\ (Laufsteuerung: combined_prompt.md, before.txt, after.txt, Zeitstempel, _meta\ (Laufsteuerung: combined_prompt.md, before.txt, after.txt, Zeitstempel,
subagenten.md/.json, anforderungen.md/.json) subagenten.md/.json, anforderungen.md/.json)
@@ -99,7 +100,8 @@ Der Skill ist zweigeteilt:
- **`## Prozess`** beschreibt werkzeugneutral, **was** je Lauf zu tun und zu erheben ist. Dieser - **`## 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. Teil gilt unabhängig davon, mit welchem LLM oder welcher CLI gearbeitet wird.
- **`## Werkzeugadapter`** beschreibt **wie** das mit einem konkreten Werkzeug umgesetzt wird. - **`## Werkzeugadapter`** beschreibt **wie** das mit einem konkreten Werkzeug umgesetzt wird.
Derzeit ist nur der Adapter für Claude Code ausgearbeitet. Ausgearbeitet sind Adapter für Claude Code und Codex CLI. 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* **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 und ist damit von jedem LLM verwendbar. Alles Werkzeug- und Ablaufbezogene – verfügbare
@@ -120,7 +122,11 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
Prompt-Versionen bleiben unverändert liegen – sie sind der Beleg dafür, unter welcher Fassung 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. 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. 2. Aus dem Metadaten-Block der Prompt-Datei (falls vorhanden) Versuch/Prompt-Version übernehmen.
3. **CLI-Pfad auflösen.** Unter Windows liegt `claude` in der Regel **nicht im PATH**. Erst 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.
Keine Modell-ID an eine CLI übergeben, die sie nicht unterstützt.
Unter Windows liegt `claude` in der Regel **nicht im PATH**. Erst
`Get-Command claude -ErrorAction SilentlyContinue` versuchen; schlägt das fehl, auf die `Get-Command claude -ErrorAction SilentlyContinue` versuchen; schlägt das fehl, auf die
VSCode-Extension zurückfallen und die höchste Versionsnummer wählen: VSCode-Extension zurückfallen und die höchste Versionsnummer wählen:
```powershell ```powershell
@@ -130,13 +136,17 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
Sort-Object FullName | Select-Object -Last 1 -ExpandProperty FullName Sort-Object FullName | Select-Object -Last 1 -ExpandProperty FullName
} }
``` ```
Findet sich keine ausführbare Datei: abbrechen und den User informieren. Den aufgelösten Für OpenAI-Modelle entsprechend `Get-Command codex -ErrorAction SilentlyContinue` verwenden;
Pfad fürs Protokoll festhalten. 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.
4. **Modell beim User erfragen.** Hat der User das Modell nicht bereits im Aufruf genannt, 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 **immer** per `AskUserQuestion` nachfragen – auch dann, wenn frühere Läufe derselben
Versuchsreihe ein bestimmtes Modell verwendet haben. Es gibt bewusst keinen Default. Versuchsreihe ein bestimmtes Modell verwendet haben. Es gibt bewusst keinen Default.
Als Optionen die vollen Modell-IDs anbieten, nicht die Kurzformen: Als Optionen die vollen Modell-IDs anbieten, nicht die Kurzformen. Nur aktuell dokumentierte
und von der installierten CLI angebotene IDs aufnehmen:
| Modell-ID | Einordnung | | Modell-ID | Einordnung |
|---|---| |---|---|
@@ -145,6 +155,12 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
| `claude-fable-5` | schnell und günstig | | `claude-fable-5` | schnell und günstig |
| `claude-haiku-4-5-20251001` | günstigstes Modell | | `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 |
In der Frage den letzten verwendeten Stand nennen, damit der User bewusst wechseln oder 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"). bewusst wiederholen kann (z. B. „Lauf 3 lief mit `claude-sonnet-5`, 11.516.200 Tokens, 23:40").
@@ -153,6 +169,11 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
Versuchsbedingung nicht reproduzierbar. Für ein 1-Mio.-Token-Fenster die Variante mit Versuchsbedingung nicht reproduzierbar. Für ein 1-Mio.-Token-Fenster die Variante mit
`[1m]`-Suffix wählen (z. B. `claude-opus-5[1m]`). `[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 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. 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 5. **Agentenmodus beim User erfragen.** Wie beim Modell: kein Default, immer nachfragen, wenn
@@ -194,23 +215,29 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
der Prompt per SHA-256 gehasht und im Protokoll geführt. So bleibt der eingefrorene Snapshot der Prompt per SHA-256 gehasht und im Protokoll geführt. So bleibt der eingefrorene Snapshot
unangetastet und die Agentenkonfiguration ist eine dokumentierte, versionierte unangetastet und die Agentenkonfiguration ist eine dokumentierte, versionierte
Versuchsbedingung. Format siehe `claude --help` zu `--agents`. Versuchsbedingung. Format siehe `claude --help` zu `--agents`.
**Codex-Adapter in 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 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`. bereits im Aufruf genannt hat. Stufen: `low`, `medium`, `high`, `xhigh`, `max`.
Übergabe per `--effort <stufe>`. Ü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 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 `high`** – geerbt aus der Sitzungseinstellung, nicht bewusst gesetzt; der Wert wurde
nachträglich aus den Transkripten belegt. Wer die Stufe wechselt, ändert die nachträglich aus den Transkripten belegt. Wer die Stufe wechselt, ändert die
Versuchsbedingung und macht den Lauf mit den bisherigen unvergleichbar. Versuchsbedingung und macht den Lauf mit den bisherigen unvergleichbar.
**Wichtig – der Effort ist aus `RawResult.json` nicht rekonstruierbar.** Das Ergebnisobjekt **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 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 (`"effort": "high"`). Fehlt er im Protokoll, ist die Bedingung nachträglich allein über das
Transkript belegbar – und das nur, solange die Session persistiert ist. Transkript belegbar – und das nur, solange die Session persistiert ist.
7. Messgrößen VOR dem Lauf erfassen (PowerShell): 7. Messgrößen VOR dem Lauf erfassen (PowerShell):
- Startzeit: `Get-Date -Format o` - Startzeit: `Get-Date -Format o`
- SHA-256 der Prompt-Datei: `(Get-FileHash <datei> -Algorithm SHA256).Hash` - SHA-256 der Prompt-Datei: `(Get-FileHash <datei> -Algorithm SHA256).Hash`
- Claude-Code-Version: `& $claude --version` - Adapter und CLI-Version: `& $cli --version`
- Git-Zustand des Root-Verzeichnisses: `git -C <root> rev-parse HEAD` und - Git-Zustand des Root-Verzeichnisses: `git -C <root> rev-parse HEAD` und
`git -C <repo> status --porcelain -- <pfad-des-roots>` (dirty ja/nein). `git -C <repo> status --porcelain -- <pfad-des-roots>` (dirty ja/nein).
**Immer pfadskopiert prüfen.** Liegt die Codebasis als Unterverzeichnis im Arbeitsrepo **Immer pfadskopiert prüfen.** Liegt die Codebasis als Unterverzeichnis im Arbeitsrepo
@@ -228,7 +255,7 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
Sort-Object { [int]($_.Name -replace '\D','') } Sort-Object { [int]($_.Name -replace '\D','') }
$iteration = if ($iterationen) { $iterationen[-1].Name } else { 'Iteration 1' } $iteration = if ($iterationen) { $iterationen[-1].Name } else { 'Iteration 1' }
$zelle = Join-Path (Join-Path (Join-Path $iteration $modell) $modus) $effort $zelle = Join-Path (Join-Path (Join-Path $iteration $modell) $modus) $effort
$skillVer = 'v4.5.0' # entspricht version: im Frontmatter dieses Skills $skillVer = 'v5.0.0' # entspricht version: im Frontmatter dieses Skills
do { do {
$id4 = '{0:x4}' -f (Get-Random -Maximum 65536) $id4 = '{0:x4}' -f (Get-Random -Maximum 65536)
$lauf = Join-Path "<promptverzeichnis>\$zelle" "<NN>_Lauf_$(Get-Date -Format 'yyyy-MM-dd_HHmmss')_${skillVer}-$id4" $lauf = Join-Path "<promptverzeichnis>\$zelle" "<NN>_Lauf_$(Get-Date -Format 'yyyy-MM-dd_HHmmss')_${skillVer}-$id4"
@@ -320,9 +347,26 @@ Schreibe ALLE zu erzeugenden Ergebnisdateien in das Verzeichnis
Verändere keine Dateien im Arbeitsverzeichnis (der analysierten Codebasis). Verändere keine Dateien im Arbeitsverzeichnis (der analysierten Codebasis).
``` ```
**Codex-Abweichung bei Block 2.** Der Codex-Adapter läuft mit einem technisch erzwungenen
`read-only`-Sandboxmodus. Er kann deshalb auch das externe Laufverzeichnis nicht direkt als
Agent beschreiben. 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. 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) ### 3. Ausführung (Headless-Lauf)
Dann den kombinierten Prompt per stdin an `claude -p` übergeben. `--add-dir` gibt dem 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` und darf
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. Headless-Lauf Schreibrecht auf das Laufverzeichnis außerhalb seines Arbeitsverzeichnisses.
Versuchsläufe können lange dauern – **immer als Background-Task starten** (`run_in_background`), Versuchsläufe können lange dauern – **immer als Background-Task starten** (`run_in_background`),
nicht mit Foreground-Timeout arbeiten: nicht mit Foreground-Timeout arbeiten:
@@ -426,8 +470,9 @@ Regeln:
### 4. Ergebnis auswerten ### 4. Ergebnis auswerten
`RawResult.json` im Laufverzeichnis lesen und defensiv parsen (Feldnamen können je nach Claude-Code-Version `RawResult.json` im Laufverzeichnis lesen und defensiv parsen. Beim Claude-Adapter ist dies die
leicht abweichen). Relevante Felder: unveränderte CLI-Antwort; beim Codex-Adapter erzeugt `normalise-codex-result.py` diese Datei aus
`RawEvents.jsonl` und `_meta\final_response.json`. Relevante Claude-Felder:
| Feld | Bedeutung | | Feld | Bedeutung |
|---|---| |---|---|
@@ -541,12 +586,12 @@ Vorgabe delegiert – gegenüber den vorformulierten Agenten aus `--agents`.
**Anforderungen auswerten** – der inhaltliche Ertrag des Laufs, nicht nur sein Aufwand: **Anforderungen auswerten** – der inhaltliche Ertrag des Laufs, nicht nur sein Aufwand:
```powershell ```powershell
python "<skillverzeichnis>nalyse-anforderungen.py" "<laufverzeichnis>" python "<skillverzeichnis>\analyse-anforderungen.py" "<laufverzeichnis>"
``` ```
Das Skript parst das im Prompt vorgegebene Blockformat (`ID:`, `Typ:`, `Belege:`, `Status:` …) Das Skript parst das im Prompt vorgegebene Blockformat (`ID:`, `Typ:`, `Belege:`, `Status:` …)
aus `Ergebnisse\StRS.md`, `SyRS.md` und `SwRS.md` und schreibt `_metanforderungen.md` aus `Ergebnisse\StRS.md`, `SyRS.md` und `SwRS.md` und schreibt `_meta\anforderungen.md`
(fertiger Protokollabschnitt) sowie `_metanforderungen.json` (maschinenlesbar). Erhoben werden: (fertiger Protokollabschnitt) sowie `_meta\anforderungen.json` (maschinenlesbar). Erhoben werden:
- **Verteilung** über die drei Ebenen - **Verteilung** über die drei Ebenen
- **Anforderungstypen** (funktional, Sicherheit, Daten, Schnittstelle …) - **Anforderungstypen** (funktional, Sicherheit, Daten, Schnittstelle …)
@@ -588,7 +633,7 @@ Der erzeugte Abschnitt wird **unverändert** als `## Gefundene Anforderungen` in
Erzeugte Dateien: Inhalt von `<laufverzeichnis>\Ergebnisse\` auflisten. Zusätzlich prüfen, 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 ob das Root unverändert blieb: `git -C <repo> status --porcelain -- <pfad-des-roots>` – mit
**demselben Pfadfilter wie in Abschnitt 1** – gegen `before.txt` **demselben Pfadfilter wie in Abschnitt 1** – gegen `before.txt`
vergleichen (Nachher-Stand nach `$lauf\_metafter.txt`) – Abweichungen als Auffälligkeit vergleichen (Nachher-Stand nach `$lauf\_meta\after.txt`) – Abweichungen als Auffälligkeit
ins Protokoll. Beim Vergleich Zeilenenden ins Protokoll. Beim Vergleich Zeilenenden
normalisieren (`before.txt` entsteht je nach Werkzeug mit CRLF, der Nachher-Stand mit LF), normalisieren (`before.txt` entsteht je nach Werkzeug mit CRLF, der Nachher-Stand mit LF),
sonst meldet ein naiver `diff` alle Zeilen als geändert. sonst meldet ein naiver `diff` alle Zeilen als geändert.
@@ -616,11 +661,14 @@ Vorlage:
## Werkzeugkonfiguration ## Werkzeugkonfiguration
- **Skill-Version:** <version aus dem Frontmatter dieses Skills> - **Skill-Version:** <version aus dem Frontmatter dieses Skills>
- **Claude-Code-Version:** <version> - **Werkzeugadapter:** <Claude Code | Codex CLI>
- **CLI-Pfad:** <aufgelöster Pfad zur claude.exe> - **CLI-Version:** <version>
- **CLI-Pfad:** <aufgelöster Pfad zur ausführbaren Datei>
- **Modell (angefordert):** <Wert von `--model`> - **Modell (angefordert):** <Wert von `--model`>
- **Modelle (tatsächlich eingesetzt):** <alle Schlüssel aus `modelUsage` mit Tokenanteil> - **Modelle (tatsächlich eingesetzt):** <alle Schlüssel aus `modelUsage` mit Tokenanteil |
- **Kontrolle Modell:** <bestanden | **verletzt**: nicht angefordertes Modell <ID> mit <N> Tokens> 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 - **Effort:** <low | medium | high | xhigh | max> (per `--effort` gesetzt; Gegenprobe im
Transkript-Feld `effort`) Transkript-Feld `effort`)
- **Laufverzeichnis-ID:** `v<skillversion>-<id4>` aus dem Verzeichnisnamen - **Laufverzeichnis-ID:** `v<skillversion>-<id4>` aus dem Verzeichnisnamen
@@ -632,9 +680,9 @@ Vorlage:
- **Sampling-Parameter:** <Temperatur u. a. | nicht steuerbar> - **Sampling-Parameter:** <Temperatur u. a. | nicht steuerbar>
- **Nur bei lokalem Modellbetrieb:** Inferenz-Runtime samt Version, Quantisierungsstufe des - **Nur bei lokalem Modellbetrieb:** Inferenz-Runtime samt Version, Quantisierungsstufe des
Modell-Builds Modell-Builds
- **Permission-Mode:** <acceptEdits | ...> - **Permission-/Sandbox-Modus:** <acceptEdits | read-only / approval never | ...>
- **Toolfreigabe:** `--allowedTools <wörtlich>` / `--disallowedTools <wörtlich>` - **Toolfreigabe:** <adapterabhängige Flags wörtlich>
- **Isolationsmechanismus:** <--safe-mode, --strict-mcp-config, ggf. --setting-sources> - **Isolationsmechanismus:** <adapterabhängige Flags wörtlich>
- **MCP-Server / Agentendateien:** <keine – aus dem Snapshot entfernt, zusätzlich --safe-mode | Liste> - **MCP-Server / Agentendateien:** <keine – aus dem Snapshot entfernt, zusätzlich --safe-mode | Liste>
- **Subagenten:** <Anzahl und Typ aus `subagent_stats`, z. B. 8 × Explore, 0 fehlgeschlagen> - **Subagenten:** <Anzahl und Typ aus `subagent_stats`, z. B. 8 × Explore, 0 fehlgeschlagen>
- **Verschachtelung:** `spawned` = <N>, davon `spawned_by_subagents` = <M>, `max_depth` = <D>. - **Verschachtelung:** `spawned` = <N>, davon `spawned_by_subagents` = <M>, `max_depth` = <D>.
@@ -676,9 +724,13 @@ Das ist die **berichtete Aufwandsgröße** der Versuchsreihe. `total_cost_usd` b
`RawResult.json` erhalten, wird aber nicht ins Protokoll übernommen: Token sind modell- und `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. 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 ## Gefundene Anforderungen
<unverändert aus `_metanforderungen.md` übernehmen – Verteilung, Typen, Belegqualität, <unverändert aus `_meta\anforderungen.md` übernehmen – Verteilung, Typen, Belegqualität,
Status, Regelkonformität> Status, Regelkonformität>
## Ergebnis ## Ergebnis
@@ -709,8 +761,9 @@ Werkzeug umgesetzt wird und welche Messgrößen dieses Werkzeug liefert.
### Adapter: Claude Code ### Adapter: Claude Code
Der einzige derzeit ausgearbeitete Adapter. Alle konkreten Aufrufe, Flags und Feldnamen im Alle mit `claude -p`, `--safe-mode`, `--allowedTools`, `modelUsage` und `subagent_stats`
Prozessteil beziehen sich auf ihn; er wurde gegen **CLI 2.1.245** entwickelt und verifiziert. 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`: **Gelieferte Messgrößen** – vollständig aus `RawResult.json`:
@@ -729,10 +782,114 @@ Prozessteil beziehen sich auf ihn; er wurde gegen **CLI 2.1.245** entwickelt und
werden nicht ausgewiesen; der Effort steht **nicht** in `RawResult.json` und ist nur über das werden nicht ausgewiesen; der Effort steht **nicht** in `RawResult.json` und ist nur über das
Session-Transkript belegbar (siehe Schritt 6 der Vorbereitung). 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:** In Version 5.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.** Codex arbeitet im Root mit `--sandbox read-only`. Anders als beim
Claude-Adapter erhält der Agent deshalb kein beschreibbares Zusatzverzeichnis. Er liefert 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. Der eigentliche
Aufruf lautet:
```powershell
$codexArgs = @(
'--model', $modell,
'-c', "model_reasoning_effort=`"$effort`"",
'-c', 'service_tier="default"',
'--sandbox', 'read-only',
'--ask-for-approval', 'never',
'--disable', 'multi_agent',
'-c', 'agents.enabled=false',
'--disable', 'plugins',
'--disable', 'apps',
'--disable', 'hooks',
'--disable', 'skill_search',
'--cd', $root,
'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 'normalise-codex-result.py') $lauf `
--model $modell --effort $effort
```
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
erzeugte `RawResult.json`. 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 read-only` | technische Schreibsperre für den Codebasis-Snapshot |
| `--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 ohne Schreibrecht des Agenten |
`--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.
### Weitere Adapter (für Versuch 4 nachzurüsten) ### Weitere Adapter (für Versuch 4 nachzurüsten)
Kapitel 4 der Arbeit sieht einen LLM-Querschnitt über Codex CLI, Qwen Code CLI über LM Studio Kapitel 4 der Arbeit sieht zusätzlich Qwen Code CLI über LM Studio und DeepSeek über die
und DeepSeek über die Cloud-API vor. Diese Adapter sind noch nicht ausgearbeitet. Damit ein Cloud-API vor. Diese Adapter sind noch nicht ausgearbeitet. Damit ein
Lauf als Messpunkt taugt, muss ein Adapter mindestens liefern: Lauf als Messpunkt taugt, muss ein Adapter mindestens liefern:
| Pflichtangabe | Zweck | | Pflichtangabe | Zweck |
@@ -789,6 +946,7 @@ der Historie unten – im selben Arbeitsschritt.
| Version | Änderung | Grund | Verwendet in | | Version | Änderung | Grund | Verwendet in |
|---|---|---|---| |---|---|---|---|
| **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`) | | **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.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.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`) |
@@ -0,0 +1,20 @@
{
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": { "type": "string", "minLength": 1 },
"content": { "type": "string" }
},
"required": ["path", "content"],
"additionalProperties": false
}
},
"summary": { "type": "string" }
},
"required": ["files", "summary"],
"additionalProperties": false
}
@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""Materialize Codex result files and normalize its JSONL measurements."""
from __future__ import annotations
import argparse
import json
from collections import Counter
from datetime import datetime
from pathlib import Path, PurePosixPath
from typing import Any
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("run_directory", type=Path)
parser.add_argument("--model", required=True)
parser.add_argument("--effort", required=True)
return parser.parse_args()
def read_jsonl(path: Path) -> tuple[list[dict[str, Any]], list[str]]:
events: list[dict[str, Any]] = []
malformed: list[str] = []
for number, line in enumerate(path.read_text(encoding="utf-8-sig").splitlines(), 1):
if not line.strip():
continue
try:
value = json.loads(line)
except json.JSONDecodeError as exc:
malformed.append(f"line {number}: {exc}")
continue
if isinstance(value, dict):
events.append(value)
else:
malformed.append(f"line {number}: JSON value is not an object")
return events, malformed
def safe_result_path(results_dir: Path, raw_path: str) -> Path:
relative = PurePosixPath(raw_path.replace("\\", "/"))
if relative.is_absolute() or not relative.parts or ".." in relative.parts:
raise ValueError(f"unsafe result path: {raw_path!r}")
if any(part in ("", ".") or ":" in part for part in relative.parts):
raise ValueError(f"invalid result path: {raw_path!r}")
target = results_dir.joinpath(*relative.parts).resolve()
root = results_dir.resolve()
if root != target and root not in target.parents:
raise ValueError(f"result path escapes Ergebnisse: {raw_path!r}")
return target
def parse_iso(path: Path) -> datetime | None:
if not path.exists():
return None
value = path.read_text(encoding="utf-8-sig").strip()
try:
return datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError:
return None
def main() -> int:
args = parse_args()
run_dir = args.run_directory.resolve()
meta_dir = run_dir / "_meta"
results_dir = run_dir / "Ergebnisse"
events, malformed = read_jsonl(run_dir / "RawEvents.jsonl")
envelope = json.loads((meta_dir / "final_response.json").read_text(encoding="utf-8-sig"))
if not isinstance(envelope, dict) or not isinstance(envelope.get("files"), list):
raise ValueError("final_response.json does not match the Codex output envelope")
results_dir.mkdir(parents=True, exist_ok=True)
seen: set[str] = set()
materialized: list[str] = []
for entry in envelope["files"]:
if not isinstance(entry, dict):
raise ValueError("file entry is not an object")
raw_path = entry.get("path")
content = entry.get("content")
if not isinstance(raw_path, str) or not isinstance(content, str):
raise ValueError("file entry requires string path and content")
target = safe_result_path(results_dir, raw_path)
key = str(target).casefold()
if key in seen:
raise ValueError(f"duplicate result path: {raw_path!r}")
seen.add(key)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding="utf-8", newline="\n")
materialized.append(str(target.relative_to(results_dir)).replace("\\", "/"))
usage = Counter()
item_types = Counter()
errors: list[Any] = list(malformed)
thread_id = None
turns = 0
for event in events:
event_type = event.get("type")
if event_type == "thread.started":
thread_id = event.get("thread_id")
elif event_type == "turn.completed":
turns += 1
turn_usage = event.get("usage") or {}
for field in ("input_tokens", "cached_input_tokens", "output_tokens", "reasoning_output_tokens"):
value = turn_usage.get(field, 0)
if isinstance(value, int):
usage[field] += value
elif event_type in ("turn.failed", "error"):
errors.append(event)
if event_type == "item.completed":
item = event.get("item") or {}
if isinstance(item.get("type"), str):
item_types[item["type"]] += 1
start = parse_iso(meta_dir / "startzeit.txt")
end = parse_iso(meta_dir / "endzeit.txt")
duration_ms = round((end - start).total_seconds() * 1000) if start and end else None
exit_code = None
exit_code_path = meta_dir / "exitcode.txt"
if exit_code_path.exists():
try:
exit_code = int(exit_code_path.read_text(encoding="utf-8-sig").strip())
except ValueError:
errors.append("invalid exitcode.txt")
if exit_code not in (None, 0):
errors.append({"exit_code": exit_code})
normalized = {
"adapter": "codex-cli",
"is_error": bool(errors),
"subtype": "success" if not errors else "error",
"session_id": thread_id,
"duration_ms": duration_ms,
"duration_api_ms": None,
"num_turns": turns,
"requested_model": args.model,
"actual_models": None,
"model_control": "not_verifiable_from_codex_exec_jsonl",
"effort": args.effort,
"usage": {
"input_tokens": usage["input_tokens"],
"cached_input_tokens": usage["cached_input_tokens"],
"output_tokens": usage["output_tokens"],
"reasoning_output_tokens": usage["reasoning_output_tokens"],
"total_tokens": usage["input_tokens"] + usage["output_tokens"],
"semantics": "cached_input_tokens is a subset of input_tokens and is not added again",
},
"item_counts": dict(sorted(item_types.items())),
"permission_denials": None,
"subagent_stats": {"spawned": 0, "source": "multi-agent disabled by configuration"},
"materialized_files": materialized,
"result": envelope.get("summary", ""),
"errors": errors,
}
(run_dir / "RawResult.json").write_text(
json.dumps(normalized, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
newline="\n",
)
return 1 if errors else 0
if __name__ == "__main__":
raise SystemExit(main())