Cairn-深度拆解-状态空间搜索与Decide-Execute
Cairn 与面向目标的状态空间搜索
本文以本地克隆的 oritera/Cairn(main 分支,提交 8e7e0ea,2026-09-07)为对象,逐文件阅读源码,对照仓库内的 docs/specs/*.md 协议文档与作者复盘,并在本机运行测试套件验证。论述重点为两项:其一,该系统如何把渗透测试重述为面向目标的状态空间搜索;其二,仅由决定与执行两类活动构成的极简循环,其运行条件与代价为何。
标注约定。 依照本专题的规则,文中区分三类陈述,以免将作者的声明与本文的推断混用:
| 标记 | 含义 |
|---|---|
| 原作自述 | 作者在 README.md、docs/specs/*.md 或复盘中明确写出 |
| 源码推得 | 作者未直接说明,但实现可逐项对应,由本文阅读代码得到 |
| 本文推断 | 本文提出、尚无上游依据的判断 |
1 对象与背景
1.1 开源版与 Cairn_Y 的区分
讨论 Cairn 时须先区分两个对象,否则容易把可读代码与公开成绩混为一谈。开源仓库 Cairn 是作者在腾讯云黑客松期间完成的一次工程实践。作者自述其定位「不是一个功能完备的渗透测试项目,而是当时我对极简主义、黑板架构、Fact-Intent 图的理解与运用的一次工程实践」。此后在同一架构上继续演进的版本称为 Cairn_Y,未开源。二者的差异集中于图结构的命名、目标层级的表达以及工程栈的选择:
| 维度 | 开源 Cairn | Cairn_Y |
|---|---|---|
| 图结构 | Fact–Intent | FGS(Fact–Goal–Step) |
| 过程节点 | intent |
step |
| 目标表达 | 单一 goal 节点 |
goal 与可动态增删的 Sub Goal |
| 搜索产物 | 仅 Fact | Fact 与 Finding |
| 两类活动 | reason / explore |
Decide / Execute |
| Agent Loop | Claude Code、Codex、Pi 适配层 | 基于 Pi 的库,仅使用其 Agent Loop |
| 实现语言 | Python | Node,后续倾向 Go 或 Rust |
公开榜单成绩属于 Cairn_Y:Cybench 满分第一,TSecBench v1 第一(63 题、74 个 flag),XBOW Validation Benchmarks 第一。作者同时说明,这些成绩未针对赛题优化提示词、未内嵌答案、未携带跨轮记忆,仅在 Harness 工程层面迭代;TSecBench 公开全部 LLM 记录并提供作弊举报通道。开源版本自身的成绩是腾讯云黑客松智能渗透挑战赛第二届的 54/54,为当场唯一 AK,总排名第三。
值得记录的还有成本变化。同一评测集全解,半年前约需 7000 元,Cairn_Y 目前不足 50 元,而架构主体并未改变。这一数量级的差异并非来自性能优化,而来自若干机制的取消:不加载 Skill、不做 RAG、不做多 Agent 之间的上下文传递、不做跨题记忆。极简在此处表现为成本结构,而非风格偏好。
1.2 问题的形态
将目标、工具与历史消息一并交给模型并令其连续调用,是当前长任务 Agent 的主流形态。该形态假设路径可以被预先枚举,因而在任务偏离预设时出现三类失效:阶段划分刚性,已进入后置阶段便难以退回;规划与执行共享同一上下文,模型易将计划误判为已完成;终止条件通常由模型自行宣告,缺乏外部结构予以否证。
Cairn 的回应是替换这一假设:路径不可枚举,但状态可枚举。由此,任务由「执行完预定流程」转为「在状态空间中寻找一条通向目标的路径」。这并非修辞上的改写,因为它同时提供了可执行的三项要素:下一步从何而来(frontier)、一步如何计为完成(后继)、何时终止(目标测试)。
2 问题的形式化
2.1 FGS:状态、算子与终止条件
开源版本以 Fact–Intent 图表达状态空间,Cairn_Y 将其正式命名为 FGS 图。作者对三者给出了明确界定:Fact 描述当前已经确认的事实,即当前的世界状态;Goal 描述项目的完成条件,即搜索的终止条件,并允许存在可动态增删的 Sub Goal;Step 描述下一步要做什么,即如何由已有事实产生新的事实,是推动世界状态演化的因果过程(原作自述)。
| 概念 | 语义 | 开源实现位置 | Cairn_Y 的变化 |
|---|---|---|---|
| Fact | 已确认的客观发现,只增不改 | server-protocol.md:47-58 |
语义不变 |
| Intent / Step | 由若干 Fact 出发的一次探索,是图的边 | server-protocol.md:60-68 |
更名 Step,强调因果过程 |
| Hint | 图外注入的人类判断,不参与因果 | server-protocol.md:89-95 |
保留 |
| Goal | 特殊的 Fact,同时为终点节点 | server-protocol.md:53-58 |
增加 Sub Goal |
| Finding | 无 | — | 新增,搜索过程的结构化产物 |
Intent 更名为 Step,其动因可从词义分辨:Intent 指行动者的主观意图,Step 指旧事实推出新事实的客观过程。更名后,图中不再记录 Agent 的打算,只记录世界演化的链条,与其「记忆外化」的主张一致。
Finding 的引入回应了一个实际需求。CTF 场景中搜索产物与 Goal 重合,flag 即目标;但渗透测试与代码审计的交付物是过程中发现的漏洞,而非「已完成全部测试」这一终态。作者据此把过程产物单独建模,并允许按任务类型动态定义 Finding 的语义。就形式化而言,这一补充是必要的:只有 Goal 的搜索系统无法表达沿路径获得的中间成果。
2.2 与经典搜索的对应
下图以开源版本的数据结构表示一次搜索过程。尖角框为 Intent(边),圆角框为 Fact(状态)。i003 表示多源超边,即 f001 与 f002 共同支撑一次探索。
1 | flowchart LR |
该结构可逐项映射到教科书中的搜索要素,且每一项都有明确的代码落点,而非仅作类比:
| 搜索要素 | Cairn 的实现 | 代码位置 |
|---|---|---|
| 状态 | facts 表的一行,内容为自然语言 |
db.py:32-37 |
| 初始状态 | 特殊 Fact origin |
projects.py:83-94 |
| 算子 | intents 表的一行,from 指向待产出事实 |
db.py:39-50 |
| 后继 | conclude 写入新 Fact |
routers/intents.py:118-147 |
| 目标测试 | complete,其 from 必须指向已存在事实 |
contracts.py:81-86 |
| frontier | to_fact_id IS NULL 的意图集合 |
loop.py:298-318 |
| 扩展顺序 | 最新优先,即栈式议程 | loop.py:315-318 |
| 终止 | 写入一条指向 goal 的边 |
projects.py:257-300 |
from 允许数组,因此 Intent 在图上等价于一条超边,多个已知事实可共同支撑一次探索。这一点区别于线性日志:因果关系不必被压缩为单条链,上下文也不会因择一主事实而丢失。
2.3 完成即一条边
多数 Agent 系统以模型输出的一句话作为完成判据。Cairn 将完成也纳入图结构。complete_project 执行三项操作:插入一条 to_fact_id='goal' 的意图、将 from 中的每个事实登记为该意图的源、将项目状态置为 completed(projects.py:257-300)。由此,成功等价于图上出现一条由既有事实指向 goal 的边。
这一处理带来三项性质。其一,完成必须引用已存在的 Fact(services.py:79-87),不能凭空宣告。其二,完成在界面上表现为一条可见的边。其三,完成可被撤销,因为它是数据而非记忆。撤销机制由 reopen 实现:外部判定原完成结论有误时,系统删除该完成边,新增一条纠错 Fact,并以 external_feedback 为描述建立一条指向原源事实的超边,项目回到 active(projects.py:303-357)。错误结论未被删除,而是转为可用于后续规划的负样本。
2.4 界面证据
仓库自带一张运行截图(README/cairn.png),可佐证上述机制在真实运行中的形态:
图中左侧为 Origin、多条带意图描述的边、若干 Fact 与 Goal,节点与边的关系与前述形式化一致。右侧详情面板列出该事实的来源:Intent i004、From f002、Creator、Worker、Concluded 00:21:11,即任一结论均可回溯至产生它的那一步、所依赖的前提与执行者。
更值得注意的是该事实的正文:f002's claim that flag was at /var/www/html/flag.php was incorrect — flag.php is the app source, not the flag。这印证了「事实只增不改、状态变化以追加表达」并非纸面约定:错误的断言未被删除,而是被后续事实修正。正文末尾的 its content was not yet read before conclude phase 则表明,该事实由收尾阶段写入,主阶段未及完成的观察仍得以保留。
3 Decide 与 Execute
3.1 两类活动的定义
系统只有两类活动,且二者是独立的运行过程,而非固定角色的 SubAgent。按作者表述,可将二者理解为同一个运行器被注入了不同的提示词与工具:
| Decide | Execute | |
|---|---|---|
| 工具面 | 仅查看与操作 FGS 图的工具 | 读图工具,及可改变世界状态的工具(read、bash、edit、write)与 submit_fact |
| 触发条件 | 任务开始,或图上信息增加、变化 | 存在可执行的 Step |
| 并发 | 串行,全局同一时刻仅一个 | 可并行 |
| 上下文 | 每次触发以干净上下文重新运行,不携带记忆 | 无跨会话记忆 |
| 职责 | 评估 Step 的新增、废弃与优先级,增删 Sub Goal,判断 Goal 是否达成 | 执行一步探索,产出新 Fact |
作者的补充说明了这套划分的实质:「两类活动的本质其实都是注入了不同 Tool 和 Prompt 的 Agent Loop,但都没有独立的跨会话记忆机制,FGS 图就是他们共同的、外化的记忆。」据此,记忆不再是一个独立子系统,没有向量库、摘要器或会话回放;需要记住的内容写入图,需要读取的内容重读图。
3.2 串行与并行的依据
Decide 串行而 Execute 可并行,并非任意约定,其依据在于二者写入的对象不同。
Decide 读取全图并写入议程,即新增、废弃或排序 Step。若两个 Decide 并行,它们将基于同一份旧图各自派生一批 Step,导致重复与优先级冲突,因此该环节必须是单写者。开源实现中对应的是项目级 reason 租约:claim_project_reason 检查 reason_worker 是否已被占用,冲突返回 409(projects.py:191-216)。
Execute 读取全图但只追加一条 Fact,不修改历史。多写者之间的结果天然可合并,这一自由度正是「事实只增不改」换来的。实现中对应的是意图级认领:首次 heartbeat 写入 worker 即取得执行权(loop.py:504),粒度远细于项目级租约。
概括而言,写入议程需要独占,写入事实可以并流。该划分使执行侧可横向扩展,同时避免议程本身出现竞争。
3.3 Decide 的输出契约
reason.md 全文 55 行,将 Decide 的可能输出限定为三种。目标已达成时返回 complete,其中 from 必须来自合法事实列表;目标未达成时应推进时返回 intents 数组;目标未达成但当前不应派生新方向时返回空对象。
1 | {"accepted": true, "data": {"complete": {"from": ["f001"], "description": "..."}}} |
输入亦仅三项:图快照、当前合法 Fact id 列表、尚未结论的意图列表(reason.md:41-55)。契约由代码强制,validate_reason_payload 实现五条约束(contracts.py:62-101):
| 约束 | 行为 | 作用 |
|---|---|---|
complete 与 intents 互斥 |
同时出现即抛出异常 | 排除「既宣告完成又继续搜索」的模糊状态 |
frontier 为空时 intents 不得为空 |
抛出异常 | 避免无待办又不派生方向导致的停滞 |
| 仅返回空对象且 frontier 非空 | 允许 | 已有足够待办时不必新增 |
intents 超出上限 |
截断至 max_intents |
抑制 frontier 膨胀 |
from 引用不存在的事实 |
服务端返回 404 | 保证引用完整性 |
表中第二条与第三条共同界定了 frontier 为空与非空两种情形下的合法输出。当 frontier 为空时,模型若声称无可推进方向,即构成契约违规;系统记录 reason created no intents 并记为失败,由下一轮重试(reason.py:263-272)。据此,系统活性由契约的不可满足性保证,而不依赖模型的自觉。
3.4 Execute 的输出契约
explore 的契约更窄,仅返回一个 description 字符串(contracts.py:157-170)。其中不含状态、边、置信度或证据对象。这一设计成立的前提是写图动作不在模型手中:模型只报告本次探索得到的增量,Dispatcher 代其调用 POST /intents/{id}/conclude,由服务端建立 Fact、建立边并将意图置为已结论(routers/intents.py:118-147)。
explore.md 另规定 description 只包含最新发现的增量事实,不重复图快照中已有信息(explore.md:22)。该规则实际承担事实原子化的职责:若每个 Worker 都复述全貌,图将被重复内容占据,Decide 每轮读到的信息熵随之下降。限定为增量,等价于要求每个 Fact 表示一次搜索步的净收益。
3.5 触发条件
Decide 并非定时运行,而由图上状态的变化触发。_reason_trigger 维护一个三元组检查点,仅在三类变化发生时返回触发原因(loop.py:704-718):
1 | if len(project.facts) > checkpoint.fact_count: |
即:事实数量增加、人类注入新的 Hint、既有待办全部结论。设计文档明确指出,意图数量增加不作为触发条件,因为该变化通常仅反映上一次 Decide 自身派生了新方向,并不代表世界状态改变(dispatcher-design.md:583)。检查点的初始化时机亦有处理:若项目启动时已存在未结论意图,Dispatcher 先建立基线检查点,避免运行期间第一次新增事实被忽略(loop.py:858-878)。
3.6 单次执行的时序
一次 Execute 的完整过程涉及认领、续租与两阶段收尾,其复杂度高于单次模型调用:
1 | sequenceDiagram |
认领即首次 heartbeat,系统未设独立的认领接口。心跳失败的处理分两类:403 与 409 立即判失败,其他错误给予两倍 interval 的宽限;一旦判定失败,直接终止仍在运行的进程(heartbeat.py:88-110),以免失去租约的执行者继续写入。服务端则以心跳超时回收僵尸意图,默认 intent_timeout 为 15 秒(db.py:13-18、services.py:222-237),Dispatcher 启动时校验该值大于自身轮询间隔(loop.py:906-929)。
两阶段收尾是本设计中较为特殊的一处。第一阶段按 300 秒超时正常探索;若超时或输出非法,则以同一 session 下发 explore_conclude.md,限时 90 秒,该提示词要求模型立即停止执行、不再等待未完成命令,仅总结已经确认的事实(explore.py:241-384、explore_conclude.md:19-25)。其作用是将超时由完全损失转为部分收益:一个未得出结论的执行,仍可交付「已确认 A、B、C」这一事实,供后续搜索使用。
3.7 写权限与记忆外置
三条边界共同构成系统的控制面:模型只输出 JSON,不解析自由文本;Dispatcher 是唯一协议写入者,Agent 不认领、不续租、不直接调用 API(dispatcher-design.md:13、:20);状态迁移由服务端的一次 SQL 更新裁决,而非模型的陈述。模型的「我已完成」只是候选信号,使项目转为 completed 的是 complete_project 中的插入语句。
图快照的处理亦值得说明。提示词中不内联图内容,仅给出容器内文件路径,由模型自行读取(common.py:56-70)。此举使提示词体积与图规模解耦,同时将「观察黑板」变为一次工具调用,使 Observe 成为显式步骤。代价是增加一次读取,且模型可能未完整读取。
3.8 实现位置
| 环节 | 函数 | 位置 |
|---|---|---|
| 主循环 | DispatcherLoop.run |
loop.py:80-110 |
| 项目调度 | _try_dispatch_project |
loop.py:254-338 |
| 触发判定 | _reason_trigger |
loop.py:704-718 |
| 派发决定 | _dispatch_reason |
loop.py:366-428 |
| 决定主体 | run_reason_task |
tasks/reason.py:33-284 |
| 派发执行 | _dispatch_explore |
loop.py:488-546 |
| 执行主体 | run_explore_task |
tasks/explore.py:30-238 |
| 收尾兜底 | _try_conclude_fallback |
tasks/explore.py:241-384 |
| 租约 | HeartbeatLease._run |
runtime/heartbeat.py:88-110 |
| 完成落图 | complete_project |
routers/projects.py:257-300 |
| 撤销与回溯 | reopen_project |
routers/projects.py:303-357 |
4 迁移解剖
判断一次迁移的深度,可问:若将原型抽离,该系统会失去什么。就 Cairn 而言,失去的不是一个比喻,而是 frontier、后继与终止三项可执行要素。因此其核心属于问题迁移,而非概念迁移。以下逐项考察它取用的六个原型。
4.1 黑板架构
协议文档将设计渊源明确写为黑板架构,并点名 CMU 的 Hearsay-II(server-protocol.md:15-21)。该原型面对的问题是:语音识别中音素、音节、词与语法多层知识无法由固定流水线组合,局部歧义需由全局约束裁决。其机制由共享黑板、独立知识源与控制器构成,核心是机会式问题求解,控制流不预先固定。
Cairn 的对应关系是:黑板为 SQLite 中的 facts 与 intents,知识源为 Worker(无固定技能,由提示词注入不同工具),控制器为 Dispatcher 的调度函数与 Decide,激活条件为新增事实、新增 Hint 或待办归零。
关键改造在于控制器。经典黑板的控制器是人工编写的启发式优先级规则,可读、可审计、行为可预期;Cairn 将其整体替换为 LLM。收益是系统能够处理从未见过的任务,代价则是收敛性、最优性与可重复性均失去保证,只能依赖目标判据与人工停止兜底。就迁移深度而言,数据结构与控制流属于架构迁移,而控制策略层已不属于原型迁移,应归入对原型的特化。
4.2 图搜索
作者直接使用「状态空间搜索」一词,但未引文献;其学术出处为 Newell 与 Simon 的问题空间假说、A* 算法及教科书中广度优先、深度优先与一致代价搜索(本文推断的出处)。原型面对的问题是:在仅知当前状态与可用算子的条件下,寻找一条到达目标的路径。
Cairn 取用的是完整的问题形式化,而非词汇。状态、初始状态、算子、后继、目标测试、frontier 与扩展顺序均有对应实现(见 §2.2)。这也构成一次可证伪的迁移:既然形式化成立,便应能用搜索领域的指标衡量它,例如到达目标所需扩展的节点数、每解成本、重复扩展比例。
未取用的部分同样明确:无 closed set,即不记录已扩展状态;无 代价与 启发值;无剪枝、双向搜索与迭代加深。这些缺口构成 §6.1 的候选清单。
4.3 工作队列
frontier 在语义上是一个任务队列:Decide 生产未结论的 Step,Execute 领取并消费,to 为空表示尚未完成。相较于成熟实现,它主动省略了三项机制:无优先级,栈式议程即全部策略;无公平性,不防饿死;无死信队列,失败的 Step 直接回到待领取状态(见 §8.2)。这属于简化式迁移,保留队列语义而移除调度策略,与「控制器交给模型」是同一取舍。
4.4 租约
租约机制源自分布式系统,用于在持有者崩溃时避免锁被永久占用:授权附期限,到期自动失效,无需检测崩溃。Cairn 的实现与该机制逐项吻合,尽管文档未引出处(本文推断的出处):
| 租约要素 | 实现 |
|---|---|
| 授予 | 首次 heartbeat 写入 worker(loop.py:504) |
| 续租 | 后台线程每 3 秒续租(heartbeat.py:88-110) |
| 过期回收 | 超过 intent_timeout 未续租则清空 worker(services.py:222-237) |
| 失效处置 | 终止仍在运行的进程(heartbeat.py:112-123) |
| 写入校验 | conclude 时校验 worker 是否为当前持有者,否则返回 409(services.py:112-121) |
最后一项承担了 fencing 的职责,其实现方式是写入前再校验持有者,而非使用单调递增的 token。该方式在单 Dispatcher 下足以拒绝过期持有者的写入,但若允许多实例则会出现经典的 fencing 缺陷,因此设计文档明确限定只支持单实例(dispatcher-design.md:588-589)。这属于以架构约束替代机制完备的做法。
4.5 只追加日志
事实只增不改,状态变化以追加表达,例如「shell 已断开」本身即一条新事实(server-protocol.md:23-31)。这与事件溯源、log-structured 存储及 Git 的只追加有向无环图同源,收益是历史可审计、错误可修正。§2.4 的截图提供了运行时的样本。
但迁移只完成了一半:保留的是结论日志,即图;缺失的是过程日志。对照 ARTEX 的 activity 表,后者记录工具、参数与 Token 消耗,Cairn 无对应结构。因此系统能够回答「确认了什么」,不能回答「如何确认」。
4.6 Stigmergy 与单写者
协调机制采用 Stigmergy,即个体不直接通信,而通过改变共享环境留下痕迹。Agent 之间零通信,只读写同一张图;同时规定 Dispatcher 为唯一协议写入者。这一组合的实际效果是消解了多 Agent 协作中最困难的部分:通信协议与冲突合并不再需要设计,因为写入被串行化,冲突不会发生。就迁移深度而言,把协作问题重述为环境标记问题,带有问题迁移的性质。
4.7 迁移深度汇总
| 原型 | 取用内容 | 位置 | 深度 | 收益 | 代价 |
|---|---|---|---|---|---|
| 黑板架构 | 共享状态面、无角色知识源、机会式调度 | db.py、loop.py:254-338 |
架构迁移 | 无预定义流程 | 控制器换为模型,收敛性无保证 |
| 图搜索 | 状态、算子、后继、目标测试、frontier | db.py:32-58、contracts.py |
问题迁移 | 可证伪的收敛判据 | 缺 closed set、优先级与剪枝 |
| 工作队列 | frontier 的生产与消费 | loop.py:298-318 |
架构迁移(简化) | 执行侧可并行 | 无优先级、公平性与死信 |
| 租约 | 授予、续租、过期回收、写入校验 | heartbeat.py、services.py:222-237 |
架构迁移(完整) | 僵尸任务自动回收 | 仅支持单实例 |
| 只追加日志 | 事实只增不改 | server-protocol.md:23-31 |
架构迁移(半程) | 历史可审计 | 无过程日志 |
| Stigmergy 与单写者 | 零通信协调、单一写入者 | dispatcher-design.md:13,20 |
架构迁移 | 无需通信协议 | 写吞吐受限于单进程 |
另需记录其主动未取用的原型:向量检索、工作流引擎、多 Agent 消息总线、PDDL 或 HTN 式规划器、技能库。这五项恰为当前 Agent 工程的主流组件,作者属于有意不用,而非未曾考虑。
5 特化与代价
5.1 为适配 LLM 所作的改造
第一项是控制器的替换。经典黑板的调度由人工规则完成,Cairn 改由模型完成,并以限制输出形状来控制风险:模型只能从三种动作中选择,开放决策被压缩为有限动作集(contracts.py:62-101)。
第二项是算子性质的改变。同一 Step 重跑两次可能得到不同结果。系统未试图消除这一差异,而是接受它并让图承担后果:错误结论由后续事实修正,完成判断有误则由 reopen 撤销。它把算子的不可靠转化为状态的可修正。
第三项是状态表示的改变。经典搜索的状态可哈希、可比对、可索引;Cairn 的状态是一段自然语言,模型可直接读懂,但机器无法判断两个状态是否等价。这一改变直接导致 closed set 无法实现(见 §6.1)。
第四项是失败检测方式的改变。模型不会抛出异常,只会给出不合规的输出。系统将语义层面的失败降级为语法层面的失败,即解析失败或校验失败,从而复用工程上已有的重试与记录路径。
5.2 阻抗失配核对
| # | 失配项 | 处理方式 | 判定 |
|---|---|---|---|
| 1 | 确定性 | 不消除,改为可修正:事实只追加、完成可撤销 | 部分处理,无法重放 |
| 2 | 寻址方式 | 无索引,每轮全量读图,以「只写增量」抑制图规模 | 未处理,是最脆弱环节 |
| 3 | 成本结构 | 增量触发、干净上下文、12 KB 提示词 | 处理较好,但与全量读图冲突 |
| 4 | 接口契约 | JSON 校验、服务端校验、契约级硬约束 | 处理最好 |
| 5 | 状态可见性 | 只外化结论,不外化推理过程 | 明确接受限制 |
| 6 | 隔离基础 | 以容器提供边界,而非提示词约束;local 模式放弃隔离 | 架构层处理,local 模式下失效 |
| 7 | 测量噪声 | 单次运行,无重复采样与置信区间 | 未处理 |
第 7 条需要单独说明。作者公布的榜单成绩是真实的,但「一次达到满分」与「稳定达到满分」是两项不同的性质。缺少方差信息时,无法区分架构有效与单次波动。这是当前 Agent 评测的普遍问题,但在一篇以架构领先为结论的论述中,该项限制应予披露。
5.3 代价汇总
| 原型提供的能力 | 系统放弃的部分 | 换取的能力 |
|---|---|---|
| 可证明的完备性与最优性 | 无 closed set、优先级与剪枝 | 可处理无法枚举的开放任务 |
| 确定性的算子语义 | 同一步骤不可重现 | 可用自然语言描述任意动作 |
| 可哈希的状态 | 无法判断状态等价、无法去重 | 状态可被模型直接读取 |
| 低成本的扩展 | 每次扩展均为一次模型调用 | 单步可完成传统算子无法完成的推理 |
| 完整的过程记录 | 无过程日志,无法复盘动作 | 实现规模小,迁移成本低 |
综合而言,该系统以放弃搜索算法的严谨性换取自然语言算子的表达能力,并以图结构、租约与契约三项工程约束,将这份自由度限制在仍可收敛的范围内。
6 联想推广
6.1 纵向:尚未取用的机制
Cairn 只取用了状态、算子、后继、目标测试与 frontier 五项,其余机制仍未被使用。下表给出各项在经典搜索中的作用、在 Cairn 中可能的对应形态与落地障碍:
| 未被取用的机制 | 经典作用 | 可能的对应 | 成本 | 主要障碍 |
|---|---|---|---|---|
| closed set | 避免重复扩展同一状态 | 事实语义去重 | 低 | 自然语言状态难判等价 |
| 失败记忆 | 记录已排除分支 | 为 Step 增加 blocked 终态 |
极低 | 仅需状态字段与一次更新 |
| 优先级队列 | 按价值排序扩展 | Decide 为 Step 评分 | 中 | 分值由模型给出,噪声较大 |
| 剪枝 | 超出预算的分支直接舍弃 | 全局步数、Token 与深度上限 | 低 | 需定义超支判据 |
| 双向搜索 | 自目标反向扩展 | 由 Goal 反推所需前置事实 | 中 | 反向算子仍需模型生成 |
| 记忆化 | 相同子问题只解一次 | 子目标级结果复用 | 中 | 子问题等价性难判定 |
| 事务 | 一批操作原子生效 | 一次 Decide 的多条 Step 原子提交 | 中 | 需 schema 支持 |
| 死信队列 | 多次失败的任务移出主队列 | 失败 Step 归档供复盘 | 低 | 与失败记忆同源 |
| 迭代加深 | 小预算多次尝试 | 预算内重启并提高探索温度 | 中 | 需可中断的会话 |
| 租约优先级继承 | 防止高优先任务饿死 | 高价值 Step 优先续租或抢占 | 低 | 与栈式议程冲突 |
其中两项的投入产出比最高。失败记忆仅需增加一个状态字段,即可消除「必然失败的方向被反复领取」这一实际成本,对应经典搜索中最简单的 closed-on-failure。双向搜索的价值在于任务形态:渗透任务的目标通常极为明确,因此可以直接询问「要达成该目标还缺哪条前置事实」,从而自目标反向派生 Step。经典搜索已证明,在分支因子可控时该策略可将指数级搜索降至平方级;而反推前置条件恰是语言模型的强项。本文将这一项视为最值得推进的未发生迁移(本文推断)。
6.2 横向:机制在其他问题上的适用性
先将 Cairn 的机制抽象为其所解决的问题形态:以一张只追加的外部化状态图,协调多个无状态、无通信、可替换的决策者,朝一个可判定的终点收敛。再对候选问题逐一询问四个角色:何者为状态,何者为算子,何者为目标测试,谁写状态。
| 候选问题 | 状态 | 算子 | 目标测试 | 写者 | 缺口 |
|---|---|---|---|---|---|
| 代码审计 | 代码事实、调用链、数据流结论 | 一次追踪或验证性 PoC | 漏洞可复现 | Execute | 无 |
| 漏洞研究 | 二进制或协议观察 | 一次动态或静态实验 | 崩溃或越权可复现 | Execute | 无 |
| 科研实验 | 实验结果与文献结论 | 一次实验 | 假设获得数据支持 | Execute | 目标测试难以程序化 |
| 数据分析 | 数据画像与口径结论 | 一次查询或建模 | 业务问题获得回答 | Execute | 目标测试主观 |
| 事故响应 | 证据与时间线片段 | 一次取证动作 | 根因获得证实 | Execute | 证据可信度无对应字段 |
前两行四项角色齐备,说明该机制具备横向迁移条件,社区中已有基于 Cairn 的二次开发可作佐证。后三行的缺口集中于同一位置:当目标测试无法程序化时,系统的收敛判据失效,需要补充人工裁决或「弱判据加复审」的角色。缺口所在即研究点所在。
6.3 反向:LLM 特化回流经典系统的条件
该方向的成立概率最低,但自检标准明确:须指明 LLM 场景的特殊约束,并判断该约束在经典场景中是否同样成立。
本文归纳的特殊约束有四条(本文推断):算子成本极高,单次扩展可能达数美分至数美元,较经典搜索高若干数量级;结果不可重放,同一输入两次运行结果不同;状态为自然语言,不可哈希、不可精确比对;评价器本身亦为模型,目标测试带有噪声。
据此筛选经典场景,符合「评估昂贵、不要求重放、状态天然为文本」三项条件者有三类:模糊测试的种子调度,可将已确认的覆盖与崩溃写入只追加事实图,并以模型作为变异算子生成器;程序合成的枚举搜索,可用事实图记录已尝试的片段组合与产出,避免重复枚举;材料或分子发现,单次实验昂贵、不要求重放、结果为结构化文本,与 LLM 搜索的成本结构最为接近。
其失效条件亦须写明:需要确定性重放、强实时约束,或状态必须可精确哈希的场景不适用。编译器优化 pass 的搜索与数据库查询计划的枚举即属此类,其算子成本低且确定,引入语言模型反而增加不确定性。
6.4 自检
| 自检项 | 结论 |
|---|---|
| 角色能否逐一指认 | 能。§2.2 给出五项要素的代码落点,§6.2 给出横向迁移的四角色表 |
| 何种条件下不成立 | 指标为重复探索率、每解 Token 成本、失败 Step 重领率;基线为当前栈式议程且无失败记忆的 v0.2.1;失效条件为图规模超出上下文可承载范围后,全量读图方案先行失效 |
| 是否被失配清单否决 | 部分否决。「以向量相似度做状态去重」被失配项 2 阻断:语义相似不等价于状态等价,且合并操作具有破坏性,与只追加原则冲突 |
最后一项是本文自行否决的联想。说明一条路径为何不可行与走通一条路径具有同等价值,因为它标定了该架构的边界:只追加的图不能执行破坏性合并。
7 与 ARTEX 的对照
二者同源于黑板架构,但在多数工程决策上取向相反。
| 维度 | ARTEX | Cairn |
|---|---|---|
| 图的数量 | 双图:资产图与探索图 | 单图:事实与意图 |
| 状态表示 | 结构化节点,七类节点、五类边 | 自然语言文本与一条超边 |
| 目标表达 | goal 节点与逐条证明 |
goal 节点,Cairn_Y 增加 Sub Goal |
| 决策者 | Planner,读取有损摘要 | Decide,读取全图,串行、无状态 |
| 执行者 | Worker,领取一条意图后停止 | Execute,一次一个 Step,可并行 |
| 上下文策略 | 有损摘要与冷节点压缩 | 全量图快照外置为文件 |
| 存储 | PostgreSQL,支持条件更新与行锁 | SQLite,单机单写者 |
| 领取控制 | 条件更新,等价于 CAS | 首次 heartbeat 覆盖写 |
| 意图状态 | 六种以上,含阻塞、耗尽、暂停 | 两种,以 to 是否为空区分 |
| 证据与置信度 | confidence 字段、证据引用、流量记录 |
无对应字段 |
| 治理 | Guard、人工审批、流量代理 | 容器隔离,无审批层 |
| 过程日志 | activity 表 |
无 |
| 交付形态 | 平台与控制台 | 引擎与协议 |
分歧的实质在于对上下文的判断。ARTEX 视上下文为稀缺资源,故构建有损摘要,使模型仅看到热工作集;Cairn 视上下文为可重建对象,故每轮重新读入整图。两种选择各有前提:前者适用于图可持续增长至数万节点的情况,后者要求每个节点被约束为一至两句增量结论。Cairn 用以维持该前提的手段是提示词中的增量要求,属于由模型自觉维持的不变量,一旦失效,全量读取将首先崩溃。
在抗幻觉问题上,二者的着力点不同。ARTEX 以置信度、证据与审批构成证据门,降低结论失真;Cairn 不设此类机制,其依据是渗透场景具备强反馈:shell 连不上、flag 不正确、下一跳不通,错误事实会被环境迅速否证。该依据在强反馈且可即时验证的任务中成立,在反馈稀疏或延迟的场景中不成立。因此 Cairn 的极简具有明确的环境前提,不能整体平移。
8 复现与实测
本机环境为 Windows 与 Python 3.14.7(仓库要求 3.12 以上)。仓库以 uv 管理依赖,本机未安装 uv,故采用虚拟环境直接安装依赖并以 PYTHONPATH 指向源码的等价方式:
1 | python -m venv ..\.venv |
| 轮次 | 条件 | 结果 |
|---|---|---|
| 一 | 直接运行全部用例 | 84 通过,14 失败 |
| 二 | 排除依赖子进程的用例 | 68 通过 |
| 三 | 增加 python3 兼容层后运行全部用例 |
91 通过,7 失败 |
第一轮失败的 14 项日志均为 code=9009,即 Windows 下找不到 python3 命令;原因在于 mock driver 将命令硬编码为 python3(workers/adapters/mock.py:134)。在虚拟环境的 Scripts 目录为 python.exe 建立 python3.exe 硬链接后,6 项 mock 端到端用例全部通过,其中包含 reason → explore → reason → complete 的完整链路。
剩余 7 项失败集中于 test_local_execution.py,原因属平台语义而非逻辑缺陷:用例使用 sleep、sh 等 POSIX 命令;local_process.py:121 调用 os.killpg 终止进程组,Windows 不支持该接口。
由此可得的结论是:核心协议、调度、契约与服务端接口均通过测试;容器模式下的 mock 全链路通过;local 模式仅支持 POSIX。不能由此得出的结论是:真实模型会严格按契约输出,或真实靶场下的解题率与 Token 成本。上述三项均需接入真实端点方可验证,本文未作此尝试。
9 评价
9.1 设计上的有效部分
形式化清晰是首要优点。状态、算子与终止条件三项均有代码落点,读者无需借助类比即可理解系统行为。其次,两类活动的接口极窄:Decide 仅三种输出,Execute 仅一个字符串,窄接口降低了输出解析失败的概率,也使模型替换的成本很低。第三,收敛判据为数据而非陈述,完成必须引用既有事实并落为一条边,因而可审计、可撤销。第四,上下文可重建,模型可替换、会话可中断、进程可重启,长期状态不受影响。第五,触发条件基于增量,无新态势时不调用模型,这一项直接决定成本结构。
此外,失败处理值得注意:收尾阶段将超时由完全损失转为部分收益,撤销机制将错误结论转为负样本。
9.2 设计代价与风险
以下各项均针对开源版 v0.2.1;Cairn_Y 未开源,除 Finding 与 Sub Goal 两项外无法核实。
预算与状态语义方面存在两项限制。系统仅有单任务 300 秒超时与单次 Intent 数量上限,没有「本项目最多运行若干轮或消耗若干 Token」的约束;若目标始终无法判定达成且每轮均能派生新方向,系统将持续扩张,唯一的制动手段是人工停止。同时,意图仅有已结论与未结论两种状态,释放后仍回到待领取,因此必然失败的方向会被反复领取。排除某方向只能写入一条否定性事实,而此类负结论易被后续规划忽略。Cairn_Y 的 Sub Goal 不解决该问题,因其属于目标层次分解,而非动作结局状态。
事实的可信度与可追溯性方面亦有两项。写入一条事实只需一段描述,模型推断与直接观察在数据结构上完全等价,后续规划均建立其上;作者明确表示不采纳幻觉门控类方案,故此项应视为自觉取舍而非疏漏,但该取舍依赖前文所述的环境前提。系统亦无过程日志,无法回答某条事实由哪条命令产生,复盘依赖模型在描述中自行记录路径。
扩展性与部署方面,全量读图存在隐患:事实数量增至数百后,提示词与注意力成本将显著上升,而抑制图增长的手段仅为提示词中的增量要求。存储为单机单写者,所有并发控制均在进程内完成,无法水平扩展。平台方面,local 模式依赖 POSIX 的进程组语义,在 Windows 上不可用。
正确性与安全方面,完成判定缺少验收器。收敛由 Decide 一次判断决定,虽有引用完整性校验,但「这些事实是否确实证明目标」没有程序化检查,人工撤销是唯一纠错通道。安全边界亦依赖容器而非系统,系统中没有 Guard、审批或流量记录;local 模式明确以用户权限运行且无沙箱。
9.3 适用边界
适用于反馈强、结果可即时验证的任务,例如 CTF、靶场、漏洞研究与具备明确测试的代码修复;亦适用于需要过程可解释的探索型任务,因此整条路径均在图上。不适用于反馈稀疏或判断主观的任务,例如合规审查与写作;不适用于需要严格成本上限或逐动作审计的生产环境;不适用于需要确定性重放的场景;商用闭源亦受 AGPL 与作者附加条款限制。
10 结论
Cairn 的贡献不在于提出新的注意力机制或提示词技巧,而在于给出一种可执行的问题重述:把长任务表示为状态空间中的定向搜索,并将全部状态外化为一张只追加的图。在此基础上,系统只需两类活动——一类维护议程,一类推进状态——即可运行,无需角色划分与预定义流程。
按本专题的判据,这属于问题迁移而非概念迁移:它取用了搜索问题的完整形式化,因而也继承了该形式化的可证伪性。其代价同样来自这一取用:为使自然语言成为算子,系统放弃了状态等价判定、优先级调度与过程记录;为使控制器具备开放语义处理能力,系统放弃了收敛性的保证。这些放弃在强反馈环境中可由环境校验补偿,在弱反馈环境中则不能。
作者对这套架构的自我限定值得引用:「大多数看起来很厉害很有道理的 Agent 架构设计其实并没有用,甚至会起反效果,真正厉害的是模型,不是外层臃肿的 Agent 架构,也不是那一堆冗余的 Skill。」该判断的准确含义并非架构无用,而是架构不应替代模型能力。按此标准,Cairn 只做三件事:外化状态、切分决策与执行、提供收敛判据;其余一概不做。
若要从该系统中取用可迁移的部分,本文认为有四条:将完成表示为一条可审计的边;将决定的输出限制为有限形状;以增量触发替代定时轮询;写入议程时独占、写入事实时并流。
参考
- 仓库:oritera/Cairn(本地克隆,main @
8e7e0ea) - 方法论总纲:《LLM新技术中的CS原型(零):专题介绍》,本地
D:\PPT\LLM新技术中的CS原型\LLM新技术中的CS原型.md;本文 §4–§6 的框架、三类迁移判据、阻抗失配清单与三向推广均依该文 - 协议文档:
docs/specs/server-protocol.md(864 行;黑板、Stigmergy 与 OODA 的设计渊源见 15-21 行) - 调度文档:
docs/specs/dispatcher-design.md(1181 行;任务模型与调度策略见 207-600 行) - 作者复盘:《AI 自动化渗透架构 Cairn 满分登顶 TsecBench Cybench —— 众人向左,我偏向右》
- 对照文章:《LLM新技术中的CS原型(四)ARTEX:双图架构解决长任务幻觉问题》
- 第三方概述(AI 生成,仅作背景参考):Wester · Cairn 拆解
- 早前尝试的博客链接
https://mp.weixin.qq.com/s/ZzKF_0MOb0cak9izhHqCUQ抓取时被微信验证码拦截(HTTP 200,内容为验证页),未采信;设计意图的判断改以本地保存的复盘与仓库文档为依据





