第三章 · RUNTIME & FLOW
运行时 · 状态机 · 流程
zeroclaw 支持三种截然不同的运行时进程模型,每种模型有独立的启动路径、并发策略和生命周期管理。 本页通过 CSS 流程图详解 Chat 状态机、Gateway 路由链路、SOP 执行流程、WASM 插件调用链,以及 tokio 并发模型。
⚡ 三种进程模型对比
💬 Chat 模式
进程类型
单进程,前台运行
I/O 模型
阻塞 TUI,同步读用户输入
异步运行时
tokio(工具执行并发)
生命周期
用户退出即终止
会话持久化
退出时写入 SQLite
适用场景
个人开发、本地使用
🌐 Gateway 模式
进程类型
长驻服务进程
I/O 模型
全异步 HTTP + WebSocket
异步运行时
tokio multi-thread
生命周期
持续运行,SIGTERM 优雅退出
会话持久化
实时写入,多租户隔离
适用场景
应用后端 API、MCP 端点
👻 Daemon 模式
进程类型
后台常驻,systemd 托管
I/O 模型
Webhook 触发,无 stdin
异步运行时
tokio + watchdog 线程
生命周期
开机自启,崩溃自恢复
会话持久化
全程持久化,任务日志
适用场景
自治 Agent、定时任务
🔄 Agent 循环与状态机
以下是 Chat 模式下 agent/loop.rs 的完整状态机,覆盖从用户输入到响应渲染的全部状态转换。
INIT · 启动初始化
加载 config · 检测 LLM 提供商 · 恢复历史会话
READY · TUI 就绪,等待用户输入
用户按下 Enter 提交
SAFETY_CHECK · 风险预评估
risk = low → 自动通过 | risk = medium → 提示确认 | risk = high → 必须显式确认
LLM_CALL · 调用 LLM 提供商 API
provider_router.complete() → 流式接收 token
PARSE_TOOLS · 解析响应中的 tool_calls
有 tool_calls → YES
TOOL_DISPATCH
tokio::spawn 并发执行
tokio::spawn 并发执行
各工具在沙箱中执行
TOOL_RESULTS
结果 append 到上下文
结果 append 到上下文
↑ 返回 LLM_CALL(继续循环)
无 tool_calls → NO(最终回复)
RENDER_OUTPUT
TUI 渲染 / stdout 输出
TUI 渲染 / stdout 输出
PERSIST_SESSION
写入 SQLite
写入 SQLite
READY / EXIT
工具调用循环:LLM_CALL → PARSE_TOOLS → TOOL_DISPATCH → TOOL_RESULTS → LLM_CALL 这个内循环
可能迭代多次,直到 LLM 返回不含 tool_calls 的最终文本响应为止。
循环上限由 agent.max_tool_rounds 配置(默认 20)。
🌐 Gateway 路由链路
Gateway 模式下每个客户端请求经过以下处理链路,全程异步,多客户端并发互不阻塞。
Client · HTTP POST /v1/chat
JSON body: { message, session_id? }
gateway/server.rs · axum 路由接收
gateway/rpc.rs · JSON-RPC 2.0 解析
gateway/session.rs · 会话查找 / 创建
session_id 存在 → 恢复上下文;否则 → 新建会话
tokio::spawn → Agent Task(独立任务)
每个请求独立 task,互不阻塞
provider_router → LLM Provider API
tool_dispatch → 沙箱工具执行
SSE 流式响应 → Client(实时 token 推送)
| Gateway API 端点 | 方法 | 说明 |
|---|---|---|
| /v1/chat | POST | 发送消息,返回 SSE 流式响应 |
| /v1/sessions | GET | 列出所有活跃会话 |
| /v1/sessions/:id | GET | 获取指定会话历史 |
| /v1/sessions/:id | DELETE | 删除会话 |
| /ws | WS | WebSocket 双向通信,JSON-RPC 2.0 |
| /health | GET | 健康检查端点 |
📋 SOP 执行流程(Daemon 模式)
Daemon 模式接收 Webhook 或定时触发的任务后,交由 sop_engine 进行任务分解与有序执行。
Task 到达 · Webhook / 定时触发
sop_engine · 任务分解为有序 Steps
分析任务依赖关系,构建执行 DAG
For each Step(按依赖顺序)
scheduler.queue(step)
agent_loop.execute(step)
调用 LLM + 工具,完成单步任务
verify result · 验证结果
验证通过 → 继续下一步;失败 → 重试或报错
所有步骤完成 → 生成执行报告
🧩 WASM 插件调用链
当 agent_loop 决定调用一个 WASM 插件提供的工具时,完整调用链如下。整个过程对 agent_loop 透明, 与内置工具调用接口完全一致。
Step 1 · 工具查找
agent_loop 决定调用工具 "my-wasm-tool",
通过 tool_registry 查找,发现该工具由 WASM 插件提供。
Step 2 · 插件实例化
wasm_runtime.instantiate(plugin.wasm) 通过 Wasmtime 加载 .wasm 文件,
初始化隔离的 WebAssembly 实例。若插件已缓存则复用实例。
Step 3 · 沙箱配置
应用插件声明的权限:限制可访问的文件系统路径(通过 WASI 配置),
限制网络访问范围,拒绝未声明的系统调用。
Step 4 · JSON-RPC 调用
通过 JSON-RPC 2.0 协议调用插件导出的函数,传入工具参数(JSON 格式)。
插件内部处理逻辑完全在 WASM 沙箱中执行。
Step 5 · 结果返回
插件通过 JSON-RPC 返回结果(JSON 格式),
wasm_runtime 解包结果并返回给 tool_registry。
Step 6 · 上下文注入
工具结果 append 到 agent_loop 的对话上下文中,作为下一轮 LLM 调用的输入,继续 Agent 循环。
# WASM 插件调用伪代码(简化) agent_loop: tool_name = "my-wasm-tool" tool_args = { "url": "https://example.com" } tool_registry: plugin = find_wasm_plugin(tool_name) // 找到对应插件 wasm_runtime: instance = wasmtime::instantiate("my-wasm-tool.wasm") apply_sandbox_policy(instance, plugin.permissions) request = json_rpc_request(tool_name, tool_args) response = instance.call_export("handle", request) // 在沙箱内执行 result = json_rpc_parse(response) agent_loop: context.append_tool_result(tool_name, result) // 继续下一轮 LLM_CALL
🔀 并发模型:tokio async
zeroclaw 全程使用 tokio 异步运行时。工具调用通过 tokio::spawn 并发分发, 结果通过 join_all 聚合,每个任务独立携带沙箱策略。
tokio 运行时内部任务分布(简化视图)
tokio Runtime (multi-thread)
Worker Thread #1
agent_loop task
tool: file_read
Worker Thread #2
tool: shell_exec
tool: web_fetch
Worker Thread #3
wasm plugin task
session persist
Worker Thread #N
LLM stream recv
tool: grep
工具调用的并发流程:
1. agent_loop 解析到多个 tool_calls → tokio::spawn 为每个工具创建独立 task
2. 每个 task 在自己的异步上下文中执行,由 tokio 调度器分配到工作线程
3. 所有 task 完成后通过 futures::future::join_all(tasks).await 聚合结果
4. 每个 task 在创建时携带独立的沙箱策略(Safety Engine 在 spawn 前注入)
1. agent_loop 解析到多个 tool_calls → tokio::spawn 为每个工具创建独立 task
2. 每个 task 在自己的异步上下文中执行,由 tokio 调度器分配到工作线程
3. 所有 task 完成后通过 futures::future::join_all(tasks).await 聚合结果
4. 每个 task 在创建时携带独立的沙箱策略(Safety Engine 在 spawn 前注入)
// agent/loop.rs 内部工具并发调用(简化) async fn dispatch_tools(tool_calls: Vec<ToolCall>) -> Vec<ToolResult> { // 为每个工具创建沙箱策略并并发 spawn let tasks: Vec<_> = tool_calls .into_iter() .map(|call| { let policy = safety_engine.policy_for(&call); tokio::spawn(async move { // 在沙箱内执行工具 sandbox.run(policy, || tool_registry.execute(call)).await }) }) .collect(); // 等待全部完成并聚合结果 futures::future::join_all(tasks).await .into_iter() .filter_map(|r| r.ok()) .collect() }
📊 运行时关键参数
| 参数 | 配置键 | 默认值 | 影响 |
|---|---|---|---|
| 最大工具调用轮数 | agent.max_tool_rounds | 20 | 防止 Agent 无限循环 |
| LLM 请求超时 | agent.llm_timeout_secs | 120s | 单次 LLM 调用最长等待时间 |
| 工具执行超时 | tools.shell_timeout | 30s | 单个工具调用最长执行时间 |
| 最大并发会话(Gateway) | gateway.max_sessions | 50 | Gateway 模式最大并发客户端数 |
| WASM 内存上限 | wasm.memory_limit_mb | 64MB | 单个 WASM 插件实例最大内存 |
| Daemon watchdog 间隔 | daemon.watchdog_interval_secs | 10s | 进程健康检测频率 |
| 向量检索 Top-K | memory.retrieval_top_k | 5 | 每次跨会话记忆检索返回的条目数 |