从远程执行到 Agent 工作基座:BaseAgent 的设计与演进

从远程执行到 Agent 工作基座:BaseAgent 的设计与演进

最初做这个项目,原因很简单。

电脑放在家里或办公室,人已经离开了,路上突然想到一个修改。我希望能从手机上把任务交给电脑里的 Claude Code,过一会儿再回来看看:它做到哪一步,改了什么,测试有没有通过。

远程桌面当然也能解决一部分问题。但远程桌面解决的是“操作另一块屏幕”,我想解决的是另一件事:把一项工作交给另一台设备上的 Agent,并拿到可以追踪、可以恢复的结果。

BaseAgent 就从这里开始。

第一版只做一条链路

我们没有从多 Agent 编排、知识图谱或者工作流引擎开始。第一版只想跑通一条尽可能短的链路:

手机 / Web
    → Core API
    → PostgreSQL 任务队列
    → 本机 Host Agent
    → Claude Code
    → 结果回到原来的对话

用户在项目对话里发出任务,服务端创建一次 AgentRun。运行在开发电脑上的 Host Agent 领取任务,在绑定的 workspace 中启动真实的编码工具。执行产生的文件变更、命令结果、日志和最终状态,再回到对话里的任务卡片。

这里有一个从一开始就确定的边界:BaseAgent 不再实现一个“会写代码的 Agent”。Claude Code 和 Codex 已经在做这件事。我们更关心它们外面的那一层:任务从哪里来,在哪台设备执行,属于哪个项目,谁批准了操作,最后留下了什么证据。

所以,项目最早的核心对象并不多:Project、Conversation、AgentRun、Host 和 Memory。后端也没有为了将来可能出现的规模提前拆微服务。目前是一套 Go API、一个独立的 Host Agent,再加 PostgreSQL。数据库既保存业务状态,也通过 FOR UPDATE SKIP LOCKED 完成任务领取。

这套方案谈不上新鲜,但部署简单,失败时也容易顺着数据库记录排查。真有一天它撑不住,再换专用消息系统也不迟。

难点很快从“调用模型”转移了

第一次真实任务跑通以后,问题没有消失,只是换了一批。

网络重试会不会把同一项任务执行两次?手机断线以后,页面怎么接着收状态?Host 执行到一半掉线,任务要不要重新派发?任务已经取消了,迟到的成功回调能不能把它重新改成成功?

这些问题和模型能力几乎没有关系,却决定了系统能不能长期使用。

我们给客户端请求增加了幂等键,让重试复用原来的执行;给会话事件增加单调序号,让 SSE 重连从上次位置继续;把数据库作为状态事实源,页面刷新后重新读取持久化的任务卡,而不是把浏览器内存当作真相。Host 领取任务后有租约,但只有持有任务的 Host 才能回写状态;已经进入成功、失败或取消终态的任务,也不能再被后续事件覆盖。

这段演进让我们逐渐意识到:一个 Agent 产品是否可靠,通常不取决于它能否生成一段更漂亮的回答,而取决于一次真实工作在异常情况下还能不能说清楚发生了什么。

对话只是入口,结果才是产品

刚开始很容易把 BaseAgent 理解成“手机上的 AI 聊天框”。真正跑过一些开发任务后,我们发现用户最关心的是另外几个问题:

  • 任务开始了吗?
  • 现在卡在哪里?
  • 改了哪些文件?
  • 测试跑过没有?
  • 为什么失败?
  • 刷新以后还能不能找到结果?

因此,后面的重点从生成文本转向了结构化结果。Provider 的差异被收敛在一个很小的接口后面:

type AgentProvider interface {
	ID() string
	Start(ctx context.Context, in Input) (<-chan Event, error)
	Cancel(ctx context.Context, runID string) error
}

Claude Code、Codex 或测试用的 fake provider 都把输出转换成统一事件:文件变化、命令验证、摘要、审批请求和终态。Host 再把这些事件整理成 changed_filesverificationlogs_tail 和 summary,存进 agent_runs.summary_json

这里最重要的不是 JSON 长什么样,而是“完成”开始有证据。Agent 说自己完成了并不算完成;文件变化、验证命令和任务终态能够对应起来,才算完成。

取消也是同样的道理。页面上的状态变成 cancelled 不够,Host 上的子进程也必须结束。状态管理听上去没有多 Agent 协作那么吸引人,但它比再接一个模型更接近产品本身。

Host 为什么要和 Core 分开

BaseAgent 的 Core API 保存项目、对话、任务和记忆,但不直接进入用户的代码目录。真正接触 workspace 和本地 CLI 的,是运行在开发电脑上的 Host Agent。

这样做首先是为了现实。用户已经在电脑上配置好了 Git、语言工具链、Claude Code 或 Codex,没有必要把整个开发环境搬到服务器。模型凭证也可以继续留在本机,Core 只负责身份、队列和状态。

它也给后续的多设备留下了自然路径。家里的 Mac、公司的开发机和云上的 Linux 主机,可以有不同的 workspace 和用途。项目回答“这件事属于哪里”,Host 回答“它应该在哪里执行”,Provider 回答“由哪个编码工具完成”。

不过,当前实现仍然只是单用户 MVP。路径守卫、提示词分级和危险命令检测也不是强沙箱,特别是 Bash 事件更多是事后记录和熔断。它适合个人可信设备、可信仓库和有 Git 兜底的环境,不适合把不可信任务直接扔进包含生产密钥的目录。

如果个人版继续往前走,执行隔离会比复杂编排更优先。

记忆不是把所有文本扔进向量库

BaseAgent 已经有文档检索、pgvector 语义召回和项目 Memory Pack,但我们一直避免让系统自动记住一切。

信息多不等于记忆好。临时方案、错误结论和过期约束一旦被长期召回,往往比没有记忆更麻烦。当前的流程因此比较保守:Agent 提出记忆建议,用户确认以后,它才成为正式的项目记忆,并同步到 workspace 中,让后续编码 Agent 能够读到。

这道确认门有些慢,却保住了记忆的基本质量。按需通过 MCP 召回和写回仍然只是一份待验证方案,我们没有因为它听起来更先进,就提前塞进主链路。

如果未来进入企业场景,这条原则会更重要。企业记忆不能只是一段文本和一个 embedding。它至少需要来源、产生时间、适用范围、确认人、权限和有效状态。模型曾经说过一句话,不代表它已经成为组织事实。

从一个人的多项目,到组织里的工作轨迹

我给项目取名 BaseAgent,不是因为它现在已经是一个完整平台,而是希望它能成为后续 Agent 开发的基石。

第一条发展路径是个人使用:一个人管理多台设备、多个项目和不同的 Agent。代码、写作、研究和个人自动化各自有项目上下文,又能共享经过确认的个人知识。用户不必记住每个 Agent 上次做到哪里,也不必在不同终端之间手工搬运背景信息。

另一条路径是企业使用。企业里的工作散落在项目、聊天、会议、邮件、文档和代码仓库里。一项决策可能在会议中提出,在聊天里确认,随后成为项目任务,由人或 Agent 执行,最后在另一个系统里发布。过几个月再追问“当时为什么这样做”,经常只能依靠参与者的记忆。

我们希望 BaseAgent 最终能够连接这样一条工作轨迹:

会议 → 决策 → 行动项 → 人或 Agent 执行
     → 文件与验证结果 → 项目时间线 → 有来源的组织记忆

会议也不应该只是上传一份转写,让模型生成摘要。原始材料、参会人和项目上下文需要保留;提取出的决策、行动项和风险需要确认;后续执行产生的变更和验证,还要重新关联回来。

“所有事情有迹可查”依赖的不是更长的聊天历史,而是来源、权限、事件和证据。当前项目里的运行事件和审计日志只是这个方向的起点,还不是企业级事件溯源或合规审计系统。

企业版也不会是个人版加一个 team_id。组织身份、项目归属、资源权限、记忆可见范围、审计保留、模型配额和执行隔离,都需要重新经过多租户边界检查。在个人版的核心链路没有被频繁使用之前,提前做完这些,很容易得到一套结构完整却没有真实工作发生的平台。

“Base” 想表达什么

回头看,BaseAgent 一直在从一个具体问题向外生长:先完成一次远程开发任务,再考虑一个人的多设备、多项目,最后才是企业里的会议、任务、执行和记忆。

三层规模不同,底层问题却很接近:

  • 这件事属于哪个项目?
  • 谁发起、谁批准、谁执行?
  • 使用了哪些上下文?
  • 产生了什么变化?
  • 有什么证据说明它完成了?
  • 哪些信息值得进入长期记忆?
  • 以后能不能还原当时发生的事情?

所以,我们更愿意把 BaseAgent 看作 Agent 工作的控制面和记录层。Agent 可以更换,模型可以升级,设备可以增加,但项目上下文、执行过程、责任边界和结果证据应该持续存在。

它离这个目标还很远。多设备统一管理、企业权限、会议模型、结构化工作项和组织级记忆都没有完成。Codex provider 目前也主要完成了 hermetic 测试,真实模型链仍需要继续验收。现在真正稳定的,仍然只是个人远程开发这一条垂直链路。

但这条链路已经让我们确认了一件事:Agent 进入真实工作以后,最有价值的不只是它能写多少代码,而是它是否可控制、可恢复、可验证、可追溯。

这就是 “Base” 这个名字想表达的东西。