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