搭了个AI公司:OpenClaw+Vercel+Supabase
如果你最近在玩 AI Agent,大概率已经把 OpenClaw 跑起来了。Claude 能用工具、能浏览网页、能跑定时任务,你给它派 cron job,它每天准时”上班”。
但两周前我发现一个尴尬的事:我的 5 个 Agent 能聊天、能出报告、能写内容,但系统本身还是我在运营。Agent 产出的东西堆在输出层,我得手动去捞、手动去发、手动标记”做完了”。
这不是”自主运营”,这是”Agent 干活,我当监工”。
先说结论。花了 2 周把 AgentForge 从”能产出”改成”能闭环”。核心不是加功能,是补上 Proposal → Mission → Step → Event → Reaction → 新 Proposal 这个循环。
下面讲我踩的坑和最终的架构取舍——有些地方和主流做法不一样,但跑了一个月确实更稳。
01 闭环到底是什么
很多人觉得”Agent 能调工具”就是闭环了。不是。
我定义的闭环是这个循环必须永远跑下去:
Agent 提一个想法 (Proposal) ↓ 被批准变成任务 (Mission) ↓ 拆解成具体步骤 (Step) ↓ Worker 认领并执行 ↓ 执行完产生事件 (Event) ↓ 事件触发新想法 → 回到起点
这个环永远在转。这就是闭环。
关键不是每一步都能跑,关键是每一步都能自动触发下一步。任何一个环节断了,系统就退回”Agent 干活,人当监工”。
整个系统的骨架就是 4 张表:
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
为什么是 4 张表而不是 1 张?因为职责不同。混在一起你没法做配额检查——你不知道哪些是已批准的、哪些还在排队。
闭环不是功能,是分水岭。能跑和能自己跑下去,差一个完整的反馈回路。
02 坑一:执行面只能有一个
这个坑最蠢,但也最容易被忽略。
我的 VPS 上有 OpenClaw worker 在认领任务。同时 Vercel 上有个 heartbeat cron 也在跑 mission-worker,也在试图认领同一个 step。两边查同一张表,抢同一个任务,各自独立执行——纯竞态条件。
偶尔一个 step 会被两边同时打上冲突的状态标签。日志看不出问题,因为两边都”成功”了,但数据库里的状态是脏的。
修复:砍掉 Vercel 的执行能力。VPS 是唯一执行面,Vercel 只做轻量控制——评估触发器、处理反应队列、清理卡死任务。
// Heartbeat 只做 6 件事,绝不碰执行 1. evaluateTriggers() — 检查触发规则 2. processReactionQueue() — 处理反应队列 3. processInitiatives() — 处理主动提案 4. promoteInsights() — 晋升洞察为永久记忆 5. recoverStaleSteps() — 恢复卡死步骤 6. dailyBriefing() — 每日简报
附带好处:省了 Vercel Pro 的钱。Heartbeat 不用 Vercel cron 了,VPS 上一行 crontab 每 5 分钟调一次:
*/5 * * * * curl -s -H "Authorization: Bearer $CRON_SECRET" https://your-domain.com/api/ops/heartbeat
每一步独立 try-catch——一个挂了不影响其他的。这个坑的教训:执行面双写不是双保险,是竞态条件。
03 坑二:提案入口不统一,触发器变孤儿
我写了触发器:推文火了自动分析、任务失败自动诊断、内容发布自动评审。
测试时发现一个诡异的现象:触发器正确检测到条件了,也创建了提案,但提案一直挂在 pending,永远没变成任务。
翻代码发现:触发器直接往 proposals 表插数据,但正常审批流程是:插入提案 → 评估自动审批 → 如果通过则创建任务+步骤。触发器跳过了后两步。
这种 bug 最阴的地方在于:日志没有报错。提案创建成功了,触发器以为自己完成了任务,但下游永远没人接。
修复:抽一个统一的提案入口管道 proposal-service.ts。不管提案从哪来——Agent 主动提的、触发器产生的、反应矩阵产生的——全部走同一个函数:
// lib/ops/proposal-service.ts — 整个系统的枢纽 1. 检查这个 Agent 今天是否已超提案上限 2. 检查 Cap Gate(配额满了?→ 立即拒绝,不创建任何 Step) 3. 插入提案记录 4. 评估是否自动过审(低风险任务自动通过) 5. 如果通过 → 创建 Mission + Steps 6. 写入事件(前端能看到)
以后任何检查逻辑(限流、黑名单、新的 cap),改这一个文件就够。这个坑的教训:所有创建提案的路径必须走同一个入口,否则触发器创建的提案会变成孤儿,永远没人执行,而且日志不会告诉你。
触发器只负责”检测条件 + 返回提案模板”,不负责创建提案。创建走统一入口,所有 cap gate 和审批逻辑自动生效。
04 坑三:配额满了还在塞,队列悄悄堆积
这个 bug 最阴。表面看一切正常——日志没报错,任务在跑,Agent 在干活。但数据库里 queued step 越堆越多。
原因:推文配额满了。但提案还在被审批通过,还在生成任务,还在生成 queued step。VPS worker 看到配额满了直接跳过——不认领、不标记失败、不通知任何人。第二天又来一批,第三天又来一批。
什么是 Cap Gate?想象你的公司有条规定——每天最多发 8 条推文。如果你不在”提交请求”这一步检查配额,请求照样被批准、任务照样排队、执行器检查发现”今天已经发了 8 条”然后跳过——但任务还在队列里堆着。等你发现时已经堆了一堆。
修复:Cap Gates——在提案入口就拦住,别让它生成 queued step:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
核心原则:在入口处拦截,不要让任务堆在队列里。被拒绝的提案要留记录(审计追踪),不能静默丢弃。
在 gate 拒掉,别在队列里堆。被拒要有记录,不能默默丢掉——否则就是定时炸弹。
05 Worker 怎么认领:CAS 原子认领
3 个坑修完,闭环能跑了。但还有个底层问题:多个 Worker 并发时,怎么保证一个 step 只被一个 Worker 认领?
主流方案是上消息队列(Redis/RabbitMQ)。但我选了更轻的:PostgreSQL 自己的行级锁 + CAS(Compare-And-Swap)。
// Worker 循环 while (true) { 1. 检查自己是否被启用(ops_policy 控制) 2. 从队列取一个 status='queued' 的 Step 3. 原子认领(CAS): UPDATE SET status='running', reserved_at=now() WHERE status='queued' — 失败者拿到零行,继续找下一个 4. 路由到对应的 Executor 执行 5. 标记成功或失败 6. 如果 Mission 所有 Step 完成 → 终结 Mission 7. sleep → 继续循环 }
原子认领听起来复杂,但就是一个数据库技巧。想象两个人同时伸手拿最后一个甜甜圈——数据库确保只有一只手拿到。不需要单独的锁服务,PostgreSQL 原生处理。
不需要 Redis,不需要 RabbitMQ。每多一个中间件,就多一个故障点、多一个运维成本。AgentForge 的 VPS 月成本 $8,能不引入新依赖就不引入。
06 让系统活过来:反应矩阵
闭环能跑了,但系统只是个”不出错的流水线”,不是”有反应的团队”。缺的是自发互动。
触发器从条件产生工作。但 Agent 之间呢?Agent A 做了件事,Agent B 怎么决定要不要响应?
这就是反应矩阵——定义 Agent 之间”谁做了什么,谁可能跟着做什么”的规则系统。规则存在 ops_policy 表里:
{ "patterns": [ { "source": "scout", // 谁做的事?"*"=任何人 "tags": ["crawl"], // 事件 tags 包含什么 "target": "quill", // 谁来响应 "type": "draft_tweet", // 响应的 step_kind "probability": 0.3, // 30% 概率响应 "cooldown": 120 // 2 小时冷却 }, { "source": "*", "tags": ["mission_failed"], "target": "nexus", "type": "diagnose", "probability": 1.0, // 100% 响应 "cooldown": 60 } ] }
读法:每当 Scout 的事件包含 crawl 标签,Quill 有 30% 概率被分配一个起草推文的任务,2 小时内不重复。任何任务失败 → 100% Nexus 会诊断。
probability 不是 bug,是 feature。100% 确定性 = 机器人。加点随机性 = 更像真实团队——”有时候有人响应,有时候没有”。
看一个完整的闭环例子,从触发到完成 6 分钟,成本约 $0.002:
09:00 心跳触发 proactive_scan 规则 → proposal-service 创建提案:"Scout 扫描 Hacker News" 09:01 提案自动过审(crawl 是低风险 step_kind) → 创建 Mission + 1 个 Step (kind=crawl, status=queued) 09:02 VPS Worker 原子认领这个 Step → 路由到 crawl 执行器 → 爬取 Hacker News,写入事件 (tags: ['crawl', 'scout']) → 标记 Step 完成 09:02 Worker 检查反应矩阵 → 事件 tags 匹配规则:source=scout, tags=["crawl"] → 掷骰子:30% 概率 → 通过 → 写入反应队列:target=quill, type=draft_tweet 09:05 下一次心跳消费反应队列 → proposal-service 给 Quill 创建提案 → 自动过审 → 创建 Mission + Step 09:06 Worker 认领 Quill 的 Step → 路由到 draft_tweet 执行器 → LLM 生成推文草稿 结果:一条推文草稿准备好了,全程无人介入。
probability 不是不确定性,是拟人化。100% 确定的反应是机器人,有概率的反应才像团队。
07 自愈:系统一定会卡住
VPS 重启、网络抖动、API 超时——step 会卡在 running 状态,没人真的在处理。这种”幽灵 step”不处理,整个系统会慢慢僵死。
heartbeat 里的 recoverStaleSteps 解决这个问题:
// 30 分钟没进展 → 标记失败 → 检查任务是否应该完结 const STALE_THRESHOLD_MS = 30 * 60 * 1000; // 找出所有卡死的 running step const { data: stale } = await sb .from('ops_mission_steps') .select('id, mission_id') .eq('status', 'running') .lt('reserved_at', staleThreshold); // 逐个标记失败,然后检查 Mission 是否应该完结 for (const step of stale) { await sb.from('ops_mission_steps').update({ status: 'failed', last_error: 'Stale: no progress for 30 minutes', }).eq('id', step.id); await maybeFinalizeMissionIfDone(sb, step.mission_id); }
maybeFinalizeMissionIfDone 的逻辑很重要:任何一个 step 失败则整个 Mission 失败,全部完成才算成功。再也不会出现”一个 step 成功了整个任务就被标记成功”的鬼情况。
还有熔断器:Worker 连续失败 3 次 → 自动禁用自己 → 触发 Telegram 告警。你会在面板上看到它坏了,而不是一周后才发现。
08 完整架构:三平面分离
最终架构是三层,职责严格分离:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
关键原则:每个平面只做自己擅长的事,不侵入其他平面。Vercel 不执行任务(避免坑一),Supabase 不写业务逻辑(保持纯净),VPS 不做审批(避免越权)。
策略全部存在 ops_policy 表里,key-value JSON 结构。凌晨 3 点系统出问题?把 enabled 改成 false 就行,不用重新部署。
09 和主流方案的差异
写到这有人会问:这和 LangChain / CrewAI / AutoGen 有什么区别?
核心差异:它们解决”编排”,我解决”状态”。
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LangChain 适合”给一个任务,跑完返回结果”的单次场景。但 AgentForge 是 5 个 Agent 持续运行,有状态演化、有并发冲突、有配额耗尽、有故障恢复。这些是状态管理问题,不是编排问题。
不是 LangChain 不好,是场景不对。框架不是越全越好,是越合身越好。
多 Agent 的瓶颈不在编排,在状态管理。框架解决”怎么跑一次”,状态机解决”怎么跑下去”。
10 最后
这 5 个 Agent 现在每天自主运营 AgentForge。我还在每天优化——调策略、扩触发器、改 Agent 协作方式。
远谈不上完美。Agent 间的协作还基础,”自由意志”主要靠概率性非确定性模拟。但系统真的在跑,真的不需要有人盯着。
技术栈:Next.js + Supabase + VPS。月成本:$8 固定 + LLM 用量。没有 OpenAI Assistants API,没有 LangChain,没有 AutoGPT。就是 PostgreSQL + 几个 Node.js Worker + 一个规则引擎。
你不需要一上来就 5 个 Agent。从 3 个开始——一个协调者、一个执行者、一个观察者——就能跑通完整闭环。
Agent 系统的价值不在”能跑”,在”能自己跑下去”。闭环不是功能,是分水岭。
你在搭 Agent 系统吗?踩过类似的坑没?评论区聊聊,每条我都看。
星标一下,下一篇发 Roundtable 实录——5 个 Agent 怎么吵起来的,含完整对话和 Challenger 硬约束代码。
夜雨聆风