Das sind die Knowledge-Base-Tools, die ein Claude-Chat oder Claude Code vom MCP-Server von osFoundry bekommt. Abgerufen wurden sie am 2026-11-08 mit einem nicht authentifizierten tools/list, gekürzt auf das, worum es hier geht:
{"name": "search_knowledge_base", "description": "Search current published articles in one granted knowledge base. Results cite the article ID and updated time; this is a live database read.", "inputSchema": {"required": ["knowledge_base_id", "query"], "properties": {"query": {"type": "string", "minLength": 2, "maxLength": 200}, "limit": {"type": "integer", "minimum": 1, "maximum": 20}}}, "annotations": {"readOnlyHint": true}} {"name": "read_knowledge_article", "description": "Read one current published article by ID from a granted knowledge base and return a stable citation.", "inputSchema": {"required": ["knowledge_base_id", "article_id"]}, "annotations": {"readOnlyHint": true}} {"name": "edit_knowledge_article", "description": "Replace article content and optionally its title. Supply the version from read_knowledge_article; concurrent changes are rejected.", "inputSchema": {"required": ["knowledge_base_id", "article_id", "content", "expected_version", "idempotency_key"]}, "annotations": {"readOnlyHint": false, "destructiveHint": true, "openWorldHint": true}} {"name": "poll_resource_events", "description": "Resume from an event sequence for one granted channel, Note, knowledge base, record, or session.", "inputSchema": {"required": ["resource_type", "resource_id"], "properties": {"after_sequence": {"type": "integer", "minimum": 0}, "limit": {"type": "integer", "minimum": 1, "maximum": 100}}}, "annotations": {"readOnlyHint": true}} Liest man die Beschreibungen zusammen, ergibt sich das Design fast von selbst. Ein Suchtreffer enthält eine Artikel-ID und einen Änderungszeitpunkt. Ein Lesezugriff liefert "a stable citation" (ein stabiles Zitat). Eine Änderung braucht "the version from read_knowledge_article" (die Version aus read_knowledge_article). Und es gibt pro Wissensdatenbank ein Event-Log, das man ab einer Sequenznummer fortsetzen kann. Der Änderungszeitpunkt im Zitat und die Version, die eine Änderung angeben muss, sind in der Praxis dieselbe Art von Wert: osFoundrys eigener Client für diese Tools, connected-cloud-tools.ts in der Desktop-App, typisiert expected_version für edit_knowledge_article als format: date-time.
Ein Zitat ist also nicht nur ein Verweis für den Leser. Es ist ein Versionsstempel, den der Agent später prüfen und direkt nach dem Lesen als Compare-and-Swap-Token zurückgeben kann. Im Folgenden geht es darum, was das bringt, gemessen an einem Nachbau.
Was sollte ein Zitat enthalten?
Drei Kandidaten:
| Zitat | Übersteht eine Umbenennung | Zeigt, dass sich der Inhalt geändert hat | Als Version für Änderungen nutzbar |
|---|---|---|---|
| Artikeltitel | nein | nein | nein |
| Artikel-ID | ja | nein | nein |
| Artikel-ID + Änderungszeitpunkt | ja | ja | ja, wenn frisch |
Der Titel ist das, was ein menschlicher Leser sehen will. Deshalb liegt es nahe, nur über den Titel zu zitieren und ihn später erneut nachzuschlagen. Das bemerkt aber nur einen Artikel, der umbenannt oder entfernt wurde. Ein Artikel, dessen Text sich unter demselben Titel geändert hat, sieht unauffällig aus. Die ID löst das Problem mit Umbenennungen. Erst der Zeitpunkt macht prüfbar, ob ein Zitat veraltet ist.
Der Nachbau
Hier läuft kein Claude-Binary. kb_store.py implementiert die vier Tools auf sqlite mit den echten Namen, Pflichtargumenten und Limits (query 2–200 Zeichen, limit bis 20 bei der Suche und bis 100 bei Events). Nur veröffentlichte Artikel sind such- oder lesbar. Die JSON-Strukturen sind die des Nachbaus, weil der echte Server sie nur an einen authentifizierten Client zurückgibt. Das Zitat ist {knowledge_base_id, article_id, updated_at}. Jedes Speichern schreibt ein Event {sequence, type, article_id, updated_at}, und edit_knowledge_article ist ein Compare-and-Swap auf updated_at mit einer Idempotenz-Tabelle, in einer einzigen BEGIN IMMEDIATE-Transaktion.
sim.py simuliert einen Support-Tag:
- 400 Artikel, 120 Fragen und 300 Speichervorgänge durch Personen über 8 Stunden.
- Die Beliebtheit folgt einer Zipf-Kurve (s = 0.9). Die Top 10 Artikel bekommen 36.7% der Fragen und 36.7% der Speichervorgänge, denn die Artikel, nach denen Leute fragen, sind genau die, die Leute ständig korrigieren.
- Von den Speichervorgängen ändern 85% den Inhalt, 10% zusätzlich den Titel, und 5% heben die Veröffentlichung auf.
- Für jede Frage ruft ein geskripteter "Claude"
search_knowledge_baseauf, liest die beiden obersten Treffer mitread_knowledge_articleund behält beide Zitate. - Jede Stunde prüft er alle bis dahin gegebenen Zitate auf drei Arten.
Seeds 1–5, Python 3.9.6 auf einem Apple M1, nur stdlib. Der komplette Lauf dauert 25.7 s.
Wie schnell veralten Zitate?
Fünf Seeds, je 120 Antworten, zwei Zitate pro Antwort:
| Stunde | Zitate bisher | Veraltet | Antworten mit veraltetem Zitat | Nur per Titel erkannt |
|---|---|---|---|---|
| 1 | 152 | 24 (15.8%) | 56.6% | 5 von 24 |
| 2 | 298 | 61 (20.5%) | 61.7% | 17 von 61 |
| 4 | 580 | 165 (28.4%) | 72.1% | 56 von 165 |
| 6 | 914 | 250 (27.4%) | 69.4% | 84 von 250 |
| 8 | 1,200 | 369 (30.8%) | 80.2% | 152 von 369 |
Am Ende des Tages zeigt fast ein Drittel aller Zitate auf einen Artikel, der sich seit Claudes Lesezugriff geändert hat, und vier von fünf Antworten enthalten mindestens ein solches Zitat. "Geändert" heißt nicht "falsch": Eine Änderung kann einen Absatz betreffen, den die Antwort nie benutzt hat. Aber ohne nachzusehen kann niemand sagen, welcher Fall vorliegt, und der Zeitpunkt im Zitat sagt Claude, wo es nachsehen muss.
Den größten Teil davon erklärt die Beliebtheit. Der Kontrolllauf verteilt dieselben 300 Speichervorgänge gleichmäßig auf alle 400 Artikel: Dann veralten 13.9% der Zitate (167 von 1,200) und 46.7% der Antworten (280 von 600). Je stärker deine Wissensdatenbank genutzt wird, desto eher sind die Artikel, die Claude zitiert, genau die, die gerade bearbeitet werden.
Die reine Titelprüfung fand 152 der 369 veralteten Zitate, also 41.2%. Das waren ausnahmslos Umbenennungen oder aufgehobene Veröffentlichungen. Eine inhaltliche Änderung sieht sie nie.
Jeden zitierten Artikel neu lesen oder das Log abfragen?
Kosten der stündlichen Prüfungen über den Tag, summiert über die fünf Seeds (40 Prüfungen):
| Strategie | Aufrufe | Zurückgegebene Daten | Pro Prüfung |
|---|---|---|---|
| Jeden zitierten Titel neu suchen | 2,426 | 3,342 KiB | 60.6 Aufrufe, 83.5 KiB |
| Jeden zitierten Artikel neu lesen | 2,264 | 3,445 KiB | 56.6 Aufrufe, 86.1 KiB |
poll_resource_events ab einem Cursor | 40 | 153 KiB | 1.0 Aufruf, 3.8 KiB |
Beide ID-basierten Strategien fanden exakt dieselben 369 veralteten Zitate wie die Ground Truth. Das Skript prüft das bei jeder Prüfung per Assert. Der Unterschied liegt in den Kosten. Neu lesen skaliert mit der Zahl der zitierten Artikel und bezahlt jedes Mal deren vollständigen Text. Polling skaliert mit der Zahl der Änderungen. Weniger als 100 Änderungen pro Stunde passen auf eine Seite, also war jede Prüfung ein einziger Aufruf, und Claude liest nur die Artikel neu, die sich bewegt haben. Der Cursor ist eine einzelne Ganzzahl, die man zwischen den Turns aufbewahrt. Vor dem ersten Poll braucht der Agent eine Startsequenz, entweder 0 oder die letzte Sequenz, die er zusammen mit seinen Zitaten gespeichert hat.
Was passiert, wenn Claude mit der Version aus einem Zitat ändert?
Das zweite Experiment nutzt denselben Tag. Nach jeder Antwort ändert Claude nach einer Verzögerung den ersten zitierten Artikel und übergibt das updated_at des Zitats als expected_version. Fünf Seeds, 150 Änderungen pro Verzögerung:
| Verzögerung nach dem Lesen | Abgelehnt, Version weitergezogen | Abgelehnt, nicht mehr veröffentlicht | Retry nach frischem Lesen |
|---|---|---|---|
| 20 s | 0 (0.0%) | 0 | — |
| 15 min | 30 (20.0%) | 2 | 30 von 30 gespeichert |
| 1 h | 57 (38.0%) | 3 | 57 von 57 gespeichert |
| 4 h | 85 (56.7%) | 14 | 85 von 85 gespeichert |
Als Version für eine Änderung taugt ein Zitat ein paar Sekunden lang gut und nach einer Stunde schlecht. Die Ablehnungen zeigen, dass das System funktioniert: Jeder dieser Schreibvorgänge hätte sonst den neueren Text einer Person durch Inhalt ersetzt, der auf einem älteren Lesestand beruht. Die Lösung ist mechanisch. Neu lesen, die Änderung auf den aktuellen Stand anwenden, mit der neuen Version schreiben. Jeder Retry war erfolgreich.
Auch der Idempotency Key wurde geprüft. Nach jeder erfolgreichen Änderung wurde derselbe Aufruf mit demselben Key noch einmal gesendet. 409 von 409 lieferten das erste Ergebnis als Replay markiert zurück und schrieben kein neues Event.
Warum antwortet osFoundry nur aus veröffentlichten Artikeln?
Weil ein Zitat auf etwas zeigen sollte, von dem eine Person entschieden hat, dass es eine Antwort ist. In der Desktop-App steht im Publishing-Tab eines Artikels "Published articles are used by this workspace's AI assistant and search. They are not visible outside the workspace." (Veröffentlichte Artikel werden vom KI-Assistenten und der Suche dieses Workspaces genutzt und sind außerhalb des Workspaces nicht sichtbar.) Entwürfe sind für search_knowledge_base und read_knowledge_article unsichtbar, und ein Artikel, dessen Veröffentlichung aufgehoben wird, fällt beim nächsten Aufruf aus beiden heraus. Im Nachbau schreibt das Aufheben der Veröffentlichung ein Event wie jedes andere Speichern. 19 der 600 verzögerten Änderungen oben trafen auf einen Artikel, der nicht mehr veröffentlicht war, und die Prüfung meldet ein solches Zitat als verschwunden, nicht als geändert.
Warum ist das Änderungs-Tool als Open-World markiert?
edit_knowledge_article ist das einzige Knowledge-Base-Tool mit openWorldHint: true. Seine Wirkung bleibt nicht in der Unterhaltung. Aus dem gespeicherten Artikel antworten als Nächstes alle anderen Assistenten und Suchen im Workspace. Die Annotations sagen dem Client, dass dieser Aufruf geteilten Zustand ändert, und Claude Code fragt vor der Ausführung nach, sofern du es nicht erlaubt hast. osFoundry fügt eine eigene Sperre hinzu, wenn ein verknüpfter Chat in der App vom Knowledge-Base-Bildschirm aus arbeitet: connected-browser-run.ts verlangt für jeden Cloud-Schreibvorgang eine Freigabe und bietet dafür keine sitzungsweite Erlaubnis an.
Wenn du das selbst baust
- Pack eine Version ins Zitat, nicht nur eine ID. Nimm denselben Wert, den dein Änderungs-Tool als
expected_versionerwartet, dann liefert dir ein Lesezugriff beides. - Führe pro Wissensdatenbank ein Event-Log mit monoton steigender Sequenz und lass den Agenten ab einer Nummer fortsetzen. Eine Ganzzahl Client-State ersetzt das erneute Lesen von allem, was er je zitiert hat.
- Antworte nur aus veröffentlichtem Inhalt und protokolliere das Aufheben der Veröffentlichung als Event, damit ein Zitat als "verschwunden" zurückkommen kann und nicht nur als "anders".
- Mach Änderungen zu Compare-and-Swap auf den gesamten Inhalt mit Pflicht-Idempotency-Key. Sag dem Modell in der Tool-Beschreibung, dass es vor dem Ändern neu lesen soll, denn eine Version aus einem eine Stunde alten Zitat scheitert in etwa vier von zehn Fällen.
- Markiere das Schreib-Tool als destructive und open-world, und behalte auch auf deiner Seite eine Freigabe pro Aufruf.
Was osFoundry unter Retrieval und Grounding versteht, steht in seinem Glossareintrag zu RAG. Wie seine Wissensdatenbanken organisiert sind, steht auf der Seite zu Knowledge Bases.
