Annexe — Harnais pi Chapitre A1 / 43

Configurer pi en profondeur & le trust en headless

Qui a créé pi et pourquoi, puis ses réglages pas à pas : le modèle de démarrage, la fusion global/projet, la clé d'API, et le piège de la confiance en headless. Comparé à Claude Code à chaque étape. La pièce du jour : .pi/settings.json.

pi en deux mots

pi est un harnais d’agent de code open source (licence MIT), créé en 2025 par Mario Zechner, développeur autrichien connu pour le framework de jeu libGDX. Depuis avril 2026, il est maintenu par Earendil, la société viennoise d’Armin Ronacher, le créateur de Flask.

Sa philosophie tient en une formule : des primitives, pas des fonctionnalités. pi livre un noyau minimal, quatre outils (read, write, edit, bash), et vous l’adaptez à votre façon de travailler par des réglages JSON et des extensions TypeScript. Claude Code fait le choix inverse : un outil complet, avec beaucoup de fonctions prêtes à l’emploi.

Ce chapitre couvre la première couche de pi : ses réglages. Vous partez d’un fichier de deux lignes et ajoutez une notion à la fois. La pièce du jour est .pi/settings.json dans plume-factory. Les cinq chapitres de l’annexe portent sur pi seul : l’adaptateur _run_claude ne change pas.

1. Un premier réglage : le modèle de démarrage

Lancez pi dans plume-factory : il démarre sur le modèle par défaut de votre poste, pas sur le workhorse de votre roster. Créez .pi/settings.json avec deux lignes :

{
  "defaultProvider": "openrouter",
  "defaultModel": "z-ai/glm-5.3"
}

defaultProvider choisit le fournisseur. defaultModel donne l’identifiant du modèle chez ce fournisseur. Un troisième réglage, defaultThinkingLevel (de off à max), fixe le niveau de réflexion. Relancez pi, répondez /trust à sa question (section 4), et il démarre sur GLM.

Ces valeurs ne servent qu’au démarrage. Un drapeau gagne toujours : pi --model … --thinking …. C’est ainsi que le runner de l’usine impose son roster, quoi que dise le fichier.

Côté Claude Code : même principe, avec la clé model dans .claude/settings.json et le drapeau --model qui gagne.

2. Deux fichiers, une fusion

pi lit deux fichiers de réglages :

FichierPortéeDans le repo ?
~/.pi/agent/settings.jsonvotre poste, tous vos projetsnon
.pi/settings.jsonce projetoui

Le projet passe par-dessus le global, clé par clé. Dans un objet imbriqué, la fusion descend : un global compaction.enabled: true et un projet compaction.reserveTokens: 8192 donnent les deux réglages. Exception : un tableau projet, comme defaultTools, remplace le tableau global.

Les chemins de ressources (skills, extensions, prompts) partent du dossier du fichier. Dans le projet, "skills": ["../.claude/skills"] désigne donc plume-factory/.claude/skills, où vivent vos skills factory et factory-orchestrator.

Côté Claude Code : cinq niveaux au lieu de deux (organisation, ligne de commande, .claude/settings.local.json, .claude/settings.json, ~/.claude/settings.json). Et les listes, comme permissions.allow, se cumulent entre niveaux au lieu de se remplacer.

3. La clé d’API

pi prend la première clé trouvée, dans cet ordre :

  1. --api-key en ligne de commande
  2. ~/.pi/agent/auth.json, écrit par /login
  3. la variable d’environnement, ici OPENROUTER_API_KEY
  4. un models.json personnalisé

Deux conséquences :

  • pi ne lit pas .env. Le runner le charge pour lui (_load_env() dans harness.py, chapitre 15). En interactif, passez par /login openrouter ou un export.
  • auth.json passe avant l’environnement. Si vous avez fait /login sur votre poste, la clé de .env est ignorée. Dans une boîte du hors-site, sans auth.json, c’est elle qui sert. Une dépense sur la mauvaise clé se diagnostique donc dans auth.json.

Côté Claude Code : /login ou ANTHROPIC_API_KEY. Pas de lecture de .env non plus.

4. La confiance, et le piège du headless

Un dossier .pi/ peut changer le comportement de pi et exécuter du code par ses extensions. pi ne le charge donc qu’avec votre accord : la confiance (project trust).

En interactif, pi pose la question. /trust enregistre la réponse dans ~/.pi/agent/trust.json, valable pour ce dossier et ses sous-dossiers, dans tous les modes.

En headless (pi -p, --mode json, --mode rpc), personne ne répond. Sans décision enregistrée, pi suit le réglage global defaultProjectTrust :

ValeurEn headless, le projet est…
ask (défaut)ignoré, sans message
neverignoré
alwayschargé

Pour un seul run, --approve (-a) charge le projet et --no-approve (-na) l’ignore.

Le piège. Sur votre poste, vous avez fait /trust et tout marche. Dans une boîte neuve du hors-site (chapitre 26), aucune décision n’existe : le run passe, mais sans vos skills ni votre niveau de réflexion, et rien ne le signale. Corrigez dans la recette setup de la boîte (chapitre 22) en écrivant un global avec "defaultProjectTrust": "always". C’est acceptable parce que la boîte est jetable et ne contient que votre dépôt.

Deux précisions :

  • defaultProjectTrust est global uniquement. Écrit dans .pi/settings.json, il est ignoré, sinon un dépôt cloné pourrait s’approuver lui-même.
  • La confiance n’est pas un bac à sable. AGENTS.md, CLAUDE.md et les extensions globales se chargent toujours. Elle empêche seulement un dépôt de modifier votre harnais sans vous.

Pour vérifier ce qu’un run headless a chargé, comparez deux runs :

pi -p --mode json "ping"                # projet approuvé : modèle de .pi/settings.json
pi -p --mode json --no-approve "ping"   # projet ignoré : modèle du global

Les champs provider et model de l’événement message_end indiquent le modèle utilisé. Si les deux runs affichent le même modèle alors que le projet en déclare un autre, la confiance manque.

Côté Claude Code : le choix inverse. claude -p n’affiche jamais le dialogue de confiance et charge quand même l’essentiel du projet : hooks, bloc env, serveurs de .mcp.json. Seules les règles permissions.allow du projet sont écartées, avec un avertissement sur stderr. pi refuse par défaut, Claude Code accepte par défaut.

5. Compaction et retry

Deux réglages comptent quand aucun humain ne regarde.

  • Compaction. Quand le contexte atteint la fenêtre moins compaction.reserveTokens (16 384 par défaut), pi résume les anciens messages et garde les keepRecentTokens derniers (20 000 par défaut). Chaque résumé coûte un appel modèle. C’est rare dans une phase courte, plus fréquent dans la boucle de correction du chapitre 13.
  • Retry. retry.maxRetries (3 par défaut, attente de 2, 4 puis 8 s) relance après une erreur passagère du fournisseur. Les reprises après une gate rouge restent au runner. Laissez retry.provider.maxRetries à 0 : un SDK qui réessaie seul peut masquer une limite d’usage et bloquer l’agent jusqu’au reset du quota.

Côté Claude Code : compaction automatique près de la limite, et /compact pour la déclencher à la main.

pi et Claude Code côte à côte

piClaude Code
Philosophienoyau minimal, à étendreoutil complet, prêt à l’emploi
Niveaux de réglagesglobal, projetorganisation, CLI, local, projet, utilisateur
Listes entre niveauxle projet remplaceelles se cumulent
Lecture de .envnonnon
Headless dans un dossier non approuvéprojet ignoré, sans messageprojet chargé, sauf permissions.allow

Fil rouge — la pièce posée aujourd’hui

.pi/settings.json règle pi quand personne ne lui passe de drapeau : vos sessions interactives, l’orchestrateur du chapitre 25, un pi -p de diagnostic. Le runner continue d’imposer le roster par drapeaux, la couture ne bouge pas. Le gain : le même nœud pi sur votre poste et dans une boîte, pour zéro jeton.


Travaux pratiques — la pièce du jour

Une pièce complète à poser dans le repo compagnon plume-factory, qui devient, chapitre après chapitre, votre usine logicielle agentique.

Prérequis de l’annexe : pi installé (npm install -g --ignore-scripts @earendil-works/pi-coding-agent, doc sur https://pi.dev), Bun (chapitre 2) et un TypeScript que vous savez lire.

Pièce — .pi/settings.json

Le fichier de la section 1, complété. Trois valeurs s’écartent des défauts : keepRecentTokens plus bas pour un petit payload, maxRetries à 2 pour laisser les reprises au runner, et provider.maxRetries à 0. Remplacez les identifiants de modèle par ceux de votre roster du jour.

{
  "defaultProvider": "openrouter",
  "defaultModel": "z-ai/glm-5.3",
  "defaultThinkingLevel": "medium",
  "modelThinkingLevels": {
    "openrouter/z-ai/glm-5.3": "medium",
    "openrouter/deepseek/deepseek-v4-flash-0731": "low"
  },
  "enabledModels": [
    "openrouter/z-ai/glm-5.3",
    "openrouter/deepseek/deepseek-v4-flash-0731"
  ],
  "skills": ["../.claude/skills"],
  "quietStartup": true,
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 12000
  },
  "retry": {
    "enabled": true,
    "maxRetries": 2,
    "baseDelayMs": 2000,
    "provider": {
      "maxRetries": 0,
      "maxRetryDelayMs": 60000
    }
  }
}

Pièce — .pi/README.md

Pour qui clone le dépôt ou le reçoit dans une boîte.

# Le nœud pi de l'usine

Ces réglages valent quand pi démarre sans les drapeaux du runner (`adws/`).

1. **pi ne lit pas `.env`.** En interactif : `/login openrouter` ou `export OPENROUTER_API_KEY=...`.
   `~/.pi/agent/auth.json` passe avant la variable d'environnement.
2. **Approuvez ce dossier une fois** : lancez `pi` ici, puis `/trust`. Sans décision, le headless
   ignore ce fichier en silence. Boîte jetable : `"defaultProjectTrust": "always"` dans
   `~/.pi/agent/settings.json`.
3. **Le `retry` d'ici couvre les erreurs du fournisseur.** Les reprises de l'usine appartiennent
   au runner.

Vérifier : `pi -p --mode json "ping"` doit afficher le modèle de ce fichier.

La gate du TP

Depuis la racine de plume-factory, après un pi suivi de /trust :

pi --version
pi -p --mode json "ping"
pi -p --mode json --no-approve "ping"
pi -p "Liste les skills disponibles, un par ligne, sans commentaire."

Résultat attendu : la deuxième commande affiche "provider":"openrouter" et "model":"z-ai/glm-5.3", la troisième le modèle du global, la quatrième factory et factory-orchestrator. Coût total : moins d’un centime, quelques secondes.


Quiz — teste tes connaissances
Annexe — Harnais pi 7 questions Objectif : 5/7 minimum
0/7
bonnes reponses
Objectif non atteint (minimum 5/7 requis).
Remonte relire la fiche memo en pretant attention aux points manques, puis cliquer sur « Recommencer » pour retenter.