guest@6admin.io:~$

Ma conf opencode, état des lieux : build local sur qwen, plan sur du gros GLM

On m’a demandé récemment comment je faisais tourner mes agents et la réponse tient moins d’un secret que d’une conf qui a fini par tenir debout. J’ai ouvert opencode pour la première fois entre décembre et janvier, par curiosité plus que par conviction. J’y passe mes journées depuis juin. Voici l’état des lieux au moment où la conf a cessé de changer tous les matins, avec les réglages qui font la différence et ceux qui ne font que meubler.

Le socle

Tout part d’un opencode.json volontairement sobre, les instructions pointent vers un AGENTS.md global qui voyage avec la conf et l’agent par défaut est plan, on démarre en réflexion et le build se mérite. La partie que je considère non négociable, c’est les permissions: le shell est large mais une liste de motifs interdits ferme les portes qui ruinent une journée.

"permission": {
  "bash": {
    "*": "allow",
    "pkill*": "deny",
    "rm -rf /": "deny",
    "rm -rf ~": "deny",
    "shutdown*": "deny",
    ":(){ :|:& };:": "deny"
  }
}

Vous remarquerez que cette bonne vieille fork bomb bash est à l’honneur, comme elle l’était en déco du blog de 2006 à 2012. Le post PAM de 2007 la mettait déjà en scène. La logique d’ensemble tient en une phrase: on autorise large, on refuse explicitement ce qui est irréversible et on glisse un niveau ask pour ce qui mérite un humain entre les deux, les répertoires hors du projet par exemple, qui déclenchent une demande avant chaque accès.

La compaction mérite qu’on s’y attarde deux secondes parce que c’est là que beaucoup perdent leurs sessions: automatique, avec purge, surtout un réglage qui préserve le jeu de travail récent, de sorte qu’une session compactée ne reparte pas les mains vides. share reste désactivé, rien ne sort de la machine sans décision explicite, l’autoupdate se contente de prévenir, un serveur MCP tient la documentation à jour.

Chaque agent son modèle

La décision structurante, c’est le découpage par agent et non par outil, chaque agent a son modèle fixé dans la conf et l’humeur du jour n’a pas le droit de vote.

"agent": {
  "build":       { "model": "llamacpp-mlai/qwen3-coder-30b-a3b-instruct" },
  "compaction":  { "model": "llamacpp-mlai/qwen3-coder-30b-a3b-instruct" },
  "general":     { "model": "llamacpp-mlai/qwen3.5-35b-a3b" },
  "mr-reviewer": { "model": "llamacpp-mlai/qwen3.5-35b-a3b" },
  "explore":     { "model": "llamacpp-mlai/qwen3.5-9b-mtp" },
  "title":       { "model": "llamacpp-mlai/qwen3.5-9b-mtp" },
  "plan":        { "model": "openrouter/z-ai/glm-5.2" }
}

Le build enchaîne des dizaines d’appels par session, lire un fichier, poser un patch, relancer les tests, il tourne en local sur le qwen3-coder 30b-a3b, trente milliards de paramètres dont trois actifs, le MoE coder qui tient en Q4 dans les 24 Go de la RTX 4090, servi par llama.cpp depuis une workstation Linux dédiée, mlai.gw.6admin.lab. Le résumé de compaction monte sur le coder aussi, résumer du code c’est encore du code. Les généralistes et les revues portent sur le qwen3.5-35b-a3b, la génération de février, en MoE avec sa tête MTP pour le débit. Les titres de session et l’agent d’exploration, eux, partaient sur la 3080, une seconde carte qui servait le petit qwen3.5-9b-mtp derrière la même gateway, sur un autre port, le modèle qui suffit quand la tâche tient dans une phrase. Le plan monte sur du gros GLM côté OpenRouter, le 5.2 en l’occurrence, le travail de lecture large et d’arbitrage, exactement celui pour lequel un gros modèle se justifie. Il n’en a besoin que quelques fois par session. Le camp qui produit est au coder, celui qui lit et juge au 35b, celui qui flâne et titre sur le petit 9b, celui qui pense chez le gros GLM. La séparation des devoirs ne coûte rien, on ne fait pas relire un patch par le modèle qui l’a écrit quand on peut l’éviter, les angles morts ne s’additionnent pas.

Multi-provider et caching

Le provider local se déclare comme n’importe quel provider openai-compatible, une baseURL qui pointe vers la box llama.cpp et des timeouts longs, parce que la génération locale est lente et l’agent attend sans se plaindre tant qu’on lui a dit que c’est normal.

"llamacpp-mlai": {
  "npm": "@ai-sdk/openai-compatible",
  "options": { "baseURL": "http://mlai.gw.6admin.lab:9931/v1" }
}

Le petit 9b, lui, répondait derrière la même gateway, sur un autre port, une ligne de plus dans la conf avec le même schéma llama.cpp, les appels pas chers et fréquents n’ont jamais touché la grosse carte.

Côté caching, le trick qui rapporte tient en une clé, setCacheKey, qui fige la clé de cache de prompt côté provider: le préfixe partagé, system prompt, AGENTS.md, skills, devient reconnaissable d’un appel à l’autre et le cache frappe réellement, la session coûte moins et répond plus vite. La contrepartie arrive au changement de modèle, le cache repart de zéro, ce qui se négocie bien quand le routage des agents a été posé une fois pour toutes.

Et parce que le deny by default s’applique aussi aux modèles, les providers grand public sont désactivés en dur dans la conf: ils existent, l’agent ne peut pas les appeler. Changer de stack se fait en commentant une ligne, le genre de bascule qui a déjà sauvé une soirée quand la box locale est partie en maintenance.

L’AGENTS.md, mode d’emploi

L’AGENTS.md global porte tout ce qui ne s’invente pas en cours de session et son existence change le comportement des agents bien plus que n’importe quel réglage du opencode.json. Quelques règles verbatim, chacune payée par une cicatrice:

  • “Repo-level AGENTS.md apply ONLY inside that repo tree. Reading repo files does NOT activate its rules elsewhere.” les règles d’un repo exigeant ne fuient pas dans les projets paisibles.
  • “Exactly ONE in_progress while working.” la discipline de todo, une seule tâche en cours, jamais de lot de validations en fin de journée pour se faire pardonner.
  • “MUST NOT idle-wait in terminal: no sleep N.” l’agent ne dort pas entre deux vérifications, il attend par le mécanisme de l’outil ou il lance en tâche de fond et il reprend au signal.
  • “MUST NOT install or invoke different version than project declares.” la version épinglée par le projet gagne, pas celle du jour ni celle que le modèle croit connaître.
  • “One topic = one worktree.” une session, un worktree, jamais de sujet mélangé dans le clone principal.
  • “MUST NOT kill opencode processes.” redémarrer l’agent est une décision humaine, pas un raccourci que le modèle se donne.

Il est long, il vit, chaque section a été ajoutée après une douleur précise plutôt qu’anticipée dans un fauteuil.

Les skills incontournables

Trois familles sortent du lot, celles que je rebranche partout.

  • La CI et la revue: le gitlab-ci-skill venu de chez GitLab pour les pipelines, le trio cavecrew, un investigateur qui retrouve le code en table fichier:ligne, un builder qui n’accepte que les retouches chirurgicales, un reviewer qui sort des lignes sévérité-tagguées. Et mr-reviewer, la porte de pre-merge qui lance elle-même la suite de tests et ne rend qu’un verdict, MERGE OK ou BLOCK.
  • Le dev et la sécurité: les skills de chez Trail of Bits pour la revue de sécurité, ui-ux-pro-max pour les interfaces, les skills d’antfu pour l’écosystème JS, ceux de Node pour le runtime.
  • L’infra maison: nginx, traefik, kubernetes, openbao et opentofu, ovh, tout ce qui évite de réécrire de mémoire des runbooks que j’ai déjà écrits une fois.

Les commandes en slash font le coller-coller des routines et le reste de la collection couvre les domaines que je traverse moins souvent, prête à servir.

Les plugins

Il faut à ce point du parcours une honnêteté d’architecte: l’AGENTS.md et les skills sont du pouvoir doux, des conventions que le modèle peut choisir de suivre ou d’ignorer, ça pèse dans le prompt mais rien ne pèse sur l’exécution. Ce qui contraint vraiment, c’est le harnais et ses plugins, du pouvoir dur sur lequel le modèle ne vote pas.

openrtk fait passer les commandes shell par RTK, le filtre qui compresse la sortie des outils avant qu’elle n’atteigne le modèle, le gain se mesure en tokens et donc en facture et en contexte disponible. Le plugin caveman tient le mode de parole et le comportement des agents, le ton laconique côté session, les agents build ou title qui restent dans le rang sans que je les recadre à chaque tour.

Les garde-fous font le reste du travail dur: worktree-guard impose une session, un worktree, workflow-guard applique la politique en hooks durs, protection des branches, discipline de tâches et de vérifications, interception des commandes destructives. damage-control a fini par passer la main: il bloquait les commandes dangereuses, workflow-guard a repris ce terrain destructif et y a ajouté la discipline de workflow, avec des vérifications que le modèle ne peut pas contourner par la discussion.

Le thème

Le terminal est transparent et les panneaux de l’interface posent par-dessus en translucide, le fond du bureau reste visible et la lecture se fait sans le carré opaque habituel. La palette tourne autour d’un primaire pêche, #fab283, avec des accents violets et des verts pour les diffs, en paire sombre et claire complète.

Et cette palette n’est pas neuve, elle est recyclée d’un thème que j’avais monté entre décembre et janvier, dont les couleurs m’ont suivi d’outil en outil jusqu’à devenir la signature de cette conf. Un thème, ça vieillit bien quand il a déjà survécu à un autre hôte.

La part honnête

Le routage par agent a un coût d’entrée: le 35b-a3b passe en Q4 dans les 24 Go de la 4090 avec de la marge, le coder 30b-a3b aussi, à condition de choisir ses quantizations et de laisser l’OS respirer, un modèle qui se recharge ou qui se fragmente se voit immédiatement dans la latence. Le cache, aussi, a son prix caché: changer de modèle le vide, ce qui rappelle qu’il vaut mieux finir le routage avant d’optimiser la facture. Le gros GLM se facture, littéralement, quelques appels par session suffisent à le rentabiliser, au-delà c’est le routage qui a un problème.

Les garde-fous bloquent parfois un mouvement que j’aurais jugé légitime, on maudit le guard le jour où il s’en mêle et on le bénit le lendemain. Et le thème translucide suppose un terminal qui rend la transparence proprement, sur un fond chargé la lisibilité tombe et il faut savoir baisser les panneaux.

Voilà l’état des lieux. Le jour où un réglage bougera ici, une douleur neuve l’aura mérité et ce sera plutôt bon signe.