Pourquoi Clawdbot est devenu viral - et comment construire des agents longue durée prêts à produire avec LangGraph et Milvus
Clawdbot (désormais OpenClaw) est devenu viral
Clawdbot, rebaptisé OpenClaw, a pris d'assaut l'internet la semaine dernière. L'assistant IA open-source créé par Peter Steinberger a atteint plus de 110 000 étoiles GitHub en quelques jours. Les utilisateurs ont posté des vidéos montrant l'assistant en train de s'enregistrer de manière autonome pour prendre l'avion, de gérer les courriels et de contrôler les appareils domestiques intelligents. Andrej Karpathy, ingénieur fondateur d'OpenAI, en a fait l'éloge. David Sacks, fondateur et investisseur de Tech, a tweeté à son sujet. Les gens l'ont appelé "Jarvis, mais en vrai".
Puis sont venus les avertissements de sécurité.
Les chercheurs ont trouvé des centaines de panneaux d'administration exposés. Le bot fonctionne par défaut avec un accès root. Il n'y a pas de bac à sable. Les vulnérabilités liées à l'injection d'invites pourraient permettre aux attaquants de détourner l'agent. Un cauchemar pour la sécurité.
Clawdbot est devenu viral pour une raison bien précise
Clawdbot est devenu viral pour une bonne raison. Il fonctionne localement ou sur votre propre serveur. Il se connecte aux applications de messagerie que les gens utilisent déjà - WhatsApp, Slack, Telegram, iMessage. Il se souvient du contexte au fil du temps au lieu de tout oublier après chaque réponse. Il gère les calendriers, résume les courriels et automatise les tâches entre les applications.
Les utilisateurs ont l'impression d'avoir affaire à une IA personnelle autonome et toujours active, et non à un simple outil d'assistance et de réponse. Son modèle open-source et auto-hébergé séduit les développeurs qui souhaitent contrôler et personnaliser l'application. Et la facilité d'intégration avec les flux de travail existants facilite le partage et la recommandation.
Deux défis pour la création d'agents à long terme
La popularité de Clawdbot prouve que les gens veulent une IA qui agit, et pas seulement qui répond. Mais tout agent qui fonctionne sur de longues périodes et accomplit des tâches réelles - qu'il s'agisse de Clawdbot ou d'un agent que vous construisez vous-même - doit relever deux défis techniques : la mémoire et la vérification.
Le problème de la mémoire se manifeste de multiples façons :
Les agents épuisent leur fenêtre contextuelle en cours de tâche et laissent derrière eux un travail à moitié terminé
Ils perdent de vue la liste complète des tâches et déclarent "terminé" trop tôt.
Ils ne peuvent pas transférer le contexte entre les sessions, de sorte que chaque nouvelle session repart à zéro.
Tous ces problèmes ont la même origine : les agents n'ont pas de mémoire persistante. Les fenêtres de contexte sont finies, la récupération entre les sessions est limitée et les progrès ne sont pas suivis d'une manière à laquelle les agents peuvent accéder.
Le problème de la vérification est différent. Même lorsque la mémoire fonctionne, les agents continuent de marquer les tâches comme achevées après un rapide test unitaire, sans vérifier si la fonctionnalité fonctionne réellement de bout en bout.
Clawdbot s'attaque à ces deux problèmes. Il stocke la mémoire localement au fil des sessions et utilise des "compétences" modulaires pour automatiser les navigateurs, les fichiers et les services externes. L'approche fonctionne. Mais elle n'est pas prête pour la production. Pour une utilisation en entreprise, vous avez besoin d'une structure, d'une auditabilité et d'une sécurité que Clawdbot n'offre pas d'emblée.
Cet article couvre les mêmes problèmes avec des solutions prêtes pour la production.
Pour la mémoire, nous utilisons une architecture à deux agents basée sur les recherches d'Anthropic: un agent initialisateur qui décompose les projets en caractéristiques vérifiables, et un agent codeur qui les traite un par un avec des transferts propres. Pour le rappel sémantique entre les sessions, nous utilisons Milvus, une base de données vectorielle qui permet aux agents d'effectuer des recherches en fonction du sens et non des mots-clés.
Pour la vérification, nous utilisons l'automatisation du navigateur. Au lieu de faire confiance aux tests unitaires, l'agent teste les fonctionnalités comme le ferait un véritable utilisateur.
Nous présenterons les concepts, puis nous montrerons une implémentation pratique utilisant LangGraph et Milvus.
Comment l'architecture à deux agents empêche l'épuisement du contexte
Chaque LLM a une fenêtre de contexte : une limite sur la quantité de texte qu'il peut traiter en même temps. Lorsqu'un agent travaille sur une tâche complexe, cette fenêtre se remplit de code, de messages d'erreur, d'historique de conversation et de documentation. Lorsque la fenêtre est pleine, l'agent s'arrête ou commence à oublier le contexte antérieur. Pour les tâches de longue durée, ce phénomène est inévitable.
Prenons l'exemple d'un agent à qui l'on donnerait une simple instruction : "Construire un clone de claude.ai". Le projet nécessite une authentification, des interfaces de chat, un historique des conversations, des réponses en continu et des dizaines d'autres fonctionnalités. Un seul agent essaiera de tout faire en même temps. À mi-chemin de l'implémentation de l'interface de chat, la fenêtre contextuelle se remplit. La session se termine avec un code à moitié écrit, aucune documentation sur ce qui a été tenté et aucune indication sur ce qui fonctionne et ce qui ne fonctionne pas. La session suivante hérite d'un gâchis. Même avec le compactage du contexte, le nouvel agent doit deviner ce que faisait la session précédente, déboguer le code qu'il n'a pas écrit et trouver où reprendre. Des heures sont perdues avant que de nouveaux progrès ne soient réalisés.
La solution de l'agent double
La solution d'Anthropic, décrite dans leur article "Effective harnesses for long-running agents", est d'utiliser deux modes d'invite différents : une invite d'initialisation pour la première session et une invite de codage pour les sessions suivantes.
Techniquement, les deux modes utilisent le même agent sous-jacent, la même invite système, les mêmes outils et le même harnais. La seule différence réside dans l'invite initiale de l'utilisateur. Mais comme ils jouent des rôles distincts, il est utile de les considérer comme deux agents distincts. C'est ce que nous appelons l'architecture à deux agents.
L'initialisateur met en place l'environnement nécessaire à un progrès progressif. Il prend une demande vague et fait trois choses :
Il décompose le projet en fonctionnalités spécifiques et vérifiables. Il ne s'agit pas d'exigences vagues comme "créer une interface de discussion", mais d'étapes concrètes et testables : "L'utilisateur clique sur le bouton Nouveau chat → une nouvelle conversation apparaît dans la barre latérale → la zone de chat affiche l'état de bienvenue. L'exemple du clone claude.ai d'Anthropic comportait plus de 200 fonctionnalités de ce type.
Crée un fichier de suivi de la progression. Ce fichier enregistre l'état d'avancement de chaque fonctionnalité, de sorte que chaque session peut voir ce qui a été fait et ce qui reste à faire.
Écrire des scripts d'installation et faire un premier commit git. Les scripts tels que
init.shpermettent aux sessions futures de démarrer rapidement l'environnement de développement. Le commit git établit une base de référence propre.
L'initialisateur ne se contente pas de planifier. Il crée une infrastructure qui permet aux futures sessions de commencer à travailler immédiatement.
L'agent de codage gère toutes les sessions suivantes. Il :
lit le fichier de progression et les journaux git pour comprendre l'état actuel
Exécute un test de base de bout en bout pour confirmer que l'application fonctionne toujours.
Choisit une fonctionnalité sur laquelle travailler
Implémente la fonctionnalité, la teste complètement, la commite sur git avec un message descriptif, et met à jour le fichier de progression.
Lorsque la session se termine, la base de code est dans un état qui peut être fusionné : pas de bogues majeurs, un code ordonné, une documentation claire. Il n'y a pas de travail à moitié terminé ni de mystère sur ce qui a été fait. La session suivante reprend exactement là où celle-ci s'est arrêtée.
Utiliser JSON pour le suivi des fonctionnalités, pas Markdown
Un détail d'implémentation qui mérite d'être noté : la liste des fonctionnalités doit être en JSON, et non en Markdown.
Lors de l'édition de JSON, les modèles d'IA ont tendance à modifier chirurgicalement des champs spécifiques. Lorsqu'ils éditent du Markdown, ils réécrivent souvent des sections entières. Avec une liste de plus de 200 fonctionnalités, les modifications en Markdown peuvent accidentellement corrompre votre suivi des progrès.
Une entrée JSON ressemble à ceci :
json
{
"category": "functional",
"description": "New chat button creates a fresh conversation",
"steps": [
"Navigate to main interface",
"Click the 'New Chat' button",
"Verify a new conversation is created",
"Check that chat area shows welcome state",
"Verify conversation appears in sidebar"
],
"passes": false
}
Chaque fonctionnalité comporte des étapes de vérification claires. Le champ passes permet de suivre l'achèvement de la tâche. Il est également recommandé de formuler des instructions fermes telles que "Il est inacceptable de supprimer ou de modifier des tests car cela pourrait entraîner des fonctionnalités manquantes ou des bogues" afin d'empêcher l'agent de jouer avec le système en supprimant des fonctionnalités difficiles.
Comment Milvus donne aux agents une mémoire sémantique entre les sessions
L'architecture à deux agents résout le problème de l'épuisement du contexte, mais pas celui de l'oubli. Même si les transferts entre les sessions se font sans problème, l'agent perd la trace de ce qu'il a appris. Il ne peut pas se rappeler que les "jetons de rafraîchissement JWT" sont liés à l'"authentification de l'utilisateur" à moins que ces mots exacts n'apparaissent dans le fichier de progression. Au fur et à mesure que le projet grandit, la recherche dans des centaines de commits git devient lente. La correspondance des mots-clés ne permet pas d'établir des liens qui seraient évidents pour un humain.
C'est là que les bases de données vectorielles entrent en jeu. Au lieu de stocker du texte et de rechercher des mots-clés, une base de données vectorielle convertit le texte en représentations numériques de la signification. Lorsque vous recherchez "authentification de l'utilisateur", elle trouve des entrées concernant les "jetons de rafraîchissement JWT" et la "gestion de la session de connexion". Ce n'est pas parce que les mots correspondent, mais parce que les concepts sont sémantiquement proches. L'agent peut demander "ai-je déjà vu quelque chose comme ça ?" et obtenir une réponse utile.
En pratique, cela fonctionne en intégrant les enregistrements de progrès et les commits git dans la base de données sous forme de vecteurs. Lorsqu'une session de codage commence, l'agent interroge la base de données avec sa tâche en cours. La base de données renvoie un historique pertinent en quelques millisecondes : ce qui a été essayé auparavant, ce qui a fonctionné, ce qui a échoué. L'agent ne part pas de zéro. Il part du contexte.
Milvus convient parfaitement à ce cas d'utilisation. Il s'agit d'un logiciel libre conçu pour la recherche vectorielle à l'échelle de la production, qui traite des milliards de vecteurs sans aucune difficulté. Pour les petits projets ou le développement local, Milvus Lite peut être intégré directement dans une application comme SQLite. Aucune configuration de cluster n'est nécessaire. Lorsque le projet prend de l'ampleur, vous pouvez migrer vers Milvus distribué sans modifier votre code. Pour générer des embeddings, vous pouvez utiliser des modèles externes comme SentenceTransformer pour un contrôle plus fin, ou faire référence à ces fonctions d'embedding intégrées pour des configurations plus simples. Milvus prend également en charge la recherche hybride, qui combine la similarité vectorielle avec le filtrage traditionnel, de sorte que vous pouvez demander "trouver des problèmes d'authentification similaires de la semaine dernière" en un seul appel.
Cela résout également le problème du transfert. La base de données vectorielles persiste en dehors de toute session, de sorte que les connaissances s'accumulent au fil du temps. La session 50 a accès à tout ce qui a été appris au cours des sessions 1 à 49. Le projet développe une mémoire institutionnelle.
Vérification de l'achèvement à l'aide de tests automatisés
Même avec l'architecture à deux agents et la mémoire à long terme, les agents peuvent toujours déclarer leur victoire trop tôt. C'est le problème de la vérification.
Voici un mode d'échec courant : Une session de codage termine une fonctionnalité, exécute un test unitaire rapide, le voit réussir et fait basculer "passes": false sur "passes": true. Mais un test unitaire réussi ne signifie pas que la fonctionnalité fonctionne réellement. L'API peut renvoyer des données correctes alors que l'interface utilisateur n'affiche rien à cause d'un bogue CSS. Le fichier de progression indique "terminé" alors que les utilisateurs ne voient rien.
La solution consiste à faire en sorte que l'agent teste comme un véritable utilisateur. Chaque fonctionnalité de la liste des fonctionnalités comporte des étapes de vérification concrètes : "l'utilisateur clique sur le bouton Nouvelle conversation → la nouvelle conversation apparaît dans la barre latérale → la zone de conversation affiche l'état de bienvenue". L'agent doit vérifier ces étapes littéralement. Au lieu d'exécuter uniquement des tests au niveau du code, il utilise des outils d'automatisation du navigateur tels que Puppeteer pour simuler une utilisation réelle. Il ouvre la page, clique sur des boutons, remplit des formulaires et vérifie que les bons éléments apparaissent à l'écran. Ce n'est que lorsque le flux complet est passé que l'agent marque l'achèvement de la fonctionnalité.
Cela permet de détecter des problèmes que les tests unitaires ne détectent pas. Une fonctionnalité de chat peut avoir une logique de backend parfaite et des réponses API correctes. Mais si le frontend ne rend pas la réponse, les utilisateurs ne voient rien. L'automatisation du navigateur permet de faire une capture d'écran du résultat et de vérifier que ce qui apparaît à l'écran correspond à ce qui devrait apparaître. Le champ passes ne devient true que lorsque la fonctionnalité fonctionne réellement de bout en bout.
Il existe toutefois des limites. Certaines fonctionnalités natives du navigateur ne peuvent pas être automatisées par des outils tels que Puppeteer. Les sélecteurs de fichiers et les boîtes de dialogue de confirmation du système en sont des exemples courants. Anthropic a noté que les fonctionnalités reposant sur des modales d'alerte natives du navigateur avaient tendance à être plus boguées parce que l'agent ne pouvait pas les voir par l'intermédiaire de Puppeteer. La solution pratique consiste à contourner ces limitations. Dans la mesure du possible, utilisez des composants d'interface utilisateur personnalisés au lieu de boîtes de dialogue natives, afin que l'agent puisse tester chaque étape de vérification de la liste des fonctionnalités.
L'assemblage : LangGraph pour l'état de la session, Milvus pour la mémoire à long terme
Les concepts ci-dessus sont réunis dans un système fonctionnel utilisant deux outils : LangGraph pour l'état de session et Milvus pour la mémoire à long terme. LangGraph gère ce qui se passe au sein d'une session unique : quelle fonction est en cours d'élaboration, ce qui est terminé, ce qui va suivre. Milvus stocke l'historique consultable des sessions : ce qui a été fait auparavant, les problèmes rencontrés et les solutions qui ont fonctionné. Ensemble, ils donnent aux agents une mémoire à court et à long terme.
Une remarque sur cette implémentation : Le code ci-dessous est une démonstration simplifiée. Il montre les principaux modèles dans un seul script, mais il ne reproduit pas complètement la séparation des sessions décrite plus haut. Dans une configuration de production, chaque session de codage serait une invocation séparée, potentiellement sur différentes machines ou à différents moments. Les pages MemorySaver et thread_id de LangGraph permettent cela en conservant l'état entre les invocations. Pour voir clairement le comportement de reprise, vous exécutez le script une fois, vous l'arrêtez, puis vous l'exécutez à nouveau avec la même adresse thread_id. La deuxième exécution reprend là où la première s'est arrêtée.
Python
from sentence_transformers import SentenceTransformer
from pymilvus import MilvusClient
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
import operator
import subprocess
import json
# ==================== Initialization ====================
embedding_model = SentenceTransformer(‘all-MiniLM-L6-v2’)
milvus_client = MilvusClient(“./milvus_agent_memory.db”)
# Create collection
if not milvus_client.has_collection(“agent_history”):
milvus_client.create_collection(
collection_name=“agent_history”,
dimension=384,
auto_id=True
)
# ==================== Milvus Operations ====================
def retrieve_context(query: str, top_k: int = 3):
"""Retrieve relevant history from Milvus (core element: semantic retrieval)“"”
query_vec = embedding_model.encode(query).tolist()
results = milvus_client.search(
collection_name=“agent_history”,
data=[query_vec],
limit=top_k,
output_fields=[“content”]
)
if results and results[0]:
return [hit[“entity”][“content”] for hit in results[0]]
return []
def save_progress(content: str):
"""Save progress to Milvus (long-term memory)“"”
embedding = embedding_model.encode(content).tolist()
milvus_client.insert(
collection_name=“agent_history”,
data=[{“vector”: embedding, “content”: content}]
)
# ==================== Core Element 1: Git Commit ====================
def git_commit(message: str):
"""Git commit (core element from the article)“"”
try:
# In a real project, actual Git commands would be executed
# subprocess.run(["git", "add", “.”], check=True)
# subprocess.run([“git", “commit", "-m", message], check=True)
print(f”[Git Commit] {message}")
return True
except Exception as e:
print(f”[Git Commit Failed] {e}")
return False
# ==================== Core Element 2: Test Verification ====================
def run_tests(feature: str):
“""Run tests (end-to-end testing emphasized in the article)“"”
try:
# In a real project, testing tools like Puppeteer would be called
# Simplified to simulated testing here
print(f”[Test Verification] Testing feature: {feature}")
# Simulated test result
test_passed = True # In practice, this would return actual test results
if test_passed:
print(f"[Test Passed] {feature}")
return test_passed
except Exception as e:
print(f"[Test Failed] {e}")
return False
# ==================== State Definition ====================
class AgentState(TypedDict):
messages: Annotated[list, operator.add]
features: list # All features list
completed_features: list # Completed features
current_feature: str # Currently processing feature
session_count: int # Session counter
# ==================== Two-Agent Nodes ====================
def initialize_node(state: AgentState):
“""Initializer Agent: Generate feature list and set up work environment""”
print(“\n========== Initializer Agent Started ==========”)
<span class="hljs-comment"># Generate feature list (in practice, a detailed feature list would be generated based on requirements)</span>
features = [
<span class="hljs-string">"Implement user registration"</span>,
<span class="hljs-string">"Implement user login"</span>,
<span class="hljs-string">"Implement password reset"</span>,
<span class="hljs-string">"Implement user profile editing"</span>,
<span class="hljs-string">"Implement session management"</span>
]
<span class="hljs-comment"># Save initialization info to Milvus</span>
init_summary = <span class="hljs-string">f"Project initialized with <span class="hljs-subst">{<span class="hljs-built_in">len</span>(features)}</span> features"</span>
save_progress(init_summary)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f"[Initialization Complete] Feature list: <span class="hljs-subst">{features}</span>"</span>)
<span class="hljs-keyword">return</span> {
**state,
<span class="hljs-string">"features"</span>: features,
<span class="hljs-string">"completed_features"</span>: [],
<span class="hljs-string">"current_feature"</span>: features[<span class="hljs-number">0</span>] <span class="hljs-keyword">if</span> features <span class="hljs-keyword">else</span> <span class="hljs-string">""</span>,
<span class="hljs-string">"session_count"</span>: <span class="hljs-number">0</span>,
<span class="hljs-string">"messages"</span>: [init_summary]
}
def code_node(state: AgentState):
“""Coding Agent: Implement, test, commit (core loop node)“"”
print(f"\n========== Coding Agent Session #{state[‘session_count’] + 1} ==========”)
current_feature = state[<span class="hljs-string">"current_feature"</span>]
<span class="hljs-built_in">print</span>(<span class="hljs-string">f"[Current Task] <span class="hljs-subst">{current_feature}</span>"</span>)
<span class="hljs-comment"># ===== Core Element 3: Retrieve history from Milvus (cross-session memory) =====</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">f"[Retrieving History] Querying experiences related to '<span class="hljs-subst">{current_feature}</span>'..."</span>)
context = retrieve_context(current_feature)
<span class="hljs-keyword">if</span> context:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f"[Retrieval Results] Found <span class="hljs-subst">{<span class="hljs-built_in">len</span>(context)}</span> relevant records:"</span>)
<span class="hljs-keyword">for</span> i, ctx <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(context, <span class="hljs-number">1</span>):
<span class="hljs-built_in">print</span>(<span class="hljs-string">f" <span class="hljs-subst">{i}</span>. <span class="hljs-subst">{ctx[:<span class="hljs-number">60</span>]}</span>..."</span>)
<span class="hljs-keyword">else</span>:
<span class="hljs-built_in">print</span>(<span class="hljs-string">"[Retrieval Results] No relevant history (first time implementing this type of feature)"</span>)
<span class="hljs-comment"># ===== Step 1: Implement feature =====</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">f"[Starting Implementation] <span class="hljs-subst">{current_feature}</span>"</span>)
<span class="hljs-comment"># In practice, an LLM would be called to generate code</span>
implementation_result = <span class="hljs-string">f"Implemented feature: <span class="hljs-subst">{current_feature}</span>"</span>
<span class="hljs-comment"># ===== Step 2: Test verification (core element) =====</span>
test_passed = run_tests(current_feature)
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> test_passed:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f"[Session End] Tests did not pass, fixes needed"</span>)
<span class="hljs-keyword">return</span> state <span class="hljs-comment"># Don't proceed if tests fail</span>
<span class="hljs-comment"># ===== Step 3: Git commit (core element) =====</span>
commit_message = <span class="hljs-string">f"feat: <span class="hljs-subst">{current_feature}</span>"</span>
git_commit(commit_message)
<span class="hljs-comment"># ===== Step 4: Update progress file =====</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">f"[Updating Progress] Marking feature as complete"</span>)
<span class="hljs-comment"># ===== Step 5: Save to Milvus long-term memory =====</span>
progress_record = <span class="hljs-string">f"Completed feature: <span class="hljs-subst">{current_feature}</span> | Commit message: <span class="hljs-subst">{commit_message}</span> | Test status: passed"</span>
save_progress(progress_record)
<span class="hljs-comment"># ===== Step 6: Update state and prepare for next feature =====</span>
new_completed = state[<span class="hljs-string">"completed_features"</span>] + [current_feature]
remaining_features = [f <span class="hljs-keyword">for</span> f <span class="hljs-keyword">in</span> state[<span class="hljs-string">"features"</span>] <span class="hljs-keyword">if</span> f <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> new_completed]
<span class="hljs-built_in">print</span>(<span class="hljs-string">f"[Progress] Completed: <span class="hljs-subst">{<span class="hljs-built_in">len</span>(new_completed)}</span>/<span class="hljs-subst">{<span class="hljs-built_in">len</span>(state[<span class="hljs-string">'features'</span>])}</span>"</span>)
<span class="hljs-comment"># ===== Core Element 4: Session end (clear session boundary) =====</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">f"[Session End] Codebase is in clean state, safe to interrupt\n"</span>)
<span class="hljs-keyword">return</span> {
**state,
<span class="hljs-string">"completed_features"</span>: new_completed,
<span class="hljs-string">"current_feature"</span>: remaining_features[<span class="hljs-number">0</span>] <span class="hljs-keyword">if</span> remaining_features <span class="hljs-keyword">else</span> <span class="hljs-string">""</span>,
<span class="hljs-string">"session_count"</span>: state[<span class="hljs-string">"session_count"</span>] + <span class="hljs-number">1</span>,
<span class="hljs-string">"messages"</span>: [implementation_result]
}
# ==================== Core Element 3: Loop Control ====================
def should_continue(state: AgentState):
"""Determine whether to continue to next feature (incremental loop development)“"”
if state[“current_feature”] and state[“current_feature”] != “”:
return “code” # Continue to next feature
else:
print(“\n========== All Features Complete ==========”)
return END
# ==================== Build Workflow ====================
workflow = StateGraph(AgentState)
# Add nodes
workflow.add_node(“initialize”, initialize_node)
workflow.add_node(“code”, code_node)
# Add edges
workflow.add_edge(START, “initialize”)
workflow.add_edge(“initialize”, “code”)
# Add conditional edges (implement loop)
workflow.add_conditional_edges(
“code”,
should_continue,
{
“code”: “code”, # Continue loop
END: END # End
}
)
# Compile workflow (using MemorySaver as checkpointer)
app = workflow.compile(checkpointer=MemorySaver())
# ==================== Usage Example: Demonstrating Cross-Session Recovery ====================
if name == "main":
print(“=” * 60)
print(“Demo Scenario: Multi-Session Development for Long-Running Agents”)
print(“=” * 60)
<span class="hljs-comment"># ===== Session 1: Initialize + complete first 2 features =====</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">"\n[Scenario 1] First launch: Complete first 2 features"</span>)
config = {<span class="hljs-string">"configurable"</span>: {<span class="hljs-string">"thread_id"</span>: <span class="hljs-string">"project_001"</span>}}
result = app.invoke({
<span class="hljs-string">"messages"</span>: [],
<span class="hljs-string">"features"</span>: [],
<span class="hljs-string">"completed_features"</span>: [],
<span class="hljs-string">"current_feature"</span>: <span class="hljs-string">""</span>,
<span class="hljs-string">"session_count"</span>: <span class="hljs-number">0</span>
}, config)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"\n"</span> + <span class="hljs-string">"="</span> * <span class="hljs-number">60</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"[Simulated Scenario] Developer manually interrupts (Ctrl+C) or context window exhausted"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"="</span> * <span class="hljs-number">60</span>)
<span class="hljs-comment"># ===== Session 2: Restore state from checkpoint =====</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">"\n[Scenario 2] New session starts: Continue from last interruption"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"Using the same thread_id, LangGraph automatically restores from checkpoint..."</span>)
<span class="hljs-comment"># Using the same thread_id, LangGraph will automatically restore state from checkpoint</span>
result = app.invoke({
<span class="hljs-string">"messages"</span>: [],
<span class="hljs-string">"features"</span>: [],
<span class="hljs-string">"completed_features"</span>: [],
<span class="hljs-string">"current_feature"</span>: <span class="hljs-string">""</span>,
<span class="hljs-string">"session_count"</span>: <span class="hljs-number">0</span>
}, config)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"\n"</span> + <span class="hljs-string">"="</span> * <span class="hljs-number">60</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"Demo Complete!"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"="</span> * <span class="hljs-number">60</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"\nKey Takeaways:"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"1. ✅ Two-Agent Architecture (initialize + code)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"2. ✅ Incremental Loop Development (conditional edges control loop)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"3. ✅ Git Commits (commit after each feature)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"4. ✅ Test Verification (end-to-end testing)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"5. ✅ Session Management (clear session boundaries)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"6. ✅ Cross-Session Recovery (thread_id + checkpoint)"</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"7. ✅ Semantic Retrieval (Milvus long-term memory)"</span>)
The key insight is in the last part. By using the same thread_id, LangGraph automatically restores the checkpoint from the previous session. Session 2 picks up exactly where session 1 stopped — no manual state transfer, no lost progress.
Conclusion
Les agents d'intelligence artificielle échouent dans les tâches de longue haleine parce qu'ils ne disposent pas d'une mémoire persistante et d'une vérification adéquate. Clawdbot est devenu viral en résolvant ces problèmes, mais son approche n'est pas prête pour la production.
Cet article présente trois solutions qui le sont :
Architecture à deux agents : Un initialisateur divise les projets en caractéristiques vérifiables ; un agent de codage les traite un par un avec des transferts propres. Cela permet d'éviter l'épuisement du contexte et de suivre les progrès réalisés.
Base de données vectorielle pour la mémoire sémantique : Milvus stocke les enregistrements de progrès et les commits git sous forme d'enchâssements, de sorte que les agents peuvent effectuer des recherches en fonction du sens, et non des mots-clés. La session 50 se souvient de ce que la session 1 a appris.
Automatisation du navigateur pour une vérification réelle : Les tests unitaires vérifient que le code fonctionne. Puppeteer vérifie que les fonctionnalités fonctionnent réellement en testant ce que les utilisateurs voient à l'écran.
Ces modèles ne se limitent pas au développement de logiciels. La recherche scientifique, la modélisation financière, l'examen de documents juridiques - toute tâche qui s'étend sur plusieurs sessions et nécessite des transferts fiables peut en bénéficier.
Les principes de base :
Utiliser un initialisateur pour diviser le travail en morceaux vérifiables.
Suivre les progrès dans un format structuré et lisible par une machine.
Stocker l'expérience dans une base de données vectorielle pour une récupération sémantique.
Vérifier l'achèvement du travail à l'aide de tests réels, et pas seulement de tests unitaires.
Concevoir des limites de session nettes afin que le travail puisse s'interrompre et reprendre en toute sécurité.
Les outils existent. Les modèles sont éprouvés. Il ne reste plus qu'à les appliquer.
Prêt à commencer ?
Explorez Milvus et Milvus Lite pour ajouter une mémoire sémantique à vos agents.
Découvrez LangGraph pour gérer l'état des sessions
Lisez la recherche complète d'Anthropic sur les harnais d'agents à longue durée d'exécution.
Vous avez des questions ou souhaitez partager ce que vous construisez ?
Rejoignez la communauté Milvus Slack pour entrer en contact avec d'autres développeurs.
Participez aux Milvus Office Hours pour des questions-réponses en direct avec l'équipe.
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word



