单 Agent 直答
最常见、最简单的情况:用户提问 → Agent 答完。没有 HITL、没有协作。一个 Run 在 T3 内核走完 8 步管道,T2 仅做路由和回收,T1 推回结果。
单 Agent + HITL 高风险审批
Agent 准备执行高风险动作(生产部署 / 数据破坏 / 金额操作),Policy 判定为 High → T3 暂停,绕去 T1 审批人界面,等批准后再恢复执行。
AWAITING_HITL 状态不释放沙箱,等审批回来直接恢复,不重起 Run、不丢上下文。
多 Agent @ 派单(Parent → Child)
协作场景:Parent Agent 在执行中需要专家协助,@ 一个子 Agent 派单。子 Agent 是独立 Run(独立沙箱、独立上下文),跑完返回结果,Parent 接着续跑。
DelegationTrace 回流。
Team @team 扇出 + Round 2 汇总
真正的"协作群":用户 @team:soc,T2 把 Team 展开成 N 个成员,并行起 N 个 Child Run(Round 1)。所有 Child 终态后,T2 回到 Parent 触发 Round 2 汇总。
GroupCollaborationCoordinator 的责任边界。
Sequential Relay · 串行接力
日常工作流模式:Run A → Run B → Run C,每个 Run 终态后由 T2 直接起下一个 Run。和场景 C 不同——没有 Parent 等 Child,每个 Run 平级独立,通过 chain_remaining 字段串成任务链。典型场景:"研究 → 写稿 → 校对"。
JobDispatcher 看 chain_remaining 字段决定起下一个。Run A 失败时整链熔断;中间某 Run 超时不影响已完成 Run 的工件。沙箱按 Skill 实例化——纯文本任务不进沙箱,代码类任务才进。
多轮对话 Session(同一 Agent 多 Run)
日常聊天模式:用户连发多条消息给同一个 Agent,每条消息是独立 Run,但共享 AgentSession 把对话历史串起来。T2 在每次起 Run 前从 Session 加载累积上下文,T3 永远在"全 history"基础上回答。
AgentSession 把跨 Run 的对话历史串起来。T3 不感知"轮次"——它每次拿到的就是累积 history,按"全部上下文"回答。所有轮次共享 agent 的同一沙箱(per-agent 模型);纯聊天 Run 根本不进沙箱,能扛更高并发。
Lead-driven · Lead 主导分派
协作三大策略中第三种:Lead Agent 拆任务 → 派 executor → 收齐 → 整合。和 fan-out(D)的区别是 Lead 二次激活——先 plan、再 consolidate;和 sequential-relay(E)的区别是 executor 之间并行而非串行。
Webhook 异步触发(非用户聊天入口)
入口不是用户消息——可能是邮件触发(用户发邮件 → 平台触发 Agent)、Cron 定时(每天 9:00 跑日报 Agent)、监控告警(系统异常 → 应急 Agent)、外部 Webhook(Github push → 代码审查 Agent)。结果回推事件源(callback URL / 邮件回执),不走 SSE。
callback_url。事件源(邮件服务 / Cron 守护进程 / 监控系统)异步消化结果。这是"无人值守 Agent"模式:监控告警自动响应、Cron 定时报告、CI/CD 触发审查都走这条路径。
Harness 在哪?什么 Skill 才进沙箱?
前面 6 张时序图都把 T3 画成一根 lifeline,但"T3 内核运行在哪儿"需要单独说清楚。常见误解:每个 Run = 一个沙箱容器。实际不是——Harness 内核就在 Celery Worker 进程里,沙箱是按 Skill 实例化的执行环境。
| Skill 类型 | 典型例子 | 是否进沙箱 | 原因 |
|---|---|---|---|
| 代码执行 | claude_code_execute · shell_exec · python_exec | 必须进 | 跑不可信代码 · 文件系统隔离 |
| 服务部署 | Vite / Streamlit / Flask / Gradio dev 服务 | 必须进 | 需要绑端口 · 端口池资源 |
| 构建 / 安装依赖 | npm install · pip install · 解压 | 必须进 | 大量文件 IO · 防止污染 worker |
| LLM 调用 | Claude / GPT API · 流式生成 | 不进 | HTTPS 出站 · 无副作用 |
| 数据库查询 | SQL · 向量检索 · Memory 召回 | 不进 | 受应用权限控制即可 |
| 外部 API | 飞书 / 邮件 / 多维表 / Webhook | 不进 | 纯 HTTP 调用 · 凭据托管 |
| 纯文本处理 | summarize / translate / classify | 不进 | 无 IO 副作用 |
| web_search | DDG / Bing API · 网页解析 | 不进 | HTTP 请求 · 无代码执行(如改用 Headless 浏览器则需进) |
沙箱使用边界 · 5 个维度
"哪些 Skill 进沙箱"只是维度 A。完整的沙箱使用边界还包括:沙箱内能用什么资源(B)、沙箱什么时候创建/销毁(C)、出问题怎么处理(D)、跨 agent 怎么不串扰(E)、宿主机文件系统怎么挂(F)。
B · 资源约束(沙箱内能用什么)
每个沙箱容器有硬性资源上限,超过即终止——保护宿主机不被单个 Run 打死。
| 资源 | 默认上限 | 超出动作 |
|---|---|---|
| CPU | 1 core / 沙箱(可上调) | 容器 throttle,不杀 |
| 内存 | 2 GB / 沙箱 | OOM Kill → 沙箱内 Skill 调用失败 → Run 进入 Recovery |
| 执行时长 | 单次 Skill 调用 ≤ 30 分钟(默认 5 分钟,可在 Skill manifest 声明) | 超时强制终止 → Tool 调用控制器收到 TIMEOUT |
| 磁盘 | 容器内 /data/projects 共享卷 ≤ 1 GB | 写满 → IO 错误,Run 内自行处理 |
| 出站网络 | 白名单:LLM API · OSS · 公开 Skill MCP · 公网 HTTPS | 非白名单访问被 nginx / iptables 拒绝 |
| 入站端口 | 仅容器 8081-8090,映射到宿主 39137-39146(10 个) | 占满 → port_registry 拒绝新 Run 申请 |
C · 生命周期(沙箱跟 agent 走)
沙箱按 agent identity 实例化——一个 agent 一个沙箱(agent 的"电脑"),多个 Run 在沙箱内串行处理。沙箱跟着 agent 一起活过整个生命周期。
| 时机 | 动作 |
|---|---|
| 创建 | agent 第一次调沙箱类 Skill 时,Sandbox Provider 为该 agent 起 1 个容器,分配端口,绑定 agent_id |
| 占用 | 该 agent 持有这个沙箱——不释放给其他 agent,包括同 user 拥有的其他 agent |
| 多 Run 复用 | 同 agent 后续 Run 都用这个沙箱串行处理(agent 一次只做一件事,像人一样) |
| 预热 | 常驻 agent(如团队 SOC agent)的沙箱保持热启动,避免每次冷启 5–15s |
| 空闲 hibernate | agent 长时间不活跃,沙箱挂起(容器 pause / 或 snapshot 销毁),保留状态、释放端口与内存 |
| 唤醒 | agent 下次激活时恢复沙箱(pause→resume,或从 snapshot 起新容器) |
| 销毁 | agent 归档 / 删除 / 所有权转移时连同沙箱一起清理 |
| 强制销毁 | OOM / panic / cleanup 失败 → 立即销毁;下次唤醒走全新沙箱(agent 状态丢失) |
D · 失败处理(沙箱出问题怎么办)
沙箱失败必须有显式兜底,不能让 Run 卡死或静默失败。
| 失败类型 | 处理 | Run 状态影响 |
|---|---|---|
| 容器启动超时(> 30s) | 放弃当前申请 → 重试 1 次 → 仍失败则 Tool 调用 FAIL | EXECUTING → FAILED → CLOSED_FAILED |
| 容器 OOM Kill | Skill 调用返回 OOMError;Run 自行降级或失败 | 由 Skill 重试策略决定(默认不重试 OOM) |
| Skill 执行超时(≥ 5min 默认) | SIGKILL 容器内进程 → 销毁容器 | Tool 调用 TIMEOUT → 由 Self-Healing 决定恢复 |
| 端口池耗尽(10 占满) | port_registry 拒绝 → Tool 调用控制器排队等待 | EXECUTING 不变,但 Run 实际阻塞在 Tool 调用 |
| 容器进程 panic | 容器内 supervisor 重启 1 次;仍崩则销毁 | 正在跑的 Skill 失败,未来 Tool 调用走新容器 |
| 沙箱网络白名单越界 | iptables / nginx 拒绝;Skill 调用收到网络错误 | 不杀沙箱,由 Skill 决定是否重试 |
E · 跨 agent 隔离(不串扰的硬约束)
沙箱按 agent identity 隔离——不同 agent 永不共用沙箱容器,即使同 user 拥有这些 agent。这是一票否决底线。
| 隔离层级 | 规则 | 说明 |
|---|---|---|
| Tenant 间 | 不同租户的 agent 严禁共用沙箱 | 一票否决错误,立即销毁容器 + 触发安全告警 |
| Agent 间 | 不同 agent identity 各自独立沙箱(即使同 user 拥有多个 agent,比如个人 + 项目 + Team agent) | 类比:员工各有自己的电脑 |
| 同 agent 多 Run | 复用同一沙箱,但串行处理,不在沙箱内并发跑多 Run | agent 一次一件事,符合人类直觉 |
| 父子 Run(A 派单给 B) | A 用 A 的沙箱,B 用 B 的沙箱——天然独立,因为是不同 agent identity | 不需要额外规则,identity 不同自动隔离 |
| Team agent | 整个 Team 是一个 agent identity,共用一个沙箱(团队工作空间) | 类比:项目共享代码仓库 / 共享研发环境 |
| Agent 转移 / 删除 | 沙箱跟随 agent 一起归档 / 销毁,不留孤儿容器 | agent 删除 → 沙箱内文件 + 凭据全部清理 |
F · 挂载策略(沙箱 ↔ 宿主机文件系统)
"沙箱"两个字最容易给人安全错觉的地方:bind mount 共享 inode 时,沙箱里 rm -rf 直接打穿宿主机。沙箱与宿主机文件系统的边界由挂载策略决定,规则非常窄:默认零挂载,必须挂的走窄白名单。
| 层级 | 规则 | 说明 |
|---|---|---|
| 默认 | 零 bind mount | 沙箱与宿主机 fs 默认完全隔离;需要传文件走 stage-in / stage-out 显式拷贝 |
| 用户 workspace | 仅挂 /data/users/<user_id>/workspace,rw | 这是沙箱里唯一能动到宿主机持久化数据的入口;按 user_id 隔离,不可越界 |
| 系统目录 | 禁挂 / · /etc · /home · 其他用户目录 | 一票否决;任何配置试图挂这些都视作高危,启动失败 |
| Docker / 容器运行时 | 禁挂 /var/run/docker.sock · /proc · /sys · cgroup fs | 挂任意一个 = 整台宿主机交出去(容器可起 privileged + 反挂宿主机根) |
| Skill manifest 声明 | 需要额外挂载(如代码仓库子目录)→ manifest 显式声明 path + ro/rw + 子路径白名单 | 不接受目录级 rw,必须细到子路径;rw 子路径触发 destructive 标记自动加 HITL |
| 临时 rw 区 | 容器内 /tmp/scratch(tmpfs,per-Run 隔离) | Skill 自由读写;Run 结束销毁;零宿主机痕迹 |
| destructive 兜底 | rm / rmtree / 覆写已有文件 / git reset --hard 即使在用户 workspace 内也走 HITL | 详见 治理与 HITL 设计 ↗ 风险矩阵;技术隔离 + 治理审批双层兜底 |
消费级 Agent 没有、企业必备的 5 条支线
8 个场景画的是主轴流程。下面 5 条才是"为什么需要平台"——每张卡说清它落在哪个场景的哪一笔。