去网上搜一下“async agents(异步代理)”这个词,你会看到几十篇文章在用这个说法。那些少数愿意给出定义的文章,谈的似乎也都不是同一件事。它出现在产品发布里、HN 讨论串里、架构博客里,总是被很随意地提起,好像含义不言自明。所以我想搞清楚:到底哪一种定义才算“正确”。
我看到最常见的定义是:异步代理就是“会运行一段时间的代理”。一个长时间运行的任务会让人感觉“异步”。如果它能在你去上厕所或去续一杯水的时候继续干活,那就很像异步。确实,任务跑得足够久,你就有机会去做别的事。但那到底要跑多久才算?一个一秒就完成的代理和一个跑一小时的代理,前者是同步、后者才是异步吗?我能不能在运行代理之前加一个 sleep 语句,就把它变成异步代理?这听起来并不令人满意。
我反复遇到的第二种定义是:异步代理是“跑在云端的代理”。Devin 是远程运行的,所以它是异步。Claude Code 是本地运行的,所以它不是。但如果我开一台 EC2,SSH 进去,装上 Claude Code,然后在那台机器上运行——我是不是就把它“变成”异步代理了?工具本身没变,只是运行它的机器变了。这不可能是核心。
第三种定义则是:异步代理是“事件驱动”的——不是由你手动触发的那种。一条 PR 合并了,代理就启动。一个 cron 定时任务触发了,代理就到 Slack 给你发消息。这种感觉确实很异步,因为在那一刻不是你在敲命令。但 cron 也可以启动任何程序。触发是异步的,并不意味着被触发的程序本身就是异步。这是调度(scheduling),不是代理自身的属性。
这些定义都不能说“完全错”,但它们都不像定义。它们只是在描述一些特征——这些特征恰好与人们想指代的东西相关联。那我们回到最基本的概念。
什么是代理?什么是异步?
正如 Simon Willison 所说,代理就是一个“在循环里调用工具的 LLM”。我会更进一步说:代理还需要“上下文的连续性”。如果我启动两个不同的 Claude Code 会话,那就是两个不同的代理。如果我在某个 Claude Code 实例里输入 /clear,我就杀死了一个代理并生成了一个新的代理。让它成为“那个代理”的,是上下文。
接下来是异步。技术上的定义是:事件会独立于主程序流程发生。关键洞见在于:一个 async 函数本身并不会“自带异步魔法”。它是否异步,取决于它相对于调用方(caller)的位置。调用方如果不等待,它就独立运行;调用方如果等待,它就等同于同步。异步的部分不在函数本身,而在调用方选择“不阻塞(not block)”的决定。
想想在代理出现之前,写代码是什么样:你在敲字;工作在发生。你停下敲字;工作就停。一次只能推进一个任务,一切都同步。
当编码代理出现后,它解锁了新东西:异步工作成为可能。你可以把工作委派出去,并不等待它完成。你可以给代理一个任务,去做午饭,然后回来看到一条完成的 PR。第一次,你的工作可以在你做别的事的时候继续推进。
但这并不意味着代理“天生异步”。你也可以转过头用同一个代理问一个很快的问题,然后等待它回答。在那一刻,你是同步地使用它。代理没有变,变的是你的行为。
所以大家总在说的“异步代理”到底是什么?它其实在观察者的眼里。真正的异步代理,其实是我们一路上交到的朋友。
不过认真说,没有任何代理是天生异步的。这取决于你等不等它做完。
异步地使用代理
如果你选择异步地使用代理,你会解锁一个强大的能力:并发(concurrency)。你可以把多个任务委派给多个代理,让它们同时推进。但一旦这么做,你会撞上一个现实问题:它们都在你机器上的同一份代码库里工作。这不是真实工程团队的工作方式。每个工程师都有自己的电脑、自己的代码副本、自己的分支。
那为什么不让大家像 Google Docs 那样实时编辑同一份代码库呢?看起来似乎能消灭一整类问题:合并冲突、相互覆盖的修改、分支分叉……基本上就是 Git 逼你处理的那一堆事。协作式编码以前有人试过,而且没流行起来,是有原因的(Replit 早期就是这么做的)。问题在于:没有隔离,一个人的半成品就可能把另一个人的“可工作代码”弄坏。你需要让每个任务都活在它自己的环境里,这样它才能独立地构建、测试、验证。
代理也会遇到同样的问题。它们需要各自的代码库副本来推进,而不会踩到彼此。这就是为什么像 Conductor 和 Omnara 这样的工具依赖 git worktrees;为什么 Boris(Claude Code 的作者)会直接克隆整份目录;为什么像 Codex Web 或 Sculptor 这样的应用会用隔离的虚拟机或本地容器。每个代理都拿到自己的工作区,这样某个代理的进展就不会把另一个代理的工作搞崩。
有了隔离的工作区之后,你就成了经理。是你在生成代理、盯着代理、协调代理。把工作委派给代理是一个巨大的“杠杆放大器”。它会显著提高产出,但也会显著增加你要管理和切换的上下文数量。这里存在一个上限。
这就引出了一个问题:代理能不能管理代理?
那么“异步代理”到底应该是什么意思?
说真的,我们大概不该用这个词。但如果你非要用,它就应该指向某个并非“所有代理都天然具备”的东西。而编程领域里有一个天然类比,正好给出正确的区分。
在软件里,async 函数与“管理它的运行时(runtime)”是两回事。async 函数可以在不阻塞调用方的情况下运行。而 async 运行时是那个管理事件循环(event loop)的东西:它生成函数、调度它们、并协调它们的结果。函数只是被带着走。做编排的是运行时。
代理完全可以一一对应。今天的每个代理都像一个 async 函数:你调用它,它开始运行,你去做别的事。但在更“有意义”的定义里,异步代理指的是运行时——一个能管理“其他代理”的事件循环的代理。
你给它一个任务;它在后台工作区里启动一个子代理,同时仍然继续跟你互动。你让它做一个登录页。“收到。”它把任务委派给后台另一个代理。你再让它做一个计费页。“当然可以——顺便说一下,登录页那个代理想确认你更喜欢 GitHub OAuth 还是 Google OAuth。”你不需要切标签页,也不需要管理十个会话。代理在管理团队。你只跟一个实体对话,而这个实体在编排其他所有实体。
你不再是开发者。你也不再是经理。你只是那个表达意图的人,而代理会把其余的事情想办法搞定。
这种模型已经有一些早期尝试。Claude 的 Agent Teams 和 Gastown 是早期例子。Cognition 的《Don’t Build Multi-Agents》那篇文章解释了为什么今天这很难:上下文共享、token 成本、代理之间推理不可靠。但这些是工程约束,不是架构约束。随着模型变得更便宜、上下文窗口扩大、代理与代理的通信能力提升,这种架构会从“实验性”变成“默认选项”。
在编程里,我们区分 async runtime 与普通程序,是因为 runtime 管理并发执行:它生成函数、调度它们、协调结果,并处理让这一切成为可能的“管道工作”:事件循环、回调、共享状态。顺序程序则不会。
同样的区分也适用于代理。一个能生成子代理、管理它们的执行、在它们之间共享上下文、并协调它们结果的代理,在架构上就不同于一个只会自己埋头做完任务的代理。
所以,如果你要把某种东西叫作“异步代理”,就叫这种。不是“跑很久的代理”。不是“跑在云端的代理”。不是“被 cron 触发的代理”。而是那种能并发地管理其他代理的代理。其他所有说法,都只是你恰好选择“不等它做完”的普通代理而已。