18 KiB
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)
-
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).
-
Transport : ADB + RPC base64-JSON. Tout passe par le ContentProvider exporté
com.kazeia.providerviaadb shell content call/query/…. Pour écrire des textes complexes (prompts multilignes,presets_json, fiches RAG), on ajoute au provider des méthodescall()prenant un JSON encodé base64 (cf. §4.4) —content --bindcasse sur:et les espaces. Pas de serveur réseau dans l'app patiente. Fonctionne sans root. -
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.
-
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)
-
Le ContentProvider est l'unique surface de pilotage. Authority :
com.kazeia.provider.exported=true, pas (encore) de permission signature → joignable par l'uidshellvia adb. C'est exactement ce que l'app admin utilise en local. -
Les conversations sont chiffrées au repos sur la tablette (SQLCipher, passphrase dans
SecureKeyStore). Conséquence :adb pull conversations.dbdonne 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. -
/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/pullvers le stockage externeAndroid/data/com.kazeia/files/. -
La sortie texte d'
adb shell contentest cassée pour le texte riche, dans les DEUX sens. En écriture,content --bindmange:et les espaces. En lecture,content queryrend duRow: 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/querybrut. -
am startserviceest throttlé sans réveil du provider ; faire uncontent query/callavant 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 clientsapp-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). Diffusecom.kazeia.action.RELOAD_CONFIG.update /profiles[/{id}],/profiles/active;delete /profiles/{id}(cascade conv.). DiffuseRELOAD_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}. DiffuseRELOAD_RAG.delete /voices/{id}(wav + 2 embeddings). DiffuseRELOAD_VOICES.
4.3 RPC existant (content call)
update_check→{accepted:true}, lanceUpdateWorker(mode=check). Poller/updates.update_install→{accepted:true}, lanceUpdateWorker(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_jsoncomplet.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/mergepresets_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é) dansAndroid/data/com.kazeia/files/exports/et renvoie le chemin ; le PC faitadb pull. C'est le chemin clinique fiable (volume + parsing + chiffrement).
Handshake — pour piloter une flotte hétérogène :
- ajouter à
/state(ou méthodecapabilities) :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 unadb pushdirect 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 queSAMPLING_ENGINE_SPEC.mdcôté kazeia-android. Tant que ces méthodes n'existent pas, fallback :adb pushd'un fichier temp +callavec le chemin. Ne jamais bricoler--bind/querypour 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_enginevestigiaux). - 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_tokenscôté sampling (cf.docs/SAMPLING_ENGINE_SPEC.mdde 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.dbest en clair (pullable pour inspection) ;conversations.dbest chiffré (inutile à puller — passer par le provider).
5. OTA / publication (WebDAV)
- Catalogue modèles :
catalog.json; voix :voices_catalog.json; sur Nextcloudbox.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 → bumpcatalog_version, puis déclencherupdate_check/update_installpar 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 :
adb pulldu WAV depuis…/files/kazeia/voix/voix/.- Appel in-process de l'encodeur Python (raison n°1 du choix Python).
adb pushdes 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 cleandans/opt/Kazeia(effacerait les dossiers non suivis).
12. Roadmap indicative (v1 = parité admin)
- Socle : wrapper adb + client provider typé + découverte device + store chiffré.
- Lecture : dashboard flotte (state/turns/crashes/config/models/updates).
- Conversations : pull via provider → archive chiffrée → export PDF/JSON + purge.
- Config & presets : édition + push (via RPC base64-JSON §4.4).
- Profils : CRUD + profil actif.
- RAG : authoring corpus, test retrieval (
/rag_query), publication OTA. - Voix : capture→encodage PC→sync flotte.
- 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édigerdocs/PROVIDER_RPC_SPEC.mden 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).