为什么 Clawdbot 会成为病毒--以及如何使用 LangGraph 和 Milvus 构建生产就绪的长期运行 Agents
Clawdbot(现更名为 OpenClaw)病毒式传播
Clawdbot 现更名为 OpenClaw,上周在互联网上掀起了一场风暴。由彼得-斯坦伯格(Peter Steinberger)开发的这款开源人工智能助手在短短几天内就在GitHub上获得了110,000+ 个星级。用户发布的视频显示,它能自动为他们办理登机手续、管理电子邮件和控制智能家居设备。OpenAI 的创始工程师 Andrej Karpathy 对它大加赞赏。科技公司创始人兼投资人大卫-萨克斯(David Sacks)在推特上对它大加赞赏。人们称它为 "真实的 Jarvis"。
然后是安全警告。
研究人员发现了数百个暴露的管理面板。机器人默认以 root 访问权限运行没有沙盒。提示注入漏洞会让攻击者劫持 Agents。安全噩梦
Clawdbot 病毒传播的原因
Clawdbot 的流行是有原因的。它可在本地或自己的服务器上运行。它可以连接到人们已经在使用的消息应用--WhatsApp、Slack、Telegram 和 iMessage。它能长期记忆上下文,而不是在每次回复后忘记所有内容。它能管理日历、汇总电子邮件,并自动执行跨应用程序的任务。
用户会感觉到它是一个不需要干预、始终在线的个人人工智能,而不仅仅是一个提示和响应工具。它的开源、自托管模型吸引了希望获得控制权和定制化服务的开发者。而且它易于与现有工作流程集成,便于分享和推荐。
构建长期运行 Agents 的两大挑战
Clawdbot 的流行证明,人们需要的是能 采取行动的人工智能,而不仅仅是回答问题。但是,任何能长期运行并完成实际任务的 Agents--无论是 Clawdbot 还是你自己构建的--都必须解决两个技术难题:内存和验证。
记忆问题有多种表现形式:
代理会在任务中期耗尽上下文窗口,留下半成品
他们忽略了完整的任务列表,过早宣布 "完成"。
他们无法在会话之间移交上下文,因此每个新会话都要从头开始
所有这些问题的根源都在于:Agent 没有持久内存。上下文窗口是有限的,跨会话检索也是有限的,而且 Agents 无法跟踪进度。
验证问题则不同。即使在内存有效的情况下,Agent 仍会在快速单元测试后将任务标记为已完成,而不检查该功能是否真的能端到端运行。
Clawdbot 解决了这两个问题。Clawdbot 跨会话本地存储内存,并使用模块化 "技能 "来自动处理浏览器、文件和外部服务。这种方法行之有效。但它并不适合生产。对于企业级应用,你需要结构、可审计性和安全性,而 Clawdbot 并不能提供这些。
本文将介绍与生产就绪解决方案相同的问题。
在记忆方面,我们采用了基于Anthropic 研究的 双 Agents 架构:一个初始化代理将项目分解为可验证的功能,另一个编码代理以干净利落的交接方式逐一完成项目。为了实现跨会话语义调用,我们使用了Milvus,这是一个向量数据库,可让 Agents 通过意义而非关键字进行搜索。
在验证方面,我们使用浏览器自动化。Agents 不信任单元测试,而是以真实用户的方式测试功能。
我们将简要介绍这些概念,然后展示使用LangGraph和 Milvus 实现的工作。
双 Agents 架构如何防止上下文耗尽
每个 LLM 都有一个上下文窗口:这是它一次能处理多少文本的限制。当 Agents 执行一项复杂任务时,这个窗口就会被代码、错误信息、对话历史和文档填满。一旦窗口填满,Agent 要么停止工作,要么开始遗忘之前的上下文。对于长期运行的任务来说,这是不可避免的。
考虑给 Agents 一个简单的提示:"构建一个 claude.ai 的克隆"。该项目需要身份验证、聊天界面、对话历史、流式响应以及其他几十种功能。单个 Agents 会尝试同时解决所有问题。在实现聊天界面的中途,上下文窗口被填满。会话结束时,代码只写了一半,没有关于尝试内容的文档,也不知道哪些可行,哪些不可行。下一次会话继承的是一团糟。即使进行了上下文压缩,新的 Agents 也不得不猜测前一个会话在做什么,调试它没有编写的代码,并找出继续运行的位置。在取得任何新进展之前,都要浪费好几个小时。
双重代理解决方案
Anthropic 的解决方案在他们的工程帖子"长期运行 Agents 的有效线束 "中有所描述,即使用两种不同的提示模式:第一会话使用初始化提示,后续会话使用编码提示。
从技术上讲,这两种模式使用相同的底层代理、系统提示、工具和线束。唯一不同的是初始用户提示。但由于它们的作用不同,将它们视为两个独立的 Agents 是一个有用的心理模型。我们称之为双 Agents 架构。
初始化器为渐进式进展设置环境。它接受一个模糊的请求,并做三件事:
将项目分解为具体的、可验证的功能。不是像 "制作一个聊天界面 "这样模糊的要求,而是具体的、可测试的步骤:"用户点击新建聊天按钮→侧边栏出现新对话→聊天区显示欢迎状态"。Anthropic的claude.ai克隆示例有200多个这样的功能。
创建进度跟踪文件。该文件记录了每个功能的完成状态,因此任何会话都能看到已完成和剩余的功能。
编写设置脚本并进行初始 git 提交。
init.sh等脚本能让未来的会话快速启动开发环境。git 提交会建立一个干净的基线。
初始化程序不仅仅是规划。它创建的基础架构能让未来的会话立即开始工作。
编码代理处理每个后续会话。它
读取进度文件和 git 日志,了解当前状态
运行基本的端到端测试,以确认应用程序仍在运行
选择一项功能
执行该功能,对其进行全面测试,提交到 git 并附上说明性信息,更新进度文件
会话结束时,代码库处于可合并状态:无重大错误、代码有序、文档清晰。没有半成品,也没有神秘的工作内容。下一次会话将从这次会话结束的地方继续进行。
使用 JSON 进行特性跟踪,而不是 Markdown
有一个实施细节值得注意:功能列表应该是 JSON 格式,而不是 Markdown 格式。
在编辑 JSON 时,人工智能模型往往会对特定字段进行外科手术式的修改。而在编辑 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 如何为代理提供跨会话语义记忆
双 Agents 架构解决了上下文枯竭问题,但并没有解决遗忘问题。即使会话之间的交接干净利落,Agents 也会丢失它所学到的知识。除非进度文件中出现 "JWT 刷新令牌 "和 "用户身份验证 "这两个准确的单词,否则它就记不起来了。随着项目的增长,在数以百计的 git 提交中搜索会变得很慢。关键字匹配会遗漏一些对人类来说显而易见的联系。
这就是向量数据库的用武之地。向量数据库不是存储文本和搜索关键字,而是将文本转换为表示意义的数字。当你搜索 "用户身份验证 "时,它会找到关于 "JWT 刷新令牌 "和 "登录会话处理 "的条目。这并不是因为单词匹配,而是因为这些概念在语义上非常接近。Agents 可以询问 "我以前见过类似的东西吗?"并得到有用的答案。
在实际操作中,这可以通过将进度记录和 git 提交作为向量嵌入数据库来实现。当编码会话开始时,Agent 会以当前任务查询数据库。数据库会以毫秒为单位返回相关历史记录:之前尝试过什么、什么成功了、什么失败了。Agents 并非从零开始。它从上下文开始。
Milvus 非常适合这种用例。它是开源的,专为生产规模的向量搜索而设计,处理数十亿向量不费吹灰之力。对于小型项目或本地开发,Milvus Lite可以直接嵌入到 SQLite 等应用程序中。无需集群设置。当项目发展壮大时,你可以迁移到分布式 Milvus,而无需更改代码。对于生成 Embeddings,你可以使用像SentenceTransformer这样的外部模型进行精细控制,也可以参考这些内置的嵌入函数进行更简单的设置。Milvus 还支持混合搜索,将向量相似性与传统过滤相结合,因此你只需一次调用就能查询 "查找上周类似的身份验证问题"。
这也解决了转移问题。向量数据库在任何一次会话之外都会持续存在,因此知识会随着时间的推移不断积累。会话 50 可以访问会话 1 到会话 49 中学习到的所有知识。该项目形成了机构记忆。
通过自动测试验证完成情况
即使有了双 Agents 架构和长期记忆,Agents 仍有可能过早宣布胜利。这就是验证问题。
下面是一种常见的失败模式:一个编码会话完成了一个功能,运行了一个快速单元测试,看到它通过了,就把"passes": false 转到"passes": true 。但是,单元测试通过并不意味着该功能真的能运行。应用程序接口可能会返回正确的数据,而用户界面却因为 CSS bug 而无法显示任何内容。进度文件显示 "完成",而用户却什么也看不到。
解决方案是让 Agents 像真实用户一样进行测试。功能列表中的每个功能都有具体的验证步骤:"用户点击新建聊天按钮 → 新对话出现在侧边栏 → 聊天区显示欢迎状态"。Agents 应按字面意思验证这些步骤。它不能只运行代码级测试,而是要使用 Puppeteer 等浏览器自动化工具来模拟实际使用情况。它会打开页面、点击按钮、填写表格,并检查屏幕上是否出现了正确的元素。只有当整个流程通过后,Agent 才会标记功能完成。
这样就能发现单元测试遗漏的问题。聊天功能可能有完美的后台逻辑和正确的 API 响应。但如果前端没有呈现回复,用户就什么也看不到。浏览器自动化可以对结果进行截图,并验证屏幕上显示的内容是否与应该显示的内容一致。只有当该功能真正实现端到端工作时,passes 字段才会变成true 。
不过,这也有局限性。Puppeteer 等工具无法自动执行某些浏览器原生功能。文件选择器和系统确认对话框就是常见的例子。Anthropic 指出,依赖于浏览器原生警报模态的功能往往漏洞较多,因为代理无法通过 Puppeteer 看到它们。实际的解决方法是围绕这些限制进行设计。尽可能使用自定义 UI 组件而不是本地对话框,这样代理就能测试功能列表中的每个验证步骤。
组合在一起:会话状态的 LangGraph 和长期记忆的 Milvus
上述概念通过两个工具整合到一个工作系统中:用于会话状态的 LangGraph 和用于长期记忆的 Milvus。LangGraph 管理单个会话中正在发生的事情:正在处理哪个功能、完成了什么、下一步是什么。Milvus 可存储跨会话的可搜索历史记录:之前做了什么、遇到了什么问题、哪些解决方案有效。它们共同为 Agents 提供了短期和长期记忆。
关于实现方法的说明:下面的代码是一个简化的演示。它在一个脚本中展示了核心模式,但并没有完全复制前面描述的会话分离。在生产设置中,每个编码会话都将是一个单独的调用,可能在不同的机器上或不同的时间进行。LangGraph 中的MemorySaver 和thread_id 通过在调用之间持久化状态来实现这一点。要清楚地看到恢复行为,可以运行一次脚本,然后停止,再以相同的thread_id 运行一次。第二次运行将从第一次中断的地方继续。
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.
结论
人工智能 Agents 无法完成长期运行的任务,是因为它们缺乏持久内存和适当的验证。Clawdbot 通过解决这些问题而走红,但它的方法并不适合生产。
本文介绍了三种可用于生产的解决方案:
双 Agents 架构:一个初始化器将项目分解为可验证的功能;一个编码 Agents 一次完成一个功能,交接干净利落。这样既能防止上下文耗尽,又能跟踪进度。
用于语义记忆的向量数据库: Milvus将进度记录和 git 提交作为 Embeddings 储存起来,因此 Agents 可以通过意义而非关键字进行搜索。会话 50 会记住会话 1 所学到的内容。
浏览器自动化实现真正的验证:单元测试验证代码是否运行。Puppeteer 通过测试用户在屏幕上看到的内容来检查功能是否真正运行。
这些模式并不局限于软件开发。科学研究、金融模型、法律文件审查--任何跨越多个会话、需要可靠交接的任务都能从中受益。
核心原则
使用初始化器将工作分解成可验证的小块
以结构化、机器可读的格式跟踪进度
将经验存储在向量数据库中,以便进行语义检索
通过实际测试而不仅仅是单元测试来验证完成情况
设计清晰的会话边界,以便安全地暂停和恢复工作
工具已经存在。模式是经过验证的。剩下的就是应用它们了。
准备好开始了吗?
探索Milvus和Milvus Lite,为您的 Agents 添加语义记忆
查看用于管理会话状态的 LangGraph
有问题或想分享您的构建成果?
加入Milvus Slack 社区,与其他开发人员交流。
参加Milvus 办公时间,与团队进行现场问答
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word



