Chapitre 25 / 39
Paperclip — là où vivent les agents en arrière-plan
Paperclip est l’endroit où vivent les agents en arrière-plan de Sodimo. Quand le brief du matin s’exécute, quand l’alerte delta se déclenche, quand le commentaire hebdomadaire se compose overnight — c’est Paperclip qui les réveille, qui trace ce qu’ils ont fait, et qui montre le résultat à qui veut le consulter.
Statut : Planifié — s’active une fois le Framework Desktop sur site et le harness en production. Paperclip est le seul service que Sodimo livre comme image dérivée plutôt que comme quadlet upstream épinglé, parce que le runtime d’agents nécessite un troisième harness CLI (pi-mono) empilé sur les deux (claude, codex) qui embarquent dans l’upstream. La seule surface appartenant à Sodimo est sinon la colle d’intégration qui attribue les entrées run_ledger aux appels d’outils MCP de Sodimo.
La fiche opérationnelle — image, env, ports, dépendances — vit dans 38-quadlet-reference.md#paperclip. Ce chapitre est l’histoire : pourquoi l’image dérivée, pourquoi Restart=on-failure, pourquoi la liste blanche de hostnames, pourquoi le fichier env diverge.
Quand y aller
Trois scénarios amènent un membre de l’équipe sur Paperclip :
- Un agent a produit quelque chose d’incorrect. Le brief du matin a cité le mauvais client ; la lettre de relance a mentionné le mauvais solde. Le détail d’issue dans Paperclip montre le prompt exact, la réponse exacte, les appels d’outils intermédiaires, et le modèle utilisé.
- Le coût mensuel semble élevé. La page Costs ventile les dépenses par agent et par modèle. Un job planifié incontrôlable est visible en un coup d’œil.
- L’agent planifié pour X tourne-t-il toujours ? La page Routines liste chaque agent planifié — ce qu’il fait, quand il se déclenche, quand il a tourné pour la dernière fois, si cette exécution a réussi.
Ce qu’on voit
Paperclip a cinq pages qui comptent au quotidien :
- Activity. Le flux en direct — chaque exécution au moment où elle se déclenche, avec statut et durée.
- Agent detail. Un agent, son historique complet d’exécutions, ses dépenses en tokens, ses routines.
- Issue detail. Une unité de travail — la tâche, l’agent qui l’a prise, l’exécution qui l’a traitée, le résultat, la chaîne de provenance si la tâche vient d’un autre agent.
- Routines. Le planning — des règles de type cron qui réveillent un agent sur un minuteur. « Chaque jour de semaine à 07:30, brief du matin pour Rani. » « Chaque lundi à 09:00, commentaire hebdomadaire pour Paul. »
- Costs. Les dépenses mensuelles par agent, en séparant les exécutions locales (sans coût à la requête) des escalades vers le cloud.
Le flux est agnostique au CLI. Certains agents tournent sous pi-mono face aux modèles locaux ; d’autres tournent sous Claude Code face au palier cloud. Même dashboard, même schéma, même vue des coûts. Le choix du CLI est un réglage de l’agent.
Pourquoi une image dérivée
Paperclip est inhabituel dans la stack Sodimo : c’est le seul service qui se livre comme image dérivée plutôt que comme quadlet upstream épinglé. La dérivation est minimale — un Dockerfile de cinq lignes dans docker/paperclip/Dockerfile qui empile @mariozechner/pi@latest sur ghcr.io/paperclipai/paperclip:sha-b8725c5 — mais elle impose deux règles qui méritent d’être connues.
Première utilisation du répertoire top-level docker/. Jusqu’à Paperclip, sodimo/dotfiles ne contenait que de la config gérée par chezmoi ; aucune image construite par Sodimo n’existait. docker/paperclip/ établit le schéma pour toute future image dérivée dont la stack aurait besoin.
L’upstream tague des SHA de commit, pas du semver. Le tag sha-b8725c5 résout ce que l’upstream appelle v2026.416.0 — les deux sont interchangeables mais seul le SHA est stable. Le schéma de tag dérivé est ghcr.io/sodimo/paperclip:v2026.416.0-pi0.x.y — calver upstream plus la version @mariozechner/pi résolue au moment du build. Le placeholder 0.x.y doit être remplacé par la version réellement résolue à la publication ; en attendant, il signale « buildé mais pas publié ». Suivi dans sodimo/dotfiles#12.
Pourquoi pi. Paperclip embarque claude et codex pré-installés comme harnesses CLI par défaut pour les spawns d’agents. pi (pi-mono) est le troisième ; c’est le CLI que Sodimo standardise pour les agents sur modèles locaux, et il doit être ajouté à l’image parce que l’upstream ne le livre pas. L’empilement est délibérément minimal — un seul npm install -g et un nettoyage /tmp + cache npm — pour qu’une future montée de version upstream soit un changement d’une ligne dans le FROM.
L’image a été buildée localement le 2026-04-22 (2,55 Go, ~5 min de build) et enregistrée dans podman images sur le dev box. Elle n’est pas encore poussée sur ghcr.io — bloqué sur l’auth write:packages au niveau de l’organisation. Le quadlet référence ghcr.io/sodimo/paperclip:… de façon anticipée ; le premier pull en production aura lieu une fois le chemin de publication débloqué.
Env, secrets, et l’exception .tmpl
Le fichier env de Paperclip vit à home/dot_config/sodimo/paperclip.env.tmpl — le premier .tmpl chezmoi dans sodimo/dotfiles. Le fichier env de chaque autre service est un *.env plat à côté de son .container. La divergence est autorisée à la passation : l’env de Paperclip porte plusieurs secrets de 32 octets hexadécimaux qui bénéficient de l’injection de secrets pilotée par les templates chezmoi, et les regrouper sous dot_config/sodimo/ rend l’histoire « trouver le fichier de secrets » uniforme pour les futurs services de même forme.
Quatre secrets sont requis pour que Paperclip démarre :
BETTER_AUTH_SECRET— clé de signature de session BetterAuth. Paperclip refuse de démarrer sans elle ; l’échec au boot est bruyant et précoce, validé lors du smoke du mercredi 2026-04-22. Le template embarque un placeholderCHANGEME-BETTER-AUTH-32-BYTE-HEX; remplacer par un vrai 32-byte hex à la mise en service. La rotation invalide toutes les sessions actives.PAPERCLIP_SECRETS_MASTER_KEY— 32 octets hex. La perte de cette clé signifie la perte de chaque secret chiffré stocké dans la propre base de données de Paperclip.- Mot de passe Postgres — pour le sidecar
paperclip-db(postgres:16). - Clé master LiteLLM — utilisée comme bearer quand Paperclip appelle LiteLLM.
Les valeurs par défaut dans le template sont délibérément conservatrices :
PAPERCLIP_DEPLOYMENT_MODE=authenticated— l’interface est protégée par l’auth propre de Paperclip ; CF Access protège le hostname au bord en plus.PAPERCLIP_PUBLIC_URL=https://paperclip.sodimo.eu.- Télémétrie (
PAPERCLIP_TELEMETRY_DISABLED=1,DO_NOT_TRACK=1) désactivée.
Tout le trafic Anthropic et OpenAI depuis les agents Paperclip — qu’ils soient spawnés sous claude ou pi — est routé via http://litellm:4000 comme ANTHROPIC_BASE_URL et OPENAI_BASE_URL, avec la clé master LiteLLM comme bearer. Aucune credential cloud directe ne vit dans Paperclip. C’est le Principe 3 en forme concrète : chaque appel IA sortant passe par la passerelle, et chaque passage est capturé dans run_ledger.
Posture rootless et le contournement gosu
L’image upstream ghcr.io/paperclipai/paperclip utilise gosu node … comme entrypoint — un pattern de descente vers les droits non-privilégiés qui suppose un runtime de conteneur rootful. Sous rootless podman 5.8.2, ce pattern échoue au boot avec :
error: failed switching to "node": operation not permitted
Le smoke de l’apps-stack du mercredi 2026-04-22 a validé un contournement au niveau du quadlet : remplacer l’entrypoint pour contourner gosu et exécuter node directement, avec un --user 1000:1000 explicite. Ce contournement n’est pas encore dans l’arbre ; deux chemins de correction sont documentés dans sodimo/dotfiles#16 :
- Remplacement au niveau du quadlet (recommandé) — local, réversible, sans rebuild d’image.
PodmanArgs=--user 1000:1000 --entrypoint '["node",…]'danspaperclip.container. - Remplacement de l’entrypoint au niveau de l’image — retourner l’entrypoint dans
docker/paperclip/Dockerfile. Permanent, mais diverge plus fortement de l’upstream et complique les futures montées de version.
Le choix de conception est en attente. En attendant, Paperclip démarre proprement sur le rig de test avec le contournement quadlet et répond 200 sur /api/health.
La liste blanche de hostnames
Paperclip a une liste blanche PAPERCLIP_ALLOWED_HOSTNAMES à l’exécution qui est strictement plus étroite que le bind HTTP. Une requête avec un header Host absent de la liste blanche reçoit un 403 auto-descriptif invitant les opérateurs à exécuter :
paperclipai allowed-hostname <host>
Ce n’est pas un bug. paperclip.sodimo.eu en production est déjà sur la liste blanche dans le template. Le comportement se manifeste lors des smokes avec exposition LAN — quand la stack est temporairement bindée sur 0.0.0.0 et accédée par IP brute — et le message 403 est suffisamment auto-instructif pour que la réponse de l’opérateur soit une seule commande.
Posture de redémarrage
Paperclip est le seul service de la stack qui utilise Restart=on-failure plutôt que le Restart=always global. La divergence est explicitée à la passation : les défaillances de Paperclip sont généralement au niveau de la configuration (secret manquant, mauvaise URL de base de données, dérive d’auth upstream) et les boucles de retry silencieuses de systemd cachent plus qu’elles n’aident. on-failure produit une sortie non nulle propre remontée par systemctl --user status plutôt qu’une boucle bruyante.
C’est révisable. Après la première semaine de runtime en production sur le harness, si on-failure s’avère masquer des reconnexions DB transitoires que always aurait récupérées, la politique change. Pour l’instant : échouer bruyamment plutôt que récupérer silencieusement.
Routage
Chemin du trafic en production : cloudflared termine paperclip.sodimo.eu → Caddy (proxy avec préservation des WebSockets) → paperclip:3100. CF Access protège le hostname au bord, en plus du mode de déploiement authenticated propre à Paperclip.
À l’intérieur du harness, PublishPort=127.0.0.1:3100:3100 se bind uniquement sur le loopback de l’hôte — jamais 0.0.0.0. Pas d’accès direct LAN à Paperclip ; chaque accès passe par le chemin Cloudflare. Le bind sur le loopback de l’hôte est ce qui permet à Caddy (tournant sur le même hôte) de proxifier vers Paperclip sans que le port soit exposé au réseau plus large.
Verdict du smoke — 2026-04-22
Le smoke de l’apps-stack du mercredi soir a levé Paperclip de bout en bout sur le dev box :
- La stack s’est composée proprement après l’atterrissage du fix
BETTER_AUTH_SECRETdanse400703et l’application du contournement rootless--user 1000:1000au niveau du quadlet. /api/healtha retourné HTTP 200.- Les migrations Postgres contre le sidecar
paperclip-dbont terminé proprement au premier boot. - La liste blanche de hostnames s’est comportée exactement comme documenté — 403 sur les hostnames non listés, 200 sur
paperclip.sodimo.eu.
Cible de la prochaine session : câbler CF Access sur le hostname, valider la chaîne cloudflared → Caddy → Paperclip de bout en bout avec une vraie connexion Google Workspace.
Comment Paperclip s’intègre au reste de la stack
Paperclip ne remplace pas OpenWebUI et ne remplace pas le CRM. La séparation :
| Surface | Ce qui s’y trouve |
|---|---|
| OpenWebUI (chapitre OpenWebUI) | Chat interactif avec les modèles locaux. Ponctuel. |
| Paperclip | Exécutions d’agents planifiés ou en arrière-plan. Audité. |
| CRM (chapitre CRM) | Humains + clients. Kanban, fiches, notes. |
| Piler (chapitre Mail) | L’archive mail. Recherche et récupération. |
Les agents tournant sous Paperclip appellent les mêmes outils que toute autre surface IA — voir le chapitre Ce à quoi l’IA peut accéder. Les appels d’outils atterrissent sur l’endpoint MCP Cloudflare exactement comme ils le feraient depuis une session Claude.ai. C’est délibéré : chaque effet secondaire causé par un agent passe par le même endpoint qu’un humain emprunterait, de sorte que la piste d’audit est unifiée. Rien dans Paperclip ne court-circuite vers la machine locale.
Règle d’application symétrique
Les agents Paperclip envoient des emails via la Cloudflare Queue email_outbox via l’outil Worker email_send — jamais via le sendmail local sur l’hôte Fedora, même si sendmail est physiquement accessible. C’est le Principe 3 — l’IA doit emprunter le même chemin qu’un appelant API humain. La règle est importante parce que Paperclip est l’endroit le plus tentant pour la briser : l’agent est sur la même machine que sendmail, et un appel bash d’une ligne « fonctionnerait ». Cela couperait aussi silencieusement l’émission dans run_ledger pour cet envoi. Voir 15-design-principles.md#principe-3.
Approbations
Paperclip a une page d’approbations pour les résultats d’agents qui nécessitent qu’un humain valide avant l’envoi. Pendant cette mission la page reste non routée — chaque résultat d’agent atterrit comme brouillon dans la file pertinente (la file de lettres de relance de Paul, par exemple) plutôt que via le flux d’approbation propre à Paperclip. La fonctionnalité est présente dans le code ; l’activer est une décision post-passation.
Quand y aller, reformulé
Paperclip est l’endroit où un membre de l’équipe va quand la question est « qu’est-ce que l’IA a fait la nuit dernière, et est-ce que ça a coûté quelque chose ». L’endroit où un membre de l’équipe va quand la question est « que doit faire l’IA ensuite » est la bibliothèque de skills et OpenWebUI. L’un est la surface d’audit ; les deux autres sont les surfaces de création et de chat. Ils ne se chevauchent pas.