Ironclaw 00 - 初见 Ironclaw

Ironclaw 是 Transformer 作者做的一个更安全的 Openclaw。LLM 的 API 调用表面上看并不复杂,和普通 HTTP API 没有本质区别,但 Agent 到底是怎么实现的、整个系统又是怎样组织起来的,一直是我很好奇、也很难只靠空想搞明白的事。

我打算用这个系列 Blog 记录自己阅读 Ironclaw 代码结构、分析实现方式,以及反过来思考“一个 Agent 系统应该如何设计”的过程。

所有文章均 co-writer with LLM (但我自己会对内容的准确性负责),喜欢纯手写 Blog 的观众可以现在就关掉这个页面了 :)

组件之间的总体运行链路

  1. 用户消息进入:来自 CLI/Telegram/Web 等 Channel → 统一变成 IncomingMessage 送进 代理循环
  2. 代理循环处理一次请求:解析/路由(命令 vs 普通对话 vs job)→ 进入 agentic loop(LLM→工具→LLM…)→ 输出响应
  3. 并行与后台:需要后台跑的任务交给 调度器;真正执行 job 的是 工作器(Worker)
  4. 容器隔离(可选):需要 Docker 沙箱的 job 由 编排器(Orchestrator) 管容器生命周期、凭据注入、内部 API
  5. Web UI:Web 网关提供 HTTP API + SSE/WS 流,把聊天、job 事件、日志推到浏览器
  6. 记忆与检索:工作空间提供持久化文件+(FTS+向量)混合搜索,供 agent/工具使用
  7. 安全:安全层在“输入→LLM→工具→输出”的关键边界做注入防护、校验、脱敏、泄漏检测
  8. 定时任务:定时任务引擎按 cron/事件触发,轻量就地执行,重任务走调度器

下面逐个展开。

1. 代理循环:主消息处理和任务协调

它是什么:主控的事件循环,持续接收消息、维护 session / thread / turn 状态,并触发一次次 agentic turn

原理和机制主要来自 src/agent/CLAUDE.md

  • 会话模型是 Session -> Thread -> Turn,每次用户输入通常对应一个 Turn,可以持久化、回放和压缩。
  • 真正“思考 + 调用工具”的循环不是手写 while,而是复用共享引擎 run_agentic_loop(),后面第 4 节会展开。
  • 关键点是:单个 thread 内顺序执行,以保证对话一致性;并行任务则交给 scheduler / job

2. 路由器:分类用户意图

它是什么:主要处理显式命令,也就是以 / 开头的输入,并把它们转换成内部的 MessageIntent,例如创建 job、取消 job、查看 help 等。

原理和机制:

  • 文档写得很明确:Router 只负责显式 /commands
  • 自然语言消息会绕过 router,直接进入对话 dispatcher。这一点很重要,因为它确保了“聊天就是聊天”,而不是先被命令系统强行解释一遍。
  • 相关说明见 src/agent/CLAUDE.md 中的 Command Routing (router.rs)

3. 调度器:管理带优先级的并行任务执行

它是什么:并发和后台任务的管理器。它会把“需要后台跑的完整 job”和“轻量子任务,例如工具执行或后台小任务”分开管理。

原理和机制,文档里给了很清晰的实现模型:

  • 内部维护两张表,而且是在锁里管理:
  • jobs:完整的、由 LLM 驱动的后台 job,带有 worker 和消息通道。
  • subtasks:轻量的 ToolExec / Background 任务。
  • 推荐入口是 dispatch_job():先创建上下文,并在需要时落库,保证后续 job_eventsllm_calls 等记录都有外键,再进入 schedule
  • 这些细节在 src/agent/CLAUDE.mdScheduler 小节里写得很明确。

你可以把它理解成:把“对话主线程”和“长时间运行的工作”解耦。

4. 工作器:执行包含 LLM 推理和工具调用的任务

它是什么:真正负责跑一个 job 的执行单元。现在的 job 执行逻辑已经放到 src/worker/job.rs,它实现了 LoopDelegate,并复用共享的 agentic loop 引擎。

原理和机制,可以从 src/worker/job.rssrc/agent/CLAUDE.md 一起看:

共享引擎 run_agentic_loop() 的固定流程大致如下:

run_agentic_loop(delegate, reasoning, reason_ctx, config)
  1. Check signals (stop/cancel) via delegate.check_signals()
  2. Pre-LLM hook via delegate.before_llm_call()
  3. LLM call via delegate.call_llm()
  4. If text response -> delegate.handle_text_response() -> Continue or Return
  5. If tool calls -> delegate.execute_tool_calls() -> Continue or Return
  6. Post-iteration hook via delegate.after_iteration()
  7. Repeat until LoopOutcome returned or max_iterations reached

Job worker 还会负责:

  • 记录和持久化 job 状态与事件。
  • 通过 SSE 把 job 的消息、工具调用、结果和状态推给 Web UI。你在 Worker::log_event() 里能看到,它会把事件映射成 SseEvent::JobMessageSseEvent::JobToolUse 等类型。

直观理解就是:Worker 是“把一个任务跑完的执行机”,而 loop 引擎则是“LLM <-> 工具”的迭代框架。

5. 编排器:容器生命周期、LLM 代理与每任务认证

它是什么:当 job 需要在 Docker 沙箱里运行时,编排器负责“起容器、管容器、给容器发任务、收回事件”,并且用每个 job 自己的 bearer token 做内部 API 鉴权。

原理和机制可以从 src/orchestrator/api.rs 看出来:

  • 编排器内部 API 会在另一个端口暴露 /worker/{job_id}/... 这一组端点,用来获取 job 描述、代理 LLM 完成请求、上报状态、上报事件、拉取 follow-up prompt,以及获取 credentials。
  • 安全上的关键点是:所有 /worker/ 路由都套了 worker_auth_middleware,使用 per-job token 鉴权,防止其他容器或进程伪造请求。

你可以把 Orchestrator 理解为“沙箱执行的控制平面”。

6. Web 网关:浏览器 UI 的统一出口

它是什么:面向浏览器的 HTTP API + SSE / WS 实时流,由 Axum 实现,把系统各子模块的状态和事件用统一方式暴露给前端。

原理和机制见 src/channels/web/CLAUDE.md

  • /api/chat/send 把消息送进 agent loop;/api/chat/events 通过 SSE 推流,也支持 WS。
  • 事件契约由 SseEvent 枚举定义,本质上是后端和前端对齐的一层协议。
  • 工具执行如果需要审批,会先发出 approval_needed,前端确认后再调用 /api/chat/approval 恢复执行。
  • 认证使用 Authorization: Bearer <GATEWAY_AUTH_TOKEN>;考虑到 EventSource / WS 的限制,少数端点也允许通过 query token 传入。

直观理解:Web 网关就是“UI 的后端适配层 + 事件总线出口”。

7. 定时任务引擎:cron 与事件触发的统一机制

它是什么:把“定时触发”和“事件触发,例如 webhook / system_event / manual”统一抽象成 Routine 的触发机制。

原理和机制见 src/agent/CLAUDE.md

  • Routine = Trigger(cron / event / system_event / manual) + RoutineAction(lightweight / full_job) + guardrails
  • RoutineEnginecron ticker + 事件匹配器 组成。
  • 轻量 action 会就地执行,也就是 inline
  • full_job 会交给 Scheduler 变成后台 job,避免阻塞主循环。

这就是很典型的“触发器 -> 执行策略”分层。

8. 工作空间:带混合搜索的持久记忆

它是什么:给 agent 使用的“持久化记忆文件系统”,支持读写任意路径,并提供 FTS + 向量 的混合搜索,也就是 RRF 融合。

原理和机制见 src/workspace/README.md

  • 项目强调一句话:Memory is database, not RAM。也就是要“记住”的东西,不应该只留在上下文里,而要写入 workspace 文件。
  • 目录结构里有一些约定文件,例如 MEMORY.mdHEARTBEAT.mddaily/
  • 搜索上采用 RRF(Reciprocal Rank Fusion),把关键词排名和向量相似度排名融合起来:
score(d) = sum 1 / (k + rank(d))
  • 后端实现上也有差异:Postgres 支持 FTS + pgvectorlibSQL 目前更偏关键词搜索,向量链路还没有完全接通。

直观理解:Workspace 就是“可审计、可检索、可持久化的外部记忆”。

9. 安全层:提示注入防御与内容清理

它是什么:在几个关键边界上,对内容和工具调用做“防御纵深”,包括输入过滤、工具参数与结果处理、输出脱敏、泄漏检测和策略约束。

原理和机制,可以从 src/agent/CLAUDE.mdsrc/worker/job.rs 的调用点看出来:

  • 文档明确写了一个关键不变量:工具结果在回到 LLM 之前,必须经过 SafetyLayer,也就是 sanitizer -> validator -> policy -> leak detector 这一整条管线。
  • 工具执行路径里还有 execute_tool_with_safety()process_tool_result() 这样的共享管线,chatjobcontainer 都会复用。

核心目的很明确:把“能触达外部世界的动作”统一收口到受控管线中。

直观理解的话,SafetyLayer 就是“LLM 与外部世界之间的防火墙 / 净化器”。