Files
ClaudeDo/docs/superpowers/specs/2026-08-10-findings-store-design.md
T

12 KiB
Raw Blame History

Findings-Store — Design

Stand: 2026-08-10.

Persistenter, projektgebundener Speicher für Fallen und Invarianten, den Agents im Moment des Schmerzes selbst befüllen und in späteren Sessions vor dem Erkunden lesen. Ziel ist Token-Ersparnis durch verhinderte Fehlläufe.


1. Ausgangslage

Auslöser war die Frage, ob ein Code-Knowledge-Graph (Graphify-Bauart) das Wiedererkunden in jeder Session einspart. Die Messung sagt: nein, nicht in dieser Form.

Die Zahlen (aus docs/usage-optimization.md, 2026-08-05)

  • Kontext-Resend = 99,3 % des Rohverbrauchs. Jeder Kontext-Token wird im Schnitt ~425× nachberechnet. Das gilt symmetrisch: für gespartes und für geladenes Wissen.
  • Explore-note ersetzt Exploration: Note 4,6k tok in Turn 5 eines 60-Turn-Runs → 4,6k × 55 ≈ 250k. Ersparte Exploration (~8k tok Findings ab Turn 10) ≈ 400k. Netto ~150k, Faktor 1,6. Wird die Note geladen, ohne gebraucht zu werden: 250k.
  • Ein verhinderter Retry: ~11 Mio Token. 27 % der Tasks brauchten einen Retry.

Faktor 27 zwischen den beiden Klassen. Deshalb ist der Inhalt dieses Stores nicht „Architektur-Map, damit ich nicht neu erkunden muss" (400k-Klasse), sondern „Agent läuft in die bekannte Falle und der Run ist Müll" (11-Mio-Klasse).

Was heute schon da ist

Ebene Größe Ladeverhalten
CLAUDE.md (root) 6,4 KB ≈ 1,6k tok immer
src/*/CLAUDE.md (6×) 1,514 KB beim Arbeiten im Verzeichnis
docs/explore-notes/ (6 Notes) 1118 KB, 92 KB gesamt on demand, ganze Datei
Auto-Memory (~/.claude/projects/.../memory/) 55 Dateien, 397 KB Index immer (nur interaktiv)

Die Sektionen der Notes sind bereits 1236 Zeilen (~150450 tok) — also schon knotengroß. Und conpty-sessions.md markiert Fallen bereits konventionell als ## ⚠️ Gotcha: … (5 Stück). Die anderen Notes tragen dieselbe Art Wissen unmarkiert im Fließtext. Das Wissen ist zu großen Teilen schon geschrieben, nur nicht einsammelbar.


2. Verworfene Alternativen

  • Code-Knowledge-Graph (Graphify o. ä.). Ein tree-sitter-Graph liefert Call-Kanten — genau die Klasse, die Grep exakt und billig liefert (Grep = 58 % des Kontexts; Read = 6068 %, das Problem ist zu viel lesen, nicht Finden). Der wertvolle Inhalt ist semantisch und nicht ableitbar („Approve = merge the whole unit", „no directory arg may end in a separator"). Dazu: Build-/Staleness-Pipeline über N parallele Worktrees und Merge-Konflikte auf einem Graph-Artefakt im Repo. AXAML wird ohnehin nicht geparst.
  • DB-Tabelle + UI-Editor. Kuration passiert in VSCode, damit entfällt der einzige Vorteil. Agent-Zugriff bräuchte ein Lese-Tool statt Read; Agents könnten veraltete Einträge nicht selbst korrigieren.
  • Hook-Injektion der Findings in den System-Prompt. Früh und groß in den Prefix ist das teuerste Muster überhaupt (Befund 5), auch wenn nichts davon gebraucht wird.
  • Post-Merge-Trigger. Ursprünglicher Entwurf. Hinfällig, weil der Aufrufer des Tools den Kontext bereits hat — siehe §4.
  • Retro-Distill aus alten Transkripten. Zurückgestellt, siehe §8.

3. Speicher

<working-dir>/.claudedo/
  INDEX.md              # eine Zeile je Finding — die einzige Datei, die immer gelesen wird
  traps/<slug>.md       # ein Finding je Datei, ~150300 tok

maps/ ist reserviert, aber nicht Teil von v1.

Ordner liegt im Repo-Root, das er beschreibt — maximale Erkennbarkeit, reist bei Bedarf mit. .claudedo ist als Name frei; die Worktrees liegen unter dem Geschwister-Pfad .claudedo-worktrees, und TranscriptUsageReader prüft auf das exakte Segment, also kein Fehlalarm bei der Scope-Erkennung.

Finding-Format

---
slug: conpty-arg-quoting
scope: src/ClaudeDo.Worker/Planning
source-task: 5d627df8
verified-against: e10634b
---

# Ein Verzeichnis-Argument, das auf `\` endet, frisst alle folgenden Argumente

<26 Sätze: was passiert, warum es nicht danach aussieht, was man stattdessen tut.>

INDEX.md

# Findings

- [conpty-arg-quoting](traps/conpty-arg-quoting.md) — Dir-Arg auf `\` escapet sein eigenes Quote
- [shared-worktree-partial-commit](traps/shared-worktree-partial-commit.md) — nie blank committen

Reine Routing-Ebene: Titel + einzeiliger Aufhänger + Pfad. Ablauf im Agenten: Index lesen (~700 tok) → ein bis zwei Findings lesen (~400 tok), statt 4 600 für eine ganze Note.

Einchecken oder nicht — Toggle je Liste

Beim Anlegen einer Liste: .claudedo einchecken", Default aus.

  • Aus/.claudedo/ wird nach .git/info/exclude geschrieben (per-Clone, unsichtbar fürs Repo, keine getrackte Datei angefasst). Mechanik existiert bereits in SessionSkillSeeder.AppendExcludeLineAsync (auflösen über git rev-parse --git-path info/exclude) — wiederverwenden, nicht neu bauen.
  • An → nichts tun, der Nutzer commitet den Ordner selbst.

Worktree-Regeln (hart)

  • Geschrieben wird ausschließlich im Haupt-Checkout, nie in einem Worktree. Sonst entstehen INDEX.md-Merge-Konflikte über parallele Tasks — exakt das Problem, das oben gegen den Graph-Ansatz spricht.
  • Gelesen wird über den absoluten Pfad auf den Haupt-Checkout. Eine Regel für beide Toggle-Stellungen; in „aus" existiert der Ordner im Worktree ohnehin nicht.
  • Falls der Store getrackt ist und der Distiller committet: pfad-skopiert (git commit -- .claudedo), nie blank — der Haupt-Checkout wird von parallelen Sessions geteilt.
  • Ein Finding je Datei heißt: parallele Schreiber kollidieren nicht. Umkämpft ist nur INDEX.md; das Tool serialisiert dessen Rewrite prozessintern.

4. Schreibweg — ein MCP-Tool

save_finding(slug, title, body, scope = "", list = "") am bestehenden claudedo-MCP-Server.

Signatur folgt den zwei test-erzwungenen Konventionen des Servers: jeder optionale Parameter hat einen C#-Defaultwert, und das Tool gibt kein blankes Task und keine nullable Payload zurück (→ docs/explore-notes/external-mcp.md). Beschreibungsstil nach External/McpToolDocs.cs.

Welcher Store wird beschrieben?

Der MCP-Server ist global, der Store projektgebunden — die Zuordnung muss explizit sein:

  1. Autonomer Run: aus dem Aufrufkontext (TaskRunMcpContext) → Task → Liste → working_dir. Der list-Parameter wird ignoriert.
  2. Interaktive Session ohne Task-Kontext: list (Name oder Id) entscheidet. Fehlt er und es existiert genau eine Liste, wird diese genommen; bei mehreren gibt das Tool einen Fehler zurück, der die verfügbaren Listen nennt. Nie raten.

Geschrieben wird immer in <list.working_dir>/.claudedo/ — also den Haupt-Checkout, auch wenn der Aufrufer in einem Worktree läuft.

Bewusst ein dummes Schreib-Tool, kein „generiere mir Findings"-Tool: der Aufrufer hat den Kontext bereits. Ein zweiter Claude-Lauf (RefineRunner-Bauart) würde ihn aus Transkripten rekonstruieren — teurer und schlechter.

Genau ein Tool. Gelesen wird mit Read auf die Dateien; das kostet nichts und braucht kein Tool. Der Server exponiert bereits ~60 Tools, eines mehr ist im Prefix Rauschen — fünf wären es nicht.

Verhalten:

  • Gleicher slugüberschreiben, nicht anlegen. Dedupe gehört ins Tool, nicht in die Prompt-Disziplin.
  • Schreibt traps/<slug>.md und aktualisiert die Zeile in INDEX.md atomar.
  • verified-against wird vom Tool aus dem aktuellen HEAD des Haupt-Checkouts gesetzt, nicht vom Aufrufer.
  • source-task wird aus dem Aufrufkontext gesetzt, wenn vorhanden (autonome Runs), sonst leer (interaktive Sessions).

Aufrufwege

  1. Im Run. Ein Task-Agent tritt in eine Falle und hält sie fest. Höchste Qualität — er hat gerade Turns dafür verbrannt. Billigster Weg, kein zusätzlicher Prozess.
  2. Merge-Helper-Abschlussphase. Der List-Handler sweept am Ende nach, was ihm über den Batch hinweg auffiel. Er sieht Diffs, nicht das Straucheln — daher Ergänzung zu (1), kein Ersatz.
  3. Interaktive Sessions. Der claudedo-MCP ist global registriert, das Tool steht dort ohne Weiteres zur Verfügung.
  4. VSCode. Nutzer korrigiert und löscht direkt in den Dateien.

Aufnahmehürde

Ein Finding ist nur, was dauerhaft, nicht offensichtlich und verhaltensändernd ist: „X sieht aus wie Y, ist aber Z — mach stattdessen W". Ein gefixter Bug ist kein Finding, das ist Git-Historie.

Diese Hürde steht in der Tool-Beschreibung. Sie ist kein Stilhinweis, sondern die Betriebsbedingung: INDEX.md wird immer gelesen; bei ~15 tok je Zeile sind 50 Findings ≈ 750 tok, 300 Findings ≈ 4 500 tok — ab da kostet der Index so viel wie früher eine ganze Note und die Ersparnis ist weg. Deshalb zusätzlich eine Warnschwelle bei 80 Einträgen, die das Tool im Ergebnis zurückmeldet.


5. Leseweg

ClaudeDo hängt einen Satz an den System-Prompt seiner eigenen Runs (--append-system-prompt, wie heute schon für andere Zwecke genutzt):

Findings-Index für dieses Projekt: <abs>/.claudedo/INDEX.md. Lies ihn, bevor du den Code erkundest, und öffne nur die Findings, die zu deiner Aufgabe passen.

~30 Token, komplett in ClaudeDo gekapselt, keine CLAUDE.md wird angefasst — weder die des Projekts noch die globale.

Interaktive Sessions bekommen diesen Zeiger nicht (ClaudeDo startet sie nicht). Sie können schreiben, aber nicht automatisch lesen. Wer das will, setzt selbst eine Zeile in seine globale CLAUDE.md — außerhalb des Scopes dieses Features.


6. UI

Ein Button an der Liste: „Findings öffnen" → öffnet <working-dir>/.claudedo/ im Datei-Explorer bzw. der Standardanwendung. Kein In-App-Editor — Kuration passiert in VSCode.

Dazu der Toggle aus §3 im Dialog „Liste anlegen" und in den Listen-Einstellungen.


7. Scope-Grenze (bewusst akzeptiert)

Die Kapselung in ClaudeDo deckelt den Lese-Nutzen auf den Agent-Anteil: laut Messung 18,4 % des Verbrauchs (interaktiv = 81,6 %). Das ist kein Argument gegen das Feature — Retries passieren ausschließlich auf der Agent-Seite, und dort sitzt der 11-Mio-Hebel. Aber „Token-Ersparnis" heißt hier: Ersparnis in den 18 %, nicht auf dem Konto insgesamt.


8. Nicht in v1

  • maps/ — Subsystem-Maps. Ordner reserviert, Inhalt später. Die bestehenden docs/explore-notes/ bleiben unangetastet.
  • Retro-Distill aus Transkripten. Erst interessant, wenn belegt ist, dass Findings überhaupt wirken. Rohmaterial bleibt verfügbar: vollständiges NDJSON je Run unter ~/.todo-app/logs/{taskId}_run{N}.ndjson, ResultMarkdown in task_runs, WorktreeEntity.BaseCommit/HeadCommit/MergeCommit für den nachträglichen Diff.
  • Ernte der bestehenden ⚠️ Gotcha:-Sektionen aus docs/explore-notes/ in den Store. Naheliegender erster Füllstand, aber eine eigene, manuelle Aktion.
  • Wirkungsmessung (welches Finding wurde je gelesen, hat es einen Retry verhindert). Braucht ein eigenes Konzept; Dateien tragen keinen Zähler.
  • Automatischer Staleness-Check gegen verified-against. Frontmatter trägt den Commit, ausgewertet wird er in v1 nur vom Menschen.

9. Testbarkeit

  • save_finding: Neuanlage, Überschreiben bei gleichem Slug, Index-Update, Slug-Validierung (kein Pfad-Escape), Warnschwelle bei 80 Einträgen, paralleler Index-Rewrite.
  • Store-Auflösung: Task-Kontext schlägt list; genau eine Liste ohne list → Treffer; mehrere Listen ohne list → Fehler statt Raten; Schreibziel ist der Haupt-Checkout, auch wenn der Aufrufer in einem Worktree sitzt.
  • Die beiden external-mcp-Konventionen (Default je optionalem Parameter, kein blankes Task/nullable Payload) sind test-erzwungen — die bestehenden Tests greifen automatisch.
  • Toggle: .git/info/exclude-Zeile wird genau einmal geschrieben (Idempotenz) — die bestehenden Tests zu SessionSkillSeeder sind die Vorlage.
  • Kein Test darf die echte claude-CLI starten.
  • IWorkerClient / WorkerHub / ViewModel-Konstruktoren ändern → handgeschriebene Fakes in beiden Testprojekten mitziehen.