Reçus d’outils
Les reçus d’outils sont des preuves cryptographiques qu’un résultat d’outil réussi provient du runtime. Lorsque les reçus sont activés, les exécutions d’outils réussies reçoivent un digest HMAC-SHA256 sur l’appel et son résultat. Le digest est ajouté au texte du tool-result et renvoyé au modèle dans le cadre de la conversation.
Le résultat pratique : le modèle ne peut pas prétendre de manière convaincante avoir exécuté un outil qu’il n’a pas exécuté, et il ne peut pas fabriquer un résultat d’outil. Ces affirmations produisent des reçus manquants ou invalides.
Le modèle de menace
Un LLM est un générateur de chaînes de caractères. Par défaut, rien ne l’empêche de raconter un appel d’outil qu’il n’a jamais effectué (« J’ai exécuté git log et le dernier commit est… »), ou d’inventer un résultat pour un appel d’outil (« L’API météo indique 72 °F », alors que l’appel a expiré). Pour un agent doté d’autonomie, c’est plus qu’un problème d’exactitude : c’est un problème de déni de responsabilité.
Les reçus d’outils comblent cette lacune avec la construction la moins chère possible : un MAC symétrique avec une clé éphémère en mémoire.
Ce que “éphemère” signifie ici. Les chemins canal-serveur conservent une clé HMAC en mémoire pendant toute la durée de vie du contexte d’exécution du canal. Les chemins de tour d’agent direct créent une nouvelle portée de reçu pour le tour. Dans les deux cas, la clé reste en mémoire, n’est jamais envoyée au modèle et ne constitue pas un vérificateur durable inter-sessions.
Based: Basu, A. (2026). « Tool Receipts, Not Zero-Knowledge Proofs: Practical Hallucination Detection for AI Agents ». arXiv:2603.10060.
Comment ça fonctionne
-
Lorsque les reçus sont activés, le runtime crée une clé de 256 bits en mémoire pour la portée de reçu active. Les chemins Channel-server conservent cette clé dans
ChannelRuntimeContext; les chemins de tour directs créent une nouvelle portée pour le tour. La clé n’est jamais écrite sur le disque, jamais envoyée au modèle et jamais journalisée. -
Après chaque exécution d’outil réussie, le runtime calcule :
receipt = HMAC-SHA256(key, tool_name || args || result || timestamp) -
Le reçu est ajouté au texte du résultat de l’outil comme suit :
[receipt: zc-receipt-<timestamp>-<base64url-digest>] -
Le résultat de l’outil (avec le reçu) est renvoyé au modèle.
Le modèle voit tous les reçus dans son historique de conversation. Il peut les reproduire dans le texte qu’il génère pour l’utilisateur. Mais il ne peut pas produire un nouveau reçu valide : le HMAC nécessite la clé de session, que le modèle ne possède pas.
Forme de reçu
zc-receipt-1774608496-gzpEBuUIRYX1vd4fQl4oYkqhq4-GnoJDStmlYzvQiWA
^ epoch seconds ^ base64url(HMAC-SHA256 digest)
Le préfixe zc-receipt- existe afin que le détecteur de fuites ne les masque pas (les reçus sont sûrs à afficher ; ils ne contiennent aucun matériel secret).
Quels reçus détecter
| Scénario | Sans reçus | Avec les reçus |
|---|---|---|
| Le modèle prétend avoir exécuté un outil, mais ne l’a pas fait. | Indétectable | Aucun reçu : fabrication visible |
| Le modèle fabrique un résultat pour un appel réel. | Indétectable | Échec de la vérification HMAC |
| Le modèle nie avoir effectué un appel réussi | Non vérifiable | Le reçu dans la conversation prouve qu’un résultat signé a été émis |
| Le modèle fabrique une chaîne de reçu plausible. | Plausible | La vérification HMAC a échoué. |
Ce que les reçus ne font pas
- Ne contraignez pas la sortie de texte. Le modèle peut toujours dire des choses non liées à un appel d’outil.
- Ne forcez pas l’utilisation d’outils. Les reçus sont générés uniquement lorsqu’un outil est appelé ; ils n’aident pas à résoudre le problème du « modèle répondant à partir de connaissances antérieures alors qu’il aurait dû effectuer une recherche ».
- Ne masquez pas les appels bloqués ou échoués. Les refus d’approbation, les timeouts, les appels bloqués et les échecs de retours d’outil sont des événements d’observabilité ou d’audit, et non des résultats d’outil accompagnés d’un accusé de réception.
- Ne pas utiliser les reçus d’une portée à une autre. Les clés de reçu sont éphémères, donc un reçu généré dans une portée ne peut pas être vérifié une fois celle-ci révolue.
- Ne isolez pas les canaux ou les conversations les uns des autres dans un contexte d’exécution de canal. Les conversations canal-serveur dans ce contexte partagent la clé. Le modèle de menace cible la falsification par un LLM au sein de l’environnement d’exécution, et non la falsification inter-canaux.
- N’étendez pas aux spawns d’arrière-plan ou de délégués détachés. Les spawns de délégués en arrière-plan et en parallèle qui se détachent du tour de l’utilisateur (
background: true) n’affichent pas les reçus dans le bloc visible par l’utilisateur, car le collecteur par tour est rendu avant la fin de ces spawns. Les reçus à l’intérieur des sous-agents délégués synchrones sont capturés.
Afficher les reçus
Dans les journaux de débogage
sh
RUST_LOG=zeroclaw_runtime::agent=debug zeroclaw daemon
Génère :
DEBUG Tool receipt generated tool=shell receipt=zc-receipt-1774604899-fVRG...
Dans les réponses visibles par l’utilisateur
Si [agent.tool_receipts] show_in_response = true, la réponse inclut un bloc de fin :
Here's the weather in Istanbul: 16°C, sunny.
---
Tool receipts:
weather: zc-receipt-1774608496-gzpEBuUIRYX1vd4fQl4oYkqhq4-GnoJDStmlYzvQiWA
Dans la sortie du LLM
Étant donné que le modèle voit les reçus dans son contexte, il peut les répéter lorsqu’il décrit les résultats des outils. Le détecteur de fuites est configuré pour laisser passer les jetons zc-receipt-* sans modification afin que cette répétition fonctionne. Si le runtime et le modèle incluent tous deux un bloc de reçus, l’utilisateur en voit deux : supprimez-en un via des règles de formatage spécifiques au canal.
Configuration
Propriétés de sécurité
- Clé éphémère en mémoire. Conservée uniquement dans la portée de réception active : les chemins serveur-canal utilisent le contexte d’exécution du canal, et les chemins de tour directs utilisent une portée par tour. Jamais persistée, jamais journalisée, jamais dans le contexte du modèle. La compromission du stockage à long terme n’apporte aucun avantage.
- Primitives MAC standard.
hmac+sha2de l’écosystème Rust. - Surcharge négligeable. <1 ms par appel d’outil.
- Aucune nouvelle dépendance externe.
Quels reçus ne sont pas
- Pas des preuves ZK. L’exécution peut vérifier les reçus car elle détient la clé. Un tiers ne peut pas le faire.
- Non signé croisé avec le hachage de la conversation. La falsification de la conversation antérieure n’annule pas les reçus suivants (le reçu ne couvre que l’appel pour lequel il a été calculé).
- Pas un remplacement des portes de validation. Un reçu prouve qu’un appel a eu lieu ; il ne décide pas s’il aurait dû se produire.
État actuel
| Fonctionnalité | Statut |
|---|---|
| Génération HMAC pour les résultats des outils réussis | Expédié |
| Reçu ajouté au résultat d’outil réussi | Expédié |
| Journal de débogage des reçus | Expédié |
show_in_response | Expédié |
| Instruction du système pour écho les reçus | Expédié |
| Base de données d’audit persistante des reçus | Prévu |
| Vérification de reçus inter-étendues | Non prévu (voir la conception des clés éphémères) |
Voir aussi
- Sécurité → Aperçu
- Niveaux d’autonomie : la couche de politique qui décide si un appel justifiant un reçu a lieu
- Référence → Config : référence de configuration générée