
localclaw — construire un agent IA autonome avec Ollama et OpenCode
J’ai conçu et développé localclaw, un agent IA autonome qui tourne entièrement en local. Il utilise Ollama pour l’inférence, expose une interface Angular 20, et orchestre lui-même l’exécution d’outils (recherche web, bash, fichiers, emails, Telegram, etc.) via du function calling. L’agent peut prendre des décisions, enchaîner des actions, et même créer dynamiquement de nouveaux outils à la volée.
Stack : Node.js/Express + SQLite + Angular 20 + Ollama + ws (WebSocket)
Pourquoi un agent local ?
Les assistants IA cloud (ChatGPT, Claude, etc.) sont puissants, mais ils soulèvent des questions de confidentialité, de coût et de dépendance réseau. L’idée de localclaw est de proposer une alternative :
- 100 % local — aucune donnée ne quitte la machine
- Sans abonnement — juste le coût du matériel
- Extensible — l’agent peut créer ses propres outils
- Autonome — il planifie, exécute et vérifie sans intervention humaine
Le nom « localclaw » vient de la contraction de local et claw (griffe) — l’agent attrape et manipule les ressources locales.
Prérequis système
Avant de vous lancer, voici ce dont vous avez besoin :
| Composant | Minimum | Recommandé |
|---|---|---|
| RAM | 8 Go | 16 Go+ |
| VRAM | 4 Go | 8 Go+ (GPU NVIDIA/AMD) |
| Disque | 10 Go libres | 30 Go+ (modèles Ollama) |
| GPU | NVIDIA (CUDA) ou AMD (Vulkan) | NVIDIA RTX 3060+ / AMD RX 6700+ |
| CPU | 4 cœurs | 8 cœurs+ |
| OS | Linux (recommandé), macOS, Windows (WSL2) | Linux |
| Docker | Docker Engine 24+ | Docker Compose V2 |
| Node.js | 18 LTS | 22 LTS |
Modèles Ollama testés :
| Modèle | Taille | VRAM | tok/s (RX 580) |
|---|---|---|---|
qwen2.5:3b | 1,9 Go | 2 Go | ~30 tok/s |
qwen2.5:7b-instruct-q3_K_M | 3,6 Go | 4 Go | ~15 tok/s |
qwen2.5:7b-instruct-q4_K_M | 4,4 Go | 6 Go | ~10 tok/s |
nomic-embed-text | 274 Mo | — | (embedding) |
Architecture générale
Browser (Angular) ←→ WebSocket ←→ Express Server ←→ Ollama |
Le point d’entrée est un serveur Express qui sert à la fois l’API REST, les fichiers statiques du frontend Angular, et le serveur WebSocket (port 4173).
Pourquoi WebSocket plutôt que SSE ?
La version initiale utilisait Server-Sent Events (SSE) pour streamer les réponses. C’est simple à mettre en œuvre, mais cela pose un problème fondamental : le client ne peut pas interagir avec le flux une fois démarré. Avec le WebSocket, nous avons :
- Bidirectionnel — le client peut envoyer des messages pendant la génération (stop, nouvelle session, etc.)
- Pas de limite de connexion — SSE est limité à 6 connexions simultanées par domaine sous Chrome
- Framing natif — pas besoin de parser des lignes
data:ou de gérer les timeouts HTTP
Le handshake WebSocket est proxyé via le dev server Angular (proxy.conf.js avec ws: true) et passe par l’upgrade HTTP standard côté serveur.
Le cœur : la boucle d’agent
L’agent loop (src/agent.ts) est le cerveau du système. Voici comment il fonctionne :
- Récupération du contexte RAG (memories + knowledge base)
- Summarisation du contexte si > 8000 caractères
- Appel à Ollama avec les outils disponibles
- Si Ollama retourne du texte → yield, fin
- Si Ollama retourne des tool_calls → exécution
- Injection du résultat, retour en 3 (max 15 itérations)
- Persistance en mémoire vectorielle du résultat
Function calling avec Ollama
Ollama expose une API compatible OpenAI pour le function calling. Chaque outil est défini par un schéma JSON :
interface ToolDefinition { |
Ces définitions sont envoyées au modèle dans le champ tools du payload /api/chat. Ollama répond avec tool_calls contenant le nom de la fonction et ses arguments en JSON. L’agent parse cette réponse, exécute l’outil, et réinjecte le résultat dans la conversation.
Gestion des erreurs et re-prompting
Un des défis majeurs est la fiabilité des modèles small (qwen2.5:3b, qwen3:1.7b). Ils ont tendance à :
- Répondre par des conseils au lieu d’utiliser des outils
- Renvoyer des réponses vides
- Abandonner après un échec
- Envoyer des placeholders au lieu de données réelles
L’agent implémente plusieurs stratégies de re-prompting :
| Situation | Action |
|---|---|
| Réponse vide | Nouvel appel avec FORCE_TOOL_MSG |
| Résultat faible (< 30 chars, « No results », « Error: ») | Nouvel essai avec approche différente |
| Réponse de type conseil (« you can », « i suggest ») | Injection d’un message système forçant l’utilisation d’outils |
| Abandon explicite (« cannot find », « pas trouvé ») | Relance avec PERSIST_MSG |
Le système inclut aussi un timeout de sécurité de 120s côté frontend pour éviter un état de chargement bloqué si l’événement done n’arrive jamais.
Le système d’outils
Architecture des plugins
Chaque outil est un module ESM exportant une interface ToolModule :
type ToolModule = { |
Le onChunk permet aux outils de streamer leur sortie en temps réel (utile pour run_bash, web_fetch, etc.). L’agent collecte ces chunks et les balance côté frontend via WebSocket toutes les 150ms.
Outils disponibles
- web_fetch — Recherche web (SearXNG ou DuckDuckGo) + fetch d’URL
- read_file / write_file — Lecture/écriture de fichiers (avec protection contre le path traversal)
- run_bash — Exécution de commandes bash (streaming, 120s timeout, sandbox Docker optionnel)
- send_email — Email via Mailgun API
- send_telegram — Messages Telegram (avec fallback env var pour le chat_id)
- weather — Météo via wttr.in / Open-Meteo
- browser_automation — Contrôle Chromium headless (captures, formulaires, clics)
- generate_image — Génération d’images via Ollama
- opencode_task — Délégation de tâches complexes à OpenCode
- create_tool — Création dynamique de nouveaux outils (JS/Python/Bash)
Protection contre le path traversal
Les outils read_file et write_file vérifient que le chemin résolu se trouve bien dans un répertoire autorisé (CWD ou DATA_DIR). Tentative de ../../etc/passwd ? Bloquée.
const resolved = path.resolve(args.path) |
RAG et mémoire vectorielle
Embeddings
À chaque exécution d’outil, le résultat est encodé vectoriellement via Ollama (/api/embed avec nomic-embed-text) et stocké dans SQLite. Au début de chaque tour de conversation, l’agent recherche les résultats d’outils précédents les plus pertinents et les injecte dans le prompt système.
Base de connaissance
Les documents uploadés (PDF, DOCX, TXT) sont découpés en chunks et indexés de la même manière. L’agent peut les interroger via l’outil search_knowledge. En complément, des tables virtuelles FTS5 (Full-Text Search) sont créées pour le fallback par mots-clés quand la recherche sémantique ne donne pas assez de résultats.
Backfill au démarrage
Au démarrage du serveur, un backfillMissingEmbeddings() scanne les entrées qui n’ont pas encore d’embedding et les encode — évitant les problèmes de migration entre versions.
Fonctionnalités
- Streaming temps réel via WebSocket avec matching des événements par
toolRunId - Bouton Stop — ferme la WebSocket, l’agent détecte la fermeture et stoppe la boucle
- Édition de messages — cliquer sur « edit » sur un message utilisateur, la conversation est tronquée après ce point
- Upload de fichiers — pièce jointe pour TXT/PDF/DOCX
- Thème clair/sombre — auto-détection via
prefers-color-scheme - Timeout de sécurité — 120s, réinitialise l’état de chargement si aucune réponse
Matching des événements d’outils
Un détail d’implémentation intéressant : chaque invocation d’outil reçoit un toolRunId (UUID) unique. Côté frontend, les événements tool_start, tool_chunk, tool_end sont appariés par cet ID. Sans cela, si le même outil est appelé deux fois, les chunks se mélangent dans l’interface.
Sécurité
Sandbox Docker
Quand LOCALCLAW_SANDBOX_ENABLED=true, les outils run_bash et create_tool s’exécutent dans des conteneurs Docker isolés :
--network none |
Pas de réseau, pas de capabilities, pas d’escalade de privilèges. Le code malveillant reste confiné.
Tokens et secrets
Tous les secrets (API keys, tokens Telegram, clés Mailgun) sont dans .env — qui est dans .gitignore. Le README documente les variables nécessaires mais avec des valeurs factices.
Déploiement
Docker Compose (recommandé)
services: |
Le stack complet démarre avec docker compose up -d. Voir la documentation Docker pour plus de détails.
GPU AMD (RX 580 4GB)
Sur ma machine de développement (RX 580, 4 Go VRAM), j’utilise le backend Vulkan via RADV (pilote Mesa) :
OLLAMA_VULKAN=1 |
Le modèle qwen2.5:7b-instruct-q3_K_M (~3,6 Go) tient dans la VRAM et tourne à ~15 tok/s. Le portage ROCm a un bug de doorbell connu avec le kernel 7.0 — Vulkan est le workaround stable.
OpenCode — l’agent de codage IA open source
OpenCode est un agent de codage IA open source qui s’exécute dans le terminal, en IDE ou en application de bureau. Il est utilisé par plus de 7,5 millions de développeurs chaque mois et compte plus de 160 000 étoiles sur GitHub.
Contrairement à localclaw qui est un agent autonome généraliste, OpenCode est spécialisé dans l’ingénierie logicielle : il peut lire et écrire du code, exécuter des commandes, créer des fichiers, et orchestrer des modifications complexes sur un projet. Il intègre nativement le LSP (Language Server Protocol) pour comprendre la structure du code, et supporte plus de 75 fournisseurs de modèles de langage.
Installation
OpenCode s’installe en une commande :
curl -fsSL https://opencode.ai/install | bash |
Ou via npm (nécessite Node.js) :
npm install -g opencode-ai |
Alternativement, sur macOS/Linux avec Homebrew :
brew install anomalyco/tap/opencode |
Configuration d’une clé API
OpenCode supporte plus de 75 fournisseurs de LLM. La configuration se fait dans le terminal via la commande /connect :
opencode |
Choisissez votre fournisseur (OpenAI, Anthropic, Ollama, OpenCode Zen, etc.) et collez votre clé API. OpenCode Zen offre des modèles testés et validés par l’équipe OpenCode avec une facturation simplifiée — idéal pour commencer. Rendez-vous sur opencode.ai/auth pour créer une clé.
Pour utiliser Ollama en local, ajoutez cette configuration dans opencode.json :
{ |
Intégration avec localclaw
localclaw expose un outil opencode_task qui permet de déléguer des tâches complexes de développement à OpenCode. L’agent localclaw peut ainsi analyser un problème, décider qu’une tâche nécessite de l’ingénierie logicielle, et sous-traiter son exécution à OpenCode — qui dispose d’une compréhension fine du projet, du LSP, et de la capacité à modifier le codebase de manière cohérente.
Liens utiles
| Ressource | URL |
|---|---|
| Site officiel | https://opencode.ai |
| Documentation | https://opencode.ai/docs |
| GitHub | https://github.com/anomalyco/opencode |
| Installation | https://opencode.ai/install |
| Desktop (beta) | https://opencode.ai/download |
| API key / Zen | https://opencode.ai/auth |
| Zen (modèles testés) | https://opencode.ai/zen |
| Go (abonnement low-cost) | https://opencode.ai/go |
| Ollama + OpenCode | https://docs.ollama.com/integrations/opencode |
Défis techniques rencontrés
1. La fiabilité des modèles small
Le plus gros défi est sans doute la qualité des modèles en < 7B paramètres. qwen2.5:3b a tendance à :
- Retourner des réponses vides
- Ne pas appeler d’outils
- Abandonner après un premier échec
- Halluciner des arguments
La solution a été un système de re-prompting agressif avec détection de motifs (advisory, giving-up, weak result) et des messages système de forçage.
Dépannage
| Problème | Cause probable | Solution |
|---|---|---|
| Ollama ne répond pas | Service non démarré | ollama serve ou systemctl start ollama |
| Modèle introuvable | Non téléchargé | ollama pull qwen2.5:7b-instruct-q3_K_M |
| Tool call vide | Modèle trop petit | Passer à qwen2.5:7b ou augmenter num_ctx (128k) |
| Erreur CUDA/Vulkan | GPU non détecté | Vérifier ollama run --verbose, définir OLLAMA_VULKAN=1 |
| Path traversal bloqué | Chemin hors CWD | Utiliser un chemin dans le répertoire du projet |
| WebSocket déconnecté | Proxy Angular manquant | Vérifier proxy.conf.js avec ws: true |
| RAG sans résultats | Embeddings manquants | Redémarrer le serveur (backfill automatique) |
| OpenCode non trouvé | Binaire pas dans $PATH | Vérifier which opencode ou npm list -g opencode-ai |
Améliorations futures
- Support de modèles plus gros — ajouter une configuration pour utiliser plusieurs GPUs ou le CPU offloading
- Planification avancée — permettre à l’agent de créer des workflows complexes avec des dépendances entre tâches
- Tools as Code — faire du
create_toolun véritable environnement de développement avec validation de schéma et tests - Multi-agents — faire communiquer plusieurs agents spécialisés (recherche, code, analyse) via le scheduler
- WebUI améliorée — ajouter une vue graphe de l’exécution des tools, un éditeur de prompts, et des presets de sessions
Conclusion
localclaw montre qu’il est possible de construire un agent IA autonome et utile avec des outils open-source et du matériel grand public. Le function calling d’Ollama, combiné à un système d’outils bien conçu et une boucle d’agent robuste, permet des cas d’usage variés : de la simple question-réponse à l’orchestration de workflows complexes.
Essaye-le maintenant :
git clone https://github.com/Giwi/localclaw.git |
Rejoins la communauté pour partager tes retours, poser des questions ou proposer des idées :
Article écrit par Xavier à l’issue du développement de la version 0.1.0 de localclaw.
