如何养活百万级 Agent?

假设老板丢给你一个任务:给一百万个用户,每人配一个专属 AI agent——一个能记住他、陪着他、随时被叫醒干活的智能体。

你心想:这不就是 new Agent() 一百万次吗?

然后你坐下来,准备认真设计。还没写第一行代码,三个问题就把你问住了。

三个把你问住的问题

第一个问题:一个用户,可以有几个 agent?

别急着说「一个」。更要紧的是反过来问:你的架构允许一个用户挂多个 agent 吗

因为很多时候,一个用户最好不是一个全能 agent,而是一个小团队——一个主 agent 做总协调,底下几个子 agent 各管一摊(查资料的、出题的、批改的),主 agent 把活拆下去、再把结果收回来。这就是 agent 编排

要支持这个,你得先能精确地按名字找到每一个 agent(「张三的主 agent」「张三的批改 agent」),它们之间还要能互相调用。如果 agent 在你的设计里是个含糊的东西——一会儿是进程、一会儿是会话——那「一个还是多个」「怎么协同」都无从谈起,后面的状态、记忆、计费也全跟着歪。

第二个问题:1 万个 agent,10 个 pod,能摊匀吗?

假设此刻有 1 万个 agent 同时活跃,你有 10 个 pod 扛着。它们能均匀地一个 pod 一千个、不偏不倚地分布吗?

更棘手的是:万一一个 pod 挂了,里面那 1000 个 agent 也跟着没了——它们恢复得快吗?还有一种情况更难发现:同一个用户的 agent,会不会因为调度抖动,同时被两个 pod 持有,各自改各自的状态,最后打架?(分布式里这叫「脑裂」,是最难写对的一类 bug。)

第三个问题:那一整天,它在干嘛?

这是最反直觉的一个。一个 agent 第一次被叫醒,干了 3 分钟活;然后用户走了,隔了整整 24 小时,第二天再回来,它又干了 6 分钟。

真正烧算力的,只有那加起来 9 分钟。那中间的一整天呢?它在干嘛?

如果它是一个常驻进程,一直挂在那儿不退出——那就是一百万个进程同时占着内存、连接和调度槽。这一天的服务器账单,你付得起吗?


三个问题,三个方向:身份与编排、并发、成本。看起来要分头解决。

但当你把答案都想透,会发现它们指向同一句话

三个问题,其实是同一个答案

先从最反直觉的第三个问题说起。

那一整天,agent 到底在干嘛?答案是:它根本不在。

我们脑子里默认有一个「agent loop」——一个读输入、想、调工具、回答的循环。我们想当然地以为,那一天它就是这个循环睡着了,挂起在那儿等下一句话。

不是的。真相是:

1
2
3
4
第一次激活:  一轮 loop 在跑(读状态 → 推理 → 调工具 → 回答)
答完那一刻: 这一轮结束,没有在途工作,loop 消失
中间那一天: 没有 loop、没有进程、没有内存,只剩存储里一行状态
24 小时后: 一个「全新」的 loop 被拉起,加载那行状态,接着跑

那一天的零成本,不是「省」出来的,是「无」。压根没有东西在跑。loop 不是一根睡了一天的线程,而是「跑完一轮就蒸发、下次重新拉起」的东西。每一轮回答结束,都是一个天然干净的存档点——没有在途工作,状态落盘,agent 从内存里消失。

反面教材是 while(true){ 等输入… } 这种写法:它会真的阻塞一整天,占着内存和连接。一百万个这样的循环同时挂着,机器根本扛不住。

想通了这个,第二个问题(pod 挂了怎么办)也松动了。

既然 agent 的「真相」是存储里那行状态,而不是某个 pod 里的内存对象——那么一个 pod 被杀,丢掉的只是 1000 份「临时工作副本」,不是 1000 个 agent。恢复,无非是换一个 pod,把那 1000 行状态重新加载进来。快不快,取决于加载一行状态有多快,而不是抢救一个进程有多难。

至于「会不会被两个 pod 同时持有」——这就是那个最难写对的保证,叫单活(同一时刻,一个 agent 只能归一个 pod 管)。好消息是:有一类专门的运行时,立身之本就是免费帮你保证这一条,外加用一致性哈希把 agent 均匀摊到所有 pod 上。这个我们下一节就会见到。

最后回到第一个问题。一个用户几个 agent?答案是「随你」——但前提是你把 agent 当成一个可按名寻址的逻辑身份,而不是一台机器、一个进程。「张三的主 agent」是一个逻辑名字(按 ID 寻址),今天它在 3 号 pod,明天在 7 号 pod,后天不在任何 pod,但始终是同一个。

一旦每个 agent 都是这样一个可寻址的实体,「一个用户挂一个 agent」还是「挂一个 agent 团队」就只是多开几个实体的区别;而 agent 之间的编排——主 agent 调子 agent、再把结果收回来——本质上就是一个实体按名字调用另一个实体,也就是经典的「actor 调 actor」。换句话说,能不能做多 agent 编排,不取决于某个花哨的框架,而取决于你有没有把 agent 设计成可寻址的状态实体

三个问题,绕了一圈,收敛成一句话:

Agent 是状态,不是进程。

「一个用户一个 agent」是一个逻辑身份,不是一百万个常驻进程。内存里那个对象只是「临时把状态捞出来跑一下」的壳,用完即弃;壳可以生灭、可以换 pod、可以崩溃重建,而 agent 始终是同一个——因为 agent 的本体,是存储里那行状态。

想清楚这一点,「养活一百万个 agent」就从「养一百万个进程」(不可能)变成了「存一百万行状态 + 维持一个小小的、按当前在线人数伸缩的 pod 池」(很现实)。

这个池子有多大?取决于并发,不是总量。一百万是总人口,真正同时在线的可能只有几万。几万个活跃 agent 摊到十几个 pod 上,剩下九十几万只是存储里安静躺着的状态行。

那这个「身体」跑在什么上面?

道理讲通了,工程上总得有个东西来真正提供「身份寻址 + 单活 + 激活休眠 + 状态加载」这套能力。这套东西,我叫它 agent 的「身体」。

身体怎么来?两条路:直接买一个现成的 agent 运行时,还是拿一个通用的有状态运行时,自己搭一个

路线一:买现成的 agent 运行时

代表是云厂商的托管 agent 运行时(比如 AWS Bedrock AgentCore 这类产品)。

它的模型是:每个活跃会话,平台帮你起一个轻量的隔离沙箱(microVM),会话结束就回收;跨会话的长期记忆交给平台托管的 memory 组件。你只交「agent 代码 + 配置」,pod、控制面、单活、伸缩,统统不用你管。

flowchart TB
  U["User app / web"]
  CF["Edge: Cloudflare or CloudFront<br/>(CDN / WAF / DNS)"]
  subgraph AWS["AWS · Bedrock AgentCore (fully managed)"]
    subgraph RT["AgentCore Runtime"]
      direction LR
      A1["session microVM 1 (user-123)<br/>agent = Docker image (OCI, push to ECR)<br/>any language; Python easiest via SDK<br/>agent loop / prompt / call LLM + tools<br/>isolated Firecracker microVM per session, up to 8h"]
      A2["session microVM 2"]
      A3["session microVM N"]
    end
    MEM["AgentCore Memory<br/>cross-session long-term profile + short-term"]
    GW["AgentCore Gateway / Identity<br/>APIs as tools + secure access"]
    DS["Existing services (profile / content)<br/>same region, no cross-cloud"]
    LLMGW["LLM gateway<br/>key / rate-limit / tiering / cost / guardrails / trace"]
    LLM["Bedrock: LLM brain"]
  end
  U --> CF --> RT
  RT <-->|persist / recall| MEM
  RT --> GW --> DS
  RT --> LLMGW --> LLM

好处:零运维,开箱即用,伸缩和隔离都是平台兜底。代价:你被这家平台锁住,控制力弱,单元(一个 microVM)比「百万轻量对象」要重,而且这类产品大多很新,百万级的生产验证还不多。

一句话:省心,但把方向盘交了出去。

路线二:拿通用有状态运行时,自己搭

如果你想攥住方向盘——掌控数据、不被锁定、用自己现有的技术栈——那就自己搭。这里有三个性格迥异的选项。

选项 A:Virtual Actor(以 Dapr 为代表)

这是最贴近常规 k8s 栈的选项。它的核心是一个 placement(放置)服务:用一致性哈希把 agent ID 均匀摊到所有 pod,并保证全局单活。前面那个「1 万 agent 摊 10 个 pod + 防脑裂」的问题,它白送

flowchart TB
  U["User app / web"]
  CF["Cloudflare edge<br/>CDN / WAF / DNS"]
  subgraph AWS["AWS · EKS (self-hosted)"]
    R["Router / Gateway"]
    subgraph DCP["Control plane (ns: dapr-system)"]
      PL["dapr-placement<br/>consistent hashing + single-activation"]
      OIS["operator + injector + sentry<br/>cluster-wide, separate pods"]
    end
    subgraph POOL["Worker pool — one Deployment, many pods"]
      direction LR
      subgraph P1["app Pod 1 (2 containers)"]
        APP1["app container = YOU write (business)<br/>actor objects live here (in-mem)<br/>agent loop / prompt / call LLM + tools"]
        DAPR1["daprd sidecar = Dapr (plumbing)<br/>routing / single-activation / state R-W / hibernate"]
        APP1 <-->|localhost| DAPR1
      end
      P2["app Pod 2<br/>app + daprd"]
      P3["app Pod N<br/>app + daprd"]
    end
    DS["Existing services (profile / content)<br/>reuse; agent is consumer"]
    LLMGW["LLM gateway<br/>key / rate-limit / tiering / cost / guardrails / trace"]
    TP["Temporal (optional)<br/>durable long-flows: wait-days / wait-human / saga"]
  end
  HOT["ElastiCache: hot state"]
  COLD["DynamoDB: durable truth"]
  LLM["Bedrock: LLM brain"]
  U --> CF --> R --> POOL
  OIS -.->|inject daprd| POOL
  PL -->|"hash(actorId) to pod, even"| P1
  PL --> P2
  PL --> P3
  POOL --> HOT
  POOL --> COLD
  POOL --> DS
  POOL --> LLMGW --> LLM
  POOL -.->|only if long tasks| TP

图里有两块要分开看:一组集群级共享的控制面 poddapr-placement 负责一致性哈希 + 单活,operator / injector / sentry 管注入和证书),和一个装着很多 app pod 的工作池。注意每个 app pod 里其实是两个容器——你写的业务容器(actor 对象就活在它的内存里),加一个叫 daprdsidecar,替你打理寻址、单活、状态读写、休眠。状态热的放 ElastiCache、冷的落 DynamoDB;pod 池按在线人数自动伸缩,深夜可以缩得很小。用你现有的语言写,概念负担最小——这通常是默认选择。

选项 B:一个对象就是一个 agent(Cloudflare Durable Objects)

这套东西最初不是为 AI 造的——它本来是给无状态的边缘函数补一个「有状态的协调点」,用来做聊天室、协同文档、游戏房间。但你会发现,「聊天室 / 游戏会话 / 一个 agent」结构上是同一种东西:长生命周期、有状态、单一协调点、多个事件汇入。所以它天生适合当 agent 的身体。

flowchart TB
  U["User app / web"]
  subgraph CFP["Cloudflare platform (fully managed)"]
    EDGE["Edge: CDN / WAF / DNS + routing"]
    WK["Worker (stateless)<br/>auth + route to the right DO"]
    subgraph DOS["Durable Objects (CF-managed)"]
      direction LR
      DO1["DO 1 = UserAgent (one per user-id)<br/>agent logic — TS (Rust via WASM / Python beta)<br/>agent loop / prompt / call LLM + tools<br/>state in co-located SQLite<br/>single-activation + hibernate = CF-managed"]
      D2["DO 2"]
      D3["DO N"]
    end
    WF["Cloudflare Workflows (optional)<br/>durable long-flows"]
  end
  subgraph AWS["AWS — your systems of record"]
    DS["Existing services (profile / content)"]
    COLD["DynamoDB: central user data"]
  end
  LLMGW["LLM gateway (CF AI Gateway or self-built)<br/>key / rate-limit / tiering / cost / guardrails / trace"]
  LLM["LLM brain: Workers AI (limited) or Bedrock"]
  U --> EDGE --> WK --> DOS
  DOS -.->|only if long tasks| WF
  DOS -->|cross-cloud boundary| DS
  DOS -->|cross-cloud boundary| COLD
  DOS --> LLMGW --> LLM

一个对象 = 一个 agent,按用户 ID 寻址,状态就贴在对象自带的 SQLite 里(不用外接 Redis/数据库),单活、休眠全是平台内部搞定、对你隐形。

注意:它虽然也免运维(你不跑 pod),但它给你的是一个通用的有状态对象——agent 的逻辑(怎么想、怎么调工具、教学策略)还是你自己写。这也是它和「买现成 agent 运行时」的区别:那个连大脑都帮你框好了,这个只给你一副好身体。代价是:开发语言基本锁定在 TypeScript,调你自己的后端数据要跨网络,数据驻留也得单独评估。

选项 C:WASM(以 Golem 为代表)

最激进的一个。WASM 实例可以做到微秒级唤醒、单机塞几万到几十万个、闲置时几乎不占内存——是「密度之王」,专为「一百万个偶尔活跃的实体」这种场景设计。Golem 在 WASM 之上又加了 durable execution:agent 的状态就是它的执行日志(oplog),闲置时驱逐,下次按日志重放恢复。

flowchart TB
  U["User app / web"]
  CF["Cloudflare edge<br/>CDN / WAF / DNS"]
  subgraph AWS["AWS · EKS (self-hosted)"]
    R["Router / Gateway"]
    subgraph GCP["Golem control plane (you install)"]
      SM["golem-shard-manager<br/>shard worker-id to executor (= placement)"]
      CS["component-service + worker-service<br/>WASM component store + invoke API"]
    end
    subgraph POOL["worker-executor pool (many pods)"]
      direction LR
      E1["executor Pod 1<br/>WASM workers = agents (Rust / TinyGo, compiled to WASM)<br/>durable: state in oplog<br/>idle evict / replay to resume<br/>agent loop / prompt / call LLM + tools"]
      E2["executor Pod 2"]
      E3["executor Pod N"]
    end
    DS["Existing services (profile / content)<br/>reuse"]
    LLMGW["LLM gateway<br/>key / rate-limit / tiering / cost / guardrails / trace"]
  end
  OPLOG["Golem oplog store (Redis + Postgres + blob)<br/>state + durable long-flows BUILT-IN"]
  COLD["DynamoDB: central user data (optional)"]
  LLM["Bedrock: LLM brain"]
  U --> CF --> R --> POOL
  SM -->|"shard worker-id, even"| E1
  SM --> E2
  SM --> E3
  POOL --> OPLOG
  POOL --> DS
  POOL --> LLMGW --> LLM
  POOL -.optional.-> COLD

听起来很美,但代价也实在:你得用 Rust 或受限的 TinyGo 写(编译进 WASM 沙箱),还要适应它一整套独立于 k8s 的编排,AI 相关的库塞不进 WASM。而且——它最大的杀手锏「密度」,你大概率用不上:你的并发只有几万,agent 闲置那天本来就已经不占资源了,几万级别的密度,前面两个选项早就够了。

横着看一眼

身体在哪 谁运维 状态存哪 语言
买现成(AgentCore) 云厂商托管 fleet 平台(零运维) 托管 memory 容器,任意语言
Dapr 你的 k8s 集群 Redis + 数据库 你现有的栈
Cloudflare DO 边缘平台 fleet 平台(零运维) 同驻 SQLite TypeScript
WASM / Golem 你的集群(叠一层) 你(+ 平行平台) oplog 内建 Rust / WASM

没有绝对的最佳,只有「你的约束下的最佳」。要掌控、用现有栈、最成熟 → Dapr;想彻底免运维、能接受锁定 → 买现成;想要极致密度、又啃得动 Rust → WASM;想要一个对象一个 agent 的优雅、又能接受 TS → DO。

痛点是相通的

兜了一大圈,你可能会说:这都是「一百万个学生 agent」这种极端场景才会遇到的麻烦吧?

恰恰相反。把场景换成一百万个客服 agent、一百万个游戏 NPC、一百万个个人助理——你撞上的,是同样的五堵墙:身份怎么定、并发怎么不打架、闲置怎么不烧钱、崩了怎么恢复、账单怎么扛得住。

更有意思的是:这五堵墙,没有一堵是 AI 带来的。

聊天室、游戏会话、电信连接、微服务实体——这些「长生命周期、有状态、偶尔活跃」的东西,几十年前就存在了,解决它们的运行时也早就成熟了。AI agent 不过是往这些老身体里,加了一句「调一下大模型」。所以你会看到,做边缘平台的顺手在有状态对象上盖了个 agent SDK,做微服务运行时的顺手做了个 agent 框架——因为它们手里那副「身体」,本来就是 agent 要的那副。

所以,当你纠结「怎么设计一个百万级 agent」时,真正在设计的,并不是 AI。

你在设计的,是一个会休眠的、有状态的实体——而这个问题,比 AI 老了几十年。