Un agent de code 100 % local — privé, sécurisé, extensible — qui rivalise avec un agent cloud sur une machine perso.
Pas d'API cloud. Aucune donnée ne quitte la machine. Le cerveau (MLX/Apple Silicon), les outils, la mémoire, le RAG, et les connecteurs (Gmail, web, MCP) tournent en local.
Étude de cas technique · Architecture · Sécurité · Pour les entreprises
Status : v2.1+ stable · 2146 tests · CI à gates (bandit / gitleaks / pip-audit / coverage) · cerveau MLX
Qwen3.6-35B-A3B· client et serveur MCP
La plupart des agents de code envoient ton code et tes données dans le cloud. Klody fait l'inverse : tout s'exécute sur ta machine. Le défi technique — faire d'un modèle local (plus petit que la frontière) un agent fiable — est résolu par l'orchestration, pas par la force brute : un routeur adaptatif, une boucle ReAct qui va au bout, un sandbox isolé, et une discipline de tests/sécurité de niveau production. Le tout extensible via MCP : Klody consomme des serveurs externes (Gmail, web…) et s'expose comme serveur pour d'autres agents.
| 🔒 Privé par conception | 100 % local. Sandbox fichiers multi-racines, fichiers sensibles bloqués partout, anti-SSRF sur le web, commits signés. |
| 🧭 Orchestration, pas brute force | Routeur (easy/medium/hard × 14 types de tâches, F1≈0,85), boucle auto-prolongée, Best-of-N conditionnel, anti-stall. |
| 🔌 Extensible via MCP | Client MCP (consomme Gmail, web, n'importe quel serveur) + serveur MCP (Cline/Zed/Continue consomment Klody). |
| 🧰 Complet | 69 outils, app desktop (Tauri/React, thème clair/sombre/auto), mémoire long terme, RAG livres, retrieval code-aware. |
| 🖥️ Pilote ton environnement | macOS (AppleScript, Spotlight, Raccourcis→HomeKit/Automator, Finder), maison connectée (MQTT : ESP32, Raspberry Pi), automatisation fichiers (renommage, organisation, sauvegarde, synchro). |
| 🔨 Toolsmithing | Klody ne se contente pas d'utiliser des outils, il les fabrique : scripts, CLI, APIs FastAPI, serveurs MCP, workflows, pipelines, plugins Klody, interfaces web — chacun livré avec son test. |
| ⚙️ Ops & génération | Introspection Docker/Kubernetes/Git (lecture seule, mutations gated), SQL SQLite sandboxé, diagrammes UML, scaffolding d'API REST/GraphQL, SDK et repository NoSQL. |
| ✅ Production-grade | 2146 tests, coverage 84,3 %, CI 5 jobs (sécurité + régression + contrat), branch protection + signed commits. |
flowchart TD
U([👤 Utilisateur]) --> UI["🖥️ App desktop · Tauri 2 + React 19<br/>thème clair/sombre/auto"]
UI <-->|WebSocket| API["⚡ FastAPI · :8000"]
API --> ORCH
subgraph ORCH["🧭 Adaptive Orchestrator — boucle ReAct"]
R["Router · easy/medium/hard × 14 task_types"]
L["Boucle auto-prolongée + anti-stall + Best-of-N"]
R --> L
end
ORCH --> TOOLS
ORCH --> MCPC["🔌 Client MCP"]
ORCH --> LLM
subgraph TOOLS["🧰 Outils natifs (69)"]
direction LR
T1["fichiers · sandbox<br/>multi-racines"]
T2["code-aware<br/>tree-sitter + bge-m3"]
T3["github · preview<br/>audio · skills · mémoire"]
T4["macOS · MQTT<br/>automatisation · toolsmithing"]
end
MCPC -->|outils Gmail| GM["📧 Serveur MCP Gmail · :8084"]
MCPC -->|outils Web| WB["🌐 Serveur MCP Web · :8085<br/>lecture seule · anti-SSRF"]
subgraph LLM["🧠 LLM 100% local"]
direction LR
B["MLX cerveau<br/>Qwen3.6-35B · :8080"]
C["MLX code<br/>Qwen3-Coder-30B · :8081"]
O["Ollama fallback · :11434"]
end
TOOLS --> RAG["📚 LibraryBrain RAG · :8765"]
API -.expose.-> MCPS["📡 Serveur MCP Klody · :8083<br/>(Cline · Zed · Continue le consomment)"]
| # | Feature | Détail |
|---|---|---|
| 1 | 100 % local & privé | MLX sur Apple Silicon (Qwen3.6-35B-A3B cerveau, Qwen3-Coder-30B code). Zéro appel cloud par défaut. |
| 2 | Routeur adaptatif | classifie chaque prompt → 3 difficultés × 14 task_types (edit, refactor, bug_fix, feature, explain, self_dev, review, test_gen, security, docs, perf, migrate, creative, music) → budget d'itérations + planner + Best-of-N (F1≈0,85). |
| 3 | Boucle qui va au bout | auto-continue quand la tâche est actionnable ; cliquet de continuation ("ok/vas-y" réutilise le routage). |
| 4 | Sandbox isolé multi-racines | venv jetable par racine, ALLOWED_ROOTS, exec auto après write (py_compile/pytest). |
| 5 | Client MCP | consomme n'importe quel serveur MCP ; outils exposés au LLM sous mcp__<srv>__<outil>. |
| 6 | Serveurs MCP fournis | Gmail (IMAP/SMTP) + Web (fetch/search lecture seule, anti-SSRF) — branchés par une ligne de .env. |
| 7 | Serveur MCP Klody | Klody = plateforme pour d'autres agents (Cline, Zed, Continue.dev). |
| 8 | Retrieval code-aware | tree-sitter (symboles/refs) + embeddings bge-m3 (find_symbol, find_relevant_files). Python/JS/TS de base ; Rust/Go/Java/PHP en option (grammaires chargées dynamiquement). |
| 9 | Best-of-N + mémoire | N candidats T variés + action override ; conventions auto-détectées + mémoire d'erreurs. |
| Composant | Tech |
|---|---|
| Runtime | Python 3.11+ · FastAPI · WebSocket |
| LLM cerveau | MLX-LM — unsloth/Qwen3.6-35B-A3B-MLX-8bit (:8080, enable_thinking=false) |
| LLM code | MLX-LM — mlx-community/Qwen3-Coder-30B-A3B-Instruct-8bit (:8081) |
| LLM fallback | Ollama — qwen2.5-coder:32b (:11434) |
| Embeddings | Ollama — bge-m3 |
| RAG livres | LibraryBrain — sqlite-vec + FTS5 (:8765) |
| MCP | FastMCP — Klody client et serveur ; connecteurs Gmail/Web |
| UI graphique | klody-ui — Tauri 2 + React 19 + Tailwind 4 (repo) |
| Tests | pytest — 2146 tests (+20 skipped) · coverage 84,3 % |
pip install mlx-lm # MLX-LM (Apple Silicon recommandé)
brew install ollama ripgrep # Ollama (embeddings + fallback), ripgrep (recherche)
pip install sqlite-vec # optionnel (LibraryBrain)# Cerveau MLX (MoE 35B / ~3B actifs, multimodal)
huggingface-cli download unsloth/Qwen3.6-35B-A3B-MLX-8bit
# Spécialiste code (optionnel, port 8081)
huggingface-cli download mlx-community/Qwen3-Coder-30B-A3B-Instruct-8bit
# Embeddings (requis pour le retrieval)
ollama serve && ollama pull bge-m3git clone https://github.com/klodynlov/klody-code-ai.git
cd klody-code-ai && python3.11 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # puis éditer (voir ci-dessous)BACKEND=mlx
MLX_BASE_URL=http://localhost:8080/v1
MLX_MODEL=unsloth/Qwen3.6-35B-A3B-MLX-8bit
MLX_CHAT_TEMPLATE_ARGS='{"enable_thinking": false}' # requis pour Qwen3.6 (thinking)
PROJECT_ROOT=/Users/ton-nom/mon-projet # racine principale du sandbox
# ALLOWED_ROOTS=/Users/ton-nom/Projets:/Users/ton-nom/work # racines supplémentaires
MAX_ITERATIONS=25 # plafond boucle ReAct
# Connecteurs MCP (optionnels) — Klody les découvre au démarrage
# KLODY_MCP_SERVERS={"gmail":"http://127.0.0.1:8084/mcp","web":"http://127.0.0.1:8085/mcp"}Deux modes, deux conventions. Ci-dessus le mode autonome : deux
mlx_lm.serverlancés par ce dépôt, adressés par id HF sur:8080/:8081. C'est le bon point de départ pour une première installation.Derrière le gateway Klody Core (
:8090), on référence au contraire des alias (MLX_MODEL=brain) — un id HF figé s'y périme à la première bascule de modèle et renvoie un 404 « modèle inconnu ». Le choix n'est pas cosmétique :start-local-ai.shlitMLX_MODELcomme un id HF, doncMLX_MODEL=brainle fait échouer, et réciproquement. Tout est détaillé dansREADME-local-ai.md.
./scripts/start-mlx.sh # 1. cerveau MLX (~30-60s de chargement)
ollama serve # 2. embeddings
python main.py # 3. CLI Rich (--resume / --session <id>)
python api/server.py # 4. (option) API WebSocket pour l'UI Tauri
./scripts/start-klody-mcp.sh --http # 5. (option) exposer Klody en serveur MCP (:8083)
# Connecteurs MCP (option) :
./scripts/start-gmail-mcp.sh --http # Gmail (:8084) — voir .env.example
./scripts/start-web-mcp.sh --http # Web (:8085) — lecture seule| Catégorie | Outils |
|---|---|
| Fichiers (multi-racines) | read_file, write_file, list_files, search_in_files |
| Code-aware | find_symbol, find_references, find_relevant_files, code_graph, analyze_dependencies, generate_uml (diagramme de classes Mermaid depuis le code) |
| Exécution | execute_command, run_in_sandbox (venv jetable par racine), run_sql (SQLite local sandboxé, lecture seule par défaut) |
| Ops / runtime | docker_control (Docker : ps/images/inspect/logs/stats en lecture seule ; run ultra-contraint si activé), kubectl_control (introspection Kubernetes lecture seule : get, describe, logs, top…), git_control (Git : status, log, diff, show, blame… en lecture seule ; add/commit locaux si GIT_WRITE_ENABLED) |
| Web preview | preview_code (auto-CDN + overlay erreurs JS), preview_file, list_previews |
| GitHub | browse_repo, read_github_file, index_github_repo, clone_github_repo, extract_best_practices, create_project |
| Audio | analyze_audio, edit_wav, mix_stems, generate_silence, convert_format, get_waveform_data |
| Génération | generate_uml (diagramme de classes Mermaid), scaffold_api (squelette CRUD : REST FastAPI ou GraphQL Strawberry), scaffold_sdk (client Python typé httpx), scaffold_nosql (repository MongoDB pymongo) |
Musique — composition (mcp__klodymusic__*) |
evaluer_tessiture, suggerer_tonalites, harmoniser, suggerer_accords, analyser_progression, reharmoniser, moduler, melodie_vers_midi, generer_basse, harmonies_vocales, idees_chanson, composer_demo |
Musique — mixage (mcp__klodymusic__*) |
recommander_eq, detecter_masquage, analyser_balance_tonale, recommander_compression, recommander_saturation (dérivés des mesures objectives d'audio_analysis : LUFS, spectre, largeur stéréo, crest) |
Musique — chant (mcp__klodymusic__*) |
evaluer_tessiture, analyser_justesse (écart en cents vs gamme), recommander_autotune (gamme/vitesse/force), harmonies_vocales |
Musique — sound design (mcp__klodymusic__*) |
generer_preset_synth (patch synthé agnostique par rôle/caractère), organiser_banque (rangement de samples par catégorie) |
| Musique — DAW / voix | mcp__reaper__* (pistes, FX, bus/sends, MIDI, régions, rendu stems, chaîne vocale, arrangement), mcp__vocalbrain__* (generer_chanson chant voix clonée, generer_instrumental morceau sans voix, entraînement de voix) |
| 🖥️ macOS (Apple Silicon) | run_applescript, spotlight_search, run_shortcut (HomeKit/Automator), list_shortcuts, reveal_in_finder |
| 🏠 Maison / IoT | mqtt_publish, mqtt_subscribe (ESP32, Raspberry Pi, Home Assistant, pont HomeKit) |
| 🧹 Automatisation | batch_rename, organize_directory, backup_directory, sync_directories (sandboxés, dry_run par défaut) |
| 🔨 Toolsmithing | scaffold_tool (script, cli, api, mcp_server, workflow, pipeline, klody_plugin, web_interface), list_tool_kinds |
| RAG / Skills | search_books, learn_from_books, get_skills, save_skill, list_skills, delete_skill |
| Mémoire | remember_fact, forget_fact |
| Connecteurs MCP | mcp__gmail__* (8 outils), mcp__web__* (fetch_url, web_search), + tout serveur MCP branché |
Le module le plus puissant : plutôt que de seulement utiliser des outils, Klody en construit de neufs, prêts à l'emploi et livrés avec leur test.
scaffold_tool(kind="mcp_server", name="capteurs maison", target_dir="~/Projets")
→ capteurs_maison/capteurs_maison_server.py (serveur MCP FastMCP)
capteurs_maison/test_capteurs_maison_server.py
capteurs_maison/requirements.txt + README.md
kind |
Produit |
|---|---|
python_script |
script CLI autonome (argparse) + test |
cli |
CLI multi-commandes (sous-commandes) + test |
api |
API FastAPI (health + echo) + test TestClient |
mcp_server |
serveur MCP FastMCP branchable dans Klody + test |
workflow |
orchestrateur d'étapes séquentielles + test |
pipeline |
pipeline ETL (extract → transform → load) + test |
klody_plugin |
plugin outil Klody (schéma registry + handler) |
web_interface |
interface web statique autonome (HTML + JS) |
- Sandbox fichiers : accès limité aux racines autorisées (
PROJECT_ROOT+ALLOWED_ROOTS) ;..//symlinks bloqués ; fichiers sensibles refusés partout (.env .key .pem .p12 .pfx .cer .crt .ppk .p8) ; 1 Mo max/écriture. - Sandbox exécution : confirmation en TTY ; blocklist pré-confirmation (
sudo,rm -rf /,mkfs, exfil SSH/AWS…) ;run_in_sandboxdans un venv jetable isolé. - SQL sandboxé (
run_sql) : base SQLite confinée aux racines autorisées, lecture seule par défaut (SQL_WRITE_ENABLED) ; authorizer sqlite3 default-deny,ATTACH/VACUUM INTO/load_extensionbloqués, verrouSQLITE_LIMIT_ATTACHED=0, anti-DoS (limites + échéance wall-clock), une seule instruction par appel. Contrôles issus d'un threat-model adversarial. - Ops en lecture seule (
docker_control,kubectl_control,git_control) : introspection uniquement (Docker : ps/images/inspect/logs/stats… ; k8s : get/describe/logs/top… ; Git : status/log/diff/show/blame…), aucune mutation ;subprocessen argv (pas de shell), sous-commandes/verbes hardcodés, entrées (cible, resource, name, namespace, ref, fichier) validées par charset strict — pas d'injection de commande ni de flag (-o jsonpath,--kubeconfig,--output…) ; dépôt Git confiné aux racines autorisées ; sortie plafonnée, timeout borné. Les mutations Git (add/commit, locales et réversibles) sont désactivées par défaut (GIT_WRITE_ENABLED) ;push/pullet les opérations destructives (reset/checkout/clean/rebase) restent hors de l'outil. Ledocker run(mutation) est doublement borné —DOCKER_WRITE_ENABLEDet une allowlist d'images non vide — et impose un durcissement figé (aucun flag utilisateur :--network none,--cap-drop ALL,no-new-privileges, limites ressources, pas de montage) ;build/exec/rmrestent exclus. - Web en lecture seule :
fetch_url/web_searchen GET, http/https only, anti-SSRF (IP privées/loopback/link-local refusées, y compris via redirection), taille plafonnée. - Pilotage macOS : garde plateforme (hors macOS → message clair, jamais d'exception) ; blocklist AppleScript (suppression, vidage corbeille, extinction,
do shell script, contrôle UI synthétique refusés) ;reveal_in_finder/Spotlight confinés aux racines autorisées ; timeouts + sortie plafonnée. - Automatisation & toolsmithing : opérations de masse en
dry_runpar défaut (montrent le plan avant d'agir) ; fichiers sensibles exclus partout ; destinations confinées aux racines autorisées ; refus d'écraser un dossier existant. MQTT borné (écoute à timeout dur, payload plafonné, broker local par défaut). - Secrets :
.envgitignoré, jamais hardcodés ni loggés. Commits signés (ED25519), branch protection surmain. - CI : bandit (HIGH), gitleaks, pip-audit
--strict, gate coverage 80 %, snapshots contrat MCP/OpenAPI. Plus un bench nightly sur runner self-hosted (non-régression vs baseline versionnée), avec sentinelle qui coupe court — et le dit — si aucun runner n'est disponible.
python -m pytest tests/ -q # 2146 passing
BACKEND=mlx python -m bench.run --category easy # bench reproductible (20 tâches)
BACKEND=mlx python -m bench.router_eval # précision du routeur (F1 macro)Le bench n'est pas décoratif : aucune amélioration ne passe sans gain chiffré (principe n°2 de la roadmap). Trois outils l'appliquent :
python -m bench.gate # non-régression vs baseline (CI nightly)
python -m bench.run --repeat 3 --label qwen # 3 passes — sinon la variance noie l'écart
python -m bench.compare -a qwen*.json -b oss*.json # A/B de deux cerveaux sur TES tâchesbench.compare sort les agrégats (succès, latence, débit, tool calls cassés,
itérations ReAct), la ventilation par catégorie et les bascules tâche par tâche. Chaque
run enregistre sa provenance — dont le modèle réellement servi, résolu derrière
l'alias du gateway — donc deux fichiers de résultats ne sont jamais confondables.
Détails : bench/README.md.
| Commande | Description |
|---|---|
/help |
Aide |
/sessions |
Liste / charge une session passée |
/memory |
Mémoire courte + souvenirs long terme |
/model · /model <id> |
Modèle actif / change à chaud |
/skills |
Compétences mémorisées |
/exit |
Quitte |
python main.py --resume # reprend la dernière session
python main.py --session <id-court> # reprend une session précise| Symptôme | Cause / solution |
|---|---|
APIConnectionError (MLX) |
Lancer ./scripts/start-mlx.sh |
Connection refused (Ollama) |
ollama serve (requis pour les embeddings bge-m3) |
model not found (MLX) |
huggingface-cli download unsloth/Qwen3.6-35B-A3B-MLX-8bit |
| Le modèle « réfléchit » sans répondre | Qwen3.6 est thinking → MLX_CHAT_TEMPLATE_ARGS='{"enable_thinking": false}' dans .env |
SandboxViolation |
Le chemin doit être sous PROJECT_ROOT/ALLOWED_ROOTS (chemins absolus existants) |
| Outil MCP absent | Vérifier KLODY_MCP_SERVERS + serveur lancé, puis redémarrer le backend (config lue au boot) |
| UI Tauri figée | Cmd+Shift+R ; vérifier python api/server.py sur :8000 |
- 📐 Étude de cas technique — les décisions d'ingénierie (routeur, MCP, sandbox, la saga
max_tokens). - 🧠 Pile d'inférence locale — gateway vs mode autonome, modèles, réglages non négociables (thinking coupé, pas de draft model sur le MoE).
- 📊 Banc de mesure — non-régression, A/B de configurations, répétitions, provenance.
- 🗺️ Roadmap — les 12 étapes livrées et les décisions qui les ont motivées.
- 🛠️ OPS — exploitation, services permanents (LaunchAgents).
- 💼 Pour les entreprises — agents IA privés / local-first.
MIT © 2026 klodynlov