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 :
| Fichier | Portée | Dans le repo ? |
|---|---|---|
~/.pi/agent/settings.json | votre poste, tous vos projets | non |
.pi/settings.json | ce projet | oui |
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 :
--api-keyen ligne de commande~/.pi/agent/auth.json, écrit par/login- la variable d’environnement, ici
OPENROUTER_API_KEY - un
models.jsonpersonnalisé
Deux conséquences :
- pi ne lit pas
.env. Le runner le charge pour lui (_load_env()dansharness.py, chapitre 15). En interactif, passez par/login openrouterou unexport. auth.jsonpasse avant l’environnement. Si vous avez fait/loginsur votre poste, la clé de.envest ignorée. Dans une boîte du hors-site, sansauth.json, c’est elle qui sert. Une dépense sur la mauvaise clé se diagnostique donc dansauth.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 :
| Valeur | En headless, le projet est… |
|---|---|
ask (défaut) | ignoré, sans message |
never | ignoré |
always | chargé |
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 :
defaultProjectTrustest 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.mdet 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 leskeepRecentTokensderniers (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. Laissezretry.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
| pi | Claude Code | |
|---|---|---|
| Philosophie | noyau minimal, à étendre | outil complet, prêt à l’emploi |
| Niveaux de réglages | global, projet | organisation, CLI, local, projet, utilisateur |
| Listes entre niveaux | le projet remplace | elles se cumulent |
Lecture de .env | non | non |
| Headless dans un dossier non approuvé | projet ignoré, sans message | projet 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.