Voici les outils de base de connaissances qu'un chat Claude ou Claude Code reçoit du serveur MCP d'osFoundry. Ils ont été récupérés avec un tools/list non authentifié le 2026-11-08 et réduits à ce qui compte ici :

{"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}} 

Quand on lit les descriptions ensemble, le design apparaît de lui-même. Un résultat de recherche porte un ID d'article et une date de mise à jour. Une lecture renvoie "a stable citation" (une citation stable). Une modification exige "the version from read_knowledge_article" (la version issue de read_knowledge_article). Et il existe un journal d'événements par base de connaissances qu'on peut reprendre à partir d'un numéro de séquence. La date de mise à jour de la citation et la version qu'une modification doit indiquer sont, en pratique, le même type de valeur : le propre client d'osFoundry pour ces outils, connected-cloud-tools.ts dans l'application desktop, type expected_version de edit_knowledge_article en format: date-time.

Une citation n'est donc pas qu'un pointeur pour le lecteur. C'est un tampon de version que l'agent peut vérifier plus tard et, juste après la lecture, renvoyer comme jeton de compare-and-swap. La suite montre ce que ça apporte, mesuré sur une reconstruction.

Que doit contenir une citation ?

Trois candidats :

Citation Résiste à un changement de titre Montre que le contenu a changé Utilisable comme version d'édition
Titre de l'article non non non
ID de l'article oui non non
ID de l'article + date de mise à jour oui oui oui, si elle est récente

Le titre est ce qu'un lecteur humain veut voir, d'où la tentation de citer par le titre seul et de le rechercher plus tard. Mais ça ne détecte qu'un article renommé ou supprimé. Un article dont le texte a changé sous le même titre a l'air correct. L'ID règle le problème des renommages. C'est la date qui permet de vérifier si une citation est périmée.

La reconstruction

Aucun binaire Claude ne tourne ici. kb_store.py implémente les quatre outils sur sqlite avec les vrais noms, arguments obligatoires et limites (query de 2–200 caractères, limit jusqu'à 20 pour la recherche et jusqu'à 100 pour les événements). Seuls les articles publiés peuvent être recherchés ou lus. Les formes JSON sont propres à la reconstruction, car le vrai serveur ne les renvoie qu'à un client authentifié. La citation est {knowledge_base_id, article_id, updated_at}. Chaque enregistrement écrit un événement {sequence, type, article_id, updated_at}, et edit_knowledge_article est un compare-and-swap sur updated_at avec une table d'idempotence, dans une seule transaction BEGIN IMMEDIATE.

sim.py simule une journée de support :

  • 400 articles, 120 questions et 300 enregistrements faits par des personnes sur 8 heures.
  • La popularité suit une courbe de Zipf (s = 0.9). Les 10 premiers articles reçoivent 36.7% des questions et 36.7% des enregistrements, parce que les articles sur lesquels les gens posent des questions sont ceux que les gens n'arrêtent pas de corriger.
  • Parmi les enregistrements, 85% modifient le contenu, 10% changent aussi le titre et 5% dépublient.
  • Pour chaque question, un "Claude" scripté appelle search_knowledge_base, lit les deux premiers résultats avec read_knowledge_article et garde les deux citations.
  • Toutes les heures, il vérifie de trois façons toutes les citations données jusque-là.

Seeds 1–5, Python 3.9.6 sur un Apple M1, stdlib uniquement. L'exécution complète prend 25.7 s.

À quelle vitesse les citations deviennent-elles périmées ?

Cinq seeds, 120 réponses chacune, deux citations par réponse :

Heure Citations jusqu'ici Périmées Réponses avec une citation périmée Détectées par le titre seul
1 152 24 (15.8%) 56.6% 5 sur 24
2 298 61 (20.5%) 61.7% 17 sur 61
4 580 165 (28.4%) 72.1% 56 sur 165
6 914 250 (27.4%) 69.4% 84 sur 250
8 1,200 369 (30.8%) 80.2% 152 sur 369

En fin de journée, près d'un tiers des citations pointent vers un article qui a changé depuis que Claude l'a lu, et quatre réponses sur cinq contiennent au moins une citation de ce genre. "Modifié" ne veut pas dire "faux" : une modification peut toucher un paragraphe que la réponse n'a jamais utilisé. Mais personne ne peut savoir lequel sans regarder, et c'est la date dans la citation qui indique à Claude où regarder.

La popularité explique l'essentiel. Le run de contrôle répartit les mêmes 300 enregistrements uniformément sur les 400 articles : 13.9% des citations deviennent périmées (167 sur 1,200) et 46.7% des réponses (280 sur 600). Plus ta base de connaissances est utilisée, plus les articles que Claude cite sont ceux qu'on est en train de modifier.

La vérification par titre seul a trouvé 152 des 369 citations périmées, soit 41.2%. Il s'agissait uniquement de changements de titre ou de dépublications. Elle ne voit jamais une modification de contenu.

Relire chaque article cité, ou interroger le journal ?

Coût des vérifications horaires sur la journée, cumulé sur les cinq seeds (40 vérifications) :

Stratégie Appels Données renvoyées Par vérification
Relancer une recherche sur chaque titre cité 2,426 3,342 KiB 60.6 appels, 83.5 KiB
Relire chaque article cité 2,264 3,445 KiB 56.6 appels, 86.1 KiB
poll_resource_events depuis un curseur 40 153 KiB 1.0 appel, 3.8 KiB

Les deux stratégies basées sur l'ID ont trouvé exactement les mêmes 369 citations périmées que la vérité terrain. Le script le vérifie par un assert à chaque contrôle. La différence, c'est le coût. Relire croît avec le nombre d'articles cités et paie leur texte complet à chaque fois. Le polling croît avec le nombre de changements. Moins de 100 changements par heure tiennent dans une page, donc chaque vérification a été un seul appel, et Claude ne relit que les articles qui ont bougé. Le curseur est un simple entier à conserver entre les tours. Avant le premier poll, l'agent a besoin d'une séquence de départ, soit 0, soit la dernière séquence qu'il a enregistrée avec ses citations.

Que se passe-t-il quand Claude modifie avec la version d'une citation ?

La deuxième expérience reprend la même journée. Après chaque réponse, Claude modifie le premier article qu'il a cité en passant le updated_at de la citation comme expected_version, après un délai. Cinq seeds, 150 modifications par délai :

Délai après la lecture Rejetées, version changée Rejetées, dépublié Nouvel essai après une relecture
20 s 0 (0.0%) 0 —
15 min 30 (20.0%) 2 30 sur 30 enregistrées
1 h 57 (38.0%) 3 57 sur 57 enregistrées
4 h 85 (56.7%) 14 85 sur 85 enregistrées

Une citation fait une bonne version d'édition pendant quelques secondes et une mauvaise au bout d'une heure. Les rejets, c'est le système qui fonctionne : chacune de ces écritures aurait sinon remplacé le texte plus récent d'une personne par un contenu construit à partir d'une lecture plus ancienne. La correction est mécanique. Relire, réappliquer le changement sur ce qui est là, écrire avec la nouvelle version. Chaque nouvel essai a réussi.

La clé d'idempotence a aussi été testée. Après chaque modification réussie, le même appel avec la même clé a été renvoyé. 409 sur 409 ont renvoyé le premier résultat marqué comme replay et n'ont écrit aucun nouvel événement.

Pourquoi osFoundry répond-il uniquement à partir d'articles publiés ?

Parce qu'une citation devrait pointer vers quelque chose qu'une personne a décidé être une réponse. Dans l'application desktop, l'onglet Publishing d'un article indique "Published articles are used by this workspace's AI assistant and search. They are not visible outside the workspace." (les articles publiés sont utilisés par l'assistant IA et la recherche de ce workspace, et ne sont pas visibles en dehors). Les brouillons sont invisibles pour search_knowledge_base et read_knowledge_article, et un article dépublié disparaît des deux au prochain appel. Dans la reconstruction, la dépublication écrit un événement comme n'importe quel enregistrement. 19 des 600 modifications différées ci-dessus sont tombées sur un article dépublié, et la vérification signale une telle citation comme disparue, pas comme modifiée.

Pourquoi l'outil de modification est-il marqué open-world ?

edit_knowledge_article est le seul outil de base de connaissances avec openWorldHint: true. Son effet ne reste pas dans la conversation. L'article enregistré est ce à partir de quoi tous les autres assistants et recherches du workspace répondront ensuite. Les annotations indiquent au client que cet appel modifie un état partagé, et Claude Code demande confirmation avant de l'exécuter, sauf si tu l'as autorisé. osFoundry ajoute son propre garde-fou quand un chat lié travaille depuis l'écran Knowledge Base dans l'application : connected-browser-run.ts demande une approbation pour chaque écriture cloud et ne propose pas d'autorisation valable pour toute la session.

Si tu le mets en place toi-même

  • Mets une version dans la citation, pas seulement un ID. Utilise la même valeur que ton outil de modification accepte comme expected_version, pour qu'une seule lecture te donne les deux.
  • Tiens un journal d'événements par base de connaissances avec une séquence monotone, et laisse l'agent reprendre à partir d'un numéro. Un entier d'état côté client remplace la relecture de tout ce qu'il a jamais cité.
  • Réponds uniquement à partir de contenu publié et journalise la dépublication comme un événement, pour qu'une citation puisse revenir comme "disparue" et pas seulement comme "différente".
  • Fais des modifications un compare-and-swap sur le contenu complet avec une clé d'idempotence obligatoire. Dis au modèle, dans la description de l'outil, de relire avant de modifier, car une version tirée d'une citation vieille d'une heure échoue environ quatre fois sur dix.
  • Marque l'outil d'écriture comme destructive et open-world, et garde aussi de ton côté une approbation par appel.

Ce qu'osFoundry entend par retrieval et grounding se trouve dans son entrée de glossaire sur le RAG. L'organisation de ses bases de connaissances est décrite sur la page des bases de connaissances.