# 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 # 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 `. 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) `. ## 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).