Aller au contenu principal
Logo GiwiSoft
localclaw — construire un agent IA autonome avec Ollama et OpenCode

localclaw — construire un agent IA autonome avec Ollama et OpenCode

Xavier MARIN Xavier MARIN IA, Réalisations 14 min

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)

localclaw — construire un agent IA autonome avec Ollama et OpenCode
localclaw — construire un agent IA autonome avec Ollama et OpenCode
localclaw — construire un agent IA autonome avec Ollama et OpenCode

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 :

ComposantMinimumRecommandé
RAM8 Go16 Go+
VRAM4 Go8 Go+ (GPU NVIDIA/AMD)
Disque10 Go libres30 Go+ (modèles Ollama)
GPUNVIDIA (CUDA) ou AMD (Vulkan)NVIDIA RTX 3060+ / AMD RX 6700+
CPU4 cœurs8 cœurs+
OSLinux (recommandé), macOS, Windows (WSL2)Linux
DockerDocker Engine 24+Docker Compose V2
Node.js18 LTS22 LTS

Modèles Ollama testés :

ModèleTailleVRAMtok/s (RX 580)
qwen2.5:3b1,9 Go2 Go~30 tok/s
nomic-embed-text274 Mo(embedding)

Architecture générale

Architecture

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). Le serveur supporte HTTP et HTTPS — si les variables LOCALCLAW_HTTPS_KEY et LOCALCLAW_HTTPS_CERT sont définies, un serveur TLS est créé automatiquement.

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 :

  1. Récupération du contexte RAG (memories + knowledge base)
  2. Summarisation du contexte si > 8000 caractères
  3. Appel à Ollama avec les outils disponibles
  4. Si Ollama retourne du texte → yield, fin
  5. Si Ollama retourne des tool_calls → exécution
  6. Injection du résultat, retour en 3 (max 15 itérations)
  7. 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 {
name: string
description: string
parameters: {
type: 'object'
properties: Record<string, { type: string; description: string }>
required: string[]
}
}

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 :

SituationAction
Réponse videNouvel 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

Arbre de décision de l'agent loop
Arbre de décision complet de la boucle d'agent avec le système de priorité à 5 niveaux

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.

Planification multi-étapes

Depuis la v0.1.3, l’agent peut décomposer une requête complexe en sous-tâches avant d’exécuter. Ollama reçoit la question + la liste des outils et produit un plan structuré :

STEP 1: Récupérer la météo à Tokyo
TOOL: weather(location="Tokyo")
STEP 2: Chercher les actualités IA
TOOL: fetch_news(query="intelligence artificielle")
STEP 3: Envoyer le résumé par email
TOOL: send_email(to="user@example.com", subject="Briefing quotidien")

Chaque étape est exécutée séquentiellement, les résultats sont injectés dans la conversation, et l’agent synthétise la réponse finale. Le plan est ignoré pour les requêtes simples (salutations, questions directes) et les requêtes mono-outil (météo, news).

Widgets structurés (ToolResult)

Les outils peuvent désormais retourner un widget avec des données typées en plus du résultat texte :

{
result: "Météo à Tokyo: 22°C, ciel clair. Vendredi 18–28°C...",
widget: {
type: "weather",
data: { city: "Tokyo", currentTemp: 22, condition: "Clear sky", forecast: [...] }
}
}

Le result alimente le LLM comme avant. Le widget est rendu directement par le frontend comme un composant natif — plus de parsing de chaînes fragile. La carte météo, avec ses icônes Bootstrap et ses prévisions sur 3 jours, persiste au rechargement de page via la colonne tool_results en base de données.

Agent proactif

Le prompt système a été réécrit pour rendre l’agent plus autonome :

  • Il anticipe les besoins : vérifie la météo, les news, les tâches planifiées sans qu’on le lui demande
  • Il ne demande jamais la permission — il agit et rapporte
  • Après avoir répondu, il propose l’étape suivante ou ajoute une information utile
  • Il détecte les salutations et le small-talk sans déclencher le re-prompting outil

Un classifieur Ollama léger (num_predict: 10, temperature: 0) détermine en < 5s si la requête est SIMPLE ou COMPLEXE, évitant des appels OpenCode coûteux pour un simple « salut ».

Gestionnaire de tâches

L’onglet Tasks a été enrichi puis extrait en page dédiée :

  • Badge de compteur sur le bouton de l’onglet
  • Affichage de la prochaine exécution (next: 22/05/2026, 08:00)
  • Bouton « Run now » pour déclencher manuellement une tâche
  • Historique d’exécution avec statut, durée, résultat/erreur
  • Pause/Reprise/Suppression par tâche

Accessible via l’icône horloge dans l’en-tête du chat, avec retour au chat en un clic.

Le système d’outils

Architecture des plugins

Chaque outil est un module ESM exportant une interface ToolModule :

type ToolModule = {
definition: ToolDefinition
execute: (args: Record<string, any>, onChunk?: (chunk: string) => void) => Promise<string>
}

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
  • schedule_task — Planification de tâches récurrentes (toutes les X minutes, quotidien, hebdomadaire)
  • search_knowledge — Recherche dans la base de connaissance RAG (sémantique + mots-clés)
  • 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)
const allowed = [CWD, DATA_DIR].map(d => path.resolve(d))
if (!allowed.some(dir => resolved.startsWith(dir))) {
return 'Error: Path traversal blocked'
}

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
  • Authentification API — protection par Bearer token optionnelle
  • HTTPS — support TLS optionnel via variables d’environnement
  • Journal d’audit — chaque appel d’outil tracé en base de données

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é

Authentification API

Quand LOCALCLAW_API_KEY est définie dans .env, toutes les routes REST (sauf /api/health) nécessitent un token Bearer :

Authorization: Bearer <votre-clé>

Le middleware auth.ts vérifie la présence et la validité du token avant chaque requête. Sans cette clé, le serveur reste ouvert (par défaut en développement local). C’est une sécurité minimale recommandée dès que le service est exposé sur un réseau.

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
--cap-drop ALL
--security-opt no-new-privileges

Pas de réseau, pas de capabilities, pas d’escalade de privilèges. Le code malveillant reste confiné.

Blocage des commandes destructrices

Même sans sandbox, l’outil run_bash intègre une liste noire de motifs dangereux :

  • rm -rf / — suppression récursive de la racine
  • mkfs, dd if=/dev/zero — destruction de disque
  • :(){ :|:& };: — fork bomb
  • chmod -R 000 / — rend le système inaccessible
  • Redirections vers /dev/sda

La vérification est effectuée avant tout appel à spawn() — la commande est rejetée avec un message d’erreur explicite.

Injection shell

Les outils dynamiques (créés via create_tool en JavaScript/Python) sont exécutés avec execFileSync au lieu d’un shell, éliminant les vecteurs d’injection par métacaractères. Les outils Bash nécessitent toujours un shell mais passent par la liste noire ci-dessus.

Journal d’audit

Chaque appel d’outil est enregistré dans une table tool_calls en SQLite avec :

  • L’identifiant de session
  • Le nom de l’outil et ses arguments (JSON)
  • Le résultat ou l’erreur
  • La durée d’exécution en millisecondes

Cette piste d’audit permet de diagnostiquer rétroactivement un comportement anormal de l’agent, sans dépendre des logs console qui disparaissent au redémarrage.

Vérification de santé au démarrage

Au lancement, le serveur vérifie :

  1. Ollama est joignable — appel à GET /api/tags
  2. Le modèle d’embedding est disponible — appel à POST /api/show avec nomic-embed-text

Si l’une de ces vérifications échoue, le serveur démarre quand même mais un avertissement est affiché dans la console. Pas de crash silencieux au premier échange.

Tokens et secrets

Tous les secrets (API keys, tokens Telegram, clés Mailgun) sont dans .env — qui est dans .gitignore. Le fichier .env.example sert de template documenté avec toutes les variables mais sans valeurs réelles.

Déploiement

Docker Compose (recommandé)

services:
ollama:
image: ollama/ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]

searxng:
image: searxng/searxng
ports: ["8888:8080"]

localclaw:
build: .
ports: ["4173:4173"]
env_file: .env

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
/connect

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 :

{
"$schema": "https://opencode.ai/config.json",
"provider": {
"ollama": {
"npm": "@ai-sdk/openai-compatible",
"name": "Ollama (local)",
"options": {
"baseURL": "http://localhost:11434/v1"
},
"models": {
"qwen2.5-coder:7b": {
"name": "Qwen 2.5 Coder 7B (local)"
}
}
}
}
}

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

RessourceURL
Site officielhttps://opencode.ai
Documentationhttps://opencode.ai/docs
Installationhttps://opencode.ai/install

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èmeCause probableSolution
Ollama ne répond pasService non démarréollama serve ou systemctl start ollama
Modèle introuvableNon téléchargéollama pull qwen2.5:7b-instruct-q3_K_M
Tool call videModèle trop petitPasser à qwen2.5:7b ou augmenter num_ctx (128k)
Erreur CUDA/VulkanGPU non détectéVérifier ollama run --verbose, définir OLLAMA_VULKAN=1
Path traversal bloquéChemin hors CWDUtiliser un chemin dans le répertoire du projet
WebSocket déconnectéProxy Angular manquantVérifier proxy.conf.js avec ws: true
RAG sans résultatsEmbeddings manquantsRedémarrer le serveur (backfill automatique)
OpenCode non trouvéBinaire pas dans $PATHVérifier which opencode ou npm list -g opencode-ai
API retourne 401LOCALCLAW_API_KEY définie mais token manquant/invalideAjouter Authorization: Bearer <clé> aux requêtes
Erreur Ollama au démarrageService injoignable ou modèle d’embedding manquantVérifier ollama serve et ollama pull nomic-embed-text

Améliorations futures

  • Support multi-GPU — ajouter une configuration pour utiliser plusieurs GPUs ou le CPU offloading
  • Multi-agents — faire communiquer plusieurs agents spécialisés (recherche, code, analyse) via le scheduler
  • Plus de widgets — galerie d’images, résultats de recherche enrichis, lecteur RSS
  • Environnement de développement — faire du create_tool un véritable IDE avec validation de schéma et tests

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. Les widgets structurés et la planification multi-étapes apportent une vraie valeur ajoutée par rapport à un simple chatbot.

Essaye-le maintenant :

git clone https://github.com/Giwi/localclaw.git
cd localclaw
cp .env.example .env
docker compose up -d

Rejoins la communauté pour partager tes retours, poser des questions ou proposer des idées :

GitHub