Depuis la fusion de la PR #21, chaque push sur main échouait en moins d'une
minute avec zéro job lancé : le YAML du workflow était devenu invalide.
La PR #21 ajoutait cache-from/cache-to après build-args, alors que le bloc
with: les portait DÉJÀ en fin de liste. Une clé dupliquée dans un mapping
YAML fait rejeter le workflow avant même son démarrage — d'où des échecs
immédiats, sans le moindre journal.
Rectification au passage : le cache n'était pas absent, il était déjà actif.
Les trente-sept minutes venaient de la nouveauté de l'étape CUDA (cache
froid) et de l'absence de borne d'architectures, corrigée elle pour de bon.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3ewMAsXhkw9RY11kb9Dc9
Le build ne mourait plus, mais tournait encore après trente-sept minutes. Et
il aurait produit un binaire inutilisable sur la moitié du matériel visé.
Les GPU de la machine cible sont une RTX 5060 Ti (Blackwell, sm_120) et une
RTX 3060 (Ampere, sm_86). L'étape de compilation était épinglée sur
nvidia/cuda:12.4.1 : ce nvcc-là ne CONNAÎT pas Blackwell. Il refuse
l'architecture, et le binaire ne tournerait au mieux que par recompilation PTX
au chargement. 12.8.1 sur Ubuntu 24.04 est désormais utilisé — exactement ce
avec quoi llama.cpp bâtit l'image amont qui fournit libcudart.
Pour la durée, deux causes distinctes :
- Sans borne, ggml compile pour TOUTES les architectures qu'il connaît, de
Maxwell à Blackwell. CMAKE_CUDA_ARCHITECTURES ramène le travail à quatre
(Turing → Blackwell), et l'ARG CUDA_ARCHS permet d'élargir sans toucher au
fichier pour un GPU plus ancien.
- Surtout : ce binaire ne dépend d'AUCUN fichier du dépôt, et il était pourtant
recompilé à chaque push. Le cache de couches GitHub Actions le réutilise tant
que ses lignes du Dockerfile ne bougent pas ; seuls le code Go et
l'assemblage de l'image se refont.
Un test vérifie le plancher CUDA et la présence de la borne d'architectures :
ces deux fautes ne se voient pas à la lecture et coûtent quarante minutes à
découvrir.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3ewMAsXhkw9RY11kb9Dc9
Le premier build de l'image avec whisper CUDA a échoué après huit minutes
sans écrire une seule ligne d'erreur : le journal s'arrête à 92 % de la
compilation, puis « Complete job ». Un compilateur qui échoue dit pourquoi ;
un compilateur tué ne dit rien.
Ces 92 % étaient atteints en TREIZE secondes. Ce ne sont pas des compilations
terminées, ce sont des nvcc lancés simultanément.
« cmake --build -j » sans nombre autorise chez Make un parallélisme illimité.
ggml-cuda compte environ 200 fichiers d'instanciation de gabarits et chaque
nvcc réclame 1 à 2 Go : le runner (16 Go) mourait d'un OOM. L'étape CPU y
survivait — peu de fichiers, compilation légère — ce qui a rendu le piège
invisible jusqu'à l'arrivée de CUDA.
- -j"$(nproc)" sur les deux étapes, et un test le vérifie désormais : cette
faute est indétectable à la lecture et ne se manifeste qu'en CI.
- Le runner libère dotnet, android et ghc avant de bâtir. L'empilement
nvidia/cuda-devel + runtime CUDA + Chromium approche les 14 Go disponibles ;
cette part-là reste une hypothèse, mais elle coûte une minute à écarter.
- whisper-server CUDA est strippé : les symboles de débogage d'un binaire
ggml-cuda pèsent plusieurs centaines de mégaoctets dans l'image finale.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3ewMAsXhkw9RY11kb9Dc9
Plus aucune compilation CUDA : le runtime part de
ghcr.io/ggml-org/llama.cpp:server-cuda (llama-server précompilé et maintenu
par l'équipe amont, backends .so chargés dynamiquement, archs GPU courantes,
repli CPU fonctionnel). Le build complet passe de ~40 min à ~4 min et tous
les pièges du build CUDA sans GPU (stubs libcuda, espace disque du runner,
choix des architectures) disparaissent.
- Dockerfile : 2 étapes (Go + image officielle), build-arg LLAMACPP_IMAGE
pour épingler une version ou passer en CPU/Vulkan
- entrypoint : BIN=/app/llama-server
- workflow GHCR : purge disque et CUDA_ARCHS supprimés, cache max
- compose/.env/README à l'avenant
Validé : build 3 min 55, UI HTTP 200, healthcheck healthy, superviseur PID
OK, llama-server démarre (repli CPU) et n'échoue que sur un modèle factice.
- NOTICE.md : Loki est un fork d'AJEAN (nathaninline, MIT), liste des
modifications ; LICENSE amont conservée à l'identique.
- README réécrit : bandeau fork, architecture conteneur, démarrage Docker,
procédure Unraid (plugin Nvidia Driver), différences avec l'amont.
- UI : le logo pixel-art épelle désormais LOKI (il épelait encore AJEAN),
infobulle d'attribution sur la marque ; index.html régénéré.
- Workflow GHCR : libération d'espace disque du runner (l'étape CUDA devel
ne tient pas dans les ~14 Go libres), build-args CUDA_ARCHS/LOKI_VERSION,
cache GHA en mode min (plafond 10 Go).
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki)
- LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ;
units loki-engine / loki-ui ; binaire et aide CLI
- updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les
binaires AJEAN amont)
Conservé à l'identique : le domaine ajean.link (service de tunnel amont),
les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et
LICENSE (historique et licence de l'amont).
go build/vet/test : verts.
Dans un terminal, cmd rendait la main avant qu'ajean n'ait rien ecrit : l'invite
se reaffichait puis la sortie arrivait par-dessus. Windows n'attend jamais la fin
d'un programme en sous-systeme graphique. La meme cause empechait les
redirections et les tubes, et rendait 'ajean chat' inutilisable (le shell et
ajean se disputaient l'entree).
Le binaire passe donc en sous-systeme console. La fenetre noire au double-clic,
que le sous-systeme graphique evitait, est traitee autrement : quand aucun autre
processus ne partage notre console, c'est que Windows l'a creee pour nous, donc
que personne ne nous a lances depuis un terminal. On la masque et on la libere.
Les raccourcis demandent en plus un demarrage minimise.
La relance interne (apres mise a jour) passe desormais 'app' explicitement : la
detection ne repond qu'a la question du double-clic, et un processus sans
console du tout ne doit pas avoir a y repondre.
Verifie : ordre d'affichage correct en session cmd, redirection et tube
fonctionnels, 'ajean' sans argument affiche l'aide, et aucune fenetre console
visible au lancement facon double-clic.
Ils prennent simplement le nom du binaire correspondant. Effet de bord corrige
au passage : tous les assets commencent desormais par « ajean- », donc le motif
« ajean-* *.zip » de SHA256SUMS aurait liste les zips DEUX fois, et un fichier
d'empreintes a doublons est precisement ce que verifie 'ajean update'.
- notes reecrites sans tiret cadratin (convention du projet), avec la marche a
suivre d'installation et la section de ce qui n'a pas ete teste
- migrate_07 archive aussi SKILLS/ (dossier mort depuis la fusion dans memory)
- commentaire d'en-tete de release.yml remis a jour
ajean-linux, ajean-linux-arm, ajean-macos, ajean-macos-arm,
ajean-windows.exe, ajean-windows-arm.exe.
Le schema ne correspond plus a ajean-<GOOS>-<GOARCH> que cherchent les 0.7 :
leur mise a jour automatique ne trouve aucun asset et echoue sans rien
remplacer. C'est voulu — sinon elle installait le binaire 0.8 sans migrer, le
service de lien repartait en boucle d'echec sur « link serve » disparu et le
moteur perdait sa configuration.
assetNameFor est isolee pour etre testee sur les six plateformes, plus un test
qui verifie explicitement l'absence de collision avec le schema 0.7.
- uiUserSvcCtl lancait encore « ajean link serve », sous-commande supprimee :
l'acces distant etait casse sur macOS et Windows (hors systemd)
- tools/migrate-0.7 : reprise d'une installation 0.7.x, hors du binaire pour ne
pas reintroduire de compat dans le produit ; procedure complete au README
- CI : staticcheck ajoute (le projet est a zero avertissement, il doit le rester)
- .gitignore purge de ses entrees mortes (jean, config.env, .api_key...)
- suppression des fonctions et types inutilises (staticcheck U1000)
- sources UI, workflows, README passes en ajean
- release.yml ne publie plus que les binaires ajean-*
- notes de release redigees
Le renommage des dossiers avait ete repercute dans release.yml mais pas
dans ci.yml, qui compilait encore ./cmd/jean : les deux jobs echouaient.
Une occurrence trainait aussi dans mac-build.yml (cmd/jean/icon.ico), que
le remplacement precedent n'avait attrapee que pour icon.png.
Un cas n'etait pas qu'un commentaire : tools/assemble-ui utilisait
"internal/jean/ui" comme chemin par defaut. La directive go:generate passe
le dossier en argument, donc la generation fonctionnait, mais l'invocation
directe documentee dans son propre en-tete (go run ./tools/assemble-ui)
aurait echoue.
Le reste etait des commentaires designant les anciens chemins, corriges
pour que le depot ne mente plus sur sa propre structure.
Job ci rejoue en entier en local : gofmt, vet, build, tests et les quatre
cross-compilations de release.
Suite du renommage. Principe directeur : le code s'adapte a ce qui est
DEJA installe sur la machine, plutot que d'exiger une migration
privilegiee qui pourrait echouer et laisser un serveur sans service.
Services (sys_unitname.go). Une mise a jour remplace le binaire, pas les
unites systemd : un serveur deja installe tourne encore sous jean.service
et jean-link.service. serviceName() et linkServiceName() resolvent donc
le nom a l'usage — l'unite qui existe reellement — au lieu de supposer
"ajean*". Sans ca, le premier systemctl restart apres MAJ echouerait, et
sur un serveur distant ca veut dire l'acces coupe sans terminal pour
reparer. Meme logique pour le label launchd, ou le prefixe com.jean. est
conserve tant que c'est lui qui est charge.
Le fichier PID du worker de lien est repris au passage (jean-link.pid ->
ajean-link.pid) : c'est lui qui dit si un worker tourne, et sans reprise
la premiere version renommee en aurait demarre un second, donnant deux
tunnels concurrents vers le relais.
Binaire. installedExePath vise desormais ajean(.exe), et l'installation
pose un alias "jean" a cote — lien symbolique sous Unix, copie sous
Windows ou les liens demandent des droits qu'on n'a pas toujours. La
commande d'hier reste tapable : on ne controle ni les alias shell, ni les
cron, ni les scripts de deploiement, ni les tutos deja ecrits.
/etc/default/ajean est ecrit avec AJEAN_HOME ET JEAN_HOME, et l'ancien
/etc/default/jean est laisse en place plutot que supprime : des scripts
d'utilisateurs le sourcent.
Releases. Les workflows produisent ajean-<os>-<arch> puis en copient un
jeu jean-<os>-<arch> identique, une fois les binaires macOS recuperes.
Les deux jeux sont dans SHA256SUMS. Ces copies sont indispensables : le
parc installe ne sait chercher que jean-*, il ne pourrait donc meme pas
atteindre la version qui apprend a lire ajean-*. A retirer quand le parc
aura bascule. Le bundle devient AJEAN.app, avec le zip publie sous les
deux noms pour ne pas casser les liens de telechargement deja diffuses.
La detection du mode application se basant sur /Contents/MacOS et non sur
le nom du bundle, elle n'est pas affectee.
- mode agent : les outils write/edit/bash résolvent les chemins relatifs dans
JEAN_HOME/workspace et le shell y démarre, au lieu du répertoire courant du
processus (Bureau, ProgramData\jean\bin). Les chemins absolus demandés
explicitement restent honorés.
- Windows : le double-clic n'installe plus en silence. Boîte de dialogue unique,
raccourcis menu Démarrer + Bureau, puis relance depuis la copie installée — un
seul binaire fait foi. Désinstallation : retrait des raccourcis.
- update : contrôle des droits AVANT le téléchargement (l'échec tombait sur le
fichier temporaire, avec un message brut), message donnant la commande exacte,
timeout UI porté de 30 s à 10 min.
- jean where + bouton « Où sont mes fichiers ? » : emplacements, et alerte quand
le binaire lancé n'est pas celui installé.
- icônes : source unique (sys_brand_icon.go) pour la zone de notification, la
barre de menus macOS et l'icône du .exe, alignée sur le favicon noir. macOS en
icône template (une icône noire figée disparaît sur une barre sombre).
- CI : le bundle macOS part de icon.png au lieu de convertir le .ico via sips,
dont l'échec était masqué par « || true » ; notes de release versionnées.
Les archives llama.cpp macOS/Linux livrent les bibliotheques sous leur nom
versionne (libllama-common.0.0.10107.dylib) PLUS un lien portant le nom
recherche par l'editeur de liens (libllama-common.0.dylib). L'extracteur
tar ignorait les entrees de type lien : backend installe mais llama-server
mort-ne sur << dyld: Library not loaded >>.
- extractArchive cree les liens (copie de repli quand os.Symlink echoue)
- marqueur de format dans VERSION : une install faite par l'ancien
extracteur est refaite au lieu d'etre declaree << deja a jour >>
- test de non-regression + go test ajoute au job macOS de la CI
- Jean.app publie par la CI (zip par archi) : ouverture depuis le Finder ->
mode application, comme le double-clic sur jean.exe sous Windows
- icone dans la barre de menus macOS (systray/Cocoa) avec Ouvrir/Quitter,
bundle marque LSUIElement (pas d'icone Dock)
- builds darwin deplacees sur un runner macOS (CGO requis pour Cocoa)
- signature ad-hoc du bundle + icone .icns
- Panneau « Accès distant » dans l'UI web : abonnement + liaison ajean.link
en un clic (popup connect.html → clé remise à l'agent local), sans terminal.
Endpoints locaux /api/link/status|connect|disconnect|paircode (web_link_api.go).
- Accès OpenAI public : refuse d'être activé sans accès distant, avec message.
- Windows : binaire compilé en sous-système GUI (-H=windowsgui) → plus aucune
fenêtre de console au double-clic ; rattachement à la console parente en CLI
(sys_console_windows.go). Suppression du hack relaunchDetachedApp.
- Windows : masquage (CREATE_NO_WINDOW) de TOUS les sous-process — git/vswhere
du panneau MOTEUR (flash à chaque refresh), llama-server, MCP stdio, winget…
- Vouvoiement du nouveau parcours.
- config/main : LOKI_VERSION (git sha), exposé via /api/health et /api/version ;
affiché dans la barre du haut. Permet de vérifier que l'image déployée est
bien à jour (cause fréquente de 'les nouveautés n'apparaissent pas').
- Dockerfile : ARG/ENV LOKI_VERSION ; workflow : build-arg = github.sha.
- bench : corrige un bug de précédence d'opérateur dans l'épreuve JSON
(le tuple de retour était malformé quand l'extraction était partielle).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
- .github/workflows/docker-publish.yml : build + push de ghcr.io/r0m1k3/loki
(linux/amd64, tags latest + sha, cache GHA) à chaque push sur main
- docker-compose.unraid.yml : utilise l'image préconstruite (plus de build/git
sur Unraid, simple pull)
- README : instructions Unraid mises à jour (image GHCR, visibilité du package)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N