Systèmes & Logiciel
minicode
Un agent de code dans le terminal, écrit à la main pour comprendre le harness
- Rôle
- Développement
- Contexte
- Projet personnel
- Stack
- Python, Anthropic SDK, Ollama, rich, prompt_toolkit
Vue d'ensemble
Un mini Claude Code en Python, sans framework d'agent. Tout le code autour du modèle est écrit à la main : la boucle d'agent, 8 outils dont des sessions interactives dans un pseudo-terminal, des permissions allow / deny, la gestion d'une fenêtre de 8 192 tokens, des sous-agents en lecture seule, un journal JSONL de chaque appel et 13 tâches d'évals. Il tourne en local et gratuitement, avec Ollama et qwen3:8b.
11/13
tâches d'évals réussies en local par qwen3:8b
Points clés
- 01Boucle d'agent, outils et permissions sans framework
- 02Contexte géré par effacement puis résumé, sous-agents à historique vide
- 03Évals de 5/10 à 11/13 avec qwen3:8b, en rendant le harness générique
Architecture
Sous le capot
Un agent de code dans le terminal, façon Claude Code, écrit à la main en Python sans framework d'agent. Le modèle ne fait que demander des outils ; tout le reste est le harness : la boucle qui le rappelle, les outils qui agissent sur le projet, les permissions qui décident, la gestion d'une fenêtre de 8 192 tokens, les sous-agents et un journal de chaque appel. Il tourne gratuitement en local, avec Ollama et qwen3:8b.
- lignes de Python, hors tests et évals
- 2 343lignes de Python, hors tests et évals
- outils, dont task pour les sous-agents
- 8outils, dont task pour les sous-agents
- tokens de fenêtre par défaut en local
- 8 192tokens de fenêtre par défaut en local
- tests, sans aucun appel au modèle
- 75tests, sans aucun appel au modèle
- tâches d'évals réussies par qwen3:8b
- 11/13tâches d'évals réussies par qwen3:8b
- étapes de la feuille de route, toutes livrées
- 10étapes de la feuille de route, toutes livrées
Qui parle à qui
Terminal
ui.py
rich et prompt_toolkit : saisie, réflexion en direct, diff, choix de permission
une demande, ajoutée à messages
Harness
minicode.py
_agent_loop : rappelle le modèle tant qu'il demande des outils, 30 appels au plus
context.py
Estime les tokens, efface les vieux résultats, résume au-delà de 70 %
agents_md.py
AGENTS.md du projet et des dossiers parents, dans le prompt système
tool_use renvoyé par le modèle
Garde-fous
tools.precheck
Un appel voué à l'échec revient au modèle sans te déranger
permissions.py
deny, puis commande composée, puis allow, sinon on demande
run_tool(name, input)
Outils
tools.py
read_file, list_dir, grep, edit_file, bash, interactive_start, interactive_send
subagent.py
task : une 2e boucle en lecture seule, 12 appels au plus
subprocess, pseudo-terminal
Projet
Workspace
Chemins relatifs au projet, bash coupé après 60 s
.minicode/
Règles de permission, historique, journaux JSONL
Services externes
Ollama · qwen3:8b
Local et gratuit, sur la carte graphique
Principes de conception
01
Le modèle n'a pas de mémoire
La conversation est une simple liste, messages, renvoyée en entière à chaque appel. /clear la vide, un sous-agent en reçoit une neuve.
02
Le modèle n'exécute rien
Il répond tool_use ; le harness exécute et renvoie un tool_result avec le même id. Même un refus renvoie un tool_result, sinon l'API rejette l'historique.
03
Le harness décide
Avant edit_file, bash ou interactive_start : une règle deny l'emporte même en mode YOLO, et une commande composée (; && | $( …) est toujours demandée.
04
Des modifications petites et vérifiées
edit_file remplace un texte exact de moins de 15 lignes, dit où le texte diverge, et refuse ce qui casserait un fichier Python ou JSON valide.
05
Le contexte se gère du moins cher au plus cher
Effacer les vieux résultats d'outils, puis résumer les tours précédents, puis ne garder que le dernier résultat. Un tool_use n'est jamais séparé de son tool_result.
06
Mesurer plutôt qu'essayer
Chaque appel est journalisé en JSONL, et 13 tâches fixes tournent dans des dossiers neufs. Un changement de prompt se juge sur le score, pas sur un essai.
Un tour de boucle
- 01fit_contextContexteAvant chaque appel : estimer, puis effacer ou résumer au-delà de 70 %
- 02call_modelAppelEn streaming ; le vrai usage recalibre l'estimation suivante
- 03precheckContrôleArguments validés, edit_file simulé avant toute question
- 04ask_permissionPermissionRègle deny, règle allow, YOLO, ou question à l'utilisateur
- 05run_toolExécutionSortie plafonnée à ~20 % de la fenêtre, code de sortie ajouté
↳ Le modèle a fini
- Plus d'outil demandé : son texte est la réponse
- Réponse vide : le harness le relance une fois
↳ Le harness arrête
- Le même appel raté 3 fois : indice au 2e, arrêt au 3e
- 30 appels sans réponse finale
- refusal, max_tokens ou Ctrl-C : historique remis à son état d'avant la demande
Un sous-agent suit la même boucle, avec son propre historique et son propre budget. Seul son rapport final devient le tool_result de task.
Exemple · « lance les tests ; si un test échoue, corrige »
- 01UI → Harnessdemande
- 02Harness → LLMstream(messages)
- 03LLM → Harnesstool_use bash
- 04Harness → Toolstests : code de sortie 1
- 05LLM → Harnesstool_use grep, puis read_file
- 06LLM → Harnesstool_use edit_file
- 07Harness → UIdiff et choix de permission
- 08UI → Harness1. Oui
- 09Harness → Toolsedit_file calc.py
- 10LLM → Harnesstool_use bash
- 11Harness → Toolstests : code de sortie 0
- 12LLM → UIend_turn : réponse
Essai réel avec qwen3:8b sur un calc.py où add soustrait : le modèle enchaîne bash, grep, read_file, edit_file et bash, sans aide. Entre deux appels, chaque tool_result repart au modèle avec tout l'historique. Ici bash est autorisé par une règle allow ; edit_file est demandé.
Évals : deux runs de qwen3:8b
| Tâche | 1er run | 2e run | Constat |
|---|---|---|---|
| lire_valeur | ✗ | ✓ | Pas visée par les changements : réussie par hasard |
| corriger_bug | ✗ | ✓ | Lance enfin python3 test_calc.py au lieu de pytest |
| renommer | ✗ | ✓ | Vérifie en lançant main.py |
| gros_fichier | ✗ | ✓ | Lit la suite avec start_line |
| compteur_jeu | ✗ | ✗ | Trouve le bug, décrit la correction, ne la fait pas |
| corriger_js | — | ✓ | Trouve npm test seul dans package.json |
| corriger_shell | — | ✗ | « Corrigez la ligne 4 » au lieu de le faire |
| modifier_json | — | ✓ | — |
| 5 autres tâches | ✓ | ✓ | — |
5/10 puis 11/13, un seul essai par tâche : qwen3 est aléatoire. Les deux échecs restants ont la même cause, le modèle décrit l'action au lieu de la faire.
Ce que qwen3:8b a appris au harness
| Constat | Changement dans le harness |
|---|---|
| Recopie l'exemple de la description d'un outil | Plus aucun exemple concret dans les descriptions |
| Ignore les outils interactifs et reste sur bash | interactive_start et interactive_send placés avant bash dans la liste |
| Fixe le nombre aléatoire à 50 pour que son test passe | Consigne : ne jamais modifier un programme juste pour un test |
| Réécrit tout le fichier de mémoire et oublie une ligne | old_string limité à moins de 15 lignes, syntaxe vérifiée |
| Refait 6 fois exactement le même appel raté | Indice au 2e échec identique, arrêt au 3e |
| Tente sudo pacman pendant une éval en mode YOLO | Règles deny dans chaque dossier d'éval, plus fortes que YOLO |
Stack complète
| Techno | Version | Rôle |
|---|---|---|
| Python | ≥ 3.12 | Langage, sans framework d'agent |
| uv | — | Dépendances et lancement (uv run) |
| pytest | 9.1.1 | 75 tests, sans appel au modèle |
| Techno | Version | Rôle |
|---|---|---|
| anthropic | 1.10.0 | Client du serveur Ollama, en streaming |
| rich | 15.0.0 | Affichage : Markdown, diff, panneaux |
| prompt_toolkit | 3.0.53 | Saisie, historique, complétion des commandes |
| Techno | Version | Rôle |
|---|---|---|
| Ollama | — | Serveur local, API compatible avec le SDK Anthropic |
| qwen3:8b | — | Le modèle utilisé, ~5 Go, sur 8 Go de VRAM |
Relevé dans le code, commit e2e1442 du 2 octobre 2026
Projet suivant MAGIC
MagicCards →