feat(cerveau): un cerveau local configurable, une table de réflexes, et le journal en fenêtre - #7
Conversation
Un audit paresseux de tout l'arbre, chaque coupe vérifiée avant d'être appliquée plutôt que crue sur parole. Les trois grosses : - `sprites/variants.py` : huit poissons d'exploration qui ont fait leur travail — les quatre retenus sont redessinés dans `parts.BODIES` depuis longtemps. Les six symboles encore vivants (CW/CH, BW/BH, pad, compose) remontent en tête de parts.py, et le fichier part avec le reste. - `sprites/preview.py` : un décodeur PNG écrit à la main pour un seul appelant, un test qui voulait la largeur d'une planche. `struct.unpack` sur l'IHDR la lit en une ligne. (La planche fait 144, pas 72 : generate rend en 2x — l'ancien test le disait, le nouveau aussi.) - `voxtype.toml` : 103 lignes dont 89 étaient la configuration d'exemple du fournisseur — un démon que Jarvis ne lance pas, un raccourci qu'Hyprland gère, des retours audio que la voix fait elle-même. Vérifié en transcrivant le même wav avec l'ancien fichier et le nouveau : sortie identique, exit 0. Le reste : deux parseurs d'arguments maison remplacés par argparse (qui rend `--help` et de meilleurs messages en prime), les quatre sondes jumelles de Journal.qml en un `component`, le second format d'options de PillRow que ses cinq appelants n'ont jamais utilisé, deux réglages de PixelSprite que personne ne règle, sept crochets morts du double de test du Touch Bar, `--all-voices` (changer `voix:` et relancer suffit), et deux options release-please inertes au-dessus de 1.0.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ls94YXz6tFuYvTzRW6if3t
…uche Le journal flottait au-dessus du travail en cours (`WlrLayer.Top`, `exclusiveZone: 0`) : il fallait le fermer pour relire ce qu'il cachait. En `FloatingWindow` — l'idiome que le greffon dev-gallery du shell emploie déjà — Hyprland le tuile comme n'importe quelle application, l'écran se partage, et il se déplace et se redimensionne avec les raccourcis que l'utilisateur connaît déjà. Vérifié : `class=org.quickshell`, `floating=False`, 780x953 à [807,34], et le terminal voisin réduit d'autant. Le clavier vient désormais du compositeur, donc toute la danse WlrKeyboardFocus disparaît avec la couche. Les deux gestionnaires de `visible` sont fusionnés — QML n'en garde qu'un : le focus à l'ouverture, et `journalOpen` remis à false quand c'est le gestionnaire de fenêtres qui ferme, sans quoi le prochain appui sur `journal` ne ferait rien. Au passage, trois verbes IPC sans aucun émetteur (`ping`, `toggle`, `journalClose`) et un `id` non référencé, trouvés par l'audit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ls94YXz6tFuYvTzRW6if3t
Jarvis peut désormais répondre sans réseau. Son oreille (voxtype) et sa voix (piper) étaient déjà locales ; seul le cerveau sortait. ## Le pont `bin/jarvis-local` parle à n'importe quel serveur compatible OpenAI — llama-server, LM Studio, Ollama, LocalAI, vLLM — et surtout n'invente pas sa propre sortie : il imite exactement les événements stream-json de `claude -p`, que `bin/jarvis-sentences` avale déjà. Le chemin de la voix ne sait pas quel cerveau lui parle, et c'est ce qui rend la bascule possible en une dizaine de lignes. Il n'a aucun outil, délibérément : un modèle de 4 milliards de paramètres qui pilote un shell est un risque qu'on ne prend pas. Trois lignes dans l'âme (`cerveau`, `cerveau-url`, `cerveau-modele`) et une section « Cerveau » dans le Control Center. Une seule entrée « compatible OpenAI » plutôt que vingt fournisseurs : ils parlent tous le même protocole. ## Ce qui a été mesuré, et qui a décidé du reste Sur ce M2 sous Asahi, Vulkan/honeykrisp bat le CPU d'un facteur 1,7 en prefill et 1,3 en génération. Avec Qwen3.5-4B en Q4_K_M, la première phrase prononcée arrive en 1,5 à 4,6 s à chaud. Deux pièges qui rendent un cerveau local muet sans rien dire : - un modèle « thinking » envoie sa réflexion dans `reasoning_content`, épuise son budget de tokens et la réponse n'arrive JAMAIS. D'où `--reasoning off` dans l'unité, obligatoire, et le filtrage côté pont. - le prefill de la persona coûte ~16 s. `omarchy-jarvis warmup` le paie au démarrage du serveur, en envoyant EXACTEMENT la persona que l'échange enverra : un préchauffage à « bonjour » sans prompt système réchauffe un préfixe que personne n'utilise, et le premier échange coûtait encore 23 s (mesuré). 23 376 ms → 2 581 ms. ## La table des réflexes Interrogé hors ligne sur l'heure, le modèle a répondu « Il est 14h37 » à 23 h passées, avec aplomb, dans le format parlé exact de Jarvis. Une valeur plausible et fausse est pire qu'un refus. `memory/REFLEXES.md` répond donc à l'heure, la date, la batterie, le ne-pas-déranger, le son, les thèmes — sans modèle, en deux cents millisecondes, et exactement. Motifs ancrés sur le transcript entier comme `NOISE.md`, slot numérique borné 0-100, liste d'interdits appliquée au chargement (ce chemin ne passe pas par settings.json, c'est sa seule porte), et `omarchy-jarvis reflexe?` pour l'asserter sans micro. 118 cas figés, dont 56 négatifs : de vraies demandes qui doivent atteindre le cerveau. C'est cette liste qui grandit quand une rangée se révèle trop gourmande. ## Trois bugs que la route locale a révélés - « Oublier » ne connaissait que les sessions claude, donc échouait sur toute la route locale — et silencieusement, le panneau appelant `undo --quiet`. Il retire maintenant la dernière paire du fil JSON, et un refus se dit par notification. - `reset` effaçait le fil mais pas `$LAST_ASK`/`$LAST_REPLY` : la carte du panneau continuait d'afficher l'échange d'avant, ce qui se lit comme « il ne s'est rien passé ». - L'onglet « Aujourd'hui » lisait le JSONL de claude et restait donc vide en local. Il se replie sur le journal du jour, qui porte l'heure et la paire, et que les deux routes écrivent. ## Aussi `brain_set` ne touche plus `$BRAIN_STATUS` sur un tour local — il décrit le nuage, et un succès local y écrivait `ok` par-dessus un quota encore valide. Le mode `auto` retente le nuage toutes les dix minutes : sans ce délai, la garde ci-dessus condamnait Jarvis à rester local à vie. `set_setting` utilise `|` comme séparateur sed : une URL contient des barres obliques, et le premier enregistrement d'adresse depuis le panneau aurait corrompu l'âme. Enfin, la part de l'audit qui touchait `bin/jarvis` et `bin/jarvis-transcript` est ici faute de pouvoir séparer les fichiers : mascot_emote/mascot_aside fusionnés, un heredoc python remplacé par `tac | awk | tac`, sept surcharges JARVIS_DEADLINE_* et JARVIS_MODEL que rien ne posait, quatre clés de `status` que le panneau ne lit pas, deux échafaudages refaits, et argparse dans jarvis-transcript. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ls94YXz6tFuYvTzRW6if3t
4699659 to
5280156
Compare
`show --projects … --json 30` : argparse regroupe les positionnels au PREMIER cluster, remplit `cmd` avec « show », et déclare le 30 final « unrecognized arguments ». Le comportement dépend de la version de Python — vert en local sur 3.14, rouge en CI — ce qui est précisément le genre de différence qu'une suite hors-ligne doit attraper, et qu'elle a attrapée. `parse_intermixed_args` existe pour ce cas exact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ls94YXz6tFuYvTzRW6if3t
Diagnostic du check rouge — tes tests dépendent du fuseau, pas de mon empaquetageJ'ai d'abord vérifié que ce n'était pas moi : Sur ta branche, en revanche : Le nombre d'échecs change avec l'heure d'exécution, ce qui est la signature d'une frontière de date. Les cas touchés écrivent un journal daté Reproduction en une ligne, ici, sans CI : TZ=UTC ./tests/runLes deux cas qui tombent à Le second n'est pas dans la liste de la CI, ce qui confirme que l'ensemble qui tombe dépend de l'heure exacte. C'est ta fonctionnalité et ton dessin de test, donc je ne touche à rien. Deux directions selon ton intention : épingler Pour mémoire : je n'ai pas mergé cette PR. Le rouge est sur tes propres tests, et merger par-dessus casserait |
Correction — mon diagnostic ci-dessus est faux, ne le suis pasTrois choses que j'aurais dû vérifier avant de le poster :
Le run rouge portait sur Il n'y a pas de flake au fuseau. Ce qui reste vrai et qui a servi : |
Jarvis peut répondre sans réseau. Son oreille (voxtype) et sa voix (piper)
étaient déjà locales ; seul le cerveau sortait.
Le cerveau local
bin/jarvis-localparle à n'importe quel serveur compatible OpenAI —llama-server, LM Studio, Ollama, LocalAI, vLLM — et imite exactement les
événements stream-json de
claude -p, quebin/jarvis-sentencesavaledéjà. Le chemin de la voix ne sait pas quel cerveau lui parle, et c'est ce
qui rend la bascule possible en une dizaine de lignes.
Aucun outil, délibérément : un modèle de 4 milliards de paramètres qui
pilote un shell est un risque qu'on ne prend pas.
Configuré par trois lignes de l'âme (
cerveau,cerveau-url,cerveau-modele) et une section « Cerveau » dans le Control Center. Uneseule entrée « compatible OpenAI » plutôt que vingt fournisseurs : ils
parlent tous le même protocole.
Mesuré sur ce M2 sous Asahi
pp2048tg128Avec Qwen3.5-4B en Q4_K_M, 1,5 à 4,6 s avant le premier mot prononcé.
Deux pièges qui rendent un cerveau local muet sans rien dire :
reasoning_content,épuise son budget et la réponse n'arrive jamais →
--reasoning offobligatoire dans l'unité, et filtrage côté pont ;
omarchy-jarvis warmuple paie audémarrage en envoyant exactement la persona que l'échange enverra — un
préchauffage à « bonjour » sans prompt système réchauffe un préfixe que
personne n'utilise. 23 376 ms → 2 581 ms au premier échange.
La table des réflexes
Interrogé hors ligne sur l'heure, le modèle a répondu « Il est 14h37 » à
23 h passées, avec aplomb, dans le format parlé exact de Jarvis. Une valeur
plausible et fausse est pire qu'un refus.
memory/REFLEXES.mdrépond à l'heure, la date, la batterie, lene-pas-déranger, le son et les thèmes sans modèle, en 200 ms, et
exactement. Motifs ancrés sur le transcript entier comme
NOISE.md, slotnumérique borné 0-100, liste d'interdits appliquée au chargement, et
omarchy-jarvis reflexe?pour l'asserter sans micro.118 cas figés, dont 56 négatifs — de vraies demandes qui doivent
atteindre le cerveau.
Le journal en fenêtre Hyprland
PanelWindow→FloatingWindow: Hyprland le tuile comme une application,l'écran se partage. Vérifié
floating=False, 780×953, le terminal voisinréduit d'autant.
Trois bugs que la route locale a révélés
(le panneau appelle
undo --quiet).resetlaissait la carte du panneau sur l'échange d'avant.claude. Il se replie sur le journal du jour, écrit par les deux routes.
Plus un verrou mortel évité de justesse : la garde qui empêche un tour local
d'écraser
$BRAIN_STATUScondamnaitautoà rester local à vie. Le moderetente le nuage toutes les dix minutes.
Audit d'excès d'ingénierie — 630 lignes en moins
sprites/variants.pyetsprites/preview.pysupprimés,voxtype.tomlde103 à 16 lignes (vérifié en transcrivant le même wav : sortie identique),
deux parseurs maison remplacés par argparse, les quatre sondes jumelles de
Journal.qmlen uncomponent, et une vingtaine d'autres coupes.408 tests, 0 échec. Rebasé sur la 1.5.0.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ls94YXz6tFuYvTzRW6if3t