渗透 Agent 拆机:高分背后,六套框架到底做对了什么
本文通过拆解六款智能渗透 Agent 的源码与真实执行链路,分析它们在状态管理、上下文控制、工具调用和证据约束上的工程设计与取舍。
渗透 Agent 拆机:高分背后,六套框架到底做对了什么
笔者近期也在尝试开发和使用各类智能渗透 Agent。恰逢腾讯安全云鼎实验室推出智能攻防 Agent 评测体系 TSecBench,并公开了基于统一评测标准得出的 TSecBench 跑分榜单。该评测覆盖国内外具有代表性的智能渗透 Agent 与国内主流大语言模型,也为我们提供了一个很自然的切入点:沿着公开结果进一步阅读其中表现亮眼项目的源码,看看这些实现采用了哪些工程设计,并尝试从中获得启发。
为此,我们把 Cairn、CHYing、LuaN1ao、BreachWeave、CyberStrikeAI 和 T3MP3ST 的源码全部拉了下来,沿着实际执行路径往里追:状态保存在哪里,任务如何切分,工具怎样暴露给模型,长输出如何处理,又是谁有权宣布任务结束。
横向看下来,比“谁的 Agent 更多”更值得关注的,是四种工程能力:
- 状态能否跨步骤留下来;
- 上下文能否只装当前需要的信息;
- 工具调用能否持续推进而不空转;
- 模型的结论能否被证据和系统状态约束。
一、逐一拆机
接下来不按功能清单介绍项目,而是为每个框架追一个最有辨识度的问题。文中对优点与取舍的判断,只限定在相应提交和可见配置范围内,是笔者对工程实现的理解,不作为对项目整体能力的定论。
Cairn:把渗透改写成状态空间搜索
跨模型平均成功率 77.70%(第 1)|多阶段渗透 44.6(全场最高)|平均 Token 458.50M(全场最高)|技术栈:Python
大多数多 Agent 框架先给角色命名,再规定它们怎样交接。Cairn 采用了另一条路线: 不预设固定角色,而是把渗透过程建模成从 Origin 到 Goal 的状态空间搜索。 核心框架负责保存状态、生成探索方向和调度并发;具体使用哪些命令和工具,则交给运行在 Kali 环境里的通用 Coding Agent。这种设计为理解它的长链任务表现与 Token 消耗提供了一条重要线索。
它的执行路径可以压缩成下面这条链:
Cairn 执行路径总览:Bootstrap 先尝试端到端直解;未直解但已有进展时,将结果沉淀为 Fact,再进入 Reason、Intent 与 Explore 构成的持续重规划循环。
这其实是一个很务实的「两速系统」:简单题不必先拆任务,Bootstrap 可以直接尝试完成;复杂题未能直解时,已经确认的发现仍可写回共享图,再切换到可并行、可反复重规划的搜索模式。
源码解剖①:不是聊天记录接力,而是可持久化的 Fact–Intent 图

图 1:models.py:13-29。重点看 from_ 与 to:它们定义了事实之间如何通过一次探索建立连接。
抽取其中与图关系直接相关的字段,可以简化为:
class Fact(BaseModel):
id: str
description: str
class Intent(BaseModel):
id: str
from_: list[str] = Field(alias="from") # 依赖哪些已确认事实
to: str | None = None # 完成后指向新事实
description: str
creator: str
worker: str | None = NoneFact 表示已经确认的状态,例如开放端口、有效凭据或已获得的权限;Intent 表示一条尚待验证的探索方向。一个 Intent 可以同时依赖多个 Fact,执行结束后只产出一个新 Fact。更关键的是,conclude 接口不是让 Worker 随意改图:它会 先生成新的 Fact ID,再把当前 Intent 的 to_fact_id 指向该 Fact。按正常 API 写入路径,图因此只向新状态追加,形成一张有向无环图,而不是容易覆盖、散落在多个会话里的聊天记录。
这张图物理化到 SQLite:facts 保存节点,intents 保存探索边,intent_sources 保存一条边与多个源节点的关系(server/db.py:32-58);连接启用 WAL(server/db.py:111),允许多个 Worker 通过同一事实板协作,并支持进程重启后的状态恢复。
这里需要把结论说准:它保存的是 Worker 提炼后的 确认事实,不是每条原始命令和完整终端输出。因此 Cairn 解决的是“关键结论不被困在某个 Agent 会话里”,并非严格意义上的“信息零损失”。与此同时,Fact 的准确性仍依赖 Worker 的提炼质量;一条未经充分验证的结论若被写入事实板,也可能影响后续 Worker 的判断。
源码解剖②:Bootstrap 直解 + Reason/Explore 搜索,长链可以随状态重规划
调度器 _try_dispatch_project 解释了 Cairn 的完整推进逻辑。图中蓝色区域就是整个决策主干:

图 2:scheduler/loop.py:286-318。初始项目优先进入 Bootstrap;进入图搜索后,Reason 负责生成方向,Explore 负责认领方向并执行。
抽掉日志和并发保护后,主干代码可以简化为:
if self._is_initial_project(project):
if self._project_requires_bootstrap(project):
return self._dispatch_initial_project(project) # 先尝试端到端直解
return self._dispatch_reason(project, export_yaml, "initial")
if project.project.reason is None:
reason_trigger = self._reason_trigger(project)
if reason_trigger is not None:
return self._dispatch_reason(project, export_yaml, reason_trigger)
if unclaimed_intents:
newest = max(unclaimed_intents, key=lambda i: i.created_at)
return self._dispatch_explore(project, export_yaml, newest)从源码机制看,其中有三点尤其值得关注:
- 状态变化才重规划。
_reason_trigger只在 Fact 增加、Hint 增加,或开放 Intent 从有到无时触发,而不是每个调度 tick 都空转。新权限、新凭据或人工提示一旦进入图,就能改变下一轮决策。 - 规划和执行解耦。 Reason 只判断“目标是否完成、还应探索什么”,并要求新 Intent 高价值、互不重叠且可独立并行;Explore 每次只负责一个 Intent。示例配置允许单项目最多 4 个 Worker 并发、每轮 Reason 最多生成 2 个 Intent(
dispatch.example.yaml:13-39)。 - 失败也尽量沉淀进度。 对于支持 session 续接和 conclude 的 Worker,Bootstrap 或 Explore 超时、输出解析失败时,调度器会尝试在同一 session 进入
conclude阶段,只总结已经确认的增量事实,而不是直接丢弃整段执行(tasks/bootstrap.py、tasks/explore.py)。
从设计目标看,这套机制适合承接侦察、立足、提权和横移等跨阶段信息:每一步只要把可复用结论写成 Fact,后续 Worker 就能从全图继续。这与 Cairn 在报告中取得的 多阶段渗透 44.6、全场最高 处于同一方向。
源码解剖③:“Zero MCP”不等于“没有工具”,而是不用工具 Schema 做中间层
以 Claude Code 适配器为例,Cairn 启动 Worker 的方式确实很直接:

图 3:workers/adapters/claudecode.py:38-51。框架把 prompt 交给 Claude Code CLI,并通过 --dangerously-skip-permissions 跳过交互式权限确认。
图 4:container/Dockerfile:1-59。这里可以确认,Zero MCP 指的是“不使用 MCP 工具封装”,不等于运行环境里没有渗透工具。
所以它真实的能力栈是:
Cairn 状态图与调度器
↓ prompt
Claude Code / Codex / Pi 等通用 Coding Agent
↓ shell
Kali 工具链 + PoC/知识库 + 每项目持久容器作者所说的 Zero MCP / Zero RAG / Zero predefined roles,准确含义是:Cairn 没有把 Nmap、Nuclei 等能力逐一包装成 MCP 工具,也没有把知识库做成向量检索,更没有预设角色工作流; 工具仍然存在,只是由 Coding Agent 通过 shell 自主选择和组合。 这种设计省去了大量工具 schema 和固定 playbook,也保留了临场组合能力。
源码解剖④:全图重读如何保持连续性,也带来上下文成本
每次 Reason 或 Explore 开始前,Cairn 都会把当时的完整图导出为 YAML 快照,写入当前项目容器,并在 prompt 中明确要求 Worker “read the entire file”:

图 5:tasks/common.py:56-70。每个任务拿到的不是一段手工摘要,而是本轮完整的图快照。
从实现看,这种完整快照有利于 Worker 恢复全局状态,同时也会带来持续增长的上下文成本:
- 图越长,单次 Reason/Explore 重读的输入越大;
- 多个 Intent 并行时,每个独立会话都会在应用层各自读取一份图;底层是否能够利用提示缓存,则取决于实际模型服务和调用配置;
- 每个 Explore 内部又是一个可持续调用 shell 工具的 Coding Agent 会话,示例配置给主阶段 300 秒,超时后还可用同一 session 再做 90 秒的事实收尾。
因此 Token 成本更接近 Σ(每次任务启动时的图大小 + 任务内工具轨迹)。它会随图规模和探索分支数快速增长,但不能严谨地称为“指数膨胀”,也不是“完全不受限”——源码里有并发数、Intent 数和超时三层硬限制。
源码地址:Cairn
CHYing:把上下文当稀缺资源,75% 成功率下的低 Token 路径
跨模型平均成功率 75.00%(第 2)|平均 Token 27.92M|CHYing + Kimi K3:15.78M Token / 85.14%|云攻击 100.0|技术栈:Python + Claude Agent SDK
如果说 Cairn 的答案是“让每个 Worker 看见完整局面”,CHYing 更强调缩小信息面: 尽量让主 Agent 只接收当前决策所需的信息。 源码固定注册 Executor、Browser、Reverse 三类子 Agent,并提供一个默认开启、可通过环境变量关闭的 C2 Agent;角色数量只是表面,更值得关注的是它设置的三道上下文闸门。
Cairn 平均使用 458.50M Token,CHYing 为 27.92M,约为前者的 6.1%,平均成功率相差 2.7 个百分点。由于两项都是同一组四款模型各取最优轮次后的汇总结果,这组数据提示我们值得进一步检查框架层的上下文设计;源码显示,CHYing 确实在工具定义、工具输出和跨会话记忆三个位置设置了上下文闸门:
CHYing 架构总览:唯一 Orchestrator 负责策略判断,专业工具按角色隔离;长输出与跨会话记忆分别通过落盘和 handoff 控制上下文体积。
这不是简单地“少给模型一些 Token”,而是把 策略上下文、专业工具上下文、原始执行数据 拆到不同层级,只在需要时建立连接。
源码解剖①:先按字符计算工具 Schema 的固定成本
先看 Orchestrator 的工具黑名单:

图 6:brain_agent/claude_advisor.py:50-65。注释直接记录了各工具定义的字符数,并估算一次可移除约 4,400 Token 的上下文。
关键代码可以压缩成:
DISALLOWED_TOOLS = [
"WebSearch", "WebFetch", "AskUserQuestion", "TodoWrite",
# 移除可节省 ~17K chars(~4,400 tokens)
"EnterPlanMode", "ExitPlanMode",
"EnterWorktree", "ExitWorktree",
"CronCreate", "CronDelete", "CronList", "NotebookEdit",
]工具不只是一个名字。每个工具都带有用途说明、参数 Schema 和调用规则,它们会进入模型上下文;工具越多,模型每轮都要在更大的动作空间中选择。CHYing 因此直接删掉 CTF 中低价值的规划、Worktree、Cron、Notebook 等工具。WebSearch / WebFetch 还有另一层作用:避免直接搜到公开 Writeup,保证测评过程不走捷径。
源码解剖②:MCP 可见性不是“少装工具”,而是让工具只在需要它的角色中出现
第二道闸门更关键。源码先给每个 MCP 服务附加可见范围:

图 7:claude_advisor.py:173-207。加载配置时保留 subagent:browser 和 subagent:reverse 可见性标记;实际过滤行为依赖项目配套的定制 CLI。
浏览器子 Agent 的注册表则明确列出了 29 个 Chrome DevTools 工具:

图 8:agents/tool_registry.py:8-38。从 click、fill_form 到网络请求、性能分析和截图,共 29 个浏览器工具。
对应的配置非常短:
"chrome-devtools": { "visibility": "subagent:browser", ... },
"ghidra-mcp": { "visibility": "subagent:reverse", ... }按照仓库作者说明及其定制 CLI 的 visibility 约定,Orchestrator 只需要知道“可以委派 Browser 或 Reverse”,不必同时读取 29 个浏览器工具和整套 Ghidra 工具的 Schema;Browser 的显式工具列表也不包含 Bash、Glob、Grep、Write 等本地执行工具,需要脚本化枚举时需交还父 Agent,再转派 Executor。 这种隔离既缩小了工具 Schema,也收窄了各角色的动作范围。
源码解剖③:用“长输出落盘 + 只返回证据和指针”控制动态上下文
工具 Schema 是固定成本,扫描器和命令输出才是会随任务不断累积的动态成本。CHYing 的 exec 工具对输出设置了 40,000 字符阈值:
图 9:claude_sdk/mcp_tools.py:624-630(阈值定义见第 46 行)。输出超过 MAX_OUTPUT_LENGTH 后调用 save_long_output,主上下文只接收摘要。
MAX_OUTPUT_LENGTH = 40_000
if len(output) > MAX_OUTPUT_LENGTH:
_, summary = save_long_output(output, cmd_prefix)
return _as_text_result(summary, is_error=exec_failed)save_long_output 会把完整结果写入 dumps/,自动提取 flag、密码、API Key、SQL 报错等证据,只向模型返回文件路径、最多 5 条关键证据和前 500 字符预览(utils/output_utils.py:70-135)。这相当于把“大段观测”从对话上下文移到文件系统:具备 Read/Grep 权限的 Agent 仍能按路径或关键词回查,而原始长输出不会直接进入后续每一轮对话。
从机制覆盖范围看,工具裁剪减少的是固定前缀,长输出落盘控制的是会随执行轮数累积的历史负担;两者共同构成了 CHYing 的上下文控制策略。至于各自对公开 Token 结果贡献了多少,仍需单独的对照实验。
源码解剖④:它不是直接删除记忆,而是先编译,再做垃圾回收
到了上下文压缩和任务重试边界,CHYing 又设置了两层“记忆编译器”:
ProgressCompiler在同一任务发生 compact 时读取progress.md + findings.log,必要时只用 Grep 回查大日志,生成compact_handoff.md;RetryHandoffCompiler在下一次重做旧题前整合发现、执行摘要、Hint 和历史日志,生成更完整的retry_handoff.md。
只有 RetryHandoffCompiler 成功生成接手报告后,才会执行下图中的清理:

图 10:retry_handoff_compiler.py:281-326。删除 progress.md、attack_timeline.md 等膨胀文件,同时明确保留 retry_handoff.md、findings.log、Hint、PoC 脚本和执行/反思摘要。
换句话说,这不是“先删日志,再祈祷模型记得”,而是:
原始轨迹 → 提取已验证发现 / 失败方向 / 复现步骤 → handoff
│
handoff 成功后再删除工作区冗余副本但它仍然是 有损压缩:哪些细节进入 handoff 由压缩模型判断,隐性的凭据关联、命令顺序或网络拓扑仍可能被遗漏。作者也在 README 中明确写道,ProgressCompiler 和 RetryHandoffCompiler 没有做过单独的 A/B 测试。因此更严谨的结论是:这套机制展示了 CHYing 如何控制上下文,同时也提示我们,长链任务中的摘要完整性仍值得关注。
源码地址:CHYing-Agent
LuaN1ao:把「结论必须有出处」编译进图结构
跨模型平均成功率 74.32%(第 3)|Web 漏洞挖掘 81.9(全场最高)|多阶段渗透 28.6|技术栈:TypeScript + Pi SDK
压缩上下文只能解决“看多少”,解决不了“写进去的东西能不能信”。LuaN1ao 接着处理后一半:它不只在 Planner、Executor、Observer 之间传递聊天记录,而是把运行状态拆成三张图。Task Graph 管任务与依赖,Operation Graph 管主机、端口、凭据和 Session,Reasoning Graph 管 Evidence、Hypothesis、Vulnerability 与 Exploit。Observer 又拆成热路径 Supervisor 和异步 Projector:前者决定继续、交接或停止,后者把执行事件转成长期图记忆。
LuaN1ao 架构总览:Planner 通过 Task Graph 调度任务;双模 Observer 分别负责热路径控制和异步投影,长期结论经过证据门后写入 Operation Graph 与 Reasoning Graph。
源码解剖①:漏洞与成功利用过不了「硬证据门」,就进不了图

图 11:src/projection.ts:914-923。两类高价值结论都必须引用当前投影批次中可解析的观测。
核心判断可以缩成四行:
if (nodeType === "Vulnerability" && !hasResolvableEvidence) errors.push(...)
if (nodeType === "Exploit"
&& nodeProperties.status === "succeeded"
&& !hasResolvableEvidence) errors.push(...)
if (errors.length > 0) throw new ProjectionDraftIntegrityError(
`...; No part of the delta was accepted`
)这不是在 Prompt 里劝模型“谨慎一点”,而是在写入层设约束:Vulnerability、状态为 succeeded 的 Exploit,以及被证伪的 Hypothesis,都必须带有本批观测中能解析的 evidenceRef;任一校验失败,整份 graph delta 原子拒绝。
它解决的是 可追溯性,并不自动等于结论绝对正确——模型仍可能把一条相关性很弱的真实观测挂到错误结论上。但与只保存自然语言总结相比,这道门至少让每个强结论都有可以回查的来源,也让 Planner 能围绕证据、假设、漏洞、利用的层级关系继续规划。
从机制上看,Web 任务中的 HTTP 响应差异、页面行为和工具结果通常较容易形成即时、可引用的证据,这与 LuaN1ao 在 Web 漏洞挖掘上的 81.9 分方向一致。不过,公开测评没有提供关闭证据门的对照结果,二者不能据此建立直接因果关系。
源码解剖②:“40 轮”并非硬上限,长链会经历多次 checkpoint 与再决策

图 12:src/controller.ts:153-168。默认任务先获 12 turns,每个执行 epoch 最多 20 turns,默认 run 时间预算为 900,000 ms。
export const DEFAULT_TASK_BUDGET = { maxTurns: 12 }
export const MAX_TASK_BUDGET = { maxTurns: 40 }
export const DEFAULT_EPOCH_TURN_SLICE = 20
export const DEFAULT_RUN_TIME_BUDGET_MS = 900_000
const DEFAULT_MAX_PARALLEL_TASKS = 2这里,MAX_TASK_BUDGET=40 只限制 初始分配;Planner 可以通过 patch_task.additionalTurns 继续增加累计额度,源码和 Prompt 都明确写着 maxTurns “不是生命周期硬上限”。达到 task/epoch 预算时,Runtime 触发的也是 handoff checkpoint,而非直接结束整个任务。
因此,多阶段渗透 28.6 不能简单归因于“12 轮或 40 轮后被截断”。源码能确认的是:短初始预算、20-turn epoch、默认两个并行任务、异步 Projector 以及 Planner 重新分配,会让长攻击链多次跨越 checkpoint 和角色边界;每次切换保留了结构化状态,也增加了协调步骤。
源码地址:LuaN1aoAgent
BreachWeave:把“复盘者”放到主解题循环之外
跨模型平均成功率 73.65%(第 4)|BreachWeave + Kimi K3:69.08M Token / 86.49%|云攻击 91.7|二进制漏洞挖掘 88.5|技术栈:TypeScript + Bun + pi-mono SDK
LuaN1ao 关注“什么结论能写进记忆”,BreachWeave 则进一步关注如何及时识别低效或重复的探索路线。Manager 负责任务调度;在启用 Observer 的 Prompt 配置下,Solver 在主会话里解题,Observer 在独立 Session 中定期阅读被裁剪过的近期轨迹。它更像一名坐在场边的教练:不替选手出手,只整理战术板,并在满足提醒条件时给出一句纠偏建议。
BreachWeave 架构总览:主 Solver 持续执行;启用 Observer 后,独立会话负责复盘、维护 Idea/Memory,并通过提醒门向主线回注短消息;最终完成状态由外部桥接查询。
源码解剖①:Observer 的推理在旁路,纠偏只回注一条短消息

图 13:observer-loop.ts:9-19。每 6 轮触发一次审查,只取最近 10 轮,并对参数、结果、摘要和 Session 注记分别限长。

图 14:observer-loop.ts:266-275。Observer 完成独立复盘后,只有通过效率提醒门槛才向主会话发送 steer。
const OBSERVER_REVIEW_EVERY_ROUNDS = 6
const OBSERVER_REVIEW_WINDOW_ROUNDS = 10
sendCorrectionNotice: async (message) => {
if (!(await shouldSendEfficiencyReminder(next, message))) return false
pi.sendUserMessage(`纠偏提醒:${message.trim()}`, { deliverAs: "steer" })
return true
}值得注意的是:Observer 是否装配由 Prompt 元数据中的 observerEnabled 决定,并非所有配置都会启用;当前提交内置的 kimi-security Prompt 将其设为 true。启用后,Observer 的完整分析轨迹不占 Solver 的主会话,但 steer 生成的那条短提醒 会进入 Solver 上下文。它还带有 6 轮冷却、12 轮重复窗口和指纹去重,所以并非每次复盘都打断执行。这种结构把“持续自我反思”的大段成本挪到旁路,只把可执行的纠偏结论送回主线。
源码解剖②:Idea 管方向,Memory 管事实,进入上下文前再裁一次

图 15:challenge/memory.ts:4-34。Idea 有验证状态,Memory 则按 fact、evidence、failure、note、hint 分类。
type IdeaStatus = "pending" | "testing" | "verified" | "failed" | "skipped"
type MemoryKind = "fact" | "evidence" | "failure" | "note" | "hint"
const SOLVER_MEMORY_LIMIT = 10
const SOLVER_IDEA_LIMIT = 8
const SOLVER_MEMORY_CONTENT_MAX_CHARS = 220
const SOLVER_IDEA_CONTENT_MAX_CHARS = 120Idea 是待验证路线,不会因为写进看板就升级成事实;Memory 保存已经值得跨轮复用的信息。Observer Prompt 的维护优先级是 NO_CHANGE > update > delete > add,目标体积为 memory 12 条以内、ideas 8 条以内;Manager 真正给 Solver 组装 handoff 时更保守,只取最多 10 条 Memory 和 8 条 Idea,并继续限长。这里的重点不是“记得越多越好”,而是让主会话反复看到一个小而稳定的决策面。
这种做法压缩了主会话反复读取的信息量,不过 Observer 本身仍会产生额外模型调用,纠偏效果也会受到近期轨迹裁剪与摘要质量的影响。
源码解剖③:结束权不交给 Solver 的主观判断
图 16:ralph-loop.ts:49-85。收到 agent_end 后先查询 challenge_is_completed;若外部状态仍未完成,正常结束会触发下一轮,连续错误则另有最多 10 次的重试限制。
pi.on("agent_end", async (event) => {
if (await isChallengeCompletedByHostBridge()) return
// 未完成:注入 continuation,并 triggerTurn
})这道外部终止门可以减少模型仅凭自身判断过早结束的情况,也能在多 flag 题中继续追踪剩余目标。它展现了对持续执行的重视,但题型难度、模型能力和工具环境同样会影响结果,不能把二进制漏洞挖掘 88.5 直接归因于续跑钩子。
分支与配置边界:本文分析的是
main(CTF)分支。README 所述的命令改写、大结果落盘等工具级压缩,在该分支的 Solver 装配中处于注释禁用状态;在observerEnabled: true的配置下,可以确认启用 Observer 输入裁剪与 Idea/Memory 看板维护,Manager 侧还会执行 handoff 限长与外部终止判断。pentest分支的增强能力不混入本文结论。
源码地址:BreachWeave
CyberStrikeAI:把 Agent 做成一套可切换、可治理的执行平台
跨模型平均成功率 71.96%(第 6)|云攻击 91.7|平均耗时 15.92h(本文六个项目中最短)|技术栈:Go + CloudWeGo Eino
前四个项目都在精雕 Agent 的“脑回路”,CyberStrikeAI 更像是在搭一座作战中心。它没有押注某个单一推理算法,而是把工具、角色、审批、审计和多种编排统一到一套平台底座上:上层可以换单 Agent、Deep、Plan-Execute 或 Supervisor,下层仍走同一套受治理的执行链路。
CyberStrikeAI 架构总览:统一入口选择不同编排模式,各模式共用角色、声明式工具、工具搜索、外部 MCP 以及审批、审计和持久化能力。
源码解剖①:编排模式不是写死的角色剧本
图 17:internal/multiagent/runner.go:538-594。Supervisor 接收子 Agent 列表;默认分支则构造 Eino deep.TypedConfig。同一文件前一分支还装配 Plan-Execute。
switch orchMode {
case "plan_execute":
da = peRoot
case "supervisor":
da, _ = supervisor.New(ctx, &supervisor.Config{...})
default:
da, _ = deep.NewTyped[*schema.AgenticMessage](ctx, dcfg)
}这让复杂任务可以按形态选择控制流:阶段清晰时先计划再执行,需要专家路由时用 Supervisor,开放探索则用 Deep。源码可以确认的是: 平台具备这些编排路径。
源码解剖②:90 份 YAML 把工具接入变成配置问题

图 18:internal/config/config.go:1405-1414。命令、参数、简介和允许退出码都由 YAML 映射到统一结构。

图 19:对所分析提交本地计数,tools/ 下共有 90 个 .yaml 文件。
name: "nuclei"
command: "nuclei"
enabled: true
short_description: "快速漏洞扫描器,使用YAML模板进行漏洞检测"
parameters:
- name: "target"
required: true
flag: "-u"这种设计把新工具接入从“改 Go 代码”降成“增加一份 recipe”,角色还可以只绑定必要工具,再通过 tool_search 按需解锁更多能力。90 份配置中能看到 cloudmapper、checkov、kube-hunter、kube-bench 等云与容器安全工具,这为云攻击任务提供了较宽的执行面。
源码解剖③:把慢工具和坏服务隔离在 Agent 推理之外

图 20:internal/mcp/external_manager.go:887-934。连续失败达到阈值后打开熔断窗口;每个服务还拥有独立 semaphore。
MaxConcurrentPerServer: 2,
MaxConcurrentTotal: 16,
CircuitFailureThreshold: 3,
CircuitCooldown: 60 * time.Second,外部 MCP 默认每服务最多 2 个并发、全局最多 16 个并发,连续失败 3 次后冷却 60 秒。长任务还可以先返回 execution_id,让 worker 在后台继续执行,Agent 随后再调用 wait_tool_execution。这能避免一个慢服务长期占住单次推理回合,也能在冷却窗口内减少对异常服务的连续调用。不过,随着编排模式、角色和工具配置增多,部署、调试与运行治理的复杂度也会相应提高。
源码地址:CyberStrikeAI
T3MP3ST:中央调度的八角色攻击链
跨模型平均成功率 70.61%(第 7)|T3MP3ST + Kimi K3:90.54%(并列最高)、74.32M Token / 14.49h|对抗规避 96.4|漏洞利用 47.2|多阶段渗透 35.7|技术栈:TypeScript
最后看 T3MP3ST。它所说的 swarm,并不是八个 Agent 在同一个群聊里自由讨论,也不是完全去中心化的蜂群,而是一套 中央调度、角色分工、共享任务状态 的攻击链编排架构。最上层的 TempestCommand 统一持有任务、角色、目标、证据和工具等子系统;任务由 MissionControl 排队,再由 OperatorCell 按 operatorType 找到对应角色的空闲 Operator 执行。
八种 Operator 分别覆盖侦察、扫描、初始访问、横向移动与提权、收集与外传、持久化与清理、任务协调、分析报告。它们并不是八套完全不同的执行引擎:每个角色都可以挂载同一套 AgentLoop + Arsenal,主要通过系统 Prompt、攻击阶段和可用工具范围形成分工。换句话说,T3MP3ST 的“群体性”首先来自 任务被拆给不同岗位,而不是 Agent 数量本身。
T3MP3ST 架构总览:TempestCommand 负责中央编排,MissionControl 管理任务阶段,OperatorCell 按角色派单;目标状态、证据和共享线索在编排层汇合,每个 Operator 内部再使用通用的 AgentLoop 与工具层执行任务。
源码解剖①:任务先进入中央队列,再按攻击阶段匹配角色
const pendingTasks = taskQueue.getPending()
const availableOps = this.cell.getAllOperators()
.filter(op => op.archetype === task.operatorType && op.isAvailable())
taskQueue.assign(task.id, operator.id)
operator.assignTask(task, target)这里的任务对象本身带有 phase 和 operatorType。主循环从队列取出待执行任务,只在角色类型匹配且处于空闲状态的 Operator 中选择执行者;如果没有对应角色,编排器还可以按容量自动补充同类 Operator。因此,同一阶段可以分派多个任务并行推进,但任务归属、依赖检查、超时回收和阶段切换仍由中央控制器掌握。
这种设计更接近一支由任务系统派单的专业行动小组:Recon 不需要承担利用和报告的全部上下文,后续角色也可以围绕各自阶段工作;与此同时,TempestCommand 与任务队列成为协作枢纽,整体推进效果会更依赖阶段划分、角色路由和共享状态是否准确。
源码解剖②:共享黑板把“工具验证的发现”变成下一阶段任务
if (this.coordinationEnabled) {
this.packBoard.postLead(operator.id, { provenance: prov, ... })
}
if (
this.coordinationEnabled &&
finding.verifyGate?.provenance === 'tool' &&
!this.spawnedFollowups.has(finding.id) &&
this.spawnedFollowups.size < this.maxFollowups
) {
queue.add({ phase: nextPhase, operatorType: nextOp, ... })
}PackBoard 才是这套 swarm 从“角色分工”走向“协作跟进”的关键。某个 Operator 发现线索后,编排器会把它写入共享黑板,并保留 tool / context / none 等来源标记。只有 provenance === "tool" 的 finding 才会触发后续动作:系统沿攻击链找到下一阶段,为对应角色创建一条高优先级任务,要求它围绕这条具体线索继续确认和推进,而不是重新进行一轮泛化扫描。
这意味着有效链路更接近“Recon 用工具确认服务 → Scanner 针对该服务验证问题 → Exploiter 接着追同一攻击面”,而不是八个角色机械地依次运行一遍。代码同时用 finding 去重,并将单次任务生成的 follow-up 默认限制在 24 个,避免一条线索被反复追逐或任务数量失控。该反馈回路需要显式开启 T3MP3ST_SWARM_COORD;仓库中另一个位于 src/stubs/ 的高级 SwarmController 仍属于接口脚手架,不应与这条已经写进主控制器的 OperatorCell + PackBoard 协作链混为一谈。
源码解剖③:每个 Operator 内部共用有预算、会并行、能反空转的 ReAct 循环
图 21:src/agent/index.ts:126-137,237-301。AgentLoop 构造器默认 15 次迭代、50,000 Token 阈值、单轮最多 5 个并发工具调用;连续 4 轮无新发现时注入换路提醒。编排层对 local-agent provider 另有默认 30 次迭代的覆盖值。
maxIterations: 15,
maxTokens: 50000,
maxToolOutputLength: 4000,
maxConcurrency: 5,
if (seenCalls.has(callHash)) return duplicateCallError
if (noProgress >= 4) messages.push(changeVectorOrFinish)这条循环有三种直接的成本控制:同一模型响应里的工具调用按最多 5 路并发执行;工具结果进入上下文前会做头尾保留式截断(默认 verboseToolOutput=true,因此 4,000 字符基准实际放宽为 16,000 字符);完全相同的工具与参数组合不会重复跑,连续四轮没有新 finding 还会被提醒换路或收尾。
maxTokens=50000 也不是严格意义上的“每题最多 50K”:代码在一次模型调用结束后才检查累计值,撞线后还会额外请求一次最多 2,048 Token 的最终总结。因此它更准确地说是 停止阈值。从实现上可以确认,这些机制为减少重复调用、控制单轮等待和限制上下文提供了具体手段。
仓库说明中给出的公开 benchmark 使用的是这条单 Agent ReAct 执行路径。放回 swarm 架构来看,它同时也是每个角色真正调用模型和工具时共用的执行内核,因此这里的预算、并发和反空转机制并非独立于 swarm,而是 Operator 接到中央派单之后的“单兵作业方式”。
源码解剖④:OPSEC 是监测信号,真正 fail-closed 的是执行前 Scope Gate
图 22:src/arsenal/index.ts:372-415。目标一旦不在授权范围,工具 handler 尚未运行就返回 SCOPE DENIED;接入 ApprovalController 后,高风险工具还会进入审批门。
const blockedHost = scopeViolation(this.scope, context)
if (blockedHost) return { success: false, error: "SCOPE DENIED ..." }这与 OPSEC 模块要分开看。opsec/index.ts 会按 active detections 与阈值的比例标注 low / medium / high / critical,并给出 abortRecommended;主控制器监听后发出 abort:recommended 事件。源码没有显示该事件会必然自动中止每次行动,因此更准确的描述是“发出中止建议”,而不是“检测到就自动减速或撤离”。
能确认执行效果的是上图的 Scope Gate:越界调用在 handler 前被拒绝;接入 ApprovalController 后,intrusive、credential、dangerous 工具还会要求批准。不过,安全护栏回答的是“允许对哪里、用什么做”,并不等价于测评里的“对抗规避”能力。
源码地址:T3MP3ST
二、结语与资料说明
把六个框架放在一起看,比“Agent 数量”更值得关注的,是状态怎样保存、上下文怎样流动、工具怎样受控,以及完成条件由谁裁决。Cairn 强调全局状态,CHYing 强调上下文隔离,LuaN1ao 强调证据约束,BreachWeave 强调旁路复盘,CyberStrikeAI 强调平台化治理,T3MP3ST 则把角色化攻击链、共享证据和受控执行循环组合在同一套中央编排中。
以上内容只是笔者基于公开源码进行的一次学习和主观解读,若与项目作者的原始设计意图存在偏差,欢迎交流指正。
文中所列成功率、分项成绩、耗时与 Token 数据,参考腾讯安全云鼎实验室发布的《智能渗透 Agent 能力测评报告·第一期(2026 Q2)》;最新结果可查看 TSecBench 跑分榜单。