Clawdbotはなぜ流行ったのか - そしてLangGraphとmilvusでプロダクション対応の長時間稼働エージェントを構築する方法
Clawdbot(現在はOpenClaw)が大流行
Clawdbot(現在はOpenClawに改名)は先週、インターネットを席巻した。ピーター・スタインバーガーによって作られたオープンソースのAIアシスタントは、わずか数日で11万以上のGitHubスターを獲得した。ユーザーは、フライトのチェックイン、Eメールの管理、スマートホームデバイスのコントロールなどを自律的に行う動画を投稿した。OpenAIの創設エンジニアであるアンドレイ・カルパシーは、これを賞賛した。テック創業者で投資家のデビッド・サックスは、この製品についてツイートした。人々は "Jarvis, but real "と呼んだ。
その後、セキュリティ上の警告が発せられた。
研究者たちは、何百もの露出した管理パネルを発見した。ボットはデフォルトでroot権限で動作する。サンドボックスはない。プロンプト・インジェクションの脆弱性により、攻撃者はエージェントを乗っ取ることができる。セキュリティの悪夢。
Clawdbotが流行ったのには理由がある
Clawdbotが流行ったのには理由がある。Clawdbotはローカルまたは自分のサーバー上で動作する。人々がすでに使っているメッセージングアプリ-WhatsApp、Slack、Telegram、iMessage-に接続する。返信のたびにすべてを忘れてしまうのではなく、時間をかけて文脈を記憶する。カレンダーを管理し、Eメールを要約し、アプリ間のタスクを自動化する。
ユーザーは、単なるプロンプト&レスポンスツールではなく、ハンズオフで常時オンのパーソナルAIの感覚を得ることができる。オープンソースのセルフホスティングモデルは、コントロールとカスタマイズを望む開発者に魅力的だ。また、既存のワークフローとの統合が容易なため、共有や推奨も容易だ。
長期間稼働するエージェントを構築するための2つの課題
Clawdbotの人気は、人々が回答だけでなく 行動するAIを求めていることを証明している。しかし、Clawdbotであろうと自分で作ったものであろうと、長期間稼働し、実際のタスクをこなすエージェントは、メモリと 検証という2つの技術的課題を解決しなければならない。
メモリの問題は様々な形で現れる:
エージェントはタスクの途中でコンテキストウィンドウを使い果たし、中途半端な仕事を残してしまう。
タスクリストの全体像を見失い、「完了」を早く宣言しすぎる。
セッション間でコンテキストを引き継ぐことができず、新しいセッションがゼロから始まる。
エージェントは永続的なメモリを持たない。コンテキストのウィンドウは有限であり、セッション間の検索は制限され、進捗はエージェントがアクセスできる方法では追跡されない。
検証の問題は異なります。メモリが動作していても、エージェントはその機能が実際にエンドツーエンドで動作するかどうかをチェックすることなく、簡単な単体テストの後にタスクを完了とマークします。
Clawdbotはその両方に対処する。セッションをまたいでメモリをローカルに保存し、モジュール化された「スキル」を使ってブラウザ、ファイル、外部サービスを自動化する。このアプローチは機能する。しかし、本番環境では使えない。企業で使うには、Clawdbotがすぐに提供できない構造、監査可能性、セキュリティが必要だ。
この記事では、本番環境向けのソリューションと同じ問題を取り上げる。
記憶については、Anthropicの研究に基づいた2つのエージェントアーキテクチャを使用しています:プロジェクトを検証可能な機能に分割するイニシャライザーエージェントと、きれいなハンドオフで一度に1つずつ作業するコーディングエージェントです。セッション間のセマンティックリコールには、Milvusというベクトルデータベースを使用し、エージェントはキーワードではなく、意味によって検索を行う。
検証にはブラウザの自動化を使う。ユニットテストを信頼する代わりに、エージェントは実際のユーザーと同じように機能をテストします。
LangGraphとMilvusを使った実装を紹介します。
つのエージェントアーキテクチャがコンテキストの枯渇を防ぐ方法
全てのLLMにはコンテキストウィンドウがあり、一度に処理できるテキスト量の制限があります。エージェントが複雑なタスクに取り組むと、このウィンドウはコード、エラーメッセージ、会話履歴、ドキュメントでいっぱいになる。ウィンドウがいっぱいになると、エージェントは停止するか、以前のコンテキストを忘れ始める。長く続くタスクの場合、これは避けられない。
単純なプロンプトを与えられたエージェントを考えてみよう:"claude.aiのクローンを作れ"。このプロジェクトでは、認証、チャット・インターフェース、会話履歴、ストリーミング応答、その他何十もの機能が必要だ。一人のエージェントがすべてを一度に取り組もうとする。チャット・インターフェースを実装する途中で、コンテキスト・ウィンドウがいっぱいになった。セッションは中途半端に書かれたコードで終わり、何が試されたのかのドキュメントもなく、何がうまくいって何がうまくいかないのかもわかりません。次のセッションは混乱を引き継ぐ。コンテキストの圧縮があったとしても、新しいエージェントは前のセッションが何をしていたかを推測し、書いていないコードをデバッグし、どこで再開すべきかを考えなければならない。新たな進歩がなされる前に、何時間も浪費されることになる。
二重のエージェントソリューション
Anthropicの解決策は、彼らのエンジニアリングポスト"Effective harnesses for long-running agents "に記載されているように、2つの異なるプロンプトモードを使用することです:最初のセッションのイニシャライザプロンプトと、その後のセッションのコーディングプロンプトです。
技術的には、どちらのモードも同じエージェント、システムプロンプト、ツール、ハーネスを使用します。唯一の違いは、最初のユーザープロンプトである。しかし、これらは異なる役割を果たすため、2つの別個のエージェントとして考えることが、有用なメンタルモデルとなります。これを2エージェントアーキテクチャと呼びます。
イニシャライザーは、漸進的な進歩のための環境を設定する。漠然としたリクエストを受け取り、3つのことを行う:
プロジェクトを具体的で検証可能な機能に分割する。"チャットインターフェイスを作れ "のような漠然とした要求ではなく、具体的でテスト可能なステップに分割します:"ユーザーが新しいチャットボタンをクリック→サイドバーに新しい会話が表示される→チャットエリアにウェルカム状態が表示される"Anthropicのclaude.aiクローンの例では、このような機能が200以上ありました。
進捗追跡ファイルを作成します。このファイルには各機能の完了ステータスが記録され、どのセッションでも何が完了し、何が残っているかを確認できる。
セットアップスクリプトを書き、最初のgitコミットを行う。
init.shのようなスクリプトにより、今後のセッションは開発環境を素早く立ち上げることができます。git commit はクリーンなベースラインを確立します。
イニシャライザーは計画を立てるだけではありません。将来のセッションがすぐに作業を開始できるようなインフラを構築するのだ。
コーディングエージェントは、後続のすべてのセッションを処理する。それは
進捗ファイルと git ログを読んで現在の状態を把握します。
基本的なエンドツーエンドテストを実行し、アプリがまだ動作することを確認します。
作業する機能を 1 つ選びます。
その機能を実装して徹底的にテストし、説明的なメッセージを添えて git にコミットし、進捗ファイルを更新します。
セッションが終了すると、コードベースはマージ可能な状態になっています。大きなバグもなく、コードも整然としていて、ドキュメントも明確です。中途半端な作業はなく、何が行われたかについての謎もありません。次のセッションは、このセッションが終了したところから始まる。
機能追跡にはMarkdownではなくJSONを使う
特筆すべき実装の詳細が1つある:機能リストはMarkdownではなくJSONであるべきだ。
JSONを編集するとき、AIモデルは特定のフィールドを外科的に修正する傾向がある。Markdownを編集する場合は、セクション全体を書き換えることが多い。200以上の機能リストでは、Markdownの編集は進捗追跡を誤って壊してしまう可能性がある。
JSONエントリーはこのようになる:
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
}
各機能には明確な検証ステップがあります。passes フィールドは完了を追跡します。また、エージェントが難しい機能を削除してシステムを悪用するのを防ぐために、「テストの削除や編集は、機能の欠落やバグにつながる可能性があるため、容認できません」というような強い言葉での指示も推奨されます。
Milvusがエージェントにセッションをまたいだセマンティックメモリを与える方法
2エージェントアーキテクチャはコンテキストの枯渇を解決するが、忘却は解決しない。セッション間をきれいにハンドオフしても、エージェントは何を学習したかを見失う。JWT refresh tokens "が "user authentication "に関連していることは、その正確な単語がプログレスファイルに出てこない限り、思い出すことができない。プロジェクトが大きくなると、何百ものgitコミットを検索するのが遅くなる。キーワードのマッチングは、人間には明らかなつながりを見逃してしまう。
そこでベクター・データベースの出番だ。ベクトル・データベースは、テキストを保存してキーワードを検索する代わりに、テキストを意味を表す数値表現に変換する。"ユーザー認証 "を検索すると、"JWTリフレッシュ・トークン "や "ログイン・セッション処理 "に関するエントリーが見つかる。単語が一致したからではなく、概念が意味的に近いからだ。エージェントは、"以前にこのようなものを見たことがあるか?"と尋ねることができ、有用な答えを得ることができる。
実際には、進捗記録とgitコミットをベクターとしてデータベースに埋め込むことで機能する。コーディングセッションが始まると、エージェントは現在のタスクをデータベースに問い合わせます。データベースは関連する履歴をミリ秒単位で返します。エージェントはゼロから始めるわけではない。コンテキストから始めるのだ。
Milvus はこのユースケースに適している。オープンソースで、プロダクション規模のベクトル検索用に設計されており、汗をかくことなく何十億ものベクトルを処理できる。小規模プロジェクトやローカル開発では、Milvus LiteをSQLiteのようなアプリケーションに直接組み込むことができる。クラスタのセットアップは不要です。プロジェクトが大きくなれば、コードを変更することなく分散Milvusに移行することができます。エンベッディングの生成には、SentenceTransformerのような外部モデルを使用してきめ細かく制御することができます。Milvusはまた、ベクトル類似度と従来のフィルタリングを組み合わせたハイブリッド検索をサポートしています。
これにより、転送問題も解決される。ベクター・データベースは単一のセッションの外でも持続するため、知識は時間とともに蓄積される。セッション50は、セッション1から49で学んだことすべてにアクセスできる。このプロジェクトは、制度的記憶を発展させる。
自動テストによる完成度の検証
2エージェントのアーキテクチャと長期記憶を持ってしても、エージェントは早すぎる勝利宣言をすることができる。これが検証の問題である。
よくある失敗例を挙げよう:コーディングセッションが機能を完成させ、ユニットテストを素早く実行し、それがパスするのを確認し、"passes": false を"passes": true に切り替える。しかし、ユニットテストがパスしたからといって、その機能が実際に動作するとは限らない。APIは正しいデータを返すかもしれないが、CSSのバグのためにUIは何も表示しない。進捗ファイルには "complete "と書かれているのに、ユーザには何も表示されない。
解決策は、エージェントを実際のユーザーのようにテストさせることです。機能リストの各機能には、具体的な検証ステップがあります:"ユーザが新規チャットボタンをクリックする → サイドバーに新規会話が表示される → チャットエリアにウェルカム状態が表示される"。エージェントはこれらのステップを文字通り検証する必要があります。コードレベルのテストだけを実行する代わりに、実際の使用をシミュレートするために Puppeteer のようなブラウザ自動化ツールを使用します。ページを開き、ボタンをクリックし、フォームに入力し、正しい要素が画面に表示されることを確認します。完全なフローが通過したときだけ、エージェントはその機能を完了とマークする。
これは、ユニットテストが見逃す問題をキャッチします。チャット機能は、完璧なバックエンドロジックと正しい API レスポンスを持っているかもしれません。しかし、フロントエンドが返信をレンダリングしなければ、ユーザーには何も見えません。ブラウザオートメーションは結果をスクリーンショットし、スクリーンに表示されるものが表示されるべきものと一致することを検証することができる。passes フィールドがtrue になるのは、その機能が純粋にエンド・ツー・エンドで機能する場合だけだ。
しかし限界もある。Puppeteerのようなツールでは自動化できないブラウザネイティブの機能もある。ファイルピッカーやシステム確認ダイアログがよくある例だ。Anthropicは、ブラウザネイティブのアラートモダルに依存する機能は、エージェントがPuppeteerを通してそれらを見ることができないため、バグが多くなる傾向があると指摘した。現実的な回避策は、これらの制限を回避するように設計することです。可能な限りネイティブダイアログの代わりにカスタムUIコンポーネントを使用し、エージェントが機能リストのすべての検証ステップをテストできるようにします。
まとめるセッション状態のLangGraph、長期記憶のmilvus
上記のコンセプトは、2つのツールを使って実用的なシステムにまとめられます:セッション・ステートはLangGraph、長期記憶はMilvusである。LangGraphは、一つのセッションの中で何が起きているかを管理します:どの機能が作業中か、何が完了したか、次は何か。Milvusは、セッションをまたいで検索可能な履歴を保存します:以前に何が行われ、どのような問題が発生し、どのような解決策が有効であったか。これらの機能により、エージェントは短期的な記憶と長期的な記憶の両方を得ることができます。
この実装についての注意:以下のコードは簡略化されたデモンストレーションである。1つのスクリプトでコアパターンを示していますが、先に説明したセッション分離を完全に再現しているわけではありません。本番環境では、各コーディング・セッションは、別々のマシンや別々の時間に、別々の呼び出しになる可能性があります。LangGraphのMemorySaver とthread_id は、起動間の状態を永続化することで、これを可能にします。レジューム動作を明確に見るには、スクリプトを一度実行し、停止させ、同じthread_id 。2回目の実行では、1回目の実行が終わったところから再開される。
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.
結論
AIエージェントは、永続的なメモリと適切な検証が欠けているため、長時間実行するタスクで失敗する。Clawdbotはこれらの問題を解決することで人気を博したが、そのアプローチは実運用に耐えうるものではない。
この記事では、3つの解決策を取り上げた:
2エージェント・アーキテクチャ:イニシャライザーがプロジェクトを検証可能な機能に分割し、コーディング・エージェントがきれいなハンドオフで一度に1つずつ作業する。これにより、コンテキストの枯渇を防ぎ、進捗を追跡可能にする。
ベクターデータベースによるセマンティック記憶: Milvusは進捗記録とgitコミットをエンベッディングとして保存するので、エージェントはキーワードではなく、意味によって検索することができる。セッション50はセッション1が学んだことを記憶する。
本当の検証のためのブラウザ自動化:ユニットテストはコードが動くかどうかを検証する。Puppeteerは、ユーザが画面上で何を見るかをテストすることで、機能が実際に動くかどうかをチェックする。
これらのパターンはソフトウェア開発に限ったことではない。科学研究、財務モデリング、法的文書のレビューなど、複数のセッションにまたがり、信頼性の高いハンドオフを必要とするあらゆるタスクが恩恵を受けることができる。
核となる原則
イニシャライザーを使って、作業を検証可能な塊に分割する。
構造化された機械可読形式で進捗を追跡する。
ベクターデータベースに経験を保存し、セマンティックな検索を可能にする。
ユニットテストだけでなく、実世界のテストで完了を検証する。
作業を安全に一時停止・再開できるように、セッションの境界をきれいに設計する。
ツールは存在する。パターンは証明されている。あとはそれを適用するだけだ。
始める準備はできていますか?
エージェントにセマンティックメモリを追加するためのMilvusと Milvus Liteをご覧ください。
セッションの状態を管理するLangGraphをチェック
ロングランエージェントハーネスに関するAnthropicの全研究を読む
質問がある場合、または構築しているものを共有したい場合は、MilvusのSlackコミュニティに参加してください。
MilvusのSlackコミュニティに参加して、他の開発者と交流しましょう。
Milvusオフィスアワーに参加し、チームとライブでQ&Aを行う。
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word



