69 lines
11 KiB
Markdown
69 lines
11 KiB
Markdown
|
|
# Architecture
|
|||
|
|
|
|||
|
|
CodexPi utilise quatre services applicatifs sur un réseau Compose privé.
|
|||
|
|
|
|||
|
|
| Service | Rôle | Port conteneur | Publication hôte |
|
|||
|
|
|---|---|---:|---|
|
|||
|
|
| `nginx` | reverse proxy unique | 8080 | `0.0.0.0:8765` (LAN) |
|
|||
|
|
| `django-web` | interface React, authentification et sessions Django | 8001 | aucune |
|
|||
|
|
| `api` | FastAPI/Uvicorn, REST, WebSockets, terminal, Git, Docker, Codex | 8000 | aucune |
|
|||
|
|
|
|||
|
|
nginx envoie `/api/auth/*`, `/api/bootstrap` et les fichiers de l'interface à Django. Les autres routes `/api/*` et `/ws/*` vont à FastAPI. FastAPI valide directement la session Django stockée en base et exige le jeton CSRF de la session pour les mutations et WebSockets.
|
|||
|
|
|
|||
|
|
Le frontend React est compilé dans l'image puis servi par Django. Les données persistantes sont dans `./data` : base Django, base CodexPi, sessions JSONL et journal de sécurité. Le conteneur API monte uniquement la racine de projets autorisée, l'identité Codex existante et le socket Docker nécessaire à la page Docker. Le bind de `/home/config` utilise une propagation `rslave` : les supports montés sous cette racine sur l'hôte, y compris après le démarrage de l'API, deviennent visibles au même chemin dans le conteneur sans propager en retour les montages du conteneur vers l'hôte.
|
|||
|
|
|
|||
|
|
Le connecteur de machine distante ouvre uniquement des requêtes sortantes vers nginx. Les tâches `list`, `read` et `write` transitent par une file SQLite ; aucun terminal ni port entrant n'est exposé sur le PC associé.
|
|||
|
|
|
|||
|
|
Le bridge IA PC suit un modèle différent parce qu’OpenCode doit conserver le
|
|||
|
|
stream HTTP d’Ollama pendant l’inférence. Une association à usage unique
|
|||
|
|
enregistre l’adresse privée du PC et un jeton chiffré côté CodexPi. Le proxy
|
|||
|
|
Python du PC écoute sur le LAN, authentifie chaque requête et n’autorise que les
|
|||
|
|
routes Ollama de découverte et de génération ; l’API Ollama brute reste liée à
|
|||
|
|
`127.0.0.1`. CodexPi produit sous `data/local-ai/` un fichier OpenCode et un
|
|||
|
|
jeton privés, puis les transmet uniquement aux workers OpenCode. Un seul bridge
|
|||
|
|
est actif à la fois afin de rendre le routage et le modèle par défaut
|
|||
|
|
déterministes.
|
|||
|
|
|
|||
|
|
Les tâches Codex écrivent un flux JSONL persistant et peuvent reprendre après fermeture du navigateur. Le terminal utilise tmux lorsqu'il est disponible. Un projet est toujours identifié par UUID ; les chemins sont canonisés côté serveur et contrôlés contre les racines autorisées/interdites.
|
|||
|
|
|
|||
|
|
L’ordonnanceur autorise par défaut trois moteurs Codex simultanés pour trois projets distincts. Des verrous de fichiers partagés entre les workers imposent à la fois la capacité globale configurée par `CODEXPI_MAX_CONCURRENT_AGENTS` et l’exclusion mutuelle au niveau du projet. Les trois espaces globaux (Agent maître, Agent Raspberry Pi et Agent Bibliothécaire) restent des projets indépendants : leur marqueur réservé `__global_agent__` sert uniquement à l’interface et ne crée pas de verrou partagé. Un groupe associe explicitement plusieurs projets locaux du même propriétaire grâce à la relation plusieurs-à-plusieurs `project_group_members` ; un projet peut donc participer à plusieurs groupes. Une tâche acquiert, dans un ordre stable, tous les verrous de ses groupes afin d’éviter les interblocages et les exécutions concurrentes sur une ressource partagée. Les chemins des autres membres sont injectés comme références de lecture pour Codex SAFE, sans être ajoutés aux racines d’écriture du sandbox, et les derniers comptes rendus alimentent un contexte commun asynchrone. Pour un projet en `FULL ACCESS`, ces chemins sont également injectés quel que soit le moteur avec une interdiction conversationnelle d’écrire ailleurs sans demande explicite ; le backend ne présente pas cette consigne comme une isolation, car le sandbox est désactivé. Les autres moteurs en SAFE ne reçoivent que les noms et comptes rendus. Ainsi, plusieurs projets indépendants avancent en parallèle et les membres d’un groupe restent sérialisés lorsqu’ils pilotent la même ressource.
|
|||
|
|
|
|||
|
|
Les orchestrations sont des machines à états persistantes dans SQLite. Les validations d’ébauche utilisent une empreinte de version ; les tests et la recette sont liés au commit vérifié. Une correction réutilise la discussion, la branche et le worktree existants. Une approbation est invalidée dès que la révision change.
|
|||
|
|
|
|||
|
|
Le chantier autonome de remédiation possède sa propre machine à états persistante (`remediation_runs`, `remediation_step_runs`, `remediation_events`, `remediation_self_repairs`). Un seul chantier peut être actif par propriétaire. Une boucle backend loue brièvement le chantier, attend la fin de la tâche courante, puis alterne agent projet et Agent maître. Le maître rend un verdict structuré `approve`, `correct` ou `block`; une correction réutilise la conversation du projet et le cycle est borné à trois reprises. Tout blocage est transmis à `CodexPi (Self)` dans une conversation persistante propre au chantier. La boucle surveille aussi cette tâche lorsque le chantier est `BLOCKED`; un verdict structuré `fixed` peut ajouter une précision bornée au prompt courant puis reprendre automatiquement le même fil, tandis que `needs_user` laisse le chantier bloqué avec une raison visible. Les auto-réparations sont bornées à trois incidents par chantier et leur relance de format à une tentative. Une reprise bloquée peut rebaser les textes du plan courant, mais le backend refuse toute modification du nombre, de l’ordre, de la cible ou du niveau de risque des étapes ; les conversations et preuves existantes sont conservées. Les étapes restent séquentielles pour respecter leurs dépendances, mais elles utilisent l’ordonnanceur normal : les autres projets et agents peuvent continuer en parallèle dans la limite globale. Le dernier état, les discussions, les auto-réparations et les événements survivent au redémarrage de l’API et à la fermeture de la PWA.
|
|||
|
|
|
|||
|
|
Le centre de coordination repose sur `agent_decisions`, `agent_incidents`, `agent_runtime_policies`, `agent_project_policies`, `specialist_agents` et `specialist_delegations`. À la fin d’une tâche, le worker interprète uniquement deux marqueurs JSON stricts : une demande de décision et un appel de compétence. Leur enregistrement appartient à la même transaction que la fin de la tâche ; une panne du sous-système de coordination ne peut toutefois pas empêcher la tâche principale de se terminer. Une décision met en pause la branche de délégation éventuelle et sa réponse est réinjectée dans la même conversation. La boucle de coordination surveille les réparations Self, l’auto-construction des spécialistes et les délégations. Une délégation imbriquée reprend d’abord le fil du spécialiste appelant, puis le résultat final remonte au projet source. La profondeur maximale vaut deux. Un bail SQLite court (`agent_coordination_lease`) garantit qu’une seule instance traite ces transitions pendant une bascule blue/green ; le bail expire automatiquement si l’instance disparaît.
|
|||
|
|
|
|||
|
|
Les politiques de ressources s’appliquent dans `session_worker` avant l’acquisition d’un emplacement moteur. Elles ordonnent les tâches en attente, limitent le nombre de moteurs simultanés, suspendent l’admission sous pression CPU/mémoire et réservent les tâches d’arrière-plan à leur fenêtre horaire. Les verrous déjà acquis et les processus en cours ne sont jamais retirés. Le budget de durée alimente un signal persistant et une notification, sans envoyer de signal au processus ni modifier son statut. Les préférences de notifications sont enregistrées par utilisateur et filtrées avant l’ajout à la file Web Push.
|
|||
|
|
|
|||
|
|
Un spécialiste est représenté par un projet global `__global_agent__` distinct, un dossier sous `data/specialists/` et une discussion de construction. Son projet est créé en SAFE : il écrit seulement dans son atelier et reçoit le chemin source comme référence de lecture. Le résultat revient au projet par une nouvelle tâche de conversation ; l’agent source reste l’unique intégrateur. Désactiver un spécialiste empêche les nouvelles délégations sans supprimer son atelier ni ses fils.
|
|||
|
|
|
|||
|
|
Le canal de l’agent maître ne passe ni par un projet ni par le socket Docker de l’API. Un petit service hôte écoute un socket Unix sous `data/host-agent/`, authentifie chaque requête avec un jeton `0600` et exécute le CLI sous son identité système dédiée. L’API impose en plus l’authentification administrateur, CSRF et une confirmation par demande privilégiée. Le service n’est pas activé automatiquement par Compose.
|
|||
|
|
|
|||
|
|
La réconciliation de stockage est séparée du runtime applicatif. Son inventaire
|
|||
|
|
lit les bases SQLite en mode `mode=ro`, recoupe les chemins persistés avec les
|
|||
|
|
worktrees Git et les verrous, puis calcule les empreintes uniquement lorsqu'un
|
|||
|
|
candidat de même catégorie, nom logique et taille existe dans une autre
|
|||
|
|
instance. La quarantaine éventuelle utilise un verrou `flock`, revalide inode,
|
|||
|
|
mtime, taille et empreinte contre le plan, puis effectue un renommage atomique
|
|||
|
|
vers `data/quarantine/<reçu>/payload`. Un manifeste `0600`, une date
|
|||
|
|
d'éligibilité après grâce et un audit JSONL permettent la restauration. Si
|
|||
|
|
l'audit échoue, le renommage est annulé avant de rendre l'erreur. Sous le verrou,
|
|||
|
|
un second inventaire relit les bases et refuse le déplacement si une référence,
|
|||
|
|
une activité ou une exclusion est apparue depuis le dry-run. Une base absente
|
|||
|
|
ou illisible, ainsi qu'un schéma sans les tables et colonnes de référence
|
|||
|
|
attendues, fait échouer la classification de toute l'instance en mode fermé.
|
|||
|
|
Pendant toute la revalidation et le renommage, une transaction
|
|||
|
|
`BEGIN IMMEDIATE` est maintenue sur chaque base dans un ordre stable : aucune
|
|||
|
|
nouvelle référence SQLite ne peut être admise entre le contrôle et le mouvement.
|
|||
|
|
L'inventaire vérifie chaque composant jusqu'à `data/` avec `lstat` et ne parcourt
|
|||
|
|
jamais une instance dont ce parent est symbolique.
|
|||
|
|
|
|||
|
|
Les caches Chromium mutualisables sont isolés du profil (cookies, historique,
|
|||
|
|
sessions) sous `data/browser-cache/<version majeure>/`. Seuls `Cache`,
|
|||
|
|
`Code Cache` et `GPUCache` peuvent être rapprochés, et jamais lorsqu'un verrou
|
|||
|
|
navigateur est détenu. Les volumes `browser-home` existants restent privés et
|
|||
|
|
hors inventaire implicite.
|
|||
|
|
|
|||
|
|
Le worker spécialisé `host_session_worker` relie à ce canal les conversations du seul projet global privilégié Pi Doctor/Agent Raspberry Pi. Il conserve les tables `conversations`, `messages` et `prompt_queue`, les verrous de projet et la limite globale d’agents ; seule l’exécution du moteur quitte le conteneur. Le chantier autonome peut ainsi reprendre le même fil après un blocage technique et soumettre le résultat hôte à l’Agent maître. Les autres projets continuent d’utiliser `session_worker` dans le conteneur.
|