bots-garden/sidekickspublic Fork
⑂ v0.1.0
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/sidekicks.git
git clone ssh://git@rickub.com/bots-garden/sidekicks.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. 8ef4097 · on v0.1.0 · k33g · 3d ago
architecture.md · 48 lines · 5.2 KBmarkdown
Blame HistoryOpen raw

Architecture — explication

De quoi s'agit-il ?

Avant d'écrire l'application, le projet a fixé le découpage du code, ce qui est stocké où, et comment le côté Go informe l'interface. Cette page en raconte les raisons ; la liste précise des packages, le modèle de données, les clés de stockage et le catalogue d'événements sont dans l'ADR 0003 (en anglais).

Packages et dépendances

La source du schéma est packages.drawio. Toutes les boîtes existent (v0.1).

Pourquoi ce découpage

Trois faits l'ont guidé :

  • Un agent est partagé. Alice peut être dans trois channels à la fois, mais c'est un seul processus avec un seul modèle en mémoire. Un package possède donc les processus des agents (internal/agents) et un autre les channels et le routage (internal/channels) ; un channel demande « une session avec Alice », il ne la démarre jamais.
  • L'interface ne doit pas connaître ACP. Si le code Svelte manipulait des messages ACP, changer de version de protocole ou d'agent se répercuterait jusque dans l'interface. Tout ce que voit l'interface passe par internal/app, sous forme d'appels et d'événements propres à Sidekicks.
  • La configuration peut changer pendant que des agents tournent. Elle est chargée, validée et surveillée à un seul endroit (internal/config), qui prévient les autres.

Où s'arrête Wails

Seul main.go, à la racine du dépôt, connaît Wails : il crée la fenêtre, branche les services de internal/app et raccorde le gestionnaire d'événements de Wails. Tout le reste est du Go ordinaire qui émet ses événements via une petite interface Emitter ; le gestionnaire d'agents, la configuration et les services se testent donc sans fenêtre. Le package principal est à la racine plutôt que dans cmd/, car les fichiers de build générés par Wails l'attendent là : lutter contre l'outil coûterait plus que cela n'apporte.

Ce qui vit dans le YAML et ce qui vit dans la base

Le fichier YAML décrit ce qui existe : agents, limites, préférences, et channels à créer. Il est fait pour être lu et modifié par une personne. Ce qui se passe — les channels créés depuis l'interface, qui les a rejoints, les messages — est stocké par l'application dans Pebble, une base clé-valeur embarquée. Un channel déclaré dans le YAML est créé une fois au démarrage, puis vit dans la base comme les autres : modifier le YAML ne supprime jamais une conversation par accident.

Chaque fichier de configuration a sa propre base : passer de « mes agents de travail » à « mes agents d'écriture » ne mélange jamais leurs historiques.

Les messages sont écrits une fois, à la fin

Pendant qu'un agent répond, la réponse vit en mémoire et l'interface reçoit chaque fragment. Elle est écrite sur disque à la fin du tour. Écrire cinquante fois par seconde userait le disque pour rien ; le prix est de perdre une réponse inachevée si l'application plante, ce qui est acceptable.

Les programmes sont trouvés comme dans un terminal

Une application ouverte depuis le Finder n'hérite pas du PATH du terminal de l'utilisateur : macOS lui donne /usr/bin:/bin:/usr/sbin:/sbin, si bien que mm, docker ou sbx installés avec Homebrew ou dans ~/.local/bin sont « introuvables », alors que l'utilisateur vient de les lancer dans un terminal. Demander à une personne qui n'est pas développeuse d'écrire des chemins complets serait la mauvaise réponse. Au démarrage, avant de lire la configuration, main.go demande son PATH au shell de l'utilisateur (internal/shellpath : un shell de connexion interactif, pour qu'il lise les mêmes fichiers qu'un terminal, avec une limite de cinq secondes) et le place devant le sien. Les agents en héritent, et les avertissements de la configuration (« programme introuvable ») sont calculés avec lui. Quand le shell ne peut pas être lu, rien ne change et le chemin complet reste la solution.

Des erreurs en phrases claires

Go, les agents, Docker et sbx signalent leurs erreurs pour des développeurs (exec: "mm": executable file not found in $PATH). Le côté Go les garde telles quelles : elles sont précises, les tests les vérifient, et c'est ce qu'il faut à un développeur dans le journal. L'interface traduit les plus courantes (frontend/src/lib/errors.ts) en une phrase qui dit ce qui ne va pas et quoi faire, dans la langue de l'interface, et garde le texte d'origine replié sous Détails techniques. Une erreur inconnue est affichée telle quelle plutôt que cachée derrière une phrase vague.

Alternatives écartées

  • Stocker les conversations dans le fichier YAML : cela transformerait un fichier que les gens modifient en base de données, en conflit avec les écritures de l'application.
  • Un package par service Wails, logique comprise : cela mélangerait l'interface et le métier, et rendrait le métier intestable sans fenêtre.

Lien avec le reste

Le côté protocole est expliqué dans Pourquoi ACP, le côté interface dans La stack de l'interface, et le format du fichier est décrit dans la référence de la configuration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# Architecture — explication

## De quoi s'agit-il ?

Avant d'écrire l'application, le projet a fixé le découpage du code, ce qui est stocké où, et comment le côté Go informe l'interface. Cette page en raconte les raisons ; la liste précise des packages, le modèle de données, les clés de stockage et le catalogue d'événements sont dans l'[ADR 0003](../../adr/0003-architecture.md) (en anglais).

![Packages et dépendances](../../diagrams/packages.png)

La source du schéma est [packages.drawio](../../diagrams/packages.drawio). Toutes les boîtes existent (v0.1).

## Pourquoi ce découpage

Trois faits l'ont guidé :

- **Un agent est partagé.** Alice peut être dans trois channels à la fois, mais c'est un seul processus avec un seul modèle en mémoire. Un package possède donc les processus des agents (`internal/agents`) et un autre les channels et le routage (`internal/channels`) ; un channel demande « une session avec Alice », il ne la démarre jamais.
- **L'interface ne doit pas connaître ACP.** Si le code Svelte manipulait des messages ACP, changer de version de protocole ou d'agent se répercuterait jusque dans l'interface. Tout ce que voit l'interface passe par `internal/app`, sous forme d'appels et d'événements propres à Sidekicks.
- **La configuration peut changer pendant que des agents tournent.** Elle est chargée, validée et surveillée à un seul endroit (`internal/config`), qui prévient les autres.

## Où s'arrête Wails

Seul `main.go`, à la racine du dépôt, connaît Wails : il crée la fenêtre, branche les services de `internal/app` et raccorde le gestionnaire d'événements de Wails. Tout le reste est du Go ordinaire qui émet ses événements via une petite interface `Emitter` ; le gestionnaire d'agents, la configuration et les services se testent donc sans fenêtre. Le package principal est à la racine plutôt que dans `cmd/`, car les fichiers de build générés par Wails l'attendent là : lutter contre l'outil coûterait plus que cela n'apporte.

## Ce qui vit dans le YAML et ce qui vit dans la base

Le fichier YAML décrit **ce qui existe** : agents, limites, préférences, et channels à créer. Il est fait pour être lu et modifié par une personne. Ce qui **se passe** — les channels créés depuis l'interface, qui les a rejoints, les messages — est stocké par l'application dans Pebble, une base clé-valeur embarquée. Un channel déclaré dans le YAML est créé une fois au démarrage, puis vit dans la base comme les autres : modifier le YAML ne supprime jamais une conversation par accident.

Chaque fichier de configuration a sa propre base : passer de « mes agents de travail » à « mes agents d'écriture » ne mélange jamais leurs historiques.

## Les messages sont écrits une fois, à la fin

Pendant qu'un agent répond, la réponse vit en mémoire et l'interface reçoit chaque fragment. Elle est écrite sur disque à la fin du tour. Écrire cinquante fois par seconde userait le disque pour rien ; le prix est de perdre une réponse inachevée si l'application plante, ce qui est acceptable.

## Les programmes sont trouvés comme dans un terminal

Une application ouverte depuis le Finder n'hérite pas du `PATH` du terminal de l'utilisateur : macOS lui donne `/usr/bin:/bin:/usr/sbin:/sbin`, si bien que `mm`, `docker` ou `sbx` installés avec Homebrew ou dans `~/.local/bin` sont « introuvables », alors que l'utilisateur vient de les lancer dans un terminal. Demander à une personne qui n'est pas développeuse d'écrire des chemins complets serait la mauvaise réponse. Au démarrage, avant de lire la configuration, `main.go` demande son `PATH` au shell de l'utilisateur (`internal/shellpath` : un shell de connexion interactif, pour qu'il lise les mêmes fichiers qu'un terminal, avec une limite de cinq secondes) et le place devant le sien. Les agents en héritent, et les avertissements de la configuration (« programme introuvable ») sont calculés avec lui. Quand le shell ne peut pas être lu, rien ne change et le chemin complet reste la solution.

## Des erreurs en phrases claires

Go, les agents, Docker et sbx signalent leurs erreurs pour des développeurs (`exec: "mm": executable file not found in $PATH`). Le côté Go les garde telles quelles : elles sont précises, les tests les vérifient, et c'est ce qu'il faut à un développeur dans le journal. L'interface traduit les plus courantes (`frontend/src/lib/errors.ts`) en une phrase qui dit ce qui ne va pas et quoi faire, dans la langue de l'interface, et garde le texte d'origine replié sous **Détails techniques**. Une erreur inconnue est affichée telle quelle plutôt que cachée derrière une phrase vague.

## Alternatives écartées

- **Stocker les conversations dans le fichier YAML** : cela transformerait un fichier que les gens modifient en base de données, en conflit avec les écritures de l'application.
- **Un package par service Wails, logique comprise** : cela mélangerait l'interface et le métier, et rendrait le métier intestable sans fenêtre.

## Lien avec le reste

Le côté protocole est expliqué dans [Pourquoi ACP](pourquoi-acp.md), le côté interface dans [La stack de l'interface](stack-interface.md), et le format du fichier est décrit dans la [référence de la configuration](../reference/configuration.md).