--- type: plan-task title: "Task 01 — verbrauch.js: Abweichung erkennen, Brückensatz, Fälle und Mutanten" status: draft summary: "verbrauch.js bekommt den Brückensatz im Auftrag, die Erkennung abgewichen mit Warnzeile und den neuen Status in meta; faelle_workflows.mjs bekommt die Fälle dazu, mutanten_workflows.py die Mutanten. Alles in Kopien im Scratchpad." tags: [plan-task, vault-tools, codeworkflow] created: 2026-10-16 updated: 2026-10-16 modell: sonnet modell_begruendung: "verbrauch.js ist Abschrift, die Fälle sind nur als Eingabe und Erwartung vorgegeben und brauchen Autorschaft." --- Übersicht: [00-uebersicht.md](00-uebersicht.md) # Task 01 — Abweichung erkennen ## Arbeitsorte `S` ist `D:/ClaudeData/claude/D--Claude/8a6a0a87-ddcf-462b-90e2-83ab3ba2be28/scratchpad`. Du schreibst **nur** diese drei Dateien: - `S/wf-neu/verbrauch.js` (Kopie von `D:/Claude/.claude/workflows/verbrauch.js`) - `S/wp-neu/faelle_workflows.mjs` (Kopie aus `D:/Claude/.vault-tools/workflow-pruefung/`) - `S/wp-neu/mutanten_workflows.py` (Kopie, ebenda) Nichts unter `D:/Claude/` anfassen. Keine Hilfsdateien. Kein Commit. ## 1 verbrauch.js Ersetze genau diese Stellen, Zeichen für Zeichen. Die Datei hat LF-Zeilenenden; behalte sie. **1a, in `meta.whenToUse`:** `Rückgabe {status: ok|nicht_gelesen|unbekannte_fenster,` wird zu `Rückgabe {status: ok|nicht_gelesen|abgewichen|unbekannte_fenster,`. Und `Ohne lesbare Ablesung gilt die alte Obergrenze zwei.` wird zu `Ohne lesbare Ablesung gilt die alte Obergrenze zwei; abgewichen heißt, der Ableser hat statt get_usage etwas anderes getan, und kommt mit einer WARNUNG im Log.` **1b, Kopfkommentar:** Vor der Zeile `// Was das Skript nicht sieht: Agenten, die nach der Ablesung in anderen Sitzungen starten;` kommen diese Zeilen: ```js // - Ob der Ableser abgelesen hat: Quelle. Der Auftrag schreibt im Fehlerfall status "fehler", // plan "" und fenster [] vor; get_usage kennt laut Werkzeugbeschreibung (gelesen 2026-10-16) // nur die Status ok, not_applicable und unavailable. Am 2026-10-14 und 2026-10-16 hat der // Ableser stattdessen die Nutzeranfrage bearbeitet und status "beantwortet: ..." bzw. // "blockiert: ..." mit einem Bericht in plan zurückgegeben (wf_7ca17029-421, wf_80616e8e-0e3). // Grund laut CLI-Abbild 2.1.299: Jeder Workflow-Agent liest zuerst die Nutzeranfrage mit dem // Satz, dass sie im Konflikt mit dem Auftrag gewinnt. Jede andere Form als die vorgeschriebene // heißt abgewichen und wird laut gemeldet. Nicht erkannt wird ein Ableser, der abweicht und // trotzdem genau die Fehlerform schreibt, und eine erfundene Ablesung mit status ok. ``` **1c, Auftrag:** Die erste Zeile des Template-Strings `AUFTRAG` ist bisher `const AUFTRAG = \`Lade mit dem Werkzeug ToolSearch ...`. Davor, als neue erste Zeile im String, kommt dieser Satz; der Rest bleibt unverändert: ```js const AUFTRAG = `Diese Ablesung gehört zur Nutzeranfrage und widerspricht ihr nicht: Sie entscheidet, wie viele Prüfagenten für die Anfrage gleichzeitig laufen dürfen. Die Anfrage selbst bearbeiten andere Agenten nach dir; du bearbeitest sie nicht, auch nicht teilweise, und liest dafür keine Dateien. Lade mit dem Werkzeug ToolSearch das Werkzeug mcp__ccd_session_mgmt__get_usage (Abfrage "select:mcp__ccd_session_mgmt__get_usage") und rufe es einmal ohne Parameter auf. ``` **1d, Erkennung:** Direkt nach der Funktion `einordnen` (nach ihrer schließenden `}` und vor der Leerzeile, auf die `pruefeArgs(args)` folgt) kommt: ```js // Form eines Ablesers, der den Auftrag verlassen hat (Geltungsbereich oben). const STATUS_BEKANNT = ['ok', 'not_applicable', 'unavailable', 'fehler'] function abgewichen(a) { if (!STATUS_BEKANNT.includes(a.status)) return true return a.status === 'fehler' && typeof a.plan === 'string' && a.plan.trim() !== '' } const kurz = t => String(t).replace(/\s+/g, ' ').trim().slice(0, 160) ``` **1e, Rückgabe:** Nach der Zeile `if (!ablesung || typeof ablesung !== 'object') return ohneAblesung('nicht_gelesen', 'kein Ergebnis vom Ableseagenten')` und vor der Zeile `if (ablesung.verfuegbar !== true || ablesung.status !== 'ok') ...` kommt: ```js if (abgewichen(ablesung)) { log(`WARNUNG Verbrauch ${args.anlass}: Der Ableser hat get_usage nicht gelesen und den Auftrag verlassen; die Bremse misst nicht. Seine Werkzeugaufrufe im Journal unter ▸ verbrauch ansehen.`) return ohneAblesung('abgewichen', `Ableser hat den Auftrag verlassen: status ${kurz(ablesung.status)}${ablesung.fehler ? ', fehler ' + kurz(ablesung.fehler) : ''}`) } ``` Sonst änderst du an `verbrauch.js` nichts. ## 2 faelle_workflows.mjs Neue Fälle im Abschnitt `verbrauch`, nach dem Fall `Agent ohne Ergebnis: ...`. Feste Testdaten, gekürzt und neutral, in der Form der echten Abweichungen: ```js // Form der echten Abweichungen vom 2026-10-14 und 2026-10-16, Texte gekürzt und neutral. const abweichung = (status, fehler) => ({ verfuegbar: false, status, plan: '**Stand:** Bericht zur Nutzeranfrage statt Ablesung.\n- Punkt 1\n- Punkt 2', fenster: [], fehler }) const BEANTWORTET = abweichung('beantwortet: offene Punkte im Projekt X, Stand 2026-10-10', 'Kein get_usage-Aufruf und kein Planverbrauch abgelesen: Die Benutzeranfrage hat laut Harness Vorrang vor dem Verbrauchsauftrag.') const BLOCKIERT = abweichung('blockiert: Folgesitzung nicht abgeschlossen; nichts geaendert', 'Blockade: Workflow- und Agent-Werkzeug fehlen. Kein get_usage-Aufruf, siehe plan.') const warnung = p => p.logs.filter(l => /^WARNUNG/.test(l)) ``` Fälle, je mit `pruefe()` und `soll` wie die Nachbarn; die Abweichung kommt als Antwort des Ablesers (`V({ anlass: 't', wunsch: 3 }, { ablesungen: [X] })`), nicht über `probe`: | Name | Eingabe | Erwartung | |---|---|---| | `Ableser beantwortet die Nutzeranfrage: abgewichen, Warnung, zwei Plätze` | `BEANTWORTET`, `wunsch: 3` | `status 'abgewichen'`, `plaetze 2`, `gebremst false`, `quelle 'agent'`, `grund` passt auf `/Auftrag verlassen/` und enthält `beantwortet:`, genau eine Warnzeile | | `Ableser meldet blockiert: abgewichen, Warnung` | `BLOCKIERT`, `wunsch: 1` | `status 'abgewichen'`, `plaetze 1`, `grund` enthält `blockiert:`, genau eine Warnzeile | | `status fehler mit Bericht in plan: abgewichen` | `{ verfuegbar: false, status: 'fehler', plan: 'Bericht', fenster: [], fehler: 'x' }` | `status 'abgewichen'`, eine Warnzeile | | `ehrlicher Fehlschlag bleibt nicht_gelesen, ohne Warnung` | `{ verfuegbar: false, status: 'fehler', plan: '', fenster: [], fehler: 'No matching deferred tools found' }` | `status 'nicht_gelesen'`, `plaetze 2`, keine Warnzeile | | `not_applicable bleibt nicht_gelesen, ohne Warnung` | `{ verfuegbar: true, status: 'not_applicable', plan: '', fenster: [], fehler: '' }` | `status 'nicht_gelesen'`, keine Warnzeile | | `grund kürzt einen langen status auf 160 Zeichen, ohne Zeilenumbruch` | `abweichung('beantwortet:\n' + 'x'.repeat(400), '')` | `status 'abgewichen'`; der Teil von `grund` hinter `status ` ist höchstens 160 Zeichen lang und enthält kein `\n` | | `Auftrag ordnet die Ablesung der Nutzeranfrage zu` | Grundlinie ohne Abweichung | `p.agents[0].prompt` enthält `gehört zur Nutzeranfrage` und `bearbeiten andere Agenten`. Kommentar über dem Fall: misst nur, dass der Satz da ist, nicht, ob er wirkt | Zwei neue Fälle im Abschnitt `plankritik`, nach `Ablesung ohne Ergebnis: Linsen zu zweit (alte Obergrenze)` (Reihenfolge: derselbe Schaden an erster und letzter Ablesung): | Name | Eingabe | Erwartung | |---|---|---| | `Abweichung bei der ersten Ablesung: Linsen zu zweit, Status abgewichen im Ergebnis` | `PK(PK_ARGS, { ablesungen: [BEANTWORTET] })` | `e.status 'fertig'`, Spitze `Plankritik`/`opus` gleich 2, `e.verbrauch[0].status 'abgewichen'`, eine Warnzeile im Protokoll | | `Abweichung bei der letzten Ablesung: Status abgewichen im Ergebnis` | `PK(PK_ARGS, { ablesungen: [undefined, undefined, BLOCKIERT] })` | `e.status 'fertig'`, `e.verbrauch[2].status 'abgewichen'`, `e.verbrauch[0].status 'ok'`, eine Warnzeile | Sonst änderst du an `faelle_workflows.mjs` nichts. ## 3 mutanten_workflows.py Diese Einträge in `MUTANTEN`, direkt nach `("verbrauch", "5-Stunden-Regex unverankert", ...)`: ```python ("verbrauch", "Abweichung nicht erkannt", "if (abgewichen(ablesung)) {", "if (false) {"), ("verbrauch", "unbekannter Status gilt als bekannt", " if (!STATUS_BEKANNT.includes(a.status)) return true\n", ""), ("verbrauch", "Bericht in plan bei status fehler uebersehen", "return a.status === 'fehler' && typeof a.plan === 'string' && a.plan.trim() !== ''", "return false"), ("verbrauch", "ehrlicher Fehler gilt als Abweichung", "'unavailable', 'fehler']", "'unavailable']"), ("verbrauch", "Abweichung als nicht_gelesen", "return ohneAblesung('abgewichen',", "return ohneAblesung('nicht_gelesen',"), ("verbrauch", "Warnzeile fehlt", "log(`WARNUNG Verbrauch ${args.anlass}: Der Ableser", "String(`WARNUNG Verbrauch ${args.anlass}: Der Ableser"), ("verbrauch", "status ungekuerzt im grund", ".trim().slice(0, 160)", ".trim()"), ("verbrauch", "Brueckensatz fehlt", "Diese Ablesung gehört zur Nutzeranfrage und widerspricht ihr nicht: Sie entscheidet, wie viele Prüfagenten für die Anfrage gleichzeitig laufen dürfen. Die Anfrage selbst bearbeiten andere Agenten nach dir; du bearbeitest sie nicht, auch nicht teilweise, und liest dafür keine Dateien.\n", ""), ``` ## Prüfen Mit `S` wie oben, je Befehl Ausgabe und Exit-Code in den Bericht: ```bash # Neue Fälle gegen den neuen Stand: alle grün node "$S/wp-neu/faelle_workflows.mjs" "$S/wf-neu"; echo "EXIT $?" # Rotlauf: neue Fälle gegen den alten Stand; die Gegenproben ehrlicher Fehlschlag und not_applicable bleiben grün, die übrigen neuen Fälle scheitern node "$S/wp-neu/faelle_workflows.mjs" "D:/Claude/.claude/workflows" | grep -v "^OK"; echo "EXIT ${PIPESTATUS[0]}" # Mutanten gegen den neuen Stand: kein UEBERLEBT, kein VERALTET (läuft einige Minuten) python "$S/wp-neu/mutanten_workflows.py" "$S/wf-neu" | grep -v "^GEFANGEN"; echo "EXIT ${PIPESTATUS[0]}" ``` Melde am Ende: geänderte Dateien, die drei Ausgaben, und **was du anders gemacht hast als beauftragt und warum**.