如一 Agent 架构解读(一):数字分身的执行模型——Run、Turn 与四个时间尺度

「如一」是我们做的一个面向 C 端的云端数字分身:它在云端 7×24 运行,有自己的浏览器、文件系统和记忆,能替你持续完成任务。这个系列逐层解读如一的真实架构------不是 Demo 级玩具,而是一个多租户生产系统。开篇先讲最核心的执行模型:一个 Agent 到底"活"在什么结构上。

为什么大多数 Agent 只是 Demo

先看几个 Agent 项目的经典死法:

  • 进程崩一次,会话上下文全丢,用户只能从头再来;
  • 模型追问"你确认吗?",然后整个线程挂在那里等用户回复,一等就是三天;
  • 模型被限流,重试逻辑无限循环,账单爆炸;
  • 上下文越来越长,直到某一天 prompt 超出窗口,整个会话永久报废。

这些都不是模型能力问题------换个更强的模型,一个都解决不了。它们是架构问题:把 Agent 当成了一次函数调用,而它实际上是一个持续运行系统。

如一从第一天就是奔着"长期在线的数字分身"去设计的,所以这些问题必须在架构层回答。这篇讲清楚我们的答案:四个时间尺度、一条决策与执行的分工线,以及几个反直觉但被生产验证过的设计决策。

先给定义:如一眼中的 Agent

Agent 不是"带工具的聊天模型",也不是一条从 Prompt 到 Answer 的请求链。如一的定义是:

一个由消息驱动、以持久事实为连续性基础、由模型进行开放决策、由运行时保证确定性执行,并能持续对外部世界产生可验证结果的智能运行系统。

五个要素,缺一不可:

  • 消息驱动:外部世界以统一消息进入。Agent 不直接依赖具体渠道------WebSocket、IM 协议、摄像头驱动,都是接入层的事。
  • 事实连续:会话历史、工具结果、交付状态进入权威存储。进程内对象不是长期记忆。
  • 开放决策:目标理解、路线选择、内容判断交给模型。
  • 确定执行:状态迁移、权限、事务、幂等、并发、恢复由程序保证。
  • 真实结果:输出不只是文本,还可以是文件、应用、卡片,以及对外部系统的真实操作。

三个平面:Input / Runtime / Output

graph TB WORLD[&#34;外部世界<br/>用户 · 设备 · 系统事件 · 定时任务&#34;] subgraph INPUT[&#34;Input Plane&#34;] IN[&#34;身份归一 · 鉴权 · 多模态接入<br/>幂等 · 排队 · 信箱&#34;] end subgraph RUNTIME[&#34;Agent Runtime Plane&#34;] RUN[&#34;会话调度 → Start Run → 恢复事实<br/>→ AgentLoop(Turn × N)→ End Run&#34;] end subgraph OUTPUT[&#34;Output Plane&#34;] MSG[&#34;Message<br/>沟通与反馈&#34;] ART[&#34;Artifact<br/>可复用成果&#34;] CMD[&#34;Command<br/>改变外部世界&#34;] end WORLD --> INPUT INPUT -- &#34;RuntimeCommand&#34; --> RUNTIME RUNTIME -- &#34;RuntimeEvent&#34; --> OUTPUT MSG --> WORLD ART --> WORLD CMD --> WORLD

三个平面各有一条容易被忽视的设计原则。

Input Plane 的核心是"信箱",它不是 UI 比喻,是并发模型。 外部消息可以随时到达;同一会话的主执行必须串行消费;进程故障后未完成的输入必须能被重新认领。Runtime 不应该知道手机号、Cookie、WebSocket 或某个 IM 的报文结构------它只处理通道中立的命令和消息。

Output Plane 的核心是"交付不等于回答"。 如一的输出至少有三类:Message(解释、确认、进度、最终文本)、Artifact(文件、报告、图片、音视频、可交互应用)、Command(对文件、浏览器、外部系统的真实操作)。三者都可以是中间结果或最终交付。是否展示、何时展示、用什么媒介展示,取决于用户目标,而不是"某个工具刚刚返回了文件"。

Runtime Plane 是决策与执行内核,也是本文剩下部分要拆的东西。

把三个平面展开,如一的完整架构长这样(实线是命令、执行与交付的主路径,虚线是发现、读取、持久化等事实关系):

graph TB WORLD[&#34;外部世界<br/>用户 · 设备 · 系统 · 时间&#34;] subgraph CHANNELS[&#34;交互与接入&#34;] direction LR WEB[&#34;Web / App / IM&#34;] MEDIA[&#34;语音 · 图像 · 视频&#34;] SENSOR[&#34;传感器 · 系统事件&#34;] TIMER[&#34;定时任务&#34;] end subgraph GATEWAY[&#34;Input / Output Plane · Gateway&#34;] direction LR INGRESS[&#34;身份与协议边界<br/>鉴权 · 归一 · 附件接入&#34;] MAILBOX[&#34;可靠消息信箱<br/>幂等 · 排队 · 同会话串行&#34;] PROJECTION[&#34;消息投影与连接<br/>历史恢复 · WebSocket&#34;] end subgraph RUNTIME[&#34;Agent Runtime Plane&#34;] direction TB SCHEDULER[&#34;会话调度<br/>认领 · 租约 · 追加消息&#34;] subgraph RUN[&#34;Agent Run · 有限执行边界&#34;] direction TB START[&#34;Start Run<br/>固定身份 · Profile · Deadline&#34;] RESTORE[&#34;Restore Facts<br/>Transcript · Compaction · Memory&#34;] subgraph LOOP[&#34;AgentLoop&#34;] direction TB GUARD[&#34;Guard<br/>Run 边界守护&#34;] BEGIN[&#34;Begin Turn<br/>turnNo + 1&#34;] OBSERVE[&#34;Observe<br/>排干消息&#34;] CONTEXT[&#34;Build Context<br/>按当前决策投影&#34;] ATTEMPT[&#34;Model Attempt<br/>attemptNo + 1 · 模型开放决策&#34;] ACT[&#34;Act<br/>工具批 / 领域用例&#34;] COMMIT[&#34;Commit Assistant<br/>提交模型事实&#34;] DECISION_ROUTE{&#34;Route Decision&#34;} TOOL_COMMIT[&#34;Commit Tools<br/>提交观察事实&#34;] FINISH_TURN[&#34;Finish Turn<br/>唯一执行终态&#34;] ROUTE{&#34;Continue Run?&#34;} GUARD --> BEGIN --> OBSERVE --> CONTEXT --> ATTEMPT ATTEMPT -- &#34;failed / retry<br/>同一 Turn&#34; --> OBSERVE ATTEMPT -- &#34;valid response&#34; --> COMMIT COMMIT --> DECISION_ROUTE DECISION_ROUTE -- &#34;recover / continuation<br/>同一 Turn&#34; --> OBSERVE DECISION_ROUTE -- &#34;tools&#34; --> ACT --> TOOL_COMMIT --> FINISH_TURN DECISION_ROUTE -- &#34;stop / abort&#34; --> FINISH_TURN FINISH_TURN --> ROUTE ROUTE -- &#34;next Turn&#34; --> GUARD end FINISH[&#34;End Run<br/>Completed · Failed · Aborted&#34;] START --> RESTORE --> GUARD ROUTE -- &#34;end Run&#34; --> FINISH end EVENTS[&#34;RuntimeEvent<br/>过程反馈 · 权威交付 · 终态&#34;] SCHEDULER --> START FINISH --> EVENTS COMMIT -. &#34;模型增量事件&#34; .-> EVENTS TOOL_COMMIT -. &#34;工具事件&#34; .-> EVENTS end subgraph CAPABILITIES[&#34;Capability & Execution Plane&#34;] direction LR SKILL[&#34;Skill<br/>领域选路&#34;] TOOL[&#34;Tool / Harness<br/>确定性能力&#34;] USECASE[&#34;Application Use Case<br/>事务与一致性&#34;] SANDBOX[&#34;Sandbox / Browser<br/>隔离 I/O&#34;] EXTERNAL[&#34;外部服务&#34;] end subgraph FACTS[&#34;Authoritative Fact Plane&#34;] direction LR MYSQL[&#34;MySQL<br/>Transcript · Run · Memory · Task&#34;] REDIS[&#34;Redis<br/>RunSlots · Queue · Stream&#34;] OBJECTS[&#34;OSS / Workspace<br/>附件 · Artifact&#34;] end subgraph DELIVERY[&#34;Delivery&#34;] direction LR MESSAGE[&#34;Message<br/>沟通与反馈&#34;] ARTIFACT[&#34;Artifact<br/>可复用成果&#34;] COMMAND[&#34;Command<br/>改变外部世界&#34;] end WORLD --> CHANNELS WEB --> INGRESS MEDIA --> INGRESS SENSOR --> INGRESS TIMER --> MAILBOX INGRESS --> MAILBOX MAILBOX -- &#34;RuntimeCommand&#34; --> SCHEDULER ATTEMPT -. &#34;发现并选择&#34; .-> SKILL ACT --> TOOL --> USECASE TOOL --> SANDBOX USECASE --> EXTERNAL SANDBOX --> EXTERNAL TOOL -- &#34;观察结果&#34; --> TOOL_COMMIT USECASE -- &#34;业务结果&#34; --> TOOL_COMMIT MAILBOX -.-> REDIS RESTORE -. &#34;读取&#34; .-> MYSQL COMMIT -. &#34;追加事实&#34; .-> MYSQL TOOL_COMMIT -. &#34;追加事实&#34; .-> MYSQL SCHEDULER -.-> REDIS EVENTS -- &#34;Stream / PubSub&#34; --> REDIS SANDBOX -.-> OBJECTS REDIS --> PROJECTION EVENTS --> PROJECTION PROJECTION --> MESSAGE PROJECTION --> ARTIFACT ACT --> COMMAND MESSAGE --> WORLD ARTIFACT --> WORLD COMMAND --> WORLD

这张图的密度很高,值得放大看的部分后面都会逐一讲到。本文聚焦 Runtime Plane 内部,也就是 Run 和 Turn 那一块。

四个时间尺度:Session / Run / Turn / Model Attempt

Agent 系统最容易混淆的概念就是这四个,它们不是同一层东西:

尺度 定义 结束条件 连续性靠什么
Session 用户与 Agent 的长期会话空间 产品生命周期决定 transcript、会话投影
Run 一次被消息触发、连续推进到终态的执行 completed / failed / aborted 持久事实 + runId
Turn Run 内一次"观察---决策---行动---提交"的节拍 继续下一 Turn 或终结 Run 本轮提交的 assistant/tool 事实
Model Attempt Turn 内真正发起的一次模型调用 合法返回、失败或取消 逐次调用事实记录

这个区分带来几个直接结论:

  • 一个 Session 包含多个 Run。用户回答 Agent 的提问,是启动一个新 Run,不是恢复一条阻塞的线程。
  • 一个 Run 通常包含多个 Turn------模型调用工具后需要看到结果再继续判断。
  • 一个 Turn 包含一到多个 Model Attempt。恢复重试、协议重建、超长续写,只增加 Attempt,不增加 Turn。
  • Model Attempt 是推理事实和计费事实,不是 Agent 任务的生命周期。

坦白说,最后一层区分是如一的最近一次线上重构里长出来的。早期实现里,模型被限流重试一次,前端的"第 N 步"就 +1,用户眼睁睁看着分身"走了 20 步"其实一件事还没做完;运行统计里 turn 数和模型调用数混在一起,排障时根本对不上。重构之后:Turn 只统计认知---行动节拍,Attempt 只统计真正进入模型的调用,恢复绝不虚增 Turn。

Run 内部的结构展开是这样:

graph TB START[&#34;Start Run<br/>固定身份 · Profile · Deadline&#34;] --> RESTORE[&#34;Restore Facts<br/>从持久存储恢复历史&#34;] RESTORE --> GUARD[&#34;Guard<br/>abort / deadline / 上限守护&#34;] GUARD --> BEGIN[&#34;Begin Turn&#34;] BEGIN --> OBSERVE[&#34;Observe<br/>排干排队消息&#34;] OBSERVE --> CONTEXT[&#34;Build Context<br/>为当前决策构造投影&#34;] CONTEXT --> ATTEMPT[&#34;Model Attempt<br/>模型开放决策&#34;] ATTEMPT -- &#34;失败 / 限流 / 截断<br/>恢复后仍在同一 Turn&#34; --> OBSERVE ATTEMPT -- &#34;合法响应&#34; --> COMMIT[&#34;Commit<br/>提交模型事实&#34;] COMMIT --> ROUTE{&#34;Route&#34;} ROUTE -- &#34;执行工具&#34; --> ACT[&#34;Act<br/>工具批执行&#34;] ACT --> TOOLCOMMIT[&#34;Commit Tools<br/>提交观察事实&#34;] TOOLCOMMIT --> FINISH[&#34;Finish Turn<br/>唯一执行终态&#34;] ROUTE -- &#34;停止 / 终止&#34; --> FINISH FINISH --> NEXT{&#34;继续?&#34;} NEXT -- &#34;下一 Turn&#34; --> GUARD NEXT -- &#34;结束&#34; --> END[&#34;End Run<br/>Completed · Failed · Aborted&#34;]

注意这张图刻意把模型放在 Turn 的"Decide"位置,而不是系统中心。系统的中心是从消息到事实、再从事实到行动的闭环,模型只是这个闭环里最擅长开放判断的那个零件。

一条分工线:决策自由与执行确定

Agent 系统设计的关键,不是尽可能多地把控制交给模型,而是把不同性质的决策放到正确的一侧。

模型负责开放决策:理解用户真正想完成什么;判断事实是否充分;选择能力、顺序和探索路径;根据工具观察调整路线;判断内容、审美和交付媒介;决定继续行动、向用户询问还是结束。

平台负责确定机制:消息幂等与同会话串行;参数与协议校验;权限、隔离、资源和 deadline;事务、幂等与副作用边界;超时、取消、重试预算和崩溃恢复。

一句话概括:

模型拥有路线选择权,平台拥有事实裁决权和执行约束权。

很多 Agent 项目的问题正是这条线画错了:让模型用 prompt 约束去"保证"幂等、用自然语言去"维护"事务、用系统提示词去"防止"路径越权。这些都是机械可验证的机制,交给一个概率模型去守护,出事只是时间问题。反过来,把路线选择写死成固定工作流的,也不叫 Agent,叫 RPA。

五个反直觉的设计决策

以下每一条都和直觉相反,也都在如一的生产环境里真实运行着。

1. Ask = 工程上结束 Run

分身需要用户补充信息或授权时怎么办?直觉答案是"挂起线程等回复"。我们的答案是:提交问题,发出事件,然后正常结束当前 Run。用户回复作为新消息启动新 Run,从持久历史里重新规划。

复制代码
当前 Run:提交问题 → 发出 QUESTION_ASKED → COMPLETED
用户回复:新 RuntimeCommand → 新 Run → 从 transcript 继续

系统里不存在 SUSPENDED 状态,没有挂起线程,没有等待句柄。分身可以 7×24 在线,靠的不是一个永不退出的 while(true),而是持久事实 + 新消息的再次驱动。

2. 恢复不是新 Turn

模型被限流、流式响应超时、prompt 超长、输出被截断------这些都在同一个 Turn 内恢复,而不是"进入下一轮"。每个已开启的 Turn 必须且只能闭合一次(继续 / 完成 / 中止 / 失败);每次真正的模型调用必须落入 completed / failed / cancelled 之一。

好处不止是统计口径干净:Turn 上限这种安全护栏不会被瞬态故障消耗掉,排障时"第 3 轮第 2 次尝试"也能精确定位到单次模型调用的耗时和用量。

3. 连续性来自提交点,不来自"模型想过什么"

分身能不能从崩溃中正确恢复,取决于哪些事实已经提交,而不是模型生成过什么。所以提交纪律是硬不变量:

  • 非法或残缺的工具调用不能提交,也不能执行;
  • assistant 的工具调用一旦提交,每个调用必须有且只有一个结果;
  • 用户取消导致未执行的工具,要补稳定的 cancelled 结果,防止历史形成残链;
  • 中断的模型输出,只保留可安全回放的正文和完整工具调用。

每个 Turn 都必须把"模型在下一次恢复时能合法理解的事实链"作为提交单位,而不是只追求实时输出看起来连续。

4. 历史 ≠ 上下文

Transcript 是已发生事实的权威记录;Context 是为了当前 Turn 的决策、从历史和其他信息中临时构造的投影。每个 Turn 可以组合:未被摘要覆盖的历史、压缩摘要、本轮刚到的用户消息、system prompt、记忆与技能提醒、当前附件。

两条推论:上下文压缩改变的是模型看到的决策界面,绝不篡改已经发生的历史;system prompt 这类"每轮计算的运行环境"不写进历史,因为它不是会话中发生的消息。

判断某段信息该不该进上下文,只需要问一个问题:

它会改变当前 Turn 的哪一个决策?

答不上来的,就不该常驻上下文。

5. 失败是追加事实,不是抹除历史

Run 异常终止时,除了向用户投影错误,如一还会向历史追加一条经过裁剪的 failure observation。下一次 Run 能知道"上一次任务没有完整完成",同时继续信任此前已经提交的消息、工具结果和交付物。

失败不是把历史回滚到"没发生过",而是向历史增加一个新的现实事实。

几条可靠性不变量

最后留几条我们认为比任何具体实现都更稳定的不变量,供对照你自己的系统:

  1. 同一会话最多一个活跃主 Run,追加消息可靠入队。
  2. 每个 Run 都能从持久存储重建,不依赖旧进程的任何对象。
  3. assistant 工具调用与工具结果必须合法配对。
  4. 已提交事实不会因后续失败被撤回;失败本身追加为新事实。
  5. 恢复必须有原因、有预算、有终点;达到上限必须明确终止,不许静默消失。
  6. 外部可见事件从单一出口产生,最终结果可由权威链路恢复。
  7. 进程内缓存、连接、注册表只能加速当前执行,不能成为可恢复业务状态。

结尾

回到开头的定义,把它串成一句话:

如一是一个以消息为刺激、以 Run 为执行边界、以 Turn 为认知节拍、以持久事实维持连续性、以模型作开放决策、以平台完成确定执行,并最终通过 Message、Artifact 和 Command 改变用户世界的智能运行系统。

这个系列后面还计划写三篇:上下文工程 (transcript / 压缩 / 记忆 / 临时投影的边界)、多 Agent 委派 (子 Agent 与后台 Worker 的生命周期和血缘)、可靠性设计(幂等、outbox、崩溃恢复的完整链路)。有兴趣可以关注,也欢迎评论区交流你踩过的坑。

相关推荐
styshoo1 小时前
NVIDIA Dynamo Snapshot 介绍
人工智能
Kyops1 小时前
GPT-6 把地图和交互组件带进 ChatGPT,答案终于可以直接操作了
人工智能
布吉岛的石头1 小时前
Java 程序员第 49 阶段11:Java 接 BERT:用 ONNX Runtime 做句向量推理
java·人工智能·python·深度学习·bert·transformer
OCR_133716212751 小时前
智能交通底层逻辑:国内车牌分类规范与AI识别适配技术解析
大数据·人工智能·分类
Vincy1231 小时前
腾讯开源 WeKnora 深度评估:3 万 Star 的 RAG 框架,你的 Milvus 能直接用吗?
人工智能
染指11101 小时前
140.Agent-多Agent框架-Agent执行Skills流程
数据库·人工智能·设计模式·langchain·agents
小慧姐姐呀1 小时前
把大模型塞进桌面端:一次本地离线 AI 推理的选型与工程实践
人工智能
Chengyunlai1 小时前
告诉模型输出 JSON,就一定能得到正确的 JSON 吗?
人工智能
万联WANFLOW1 小时前
从 ARTEX 事件看 AI Agent 安全:工具调用链为何成为新的风险入口?
人工智能·安全·测试