448 lines
18 KiB
Markdown
448 lines
18 KiB
Markdown
# Ausbaustufen — Munin
|
||
|
||
Was bewusst **nicht** gebaut wurde, warum, und woran man merkt, dass es Zeit dafür ist.
|
||
|
||
Der Sinn dieses Dokuments ist, dass niemand — auch nicht wir selbst in einem
|
||
halben Jahr — dieselben Fragen noch einmal von vorne durchdenken muss. Wo Zahlen
|
||
stehen, sind sie gemessen, nicht geschätzt; die Messumgebung ist jeweils genannt.
|
||
|
||
---
|
||
|
||
## Teil 1 — Entschieden und aufgeschoben
|
||
|
||
Diese Punkte sind durchgerechnet. Die Entscheidung lautet jeweils „später",
|
||
nicht „nie".
|
||
|
||
### 1.1 FTS5 statt eigenem Suchindex
|
||
|
||
**Stand heute:** Ein handgeschriebener BM25-Index im Arbeitsspeicher
|
||
(`internal/search`, 464 Zeilen plus 195 Zeilen Tests), einer je Projekt, beim
|
||
ersten Zugriff aufgebaut.
|
||
|
||
**Gemessen** (10.000 Einträge à 150 Wörter, Wortschatz 5.000, Zipf-verteilt):
|
||
|
||
| | eigener Index | FTS5 |
|
||
|---|---|---|
|
||
| Arbeitsspeicher | 65 MB | 0 |
|
||
| Platte | 0 | 54 MB |
|
||
| Aufbau | 171 ms | 280 ms |
|
||
| Suche | 12 µs | 208 µs |
|
||
|
||
FTS5 ist im reinen Go-Treiber verfügbar (`ENABLE_FTS5`), der statische Build
|
||
bliebe also erhalten.
|
||
|
||
**Warum aufgeschoben:**
|
||
|
||
- Der Suchindex ist **abgeleitete Daten**. Er lässt sich jederzeit aus `entries`
|
||
neu aufbauen — eine Umstellung später bedeutet keine Datenmigration. Der übliche
|
||
Grund, so etwas früh zu erledigen, greift hier nicht.
|
||
- Es löst nicht den eigentlichen Engpass. Der ist die semantische Trefferqualität
|
||
(siehe 2.2), und die kann FTS5 so wenig wie BM25 von Hand.
|
||
- FTS5 gibt es nur in SQLite. Der eigene Index ist von der Datenbank unabhängig
|
||
und hält damit den Weg zu PostgreSQL offen.
|
||
|
||
**Auslöser für die Umstellung:**
|
||
|
||
- Ein Projekt jenseits von grob 50.000 Einträgen (~300 MB Arbeitsspeicher allein
|
||
für dessen Index)
|
||
- Der Start dauert spürbar, weil viele große Projekte gleichzeitig angefragt werden
|
||
- Phrasensuche, `NEAR` oder boolesche Operatoren werden gebraucht — die bringt
|
||
FTS5 mit, der eigene Index nicht
|
||
|
||
**Aufwand:** ein bis zwei Tage. Virtuelle Tabelle mit `content=`-Verweis auf
|
||
`entries`, Befüllung aus dem Bestand, `internal/search` entfällt.
|
||
|
||
### 1.2 Eine SQLite-Datei je Projekt
|
||
|
||
**Stand heute:** Eine Datei für alles, zwei Verbindungspools — acht Verbindungen
|
||
zum Lesen, eine zum Schreiben.
|
||
|
||
**Gemessen:**
|
||
|
||
| | |
|
||
|---|---|
|
||
| Schreibvorgänge über die eine Schreibverbindung | 8.656/s |
|
||
| Lesen in Projekt A, ungestört | 82 µs |
|
||
| Lesen in Projekt A, während drei Projekte im Dauerlauf schreiben | 121 µs |
|
||
|
||
Hundert Agenten, die jede Sekunde einen Eintrag ablegen, sind 100
|
||
Schreibvorgänge/s — etwa **1 % der Kapazität**.
|
||
|
||
**Warum aufgeschoben:** Es bräuchte trotzdem eine zentrale Datenbank, denn drei
|
||
Dinge braucht man, *bevor* man das Projekt kennt: `ResolveKeyHash` (Key →
|
||
Projekt), `ListProjects` und `EnsureWorkspace`. Daraus folgt:
|
||
|
||
- Globales Protokoll und „Projekte mit Eintragszahl" werden aus einer Abfrage
|
||
N Abfragen plus Zusammenführen in Go
|
||
- Migrationen laufen über jede Datei, auch beim Anlegen eines Projekts
|
||
- Verbindungsverwaltung mit Verdrängung nötig (50 Projekte × 9 Verbindungen)
|
||
- Kein gemeinsamer Commit über zentrale und Projektdatenbank — „Projekt anlegen
|
||
samt erstem Key" wäre nicht mehr atomar
|
||
|
||
**Auslöser:**
|
||
|
||
- Anhaltend über ~1.000 Schreibvorgänge/s (10 % der Kapazität)
|
||
- Ein Projekt jenseits weniger GB, sodass Sicherung und `VACUUM` unangenehm werden
|
||
- Verbindliche Löschzusagen je Projekt, bei denen „Zeilen gelöscht" nicht reicht
|
||
|
||
**Billigere Schritte davor:** mehrere Einträge in einer Transaktion bündeln,
|
||
WAL-Prüfpunkte seltener setzen.
|
||
|
||
### 1.3 Zeichenketten im Suchindex teilen
|
||
|
||
`internal/search` hält in `docTerms` je Dokument eine Liste **aller seiner
|
||
verschiedenen Wörter** als eigene Zeichenketten. Bei 10.000 Einträgen sind das
|
||
über eine Million einzeln abgelegte Zeichenketten, obwohl im Projekt vielleicht
|
||
nur 20.000 verschiedene Wörter vorkommen.
|
||
|
||
Die Liste ist nötig: ohne sie müsste beim Ändern oder Löschen der komplette Index
|
||
durchsucht werden. Sie ließe sich aber verkleinern, indem gleiche Wörter dieselbe
|
||
Zeichenkette teilen. **Geschätzt ein Drittel bis die Hälfte** des Speichers.
|
||
|
||
**Warum aufgeschoben:** Löst dasselbe Problem wie 1.1, nur halb. Wenn der
|
||
Speicher wirklich drückt, ist FTS5 der gründlichere Schritt.
|
||
|
||
### 1.4 sqlc für die Datenschicht
|
||
|
||
War empfohlen, dann verworfen: sqlc ist nicht installiert, und ein zusätzlicher
|
||
Codegenerator bedeutet, dass `go build` allein nicht mehr reicht. Handgeschriebenes
|
||
`database/sql` ist außerdem besser lesbar für jemanden, der Go lernt.
|
||
|
||
**Nachrüstbar,** ohne eine Zeile SQL zu ändern — das SQL ist bereits so
|
||
geschrieben, wie sqlc es erwartet.
|
||
|
||
---
|
||
|
||
## Teil 2 — Trefferqualität
|
||
|
||
**Hier liegt der eigentliche Engpass.** Alles andere in diesem Dokument ist
|
||
Ressourcenoptimierung; das hier entscheidet darüber, ob ein Agent findet, was er
|
||
sucht.
|
||
|
||
### 2.1 Umlaute im Tokenizer falten — ✅ umgesetzt
|
||
|
||
`tokenize` bildet `ä→a`, `ö→o`, `ü→u` und `ß→ss` ab, beim Indizieren wie beim
|
||
Suchen (siehe `faltUmlaut` in `internal/search/index.go`).
|
||
|
||
```
|
||
prüfung ↔ prufung ✓
|
||
größe ↔ grosse ✓
|
||
prüfung ↔ pruefung ✗
|
||
```
|
||
|
||
**Korrektur zur früheren Fassung dieses Abschnitts:** Dort stand, die Umschreibung
|
||
mit e (`ue`, `oe`, `ae`) werde mit abgedeckt. Das stimmt nicht. Sie ließe sich nur
|
||
abdecken, indem man auch `ue` zu `u` zusammenzieht — dann würde aber aus „neue"
|
||
ein „nu" und aus „Steuer" ein „Stur". Der Schaden wäre größer als der Nutzen.
|
||
Die Punkte wegzulassen ist ohnehin die weit häufigere Schreibweise.
|
||
|
||
Gegenüber FTS5 mit `remove_diacritics` ein Vorteil: Das faltet zwar Umlaute, aber
|
||
kein `ß` — dort findet „grosse" die „Größe" nicht.
|
||
|
||
### 2.2 Semantische Suche
|
||
|
||
**Problem:** BM25 vergleicht Wörter. Fragt ein Agent „wie deployen wir", findet er
|
||
einen Eintrag namens „Release-Prozess" nicht — kein Wort passt.
|
||
|
||
**Lösung:** Einbettungen (Embeddings) plus Vektorsuche, kombiniert mit BM25
|
||
(„hybride Suche"): BM25 ist stark bei seltenen, exakten Begriffen — Fehlercodes,
|
||
Eigennamen —, die Vektorsuche bei Umschreibungen.
|
||
|
||
**Bauform:** ein eigener kleiner Dienst, angebunden über HTTP. Python hat dafür
|
||
das deutlich reichere Ökosystem; die Go-Anwendung bliebe ein Binary.
|
||
|
||
**Aufwand:** mehrere Tage, plus laufender Betrieb eines zweiten Dienstes und
|
||
eines Einbettungsmodells.
|
||
|
||
**Vorher versuchen:** die `instructions` an echten Anfragen schärfen (siehe 3.1).
|
||
Erfahrungsgemäß holt das einen guten Teil des Unterschieds, kostet aber nichts.
|
||
|
||
### 2.3 Lange Einträge zerlegen
|
||
|
||
`get` liefert einen Eintrag ganz oder gar nicht. Ein Eintrag mit 3.000 Wörtern
|
||
belegt beim Agenten Kontext, von dem er zwei Absätze braucht.
|
||
|
||
**Lösung:** Einträge in Abschnitte zerlegen (an Überschriften) und abschnittsweise
|
||
zurückgeben, mit Verweis auf den vollständigen Eintrag.
|
||
|
||
**Vorher prüfen:** ob überhaupt so lange Einträge entstehen. Bei einem
|
||
Agentengedächtnis sind die meisten Einträge kurz — dann wäre das eine Lösung ohne
|
||
Problem.
|
||
|
||
### 2.4 Stoppwörter und Wortstämme
|
||
|
||
Häufige Wörter („und", „der", „ist") blähen den Index auf, ohne zu unterscheiden.
|
||
Und „Deployment" findet „Deployments" nicht.
|
||
|
||
Beides ist Standardwerkzeug jeder Suchmaschine, kostet aber Genauigkeit bei
|
||
Fachbegriffen. **Erst messen, ob es im Alltag stört** — BM25 gewichtet häufige
|
||
Wörter ohnehin bereits herunter.
|
||
|
||
---
|
||
|
||
## Teil 3 — Funktionen für Agenten
|
||
|
||
### 3.1 Die `instructions` an echten Anfragen schärfen
|
||
|
||
Der Text in `internal/mcpserver/server.go` steht bei jedem Client im Kontext und
|
||
entscheidet darüber, ob ein Agent vor dem Schreiben sucht oder blind Dubletten
|
||
anlegt.
|
||
|
||
**Vorgehen:** Nach ein paar Wochen Betrieb ins Protokoll schauen — wie viele
|
||
`remember`-Aufrufe legen neu an, wie viele aktualisieren? Ein hoher Anteil
|
||
Neuanlagen bei ähnlichen Themen heißt: Die Anleitung greift nicht.
|
||
|
||
**Kostet nichts und wirkt sofort.** Deshalb steht es hier ganz oben.
|
||
|
||
### 3.2 Importer als Gegenstück zum Export
|
||
|
||
Der Markdown-Export ist heute eine Einbahnstraße. Das Dateiformat trägt alle
|
||
Felder im Kopf, ein Einlesen wäre also möglich.
|
||
|
||
**Wozu:** Umzug auf einen anderen Server, Wiederherstellung aus der Sicherung,
|
||
Erstbefüllung aus vorhandener Dokumentation.
|
||
|
||
**Aufwand:** ein halber Tag. Zu klären wäre, was bei einem Zusammenstoß passiert
|
||
— überschreiben, überspringen oder abbrechen.
|
||
|
||
### 3.3 Dubletten über Ähnlichkeit erkennen
|
||
|
||
Heute verhindert nur der gleiche Bezeichner eine Dublette. Wählt ein Agent einen
|
||
neuen Slug für dasselbe Thema, steht die Erkenntnis zweimal da.
|
||
|
||
**Lösung:** Bei `remember` mit neuem Bezeichner den vorhandenen Suchindex über
|
||
Titel und Inhalt laufen lassen. Liegt ein Treffer über einer Schwelle, den
|
||
Eintrag **nicht** anlegen, sondern die Kandidaten zurückgeben und den Agenten
|
||
entscheiden lassen:
|
||
|
||
```json
|
||
{
|
||
"created": false,
|
||
"judgment_required": true,
|
||
"candidates": [
|
||
{ "slug": "deployment-prozess", "title": "Deployment-Prozess", "score": 0.81 }
|
||
],
|
||
"hint": "Passt einer davon? Dann mit dessen slug erneut remember aufrufen.
|
||
Sonst mit force: true anlegen."
|
||
}
|
||
```
|
||
|
||
Der entscheidende Punkt ist, die Entscheidung **zurückzugeben** statt sie zu
|
||
treffen. Ein Agent, der einen Hinweis bekommt, korrigiert sich; einer, dem der
|
||
Aufruf stillschweigend abgelehnt wird, versucht es mit einem noch schlechteren
|
||
Bezeichner.
|
||
|
||
Braucht keine Einbettungen — der BM25-Index liegt bereits vor.
|
||
|
||
**Vorbild:** [Engram](https://github.com/Gentleman-Programming/engram) macht genau
|
||
das über FTS5 (`candidates[]` plus `judgment_required` bei `mem_save`). Das Muster
|
||
ist gut und ließe sich mit unserem Index eins zu eins übernehmen.
|
||
|
||
### 3.4 Bezeichner vorschlagen lassen
|
||
|
||
Der slug ist der Schlüssel gegen Dubletten (siehe 3.3), aber der Agent muss ihn
|
||
raten. Ein Werkzeug `suggest_slug(thema)` würde ihm die Arbeit abnehmen:
|
||
|
||
- gibt es zum Thema bereits einen Eintrag → dessen slug zurückgeben
|
||
- sonst einen neuen vorschlagen, der zu den vorhandenen passt
|
||
|
||
Damit wird aus „hoffentlich wählt das Modell einen guten Bezeichner" ein
|
||
Nachschlagevorgang. **Aufwand gering**, die Bausteine (Suche, `Slugify`) liegen vor.
|
||
|
||
**Vorbild:** Engrams `mem_suggest_topic_key`.
|
||
|
||
### 3.5 Aktualität von Wissen
|
||
|
||
Ein Agentengedächtnis veraltet still. Ein Eintrag über den Deployment-Prozess von
|
||
vor zwei Jahren sieht genauso aus wie einer von gestern — beide kommen mit
|
||
demselben Gewicht aus `recall` zurück.
|
||
|
||
**Kleiner Schritt zuerst:** Das Änderungsdatum steht bereits in jeder Antwort,
|
||
wird aber nicht betont. Einträge jenseits einer Frist zusätzlich mit einem
|
||
sichtbaren Vermerk versehen:
|
||
|
||
```json
|
||
{ "slug": "deployment-prozess", "updated_at": "2024-03-11",
|
||
"stale": true, "hint": "Seit 18 Monaten unverändert — vor Verwendung prüfen." }
|
||
```
|
||
|
||
**Größerer Schritt:** eine Prüffrist je Eintrag, die nach Art unterschiedlich
|
||
schnell abläuft — ein Zugangsverfahren altert schneller als eine
|
||
Grundsatzentscheidung. Dazu eine Ansicht „fällige Einträge" für Menschen und ein
|
||
Bestätigen, das die Frist zurücksetzt.
|
||
|
||
**Vorbild:** Engram führt dafür `review_after` und `expires_at` je Beobachtung,
|
||
mit einer Verfallslogik je Typ, sowie ein `mem_review` zum Abarbeiten. Deutlich
|
||
ausgebauter als das, was wir bräuchten — aber die Richtung stimmt.
|
||
|
||
### 3.6 Änderungshistorie
|
||
|
||
`entries.version` zählt hoch, aber alte Fassungen werden nicht aufgehoben.
|
||
Überschreibt ein Agent etwas Richtiges mit etwas Falschem, ist das Richtige weg.
|
||
|
||
**Lösung:** Tabelle `entry_versions` mit dem vorherigen Inhalt bei jedem
|
||
Schreibvorgang. Kostet Platz, gibt aber ein Zurück — und macht die Frage „wer hat
|
||
das wann geändert" beantwortbar.
|
||
|
||
**Alternative ohne Aufwand:** der Markdown-Export in einem Git-Repository. Dann
|
||
liegt die Historie dort, mit `git log` und `git diff`.
|
||
|
||
### 3.7 Verwandte Einträge
|
||
|
||
Der Verweisgraph (`entry_links`) wird bisher nur für Rückverweise genutzt. Ein
|
||
Werkzeug `related(slug)`, das Nachbarn im Graphen liefert, wäre für einen Agenten
|
||
ein zweiter Weg zum Kontext — neben der Suche.
|
||
|
||
**Aufwand:** gering, die Daten liegen vor.
|
||
|
||
### 3.8 Obergrenze gegen durchdrehende Agenten — ✅ umgesetzt
|
||
|
||
Ein gültiger Key umgeht das Rate-Limit bewusst (siehe `APIAuth` in
|
||
`internal/web/middleware.go`) — richtig für Menschen hinter einer gemeinsamen
|
||
Adresse, aber eine Schleife im Agenten hatte damit kein Limit.
|
||
|
||
**Umgesetzt als `auth.WriteBudget`:** ein Fixed-Window-Zähler über schreibende
|
||
MCP-Aufrufe, Vorgabe **1.000 je Stunde** (`WRITE_LIMIT_HOURLY`), durchgesetzt in
|
||
`Handler.requireWrite` in `internal/mcpserver/tools.go`. Lesen bleibt
|
||
unbegrenzt.
|
||
|
||
**Korrektur zum ursprünglichen Vorschlag:** Dort stand „je Key und Stunde". Das
|
||
Budget hängt jetzt am **Projekt**. Der Grund: Ein Limit je Key lässt sich
|
||
umgehen, indem man einen zweiten Key anlegt — und das darf jeder Projekt-Admin
|
||
selbst. Es hätte also nur vor dem Versehen geschützt, nicht vor dem Verbrauch.
|
||
Einzelne Projekte bekommen unter `/admin/projects` eine eigene Obergrenze; setzen
|
||
darf sie nur der Plattform-Admin, aus demselben Grund.
|
||
|
||
**Drei Dinge, die beim Bauen auffielen:**
|
||
|
||
1. **Die Obergrenze darf nicht zwischengespeichert werden.** Die MCP-Server je
|
||
Projekt sind Closures über eine Projektzeile, die beim ersten Zugriff gelesen
|
||
wurde — eine Änderung in der Verwaltung wäre dort nie angekommen. Genommen
|
||
wird deshalb `sess.Project.WriteLimitHourly` aus der Sitzung: Die hat
|
||
`APIAuth` für genau diesen Request frisch aus der Datenbank geholt. Aktuell,
|
||
und ohne zusätzliche Abfrage im Schreibpfad.
|
||
|
||
2. **Jede Ablehnung zu protokollieren hätte das Problem nur verschoben.** Ein
|
||
Agent in einer Schleife hätte statt der Einträge das Protokoll vollgeschrieben.
|
||
Deshalb gibt `Consume` als dritten Wert zurück, der wievielte Aufruf über der
|
||
Grenze das war — vermerkt wird nur der erste je Fenster.
|
||
|
||
3. **Die Fehlermeldung geht an ein Sprachmodell, nicht an einen Menschen.** Ein
|
||
nackter Fehler führt dazu, dass es sofort wieder versucht oder auf ein anderes
|
||
Werkzeug ausweicht. Die Meldung sagt darum ausdrücklich, was zu tun ist:
|
||
abbrechen und das Limit melden.
|
||
|
||
**Was das Budget nicht ist:** Der Zählerstand liegt im Arbeitsspeicher und ist
|
||
nach einem Neustart weg. Als Netz gegen eine Schleife reicht das. Sobald eine
|
||
verbindliche Zusage gebraucht wird — oder mehr als ein Prozess läuft —, gehört
|
||
der Zähler in die Datenbank.
|
||
|
||
---
|
||
|
||
## Teil 4 — Betrieb
|
||
|
||
### 4.1 Zugriffsprotokoll — ✅ umgesetzt
|
||
|
||
`LogRequests` in `internal/web/middleware.go` schreibt je Anfrage eine Zeile:
|
||
|
||
```
|
||
200 POST /mcp 1.475ms claude-desktop
|
||
202 POST /mcp 149µs claude-desktop
|
||
401 POST /mcp 193µs —
|
||
```
|
||
|
||
Statische Dateien und der Healthcheck bleiben draußen (Rauschen), die
|
||
Query-Zeichenkette ebenfalls — dort stehen Suchbegriffe, und die sind Inhalt,
|
||
kein Betriebsdatum.
|
||
|
||
**Zwei Fallstricke, die beim Bauen auffielen:**
|
||
|
||
1. Der Mantel um den `ResponseWriter` verdeckte `http.Flusher`. Der MCP-Transport
|
||
kommt über `http.ResponseController` und damit über `Unwrap()` heran, die
|
||
verbreitetere direkte Typzusicherung `w.(http.Flusher)` hätte aber lautlos
|
||
fehlgeschlagen — der Aufrufer nimmt dann an, Leeren werde nicht unterstützt.
|
||
Deshalb ist `Flush()` ausdrücklich implementiert.
|
||
|
||
2. Der Zugang stand nicht in der Zeile. Das Protokoll liegt ganz außen, damit es
|
||
auch abgewiesene Anfragen erfasst — zu diesem Zeitpunkt ist aber noch niemand
|
||
angemeldet, und die Sitzung landet später in einer abgeleiteten Kopie des
|
||
Requests. Gelöst über einen veränderbaren Halter im Context (`aktorHalter`),
|
||
den die Anmelde-Middleware befüllt.
|
||
|
||
### 4.2 Automatisierte Tests bei jeder Änderung — ✅ umgesetzt
|
||
|
||
`.github/workflows/test.yml` prüft bei jedem Push und jedem Pull Request:
|
||
Formatierung, `go vet`, `go test -race` und einen Bau mit `CGO_ENABLED=0`.
|
||
|
||
`-race` ist hier kein Beiwerk: Suchindex, Rate-Limiter und die Datenbankpools
|
||
werden von vielen Anfragen gleichzeitig benutzt. Der Bau ohne CGO schlägt fehl,
|
||
sobald jemand eine Abhängigkeit hinzufügt, die C braucht — das statische Binary
|
||
ist die Grundlage des ganzen Deployments.
|
||
|
||
### 4.3 Keycloak statt Key-Anmeldung
|
||
|
||
Vorgesehen und im Schema bereits berücksichtigt: Das Protokoll verweist auf
|
||
`api_key_id`, nicht auf Klarnamen. Kommt eine Nutzerverwaltung dazu, lässt sich
|
||
`user_id` danebenstellen, ohne die Historie zu verlieren.
|
||
|
||
**Zu ergänzen:** Tabellen `users` und `memberships`, OIDC-Anmeldung, Rollen an
|
||
Personen statt an Keys.
|
||
|
||
### 4.4 Mehrere Workspaces
|
||
|
||
Die Spalte `workspace_id` steckt von Anfang an in allen Tabellen, es gibt aber nur
|
||
einen und keine Oberfläche dafür. Nötig, sobald Abteilungen sich gegenseitig
|
||
nicht sehen sollen.
|
||
|
||
### 4.5 Kennzahlen
|
||
|
||
Ein `/metrics`-Endpunkt mit Anfragen, Dauern, Eintragszahlen. Sinnvoll, sobald es
|
||
ein Überwachungssystem gibt, in das es hineinfließt — vorher nicht.
|
||
|
||
---
|
||
|
||
## Teil 5 — Oberfläche
|
||
|
||
| | Nutzen | Aufwand |
|
||
|---|---|---|
|
||
| Vorschau beim Bearbeiten | Markdown sofort sehen | mittel (braucht JavaScript) |
|
||
| Autovervollständigung für `[[…]]` | Verweise ohne Abtippen | mittel |
|
||
| Graph-Ansicht der Verweise | Zusammenhänge sehen | hoch |
|
||
| Vergleich zweier Fassungen | setzt 3.6 voraus | mittel |
|
||
| Einträge in großer Zahl bearbeiten | Schlagworte nachziehen | gering |
|
||
|
||
Nichts davon ist dringend: Die Oberfläche ist bewusst der zweite Zugang, MCP ist
|
||
der erste.
|
||
|
||
---
|
||
|
||
## Empfohlene Reihenfolge
|
||
|
||
Wenn es demnächst weitergehen soll, in dieser Folge — jeweils kleiner Aufwand,
|
||
sichtbarer Gewinn:
|
||
|
||
~~1. Zugriffsprotokoll (4.1)~~ · ~~2. Umlaute falten (2.1)~~ ·
|
||
~~3. Automatisierte Tests (4.2)~~ — erledigt.
|
||
|
||
~~4. Obergrenze je Projekt (3.8)~~ — erledigt.
|
||
|
||
Als Nächstes:
|
||
|
||
1. **`instructions` schärfen** (3.1) — braucht ein paar Wochen echter Nutzung.
|
||
Das Zugriffsprotokoll und `/p/<projekt>/audit` liefern jetzt die Daten dafür:
|
||
Wie viele `remember`-Aufrufe legen neu an, wie viele aktualisieren?
|
||
2. **Kandidaten bei Dubletten** (3.3) und **Bezeichner vorschlagen** (3.4) —
|
||
beide ändern den MCP-Vertrag und sollten bewusst entschieden werden, nicht
|
||
nebenbei.
|
||
|
||
Nebenbei zu beobachten: ob 1.000 Aufrufe je Stunde die richtige Zahl sind. Die
|
||
Vermutung ist, dass echte Projekte weit darunter bleiben — nachsehen lässt es
|
||
sich in der Spalte „Schreibbudget" unter `/admin/projects`. Wenn dort nie etwas
|
||
knapp wird, ist die Zahl gut gewählt; wird sie regelmäßig erreicht, ist entweder
|
||
das Limit zu eng oder ein Agent tut etwas Unsinniges.
|
||
|
||
Alles darüber hinaus erst, wenn der Betrieb zeigt, dass es gebraucht wird.
|
||
|
||
**Was ausdrücklich nicht auf dieser Liste steht:** FTS5, getrennte Datenbanken,
|
||
semantische Suche. Alle drei sind gute Ideen für den Fall, dass ihre Auslöser
|
||
eintreten — und drei gute Wege, Zeit in Probleme zu stecken, die es noch nicht gibt.
|