Cet article est une mise en œuvre de l’article sur l’identité des agents : nous allons voir comment mettre en place ça pour de vrai. Les exemples reprennent mon nom de domaine, kaizensphere.dev, à remplacer par le vôtre.

Prérequis : l’identité

Dans l’article sur le concept d’identité vérifiable, nous avions dit que la première étape est de fournir une identité à notre agent, le DID (Decentralized Identifier) de l’agent. Ici nous aurons, pour créer l’identité de l’agent, besoin de créer notre identité (mandant), did:web:kaizensphere.dev, qui renvoie vers https://kaizensphere.dev/.well-known/did.json, et ensuite seulement nous pourrons créer l’identité de l’agent, qui sera stockée dans son gestionnaire d’attestations (j’abandonne le terme wallet pour credential manager, utilisé par le W3C).

RôleIdentifiantOù vit la clé privée
Mandantdid:web:kaizensphere.devsur votre poste
Agentdid:web:kaizensphere.dev:agents:redactiondans le gestionnaire d’attestations de l’agent

Les deux documents DID doivent être hébergés quelque part, en HTTPS et au chemin exact de l’identifiant. Moi, je vais les héberger sur Codeberg Pages, parce que mon domaine nu ne peut pas pointer vers l’hébergement qui sert ce blog ; l’hébergeur reste à votre choix. Il faut aussi un nom de domaine dont vous gérez la zone DNS, Docker avec Compose et un compte claude.ai.

Générer la clé du mandant

Nous allons générer une paire de clés Ed25519 dans un dossier réservé, nous la placerons dans ~/.config/did/.

Nous avons choisi le type Ed25519, car dans la spécification KYA-OS c’est la clé choisie. Cela dit, il offre déjà beaucoup d’avantages, car c’est un algorithme simple à déployer : il offre une signature déterministe, il est pris en charge par la quasi-totalité de l’outillage des développeurs (OpenSSH, OpenSSL, les bibliothèques JOSE…) et enfin il est rapide et petit, ce qui est un avantage car chacune de nos requêtes sera signée avec cette clé.

Créez le dossier :

mkdir -p ~/.config/did

Réservez le dossier à votre seul utilisateur :

chmod 700 ~/.config/did

Générez la paire de clés, enregistrée dans un fichier PEM :

openssl genpkey -algorithm ed25519 -out ~/.config/did/key.pem

Rendez ce fichier lisible par vous seul :

chmod 600 ~/.config/did/key.pem

La clé privée reste sur votre machine. Pensez à la sauvegarder.

Encoder la clé publique

Pour l’encodage, nous allons partir sur le format Multikey. Pourquoi le format Multikey ? Eh bien parce que toute la clé tient dans une seule chaîne de caractères. On retrouve ce format avec les identifiants did:key et les preuves Data Integrity du W3C. Le document de l’agent, quant à lui, utilisera l’autre format que vous connaissez bien, le JWK, parce que la spécification KYA-OS le demande pour les agents.

Cette commande la calcule à partir de la clé privée :

CLE_PUBLIQUE=$(openssl pkey -in ~/.config/did/key.pem -pubout -outform DER | tail -c 32 | python3 -c '
import sys
octets = bytes([0xed, 0x01]) + sys.stdin.buffer.read()
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
n = int.from_bytes(octets, "big"); texte = ""
while n: n, r = divmod(n, 58); texte = alphabet[r] + texte
print("z" + texte)')
echo "$CLE_PUBLIQUE"

Le résultat commence par z6Mk. Conservez cette variable, nous allons nous en servir à l’étape suivante.

Créer le document DID

Créez le dossier .well-known, celui que l’identifiant désigne :

mkdir -p ~/did/.well-known

Placez-vous dans le dossier :

cd ~/did

Écrivez le document. Le terminal y remplace $CLE_PUBLIQUE par la valeur de l’étape précédente :

cat > .well-known/did.json <<EOF
{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/multikey/v1"
  ],
  "id": "did:web:kaizensphere.dev",
  "verificationMethod": [
    {
      "id": "did:web:kaizensphere.dev#key-1",
      "type": "Multikey",
      "controller": "did:web:kaizensphere.dev",
      "publicKeyMultibase": "$CLE_PUBLIQUE"
    }
  ],
  "authentication": ["did:web:kaizensphere.dev#key-1"],
  "assertionMethod": ["did:web:kaizensphere.dev#key-1"]
}
EOF

Configurer le DNS

Quel que soit l’hébergeur, le domaine doit pointer vers lui. Avec Codeberg Pages, le document est poussé sur la branche pages d’un dépôt, et trois enregistrements dans la zone DNS du domaine font le lien :

TypeNomValeur
Akaizensphere.dev217.197.84.141
AAAAkaizensphere.dev2a0a:4580:103f:c0de::2
TXT_git-pages-repository.kaizensphere.devhttps://codeberg.org/kaizencode/did.git

Les deux adresses sont celles de Codeberg Pages à la date de rédaction, à contrôler dans sa documentation.

Vérifier la résolution

Vérifiez que l’adresse IP est en place :

dig +short kaizensphere.dev A

Puis l’enregistrement TXT :

dig +short _git-pages-repository.kaizensphere.dev TXT

Chaque commande doit renvoyer la valeur saisie.

Le document doit ensuite répondre à son adresse :

curl -i https://kaizensphere.dev/.well-known/did.json
Sortie de la commande curl : réponse HTTP/2 200, type application/json, puis le début du document DID avec son contexte, son identifiant did:web:kaizensphere.dev et sa méthode de vérification key-1

Le document répond à son adresse, servi par Codeberg Pages.

Le résolveur universel confirme le résultat avec l’identifiant did:web:kaizensphere.dev.

Isoler l’agent

Un agent peut atteindre le réseau de dix façons, curl, un script Python, un outil intégré, et chacune de ces requêtes part sans identité. J’ai envisagé des consignes dans son fichier CLAUDE.md, qu’il suit de bonne volonté sans que rien ne l’y oblige ; des règles de permission qui interdisent curl ou wget ; et un hook qui inspecte chaque commande avant son exécution. Filtrer les commandes une par une est un jeu sans fin.

J’ai pris l’habitude d’isoler mes agents dans des sandboxes Docker, ce qui est plutôt bénéfique comme vous allez le voir. Je vous ai préparé mon environnement, que vous trouverez dans le dépôt did-agent-env. On y trouve la description de trois conteneurs :

  • portefeuille est le gestionnaire d’attestations : il détient la clé de l’agent et signe une preuve par requête ;
  • agent contient Claude Code et la commande portefeuille ;
  • proxy est le seul point d’accès à Internet de l’agent. On peut envisager d’ajouter un contrôle sur les en-têtes pour bloquer toute requête sans en-tête d’identité.

L’environnement revient à ceci :

Schéma de l'environnement : sur le poste, le mandant signe le mandat et le transmet au gestionnaire d'attestations ; dans un réseau interne sans accès à Internet, l'agent demande ses preuves au gestionnaire d'attestations et sort uniquement par le proxy, qui affiche les requêtes avant de les transmettre à Internet

L’agent n’a ni clé ni accès direct à Internet : il demande ses preuves au gestionnaire d’attestations et sort par le proxy.

Nommer l’agent et démarrer le gestionnaire d’attestations

À la suite du clone du dépôt did-agent-env, vous pouvez modifier le modèle pour y mettre les valeurs correctes qui correspondent à votre poste :

cp env.exemple .env
DID_AGENT=did:web:kaizensphere.dev:agents:redaction
DID_MANDANT=did:web:kaizensphere.dev

Démarrez le gestionnaire d’attestations et le proxy. Le gestionnaire d’attestations génère la clé de l’agent à son premier démarrage :

docker compose up -d --build portefeuille proxy
docker compose logs portefeuille

Le journal confirme l’identifiant de l’agent et affiche son document DID :

Journal du conteneur portefeuille : la ligne agent : did:web:kaizensphere.dev:agents:redaction, puis le document DID de l'agent, avec sa clé publique au format JWK et le mandant did:web:kaizensphere.dev comme controller

Au premier démarrage, le gestionnaire d’attestations génère la clé de l’agent et produit son document.

Publier le document DID de l’agent

Créez le dossier du document, à l’adresse que l’identifiant de l’agent désigne :

mkdir -p ~/did/agents/redaction

Demandez le document au gestionnaire d’attestations et écrivez-le dans ce dossier :

docker compose exec -T portefeuille python /serveur.py document > ~/did/agents/redaction/did.json

C’est le document que le gestionnaire d’attestations affiche au démarrage, dans la capture ci-dessus.

Publiez-le au même endroit que le vôtre, puis vérifiez :

curl https://kaizensphere.dev/agents/redaction/did.json

Signer le mandat

Le mandat est l’attestation par laquelle le mandant (moi) dit ce que l’agent a le droit de faire, et jusqu’à quand. C’est la delegation credential de l’article précédent : une attestation vérifiable dont le sujet est une capacité, signée par le mandant. L’agent la joint à chaque requête, avec une preuve qu’il détient la clé que le mandat désigne.

Signez le mandat de l’agent avec votre clé, sur votre poste :

cd ~/did-agent-env
./signer-mandat.py --cle ~/.config/did/key.pem \
    --mandant did:web:kaizensphere.dev \
    --agent did:web:kaizensphere.dev:agents:redaction

Par défaut, il autorise la lecture de tout service pendant huit heures :

{
  "type": ["VerifiableCredential", "DelegationCredential"],
  "issuer": "did:web:kaizensphere.dev",
  "validUntil": "2026-10-07T02:16:13Z",
  "credentialSubject": {
    "id": "urn:zcap:del_2026_10_06_3c7e70",
    "invoker": "did:web:kaizensphere.dev:agents:redaction",
    "invocationTarget": "*",
    "parentCapability": "*",
    "allowedAction": ["web.read"]
  }
}

Les options --actions, --heures et --cible du script changent ces valeurs.

Pour modifier le mandat lui-même, c’est dans signer-mandat.py, à la racine du dépôt, que sa structure est écrite.

Lancer l’agent

Si vous voulez voir ce qui transite vers Internet de votre agent, vous pouvez lancer un terminal :

docker compose logs -f proxy

Dans un autre terminal, lancez l’agent :

docker compose run --rm agent

Au premier lancement, connectez-vous avec votre compte claude.ai : l’agent affiche une adresse à ouvrir dans le navigateur. Acceptez ensuite la confiance pour le dossier /travail. /travail est le dossier de travail de l’agent dans son conteneur. Il est copié depuis agent/travail du dépôt, où se trouvent ses consignes (CLAUDE.md) et ses réglages.

Lire ses requêtes dans le proxy

Parlez à l’agent comme d’habitude, par exemple : « Peux-tu me résumer le dernier article de codeka.io ? » (lisez codeka.io, ce blog est trop bien)

Session de l'agent dans son conteneur : à la question « peux-tu me résumer le dernier article de codeka.io ? », il lance deux commandes puis résume l'article, section par section

L’agent répond comme d’habitude. Ses deux commandes sont passées par son gestionnaire d’attestations.

Ses consignes lui demandent de passer par son gestionnaire d’attestations pour tout appel réseau. Le premier terminal affiche alors les requêtes telles qu’elles partent :

Journal du proxy : la requête GET vers l'article de codeka.io, avec ses en-têtes Kya-Os-Agent-Did, Kya-Os-Delegation-Credential, Kya-Os-Delegation-Proof, Kya-Os-Delegation-Chain et Kya-Os-Granted-Scopes, puis la réponse 200 du site

Chaque requête de l’agent porte son identité, son mandat et sa preuve.

Les en-têtes Kya-Os-… suivent la spécification KYA-OS. Le mandat et la preuve sont des JSON Web Tokens (JWT). Décodez la preuve, c’est-à-dire la valeur de l’en-tête Kya-Os-Delegation-Proof :

echo 'eyJ...' | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq .
La commande echo, cut, tr, base64 et jq appliquée au jeton de preuve, et le JSON obtenu : iss est le DID de l'agent, sub celui du mandant, aud codeka.io, puis iat, exp, jti, delegation_id, delegation_chain, scope web.read, htm GET et htu l'adresse de l'article de codeka.io

La preuve décodée : elle nomme l’agent, son mandant, le service visé et l’action, et vaut soixante secondes.

Demandez ensuite une écriture : « Envoie un POST sur https://example.com. » Le gestionnaire d’attestations refuse de signer :

Session de l'agent : à la demande « envoie un Post sur https://example.com », il lance une commande puis rapporte le refus du portefeuille, action hors mandat : web.write, et explique que son mandat n'autorise que web.read jusqu'au 7 octobre 2026 à 22 h 02 UTC

L’écriture est refusée par le gestionnaire d’attestations : l’agent rapporte le motif et ne contourne pas.

Pour arrêter l’environnement en gardant la clé de l’agent :

docker compose --profile agent down

Limites

Les limites découlent d’une même cause : aucun service en face ne vérifie.

Les signatures ne sont vérifiées par personne, et les sites appelés ignorent ces en-têtes.

L’environnement s’écarte de la spécification KYA-OS sur deux points :

  • Il n’ouvre pas de session. La spécification prévoit une poignée de main préalable, où le service vérifie l’agent et lui ouvre une session dont l’identifiant accompagne ensuite chaque requête dans l’en-tête KYA-OS-Session-Id. Comme les services ne font pas de vérifications, il n’y a pas de création de session : l’agent envoie son mandat et sa preuve à chaque requête, directement.
  • J’ai ajouté les champs htm et htu à la preuve pour pallier l’absence de session, qui aurait lié la présentation à la session. Ils lient la preuve à la méthode et à l’adresse de la requête. Les deux noms viennent de DPoP (RFC 9449).