gpt skill ergünzt
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
---
|
||||
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>
|
||||
version: 4.5.0
|
||||
version: 5.0.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** von Claude Code ausgeführt, damit
|
||||
Tokenverbrauch und Modell exakt und maschinenlesbar erfasst werden. Anschließend
|
||||
ü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
|
||||
@@ -39,7 +39,8 @@ Laufverzeichnis neben der Prompt-Datei**, niemals im Root-Verzeichnis:
|
||||
<Iteration>\<ModellID>\<Agentenmodus>\<Effort>\
|
||||
<NN>_Lauf_<yyyy-MM-dd_HHmmss>_v<skillversion>-<id4>\
|
||||
Protokoll.md
|
||||
RawResult.json
|
||||
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)
|
||||
@@ -99,7 +100,8 @@ 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.
|
||||
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*
|
||||
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
|
||||
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. **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
|
||||
VSCode-Extension zurückfallen und die höchste Versionsnummer wählen:
|
||||
```powershell
|
||||
@@ -130,13 +136,17 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
|
||||
Sort-Object FullName | Select-Object -Last 1 -ExpandProperty FullName
|
||||
}
|
||||
```
|
||||
Findet sich keine ausführbare Datei: abbrechen und den User informieren. Den aufgelösten
|
||||
Pfad fürs Protokoll festhalten.
|
||||
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.
|
||||
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:
|
||||
Als Optionen die vollen Modell-IDs anbieten, nicht die Kurzformen. Nur aktuell dokumentierte
|
||||
und von der installierten CLI angebotene IDs aufnehmen:
|
||||
|
||||
| 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-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
|
||||
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
|
||||
`[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
|
||||
@@ -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
|
||||
unangetastet und die Agentenkonfiguration ist eine dokumentierte, versionierte
|
||||
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
|
||||
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
|
||||
`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.
|
||||
|
||||
**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
|
||||
(`"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`
|
||||
- Claude-Code-Version: `& $claude --version`
|
||||
- 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
|
||||
@@ -228,7 +255,7 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
|
||||
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 = 'v4.5.0' # entspricht version: im Frontmatter dieses Skills
|
||||
$skillVer = 'v5.0.0' # 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"
|
||||
@@ -320,9 +347,26 @@ Schreibe ALLE zu erzeugenden Ergebnisdateien in das Verzeichnis
|
||||
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)
|
||||
|
||||
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.
|
||||
Versuchsläufe können lange dauern – **immer als Background-Task starten** (`run_in_background`),
|
||||
nicht mit Foreground-Timeout arbeiten:
|
||||
@@ -426,8 +470,9 @@ Regeln:
|
||||
|
||||
### 4. Ergebnis auswerten
|
||||
|
||||
`RawResult.json` im Laufverzeichnis lesen und defensiv parsen (Feldnamen können je nach Claude-Code-Version
|
||||
leicht abweichen). Relevante Felder:
|
||||
`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`. Relevante Claude-Felder:
|
||||
|
||||
| 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:
|
||||
|
||||
```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:` …)
|
||||
aus `Ergebnisse\StRS.md`, `SyRS.md` und `SwRS.md` und schreibt `_metanforderungen.md`
|
||||
(fertiger Protokollabschnitt) sowie `_metanforderungen.json` (maschinenlesbar). Erhoben werden:
|
||||
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 …)
|
||||
@@ -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,
|
||||
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\_metafter.txt`) – Abweichungen als Auffälligkeit
|
||||
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.
|
||||
@@ -616,11 +661,14 @@ Vorlage:
|
||||
|
||||
## Werkzeugkonfiguration
|
||||
- **Skill-Version:** <version aus dem Frontmatter dieses Skills>
|
||||
- **Claude-Code-Version:** <version>
|
||||
- **CLI-Pfad:** <aufgelöster Pfad zur claude.exe>
|
||||
- **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>
|
||||
- **Kontrolle Modell:** <bestanden | **verletzt**: nicht angefordertes Modell <ID> mit <N> Tokens>
|
||||
- **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
|
||||
@@ -632,9 +680,9 @@ Vorlage:
|
||||
- **Sampling-Parameter:** <Temperatur u. a. | nicht steuerbar>
|
||||
- **Nur bei lokalem Modellbetrieb:** Inferenz-Runtime samt Version, Quantisierungsstufe des
|
||||
Modell-Builds
|
||||
- **Permission-Mode:** <acceptEdits | ...>
|
||||
- **Toolfreigabe:** `--allowedTools <wörtlich>` / `--disallowedTools <wörtlich>`
|
||||
- **Isolationsmechanismus:** <--safe-mode, --strict-mcp-config, ggf. --setting-sources>
|
||||
- **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>
|
||||
- **Subagenten:** <Anzahl und Typ aus `subagent_stats`, z. B. 8 × Explore, 0 fehlgeschlagen>
|
||||
- **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
|
||||
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 `_metanforderungen.md` übernehmen – Verteilung, Typen, Belegqualität,
|
||||
<unverändert aus `_meta\anforderungen.md` übernehmen – Verteilung, Typen, Belegqualität,
|
||||
Status, Regelkonformität>
|
||||
|
||||
## Ergebnis
|
||||
@@ -709,8 +761,9 @@ Werkzeug umgesetzt wird und welche Messgrößen dieses Werkzeug liefert.
|
||||
|
||||
### Adapter: Claude Code
|
||||
|
||||
Der einzige derzeit ausgearbeitete Adapter. Alle konkreten Aufrufe, Flags und Feldnamen im
|
||||
Prozessteil beziehen sich auf ihn; er wurde gegen **CLI 2.1.245** entwickelt und verifiziert.
|
||||
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`:
|
||||
|
||||
@@ -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
|
||||
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)
|
||||
|
||||
Kapitel 4 der Arbeit sieht einen LLM-Querschnitt über Codex CLI, Qwen Code CLI über LM Studio
|
||||
und DeepSeek über die Cloud-API vor. Diese Adapter sind noch nicht ausgearbeitet. Damit ein
|
||||
Kapitel 4 der Arbeit sieht zusätzlich Qwen Code CLI über LM Studio und DeepSeek über die
|
||||
Cloud-API vor. Diese Adapter sind noch nicht ausgearbeitet. Damit ein
|
||||
Lauf als Messpunkt taugt, muss ein Adapter mindestens liefern:
|
||||
|
||||
| Pflichtangabe | Zweck |
|
||||
@@ -789,6 +946,7 @@ der Historie unten – im selben Arbeitsschritt.
|
||||
|
||||
| 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`) |
|
||||
| **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`) |
|
||||
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user