323 lines
18 KiB
Markdown
323 lines
18 KiB
Markdown
# Kazeia-central — instructions projet
|
|
|
|
> Client de poste (PC/Mac) qui pilote les tablettes Kazeia branchées en USB :
|
|
> récupération des conversations cliniques, gestion du corpus RAG, et **parité
|
|
> complète avec Kazeia-admin** (config, presets, profils, voix, mises à jour).
|
|
>
|
|
> Ce fichier est le contrat de référence. Il prime sur toute supposition.
|
|
|
|
---
|
|
|
|
## 1. Mission et place dans l'écosystème Kazeia
|
|
|
|
Kazeia est un agent de soutien psychologique **on-device** tournant sur tablettes
|
|
Android (OnePlus Pad 3). L'écosystème comprend :
|
|
|
|
| Composant | Rôle | Repo |
|
|
| --- | --- | --- |
|
|
| **App patiente** `com.kazeia` | Pipeline STT→LLM→TTS, stocke conversations/profils/RAG/voix | `/opt/Kazeia/kazeia-android/app` |
|
|
| **App admin** `com.kazeia.admin` | Console de config **on-device**, parle au provider en local | `/opt/Kazeia/kazeia-android/app-admin` |
|
|
| **kazeia-engine** | Moteur natif (GGUF i8mm CPU + .pte QNN NPU), libs JNI | `/opt/Kazeia-engine` (repo séparé) |
|
|
| **Kazeia-central** | **Ce projet.** Console de poste qui pilote N tablettes via USB | `/opt/Kazeia-central` (repo séparé, **sibling** de `/opt/Kazeia` et `/opt/Kazeia-engine`) |
|
|
|
|
Kazeia-central **n'est pas un nouveau pipeline** : c'est une **télécommande de flotte**.
|
|
Il parle le **même protocole que Kazeia-admin** (le ContentProvider exporté
|
|
`com.kazeia.provider`), mais à distance via `adb` au lieu d'un `ContentResolver`
|
|
local. Tout ce que l'admin fait on-device, Kazeia-central doit pouvoir le faire
|
|
depuis le PC, sur plusieurs tablettes à la fois.
|
|
|
|
**Pourquoi un PC plutôt que l'app admin ?** Saisie clavier confortable pour
|
|
l'authoring RAG et les prompts, archivage clinique centralisé hors tablette,
|
|
gestion de flotte (plusieurs patients/tablettes), et accès à l'**encodeur de voix
|
|
Python x86** qui ne tourne que sur PC (cf. §6).
|
|
|
|
---
|
|
|
|
## 2. Décisions d'architecture (figées)
|
|
|
|
1. **Stack : Python (FastAPI) + UI web locale.**
|
|
Daemon Python = cœur métier (wrapper adb, store chiffré, intégration encodeur
|
|
voix, publication WebDAV). UI = web servie en local (navigateur), wrappable en
|
|
app desktop via **pywebview**. Choix motivé par la réutilisation directe de
|
|
l'encodeur voix CosyVoice (déjà Python x86) et de l'expertise Python de
|
|
l'équipe (Kaz, beta_kazeia).
|
|
|
|
2. **Transport : ADB + RPC base64-JSON.**
|
|
Tout passe par le ContentProvider exporté `com.kazeia.provider` via
|
|
`adb shell content call/query/…`. Pour écrire des textes complexes (prompts
|
|
multilignes, `presets_json`, fiches RAG), on **ajoute au provider des méthodes
|
|
`call()` prenant un JSON encodé base64** (cf. §4.4) — `content --bind` casse sur
|
|
`:` et les espaces. **Pas de serveur réseau dans l'app patiente.** Fonctionne
|
|
sans root.
|
|
|
|
3. **Périmètre v1 : parité admin complète.**
|
|
Config + presets sampling, profils, conversations, RAG, voix, mises à jour —
|
|
tout Kazeia-admin, depuis le PC, en multi-tablette.
|
|
|
|
4. **Conversations cliniques chiffrées au repos sur le PC.**
|
|
Base locale chiffrée (SQLCipher ou âge/`age`), déverrouillée par mot de passe
|
|
opérateur. On **conserve le niveau de protection du device**. Rétention et purge
|
|
explicites. Contexte RGPD santé (Luxembourg).
|
|
|
|
---
|
|
|
|
## 3. Contraintes dures imposées par le code patient (ne pas les contourner)
|
|
|
|
1. **Le ContentProvider est l'unique surface de pilotage.**
|
|
Authority : `com.kazeia.provider`. `exported=true`, pas (encore) de permission
|
|
signature → joignable par l'uid `shell` via adb. C'est *exactement* ce que
|
|
l'app admin utilise en local.
|
|
|
|
2. **Les conversations sont chiffrées au repos sur la tablette** (SQLCipher,
|
|
passphrase dans `SecureKeyStore`). Conséquence : `adb pull conversations.db`
|
|
donne un fichier **illisible**. **Les conversations ne se récupèrent QUE via le
|
|
provider** (`/conversations`, `/conversations/session/{id}`), qui les déchiffre
|
|
et les sert. Aucun raccourci par le filesystem.
|
|
|
|
3. **`/data/data/com.kazeia/` est inaccessible en adb** sur device de prod
|
|
non-rooté. Donc : config/profils/voix/RAG/updates → **provider** ; gros fichiers
|
|
(WAV de voix, modèles) → `adb push/pull` vers le **stockage externe**
|
|
`Android/data/com.kazeia/files/`.
|
|
|
|
4. **La sortie texte d'`adb shell content` est cassée pour le texte riche, dans
|
|
les DEUX sens.** En écriture, `content --bind` mange `:` et les espaces. En
|
|
lecture, `content query` rend du `Row: 0 col=val, col=val` : dès qu'une valeur
|
|
contient un retour à la ligne ou un `:` (ex. `speaker_system_prompt`,
|
|
multiligne), le parsing est corrompu. → **Toute I/O de texte non trivial passe
|
|
par le RPC base64-JSON (§4.4)**, jamais par `--bind`/`query` brut.
|
|
|
|
5. **`am startservice` est throttlé sans réveil du provider** ; faire un `content
|
|
query`/`call` *avant* pour réveiller le process. (Gotchas test device.)
|
|
|
|
---
|
|
|
|
## 4. Contrat de protocole (le « wire »)
|
|
|
|
Authority : `content://com.kazeia.provider`. Pilotage via
|
|
`adb shell content {query|update|insert|delete|call} --uri … [--bind …] [--method …]`.
|
|
|
|
> ⚠️ La source de vérité reste le code : `KazeiaTelemetryProvider.kt` (patient) et
|
|
> les clients `app-admin/.../data/source/Kazeia*Client.kt`. Ce qui suit en est le
|
|
> reflet à jour ; re-vérifier avant d'implémenter un endpoint.
|
|
|
|
### 4.1 Lecture (`content query`)
|
|
| URI | Colonnes clés |
|
|
| --- | --- |
|
|
| `/state` | `pid, uptime_seconds, pss_mb, ion_mb, …` + **handshake à ajouter** : `app_version_code, provider_schema, supported_calls` (cf. §4.4) |
|
|
| `/turns` | télémétrie pipeline (timings STT/LLM/TTS par tour) |
|
|
| `/crashes` | `timestamp, component, message` |
|
|
| `/config` | config complète (cf. §4.5) |
|
|
| `/models` | `id, display_name, pte_path, tokenizer_path, role, size_mb, max_seq_len, file_exists, notes` |
|
|
| `/profiles`, `/profiles/active`, `/profiles/{id}` | profils patients |
|
|
| `/conversations` | sessions : `profile_id, session_id, started_at, ended_at, turn_count` |
|
|
| `/conversations/profile/{id}` | sessions d'un profil |
|
|
| `/conversations/session/{id}` | tours : `id, profile_id, session_id, timestamp, role(PATIENT\|KAZEIA), text, ttft_ms, total_ms, voice_used, model_used` |
|
|
| `/dist_config` | `webdav_base, webdav_user, has_password, is_customized` (jamais le pass en clair) |
|
|
| `/rag`, `/rag/{source}` | corpus : index + texte d'un doc |
|
|
| `/rag_status` | `enabled, ready, model, dim, doc_count, chunk_count, pending` |
|
|
| `/rag_query` (selectionArgs[0] = requête) | `source, score, text` (test retrieval) |
|
|
| `/updates` | `phase, app_update_available, app_version_name, app_size, content_count, content_size, progress_done, progress_total, label, error, last_check` |
|
|
| `/voices` | `id, name, state, wav_exists, prefix_exists, suffix_exists, wav_size_bytes, wav_duration_seconds, wav_path, created_at` |
|
|
|
|
### 4.2 Mutation (`content update/insert/delete`) — champs simples
|
|
- `update /config` : merge partiel (cf. §4.5). Diffuse `com.kazeia.action.RELOAD_CONFIG`.
|
|
- `update /profiles[/{id}]`, `/profiles/active` ; `delete /profiles/{id}` (cascade conv.). Diffuse `RELOAD_PROFILES`.
|
|
- `delete /conversations/session/{id}` et `/conversations/profile/{id}`.
|
|
- `update /dist_config` : `webdav_base, webdav_user, webdav_pass, reset`.
|
|
- `insert /rag` (`source`,`text`) ; `delete /rag/{source}`. Diffuse `RELOAD_RAG`.
|
|
- `delete /voices/{id}` (wav + 2 embeddings). Diffuse `RELOAD_VOICES`.
|
|
|
|
### 4.3 RPC existant (`content call`)
|
|
- `update_check` → `{accepted:true}`, lance `UpdateWorker(mode=check)`. Poller `/updates`.
|
|
- `update_install` → `{accepted:true}`, lance `UpdateWorker(mode=install)`. Poller `/updates`.
|
|
|
|
### 4.4 RPC base64-JSON — À AJOUTER côté app patiente (modif provider)
|
|
Motivation : §3.4 — l'I/O texte d'`adb content` est cassée **dans les deux sens**.
|
|
La modif côté Kazeia est **additive** (nouvelles méthodes `call()`, aucun
|
|
comportement existant changé → compatible `FROZEN.md`). C'est **le livrable n°1**,
|
|
prérequis bloquant des étapes 3-6 de la roadmap. Forme cible :
|
|
|
|
```
|
|
# écriture
|
|
adb shell content call --uri content://com.kazeia.provider \
|
|
--method cfg_apply_json --arg <base64(JSON)>
|
|
# lecture (résultat = base64(JSON) dans le Bundle de retour)
|
|
adb shell content call --uri content://com.kazeia.provider --method cfg_dump_json
|
|
```
|
|
|
|
Le `arg` et le retour sont du **base64 d'un objet JSON** (zéro caractère spécial →
|
|
survit au shell ET au parsing). Méthodes à spécifier/implémenter :
|
|
|
|
**Lecture (dump)** — remplace `query` pour tout champ à texte riche :
|
|
- `cfg_dump_json` — config complète (prompts multilignes inclus).
|
|
- `presets_dump_json` — `presets_json` complet.
|
|
- `profile_dump_json` (arg = id) — profil complet (prompt override inclus).
|
|
|
|
**Écriture (apply/upsert)** — remplace `--bind` :
|
|
- `cfg_apply_json` — patch partiel de config (mêmes clés que §4.5), prompts inclus.
|
|
- `presets_apply_json` — remplace/merge `presets_json`.
|
|
- `rag_upsert_json` — `{source, text}` (fiche multiligne).
|
|
- `profile_upsert_json` — profil complet (prompt override inclus).
|
|
|
|
**Export conversations (file-drop)** — PAS via Bundle (limite binder ~1 Mo →
|
|
`TransactionTooLargeException` sur un historique réel) :
|
|
- `conversations_export` (arg = base64 d'un filtre `{profile_id?, since?, until?}`)
|
|
→ le provider **écrit un NDJSON (idéalement chiffré)** dans
|
|
`Android/data/com.kazeia/files/exports/` et renvoie le chemin ; le PC fait
|
|
`adb pull`. C'est le chemin clinique fiable (volume + parsing + chiffrement).
|
|
|
|
**Handshake** — pour piloter une flotte hétérogène :
|
|
- ajouter à `/state` (ou méthode `capabilities`) : `app_version_code`,
|
|
`provider_schema`, `supported_calls` (liste des méthodes présentes). Kazeia-central
|
|
s'adapte à la version de chaque tablette au lieu de planter.
|
|
|
|
**Contrôle d'accès (à spécifier maintenant, activer plus tard)** — le provider est
|
|
`exported=true` **sans permission** : toute app sideloadée lit les conversations.
|
|
Une `signature permission` casserait l'uid `shell` (donc Kazeia-central) ET l'app
|
|
admin → **mauvaise piste**. Retenir un **secret partagé** : token provisionné une
|
|
fois, stocké en `EncryptedSharedPreferences`, passé dans les `extras` du `call()` et
|
|
vérifié par le provider sur les endpoints sensibles. Préserve l'accès adb.
|
|
|
|
**Confort authoring local (nice-to-have)** :
|
|
- `rag_sync`, `voices_reload` — déclenchent un re-scan après un `adb push` direct de
|
|
fichiers, pour éviter l'aller-retour WebDAV en dev mono-tablette.
|
|
|
|
> **À faire en premier dans ce repo** : rédiger `docs/PROVIDER_RPC_SPEC.md` (signatures,
|
|
> schémas JSON, token), à livrer au dev de l'app patiente — même démarche que
|
|
> `SAMPLING_ENGINE_SPEC.md` côté kazeia-android.
|
|
> Tant que ces méthodes n'existent pas, fallback : `adb push` d'un fichier temp +
|
|
> `call` avec le chemin. **Ne jamais bricoler `--bind`/`query` pour du texte riche.**
|
|
|
|
### 4.5 Schéma config (`/config`, miroir de `ConfigStore.kt`)
|
|
- Cascade/TTS : `cascade_enabled`, `tts_enabled`, `stt_engine`, `llm_engine`, `tts_engine`
|
|
(prod actuelle : `tts_engine="cosyvoice"`, seul TTS ; `llm_engine`/`stt_engine` vestigiaux).
|
|
- RAG : `rag_enabled`, `rag_threshold` (déf. 0.82), `rag_top_k` (déf. 3).
|
|
- Debug : `debug_enabled` (masque le bouton logs/métriques côté patient).
|
|
- Speaker : `speaker_model_id, speaker_system_prompt, speaker_temperature, speaker_top_p,
|
|
speaker_top_k, speaker_repeat_penalty, speaker_presence_penalty, speaker_frequency_penalty,
|
|
speaker_max_tokens, speaker_preset_name`.
|
|
- Thinker : `thinker_*` (mêmes champs).
|
|
- Presets : `presets_json` = tableau `{name, temperature, top_p, top_k, repeat_penalty,
|
|
presence_penalty, frequency_penalty, max_tokens}`.
|
|
|
|
> ⚠️ Le moteur natif n'honore aujourd'hui que `max_tokens` côté sampling
|
|
> (cf. `docs/SAMPLING_ENGINE_SPEC.md` de kazeia-android). Kazeia-central pousse
|
|
> tous les champs ; leur effet réel dépend de l'avancement du moteur.
|
|
|
|
### 4.6 Transfert de fichiers (hors provider)
|
|
- **Stockage externe** (push/pull OK) : `Android/data/com.kazeia/files/kazeia/…`
|
|
- `models/` (LLM/STT/TTS), `voix/voix/{id}.wav`, `qwen3-tts-npu/{id}_voice_{prefix,suffix}.bin`, `rag_corpus/*.{txt,md}`.
|
|
- **Bases SQLite** : `kazeia_rag.db` est en **clair** (pullable pour inspection) ;
|
|
`conversations.db` est **chiffré** (inutile à puller — passer par le provider).
|
|
|
|
---
|
|
|
|
## 5. OTA / publication (WebDAV)
|
|
- Catalogue modèles : `catalog.json` ; voix : `voices_catalog.json` ; sur Nextcloud
|
|
`box.kazeia.com/.../soft/`. Schéma : `{schema, catalog_version, min_app_version_code,
|
|
app{version_code,version_name,remote,size,sha256}, components[{id,label,version,
|
|
mandatory,files[{remote,local,size,sha256}]}]}`.
|
|
- Génération : script type `make_catalog.py` (calcule taille + sha256). Kazeia-central
|
|
doit pouvoir **éditer le corpus localement → publier sur WebDAV → bump
|
|
`catalog_version`**, puis déclencher `update_check`/`update_install` par tablette.
|
|
- Compte service WebDAV stocké côté app (`/dist_config`), modifiable à distance.
|
|
- L'install OTA est **manuelle** (boutons), jamais automatique au lancement.
|
|
|
|
---
|
|
|
|
## 6. Intégration encodeur de voix (atout PC)
|
|
Workflow voix : **capture** WAV sur tablette → **extraction** des embeddings sur PC
|
|
(encodeur CosyVoice/Qwen3-TTS x86, Python) → **sync** des `.bin`/`.cvps` vers N
|
|
tablettes. Kazeia-central orchestre les 3 étapes :
|
|
1. `adb pull` du WAV depuis `…/files/kazeia/voix/voix/`.
|
|
2. Appel **in-process** de l'encodeur Python (raison n°1 du choix Python).
|
|
3. `adb push` des embeddings + mise à jour du catalogue voix.
|
|
|
|
État machine voix : `recorded → processing → ready/error` (refléter dans l'UI).
|
|
|
|
---
|
|
|
|
## 7. Organisation du repo (proposée)
|
|
|
|
```
|
|
kazeia-central/
|
|
CLAUDE.md # ce fichier
|
|
pyproject.toml # FastAPI, uvicorn, pywebview, sqlcipher/age, httpx, pydantic
|
|
kazeia_central/
|
|
adb/ # wrapper adb : device discovery, content call/query, push/pull
|
|
provider/ # client typé du wire (§4) — pydantic models par endpoint
|
|
store/ # base chiffrée locale (conversations archivées, audit)
|
|
voice/ # bridge encodeur Python CosyVoice
|
|
ota/ # édition corpus, make_catalog, push WebDAV
|
|
api/ # routes FastAPI consommées par l'UI
|
|
web/ # UI (templates/SPA) servie en local
|
|
docs/
|
|
PROVIDER_RPC_SPEC.md # spec base64-JSON à livrer au dev app patiente (§4.4)
|
|
tests/
|
|
```
|
|
|
|
**Repo git autonome à `/opt/Kazeia-central`** — un **sibling** de `/opt/Kazeia` et
|
|
`/opt/Kazeia-engine`, PAS un sous-dossier de `/opt/Kazeia`. Raison : le `.gitignore`
|
|
whitelist du repo `/opt/Kazeia` ne suit que `kazeia-android/` ; placer Kazeia-central
|
|
*dans* `/opt/Kazeia` l'exposerait à ce piège (et un `git clean` y est dangereux). En
|
|
sibling, le risque disparaît par construction, exactement comme kazeia-engine.
|
|
|
|
---
|
|
|
|
## 8. Sécurité & conformité (RGPD santé)
|
|
- Conversations rapatriées = **PII de santé**. Au repos sur PC : **chiffrées**
|
|
(SQLCipher/age), clé dérivée d'un mot de passe opérateur, jamais en clair sur disque.
|
|
- Journaliser les accès (qui a exporté quoi, quand) — piste d'audit.
|
|
- Rétention/purge explicites et configurables ; export PDF/JSON sur action explicite.
|
|
- Le mot de passe WebDAV ne transite jamais en clair via `/dist_config` (le provider
|
|
ne le renvoie pas) — ne pas le logger.
|
|
- Le PC devient le point de concentration clinique : disque chiffré OS **en plus** du
|
|
store applicatif chiffré.
|
|
|
|
---
|
|
|
|
## 9. Multi-tablette (flotte)
|
|
- Découverte par `adb devices -l` ; identifier chaque tablette (serial → label patient).
|
|
- Toute action ciblée `-s <serial>`. Opérations de flotte (push corpus, bump OTA) =
|
|
fan-out séquentiel par device, avec rapport par tablette (le harness de test montre
|
|
que les commandes adb échouent silencieusement par device → toujours vérifier le retour).
|
|
|
|
---
|
|
|
|
## 10. Préférences de travail (Richard)
|
|
- Répondre en **français**. Code/identifiants dans leur forme d'origine.
|
|
- Quand un choix est délégué : **une** recommandation défendue, pas un menu.
|
|
- Pas d'empilement de questions ; faire l'hypothèse raisonnable, l'énoncer, avancer.
|
|
- Honnêteté technique : valider/rejeter avec arguments concrets, pas de complaisance.
|
|
- **Local-first / auto-hébergé** par défaut (pas de SaaS).
|
|
- Vérifier en ligne ce qui dépend d'une version/API récente plutôt que deviner.
|
|
- Commits : ne committer que sur demande ; finir par
|
|
`Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.
|
|
|
|
## 11. Intangibles (ne JAMAIS toucher)
|
|
- `/opt/Kazeia/beta_kazeia/` — référence comportementale FR thérapeutique.
|
|
- `/opt/Kazeia/root_oneplus/` — firmware/Magisk.
|
|
- `/opt/Kazeia/Kaz/` — chatbot RAG Python du binôme (à *lire* pour s'inspirer du RAG
|
|
et des garde-fous, jamais à modifier).
|
|
- `/opt/Kazeia/keystore/` — clé de signature (critique).
|
|
- Stack patiente **figée** (`/opt/Kazeia/FROZEN.md`) : toute modif du provider (§4.4)
|
|
passe par une spec livrée au dev, pas par une réécriture sauvage.
|
|
- Jamais de `git clean` dans `/opt/Kazeia` (effacerait les dossiers non suivis).
|
|
|
|
---
|
|
|
|
## 12. Roadmap indicative (v1 = parité admin)
|
|
0. **Socle** : wrapper adb + client provider typé + découverte device + store chiffré.
|
|
1. **Lecture** : dashboard flotte (state/turns/crashes/config/models/updates).
|
|
2. **Conversations** : pull via provider → archive chiffrée → export PDF/JSON + purge.
|
|
3. **Config & presets** : édition + push (via RPC base64-JSON §4.4).
|
|
4. **Profils** : CRUD + profil actif.
|
|
5. **RAG** : authoring corpus, test retrieval (`/rag_query`), publication OTA.
|
|
6. **Voix** : capture→encodage PC→sync flotte.
|
|
7. **Updates** : check/install OTA piloté, bump catalogue.
|
|
|
|
> Prérequis bloquant des étapes 2-6 : les méthodes `call()` base64-JSON + l'export
|
|
> conversations côté app patiente (§4.4). Rédiger `docs/PROVIDER_RPC_SPEC.md` en
|
|
> premier, le livrer au dev kazeia-android, puis démarrer le socle Python en
|
|
> parallèle (l'étape 0-1, lecture, ne dépend que des endpoints existants).
|