Iteration Budget: fehlgeschlagene Validierung zählt, und lint zwingt zum Doppelaufruf #13

Closed
opened 2026-08-30 19:44:23 +00:00 by torben · 1 comment
Owner

Beobachtet im Ingest vom 2026-08-30, der das Budget von 30 Aufrufen erschöpfte und publish nur noch mit --override-budget erreichte. Zwei der Aufrufe waren vermeidbar, und beide gehen auf das Werkzeug zurück, nicht auf die Aufgabe.

1. Ein Validierungsfehler verbraucht einen Budget-Slot, obwohl nichts geschah

$ tools/wikitool new source --name "..." --set "raw_files=...Versioning, CI-CD..."
ERROR raw_files path does not exist: raw/notes/Conversation Transcript -
Versioning

Der Aufruf schrieb keine Datei und änderte keinen Zustand, wurde aber gezählt. tools/CONTRACT.md führt genau diesen Fall als „Not transient; fix the argument and retry once" — die vorgeschriebene Reaktion kostet also zwangsläufig zwei Slots für eine Operation.

Das Budget existiert, um Iterieren zu begrenzen. Ein Aufruf, der an der Argumentprüfung scheitert, ist kein Iterationsschritt; er ist ein Tippfehler.

Vorschlag: Nicht zählen, wenn die Ausführung vor jeder Zustandsänderung an einer Validierung abbricht. Der Loop-Breaker (3 identische Aufrufe hintereinander) fängt den Fall ab, dass jemand denselben falschen Aufruf wiederholt, und ist dafür das passendere Werkzeug.

2. lint nennt seinen Report-Pfad nicht, also wird es erneut aufgerufen

Der Bericht ist lang; wer ihn abschnittsweise liest, schneidet mit head/tail und braucht dann einen zweiten Blick auf einen anderen Teil. lint schreibt den Bericht nach reports/, sagt aber im Terminal nicht, wohin — so ist der naheliegende Reflex ein zweiter lint-Aufruf statt ein kostenloser Read auf die Datei. Im genannten Ingest passierte genau das, zweimal.

Erschwerend: In reports/ liegt ein Unterverzeichnis telemetry/, sodass ein ls -t reports/ | head -1 auf ein Verzeichnis zeigt statt auf den jüngsten Bericht.

Vorschläge:

  • lint gibt am Ende eine Zeile mit dem geschriebenen Report-Pfad aus (OK Report written to reports/Lint Report YYYY-MM-DD.md), analog zu index rebuild und sources rebuild-index.
  • Optional lint --summary, das nur die Abschnitte mit Befunden ausgibt. Der Vollbericht ist beim sauberen Korpus zu über 90 % „None found."
  • Die Ausgabe von lint könnte, wie search, vom Budget ausgenommen sein: Sie ist eine Leseoperation über den Korpus und ändert nichts. Das ist die weitergehende Variante und sollte getrennt entschieden werden.

Kontext

Beide Punkte betreffen den in instructions/gates.md kalibrierten Rahmen: „15-25 Aufrufe für einen komplexen Multi-Tool-Workflow wie einen Ingest". Der Ingest vom 2026-08-30 lag inhaltlich in diesem Rahmen; die Überschreitung kam aus dem Werkzeug.

Verwandt: #12 (--set kann keine Array-Werte mit Komma ausdrücken) — der Auslöser des ersten Punkts.

Beobachtet im Ingest vom 2026-08-30, der das Budget von 30 Aufrufen erschöpfte und `publish` nur noch mit `--override-budget` erreichte. Zwei der Aufrufe waren vermeidbar, und beide gehen auf das Werkzeug zurück, nicht auf die Aufgabe. ## 1. Ein Validierungsfehler verbraucht einen Budget-Slot, obwohl nichts geschah ``` $ tools/wikitool new source --name "..." --set "raw_files=...Versioning, CI-CD..." ERROR raw_files path does not exist: raw/notes/Conversation Transcript - Versioning ``` Der Aufruf schrieb keine Datei und änderte keinen Zustand, wurde aber gezählt. `tools/CONTRACT.md` führt genau diesen Fall als „Not transient; fix the argument and **retry once**" — die vorgeschriebene Reaktion kostet also zwangsläufig zwei Slots für eine Operation. Das Budget existiert, um Iterieren zu begrenzen. Ein Aufruf, der an der Argumentprüfung scheitert, ist kein Iterationsschritt; er ist ein Tippfehler. **Vorschlag:** Nicht zählen, wenn die Ausführung vor jeder Zustandsänderung an einer Validierung abbricht. Der Loop-Breaker (3 identische Aufrufe hintereinander) fängt den Fall ab, dass jemand denselben falschen Aufruf wiederholt, und ist dafür das passendere Werkzeug. ## 2. `lint` nennt seinen Report-Pfad nicht, also wird es erneut aufgerufen Der Bericht ist lang; wer ihn abschnittsweise liest, schneidet mit `head`/`tail` und braucht dann einen zweiten Blick auf einen anderen Teil. `lint` schreibt den Bericht nach `reports/`, sagt aber im Terminal nicht, wohin — so ist der naheliegende Reflex ein zweiter `lint`-Aufruf statt ein kostenloser `Read` auf die Datei. Im genannten Ingest passierte genau das, zweimal. Erschwerend: In `reports/` liegt ein Unterverzeichnis `telemetry/`, sodass ein `ls -t reports/ | head -1` auf ein Verzeichnis zeigt statt auf den jüngsten Bericht. **Vorschläge:** - `lint` gibt am Ende eine Zeile mit dem geschriebenen Report-Pfad aus (`OK Report written to reports/Lint Report YYYY-MM-DD.md`), analog zu `index rebuild` und `sources rebuild-index`. - Optional `lint --summary`, das nur die Abschnitte mit Befunden ausgibt. Der Vollbericht ist beim sauberen Korpus zu über 90 % „None found." - Die Ausgabe von `lint` könnte, wie `search`, vom Budget ausgenommen sein: Sie ist eine Leseoperation über den Korpus und ändert nichts. Das ist die weitergehende Variante und sollte getrennt entschieden werden. ## Kontext Beide Punkte betreffen den in `instructions/gates.md` kalibrierten Rahmen: „15-25 Aufrufe für einen komplexen Multi-Tool-Workflow wie einen Ingest". Der Ingest vom 2026-08-30 lag inhaltlich in diesem Rahmen; die Überschreitung kam aus dem Werkzeug. Verwandt: #12 (`--set` kann keine Array-Werte mit Komma ausdrücken) — der Auslöser des ersten Punkts.
Author
Owner

Behoben in 1.2.0 (40adbb7). Zwei der drei Vorschläge umgesetzt, der dritte bewusst nicht.

1. Validierungsfehler kostet keinen Slot mehr

Ein Aufruf, der über _util.fail() mit Exit 1 endet, wird zurückerstattet (run_budget.refund(), ausgelöst in cli._run_traced).

Die Abgrenzung liegt bewusst an fail() und nicht am Exit-Code: fail() heißt „das Kommando hat abgelehnt" — abgewiesenes Argument oder ein lesender Check, der Befunde meldet. Ein Kommando, das seine Arbeit schon getan hat und dann ungleich null endet, wirft typer.Exit(1) direkt und bleibt gezählt. lint --fail-on-error ist genau dieser Fall: es hat den Bericht bereits geschrieben.

Die Historie bleibt. Der Aufruf landet weiter in recent, der Loop-Breaker sieht ihn also. Drei identische kaputte Aufrufe brechen weiterhin ab — es gibt einen Test dafür, weil das die Gegenprobe zur Erstattung ist.

Real nachgemessen:

$ tools/wikitool new source --name "Refund Probe" --set "raw_files=raw/notes/Versioning, CI-CD.md"
ERROR raw_files path does not exist: raw/notes/Versioning
  This is one element after splitting the value on commas. ...

$ tools/wikitool budget status
Calls so far: 0 (limit 60)
Recent calls:
  - new source --name Refund Probe --set raw_files=raw/notes/Versioning, CI-CD.md

2. lint nennt seinen Reportpfad

Der Befund im Issue war schärfer als die Überschrift: ohne --markdown schrieb lint gar keine Datei, es kippte den Vollbericht nach stdout. Deshalb gab es keinen Pfad zu nennen — und keinen Weg zurück zum übersprungenen Teil außer einem zweiten Lauf.

  • Der Vollbericht wird jetzt immer geschrieben, per Default nach reports/Lint Report <date>.md, und der Pfad wird ausgegeben. --markdown überschreibt das Ziel wie bisher.
  • Gedruckt werden nur die Abschnitte mit Befunden (--summary aus dem Issue ist damit das Standardverhalten, nicht ein Flag). --full druckt alles, --json schreibt weiterhin nichts.
  • wiki-lint und wiki-status sagen jetzt beide ausdrücklich: die Datei lesen, nicht lint erneut aufrufen. wiki-status Schritt 3 holt die Hub-Statistik aus dem Bericht — sie ist kein Befund und steht deshalb nicht mehr in der Zusammenfassung.

Der reports/-Unterordner telemetry/, der ls -t in die Irre führte, ist damit kein Problem mehr: der Pfad steht in der Ausgabe.

3. Ceiling 30 → 60

Auf Anweisung mit umgesetzt. Das Kalibrierungsband (5-15 einfach, 15-25 komplex) bleibt unverändert — es beschreibt die Arbeit. Die Decke beschrieb bisher nichts: sie lag so dicht am Band, dass der Overhead eines echten Ingests sie allein erreichte. AGENTS.md, instructions/gates.md, tools/CONTRACT.md, README.md, das work plan-Template und die Unit-Größe in migrate-corpus.md (jetzt „nahe 55 Seiten; ziele auf 48") sind mitgezogen.

Der Loop-Breaker bleibt bei 3. Er ist kein Budget, sondern ein Detektor für dreimal denselben Aufruf; ihn zu verdoppeln hieße, einen feststeckenden Agenten doppelt so lange laufen zu lassen.

Nicht umgesetzt

lint ganz vom Budget zu befreien — das Issue sagt selbst, das gehört getrennt entschieden. Der Fall hat sich außerdem verschoben: lint schreibt jetzt eine Datei, ist also nicht mehr rein lesend.

Behoben in **1.2.0** (`40adbb7`). Zwei der drei Vorschläge umgesetzt, der dritte bewusst nicht. ## 1. Validierungsfehler kostet keinen Slot mehr Ein Aufruf, der über `_util.fail()` mit Exit 1 endet, wird zurückerstattet (`run_budget.refund()`, ausgelöst in `cli._run_traced`). Die Abgrenzung liegt bewusst an `fail()` und nicht am Exit-Code: `fail()` heißt „das Kommando hat abgelehnt" — abgewiesenes Argument oder ein lesender Check, der Befunde meldet. Ein Kommando, das seine Arbeit schon getan hat und *dann* ungleich null endet, wirft `typer.Exit(1)` direkt und bleibt gezählt. `lint --fail-on-error` ist genau dieser Fall: es hat den Bericht bereits geschrieben. **Die Historie bleibt.** Der Aufruf landet weiter in `recent`, der Loop-Breaker sieht ihn also. Drei identische kaputte Aufrufe brechen weiterhin ab — es gibt einen Test dafür, weil das die Gegenprobe zur Erstattung ist. Real nachgemessen: ``` $ tools/wikitool new source --name "Refund Probe" --set "raw_files=raw/notes/Versioning, CI-CD.md" ERROR raw_files path does not exist: raw/notes/Versioning This is one element after splitting the value on commas. ... $ tools/wikitool budget status Calls so far: 0 (limit 60) Recent calls: - new source --name Refund Probe --set raw_files=raw/notes/Versioning, CI-CD.md ``` ## 2. `lint` nennt seinen Reportpfad Der Befund im Issue war schärfer als die Überschrift: ohne `--markdown` schrieb `lint` gar keine Datei, es kippte den Vollbericht nach stdout. Deshalb gab es keinen Pfad zu nennen — und keinen Weg zurück zum übersprungenen Teil außer einem zweiten Lauf. - Der Vollbericht wird jetzt **immer** geschrieben, per Default nach `reports/Lint Report <date>.md`, und der Pfad wird ausgegeben. `--markdown` überschreibt das Ziel wie bisher. - Gedruckt werden nur die Abschnitte mit Befunden (`--summary` aus dem Issue ist damit das Standardverhalten, nicht ein Flag). `--full` druckt alles, `--json` schreibt weiterhin nichts. - `wiki-lint` und `wiki-status` sagen jetzt beide ausdrücklich: die Datei lesen, nicht `lint` erneut aufrufen. `wiki-status` Schritt 3 holt die Hub-Statistik aus dem Bericht — sie ist kein Befund und steht deshalb nicht mehr in der Zusammenfassung. Der `reports/`-Unterordner `telemetry/`, der `ls -t` in die Irre führte, ist damit kein Problem mehr: der Pfad steht in der Ausgabe. ## 3. Ceiling 30 → 60 Auf Anweisung mit umgesetzt. Das Kalibrierungsband (5-15 einfach, 15-25 komplex) bleibt unverändert — es beschreibt die Arbeit. Die Decke beschrieb bisher nichts: sie lag so dicht am Band, dass der Overhead eines echten Ingests sie allein erreichte. `AGENTS.md`, `instructions/gates.md`, `tools/CONTRACT.md`, `README.md`, das `work plan`-Template und die Unit-Größe in `migrate-corpus.md` (jetzt „nahe 55 Seiten; ziele auf 48") sind mitgezogen. **Der Loop-Breaker bleibt bei 3.** Er ist kein Budget, sondern ein Detektor für dreimal denselben Aufruf; ihn zu verdoppeln hieße, einen feststeckenden Agenten doppelt so lange laufen zu lassen. ## Nicht umgesetzt `lint` ganz vom Budget zu befreien — das Issue sagt selbst, das gehört getrennt entschieden. Der Fall hat sich außerdem verschoben: `lint` schreibt jetzt eine Datei, ist also nicht mehr rein lesend.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#13