Kazeia-central/CLAUDE.md

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)

  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_jsonpresets_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)

  1. Socle : wrapper adb + client provider typé + découverte device + store chiffré.
  2. Lecture : dashboard flotte (state/turns/crashes/config/models/updates).
  3. Conversations : pull via provider → archive chiffrée → export PDF/JSON + purge.
  4. Config & presets : édition + push (via RPC base64-JSON §4.4).
  5. Profils : CRUD + profil actif.
  6. RAG : authoring corpus, test retrieval (/rag_query), publication OTA.
  7. Voix : capture→encodage PC→sync flotte.
  8. 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).