docs(findings): spec the .claudedo findings store
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
# 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,5–14 KB | beim Arbeiten im Verzeichnis |
|
||||
| `docs/explore-notes/` (6 Notes) | 11–18 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 12–36 Zeilen (~150–450 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 = 5–8 % des Kontexts; Read =
|
||||
60–68 %, 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, ~150–300 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
|
||||
|
||||
```markdown
|
||||
---
|
||||
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
|
||||
|
||||
<2–6 Sätze: was passiert, warum es nicht danach aussieht, was man stattdessen tut.>
|
||||
```
|
||||
|
||||
### INDEX.md
|
||||
|
||||
```markdown
|
||||
# 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.
|
||||
Reference in New Issue
Block a user