FFAI Agent 实现架构全解剖
姊妹篇《Claude Code 源码架构全解剖》讲的是「一条消息在 CC 里的一生」;本文用同样的解剖刀切开我们自己的系统:FFAI Agent(NestJS 后端 254 个 TS 文件 / 约 4.5 万行),逐层拆解实现,并在每一层与 Claude Code 做机制级对比。所有结论标注 文件:行号(相对 backend/src/modules/agent/),来自 2026-07-07 对真实代码的核验。
00全景架构:六层 vs 六层
CC 是「React 渲染到终端的前端 + while(true) 引擎 + 工具执行器」;FFAI 是「多组织服务端的同一个 while(true),外面包一圈企业治理」。
两者内核惊人一致:没有计划器,只有一个 while(true)——模型不停要工具,循环不停喂结果。根本差异只有一个:信任边界的位置。CC 跑在用户自己的机器上,信任边界是「这台机器的主人」,安全靠权限弹窗;FFAI 跑在公司服务端、服务全组织员工,信任边界是「RBAC + 多组织隔离 + 不可篡改审计」,安全必须焊死在代码结构里(收口点、fail-closed、哈希链),不能依赖任何人点弹窗。
API-first 多端平行;CC 对应层 = Ink 终端 UI + print 模式 + Agent SDK(单机单用户)
SSE Controller、Teams webhook、Desktop WS 桥;actor 解析(JWT → userId/orgId)+ 设备绑定校验。CC 对应 = entrypoints/cli.tsx 的 flag 分流
runTurnStream = 真正的 TAOR while(true)(≈ CC queryLoop);QueryEngine 只做历史 compaction(早期 TAOR 骨架已删)。子 agent 同层递归(delegate_task)
ToolRegistry(85 个 @AgentTool + MCP 动态)、SkillLoader(4 层 scope)、HooksRegistry(13 事件)。CC 对应 = tools.ts 注册表 + skills/ + hooks
四轨执行器(service/mcp/cli/client)+ 5 家 provider 经厂商中立 IR + ★ModelRouter(CC 完全没有的一层)+ per-user Docker 沙箱
Canvas artifact(13 类型 + append-only 版本链 + 自动存盘工作区)。CC 对应 = 终端里的工具卡片折叠 + StreamingMarkdown
HMAC 审计哈希链、org/user 双 scope 月度配额、blob 存储、文件化记忆、设备管理。CC 对应 = cost-tracker + transcript 落盘(本地 JSONL,无审计语义)
三个最重要的文件:如果只看三个文件,看 services/messages.service.ts(2947 行,主循环 + failover + 上下文组装全在这)、tools/tool-registry.service.ts(工具怎么被治理着执行——所有调用的唯一收口)、protocols/(厂商中立 IR + 双协议编解码,多 provider 的地基)。对应 CC 的 query.ts / toolExecution.ts / claude.ts。
01一条消息的一生 ★
与 CC 篇同构的主线:从前端 POST 到 SSE 流回完整回复,共 12 站。每站底部灰绿色小字是 CC 的对应站点。
HTTP 入站:SSE Controller
前端 POST /agent/messages/stream。Controller 设 SSE 头(X-Accel-Buffering: no + flushHeaders)、每 15s 写 : ping 心跳注释行;resolveActor 从 JWT 解出 userId/orgId,devices.touchAndAssert 校验设备未被撤销(撤销 → 403)。
controllers/messages.controller.ts:124,146-172 · devices/devices.service.ts:31
CC 站 1-3:终端按键 → Ink 事件总线 → processUserInput 分流。CC 没有「设备」概念——终端就是设备。
前置门:hook 拦截 → 超长守卫 → 配额
fireUserPromptSubmit(hook 可 reject 或改写 prompt)→ 输入超长边界(MAX_USER_PROMPT_CHARS=50000,超了回提示气泡拒绝而非静默截断,不调 LLM 不计 quota,I-24)→ quota.assertAllowed(org+user 双 scope 月度配额,超硬限 403)。
messages.service.ts:1084-1191 · utils/prompt-size-guard.util.ts · quota/quota.service.ts:45,81
CC 站 3:UserPromptSubmit hook exit 2 拦截——同一个钩子位。CC 无配额层(个人订阅限流在 API 侧)。
历史加载:树形消息沿 active path 回溯
消息是树不是链(编辑/重生成 = append 兄弟节点 + 移 activeLeafMessageId 指针,append-only,I-14)。buildActivePath 沿 parentMessageId 从叶子回溯得到喂模型的单链;大字段经 blobService.hydrateMessages 从 MinIO 还原(>64KB 的 content 落 blob,行内只存 ref)。
messages.service.ts:694,1253-1270 · core/storage/platform-storage.service.ts:48-53
CC:本地 JSONL transcript 顺序读,无树形分支。CC 的「大输出落盘」对应 FFAI 的 blob(CC 落文件系统,FFAI 落 MinIO + org 隔离 sha256 去重)。
组装 system prompt:八段拼接
buildSystemContent 拼:BASE_SYSTEM_PROMPT(身份+工具规则)→ persona 指令 → org 记忆段(usage 衰减选材)→ 环境段(日期只到「日」,避免 bust prompt cache)→ coordinator 段 → skills 清单 → Canvas artifactRefs(cap 5)+ storageFileRefs(cap 10)→ 会话工作区文件 manifest(根治「附件只活一轮」失忆)→ 个人记忆快照(USER.md + MEMORY.md,共享上下文时跳过——隐私边界 I-31)。
messages.service.ts:2541-2697 · services/memory-snapshot-cache.service.ts
CC 站 4/6:system prompt 数组分段缓存 + CLAUDE.md。关键差异:CC 把 CLAUDE.md 拼在消息数组最前而非 system prompt;FFAI 全部进 system content。CC 的静态段打全局缓存断点,FFAI 靠「日期粒度到日」保前缀稳定。
入循环前压缩 + 每轮 context_usage
turn 起点跑 summarizeOldHistory(超 8000 token 让 qwen-turbo 把旧历史压成一条 9 段摘要)+ compactHistory(预算 12000,保护当前 ask 不截断)。循环内每 iter 发 context_usage 事件(percentUsed + ok/warn/auto/critical 四档),达到 autoCompactThreshold 触发全量压缩(连续失败 3 次熔断)。
messages.service.ts:1334-1345,1565-1703 · engine/query-engine.service.ts:128-189,173-185
CC 站 5:queryLoop 每轮开头四连(裁剪+microcompact+auto-compact 检查)。阈值公式两边一致到常量级:都是 window − 20000(摘要预留)− 13000(缓冲)。这不是巧合——FFAI 显式对标了 CC autoCompact.ts。
★ ModelRouter 决策(CC 没有这一站)
modelRouter.decide:并发拉 PROJECT → USER → ORG 三层规则,按 priority 内存匹配(AND 语义 + 数值比较);无规则命中走 default tier(openai 可用则 gpt-5.5),轻 turn(无媒体 + 上下文 ≤1500 + 非 code/reasoning)自动降 small 档省钱。决策连同 reasoning 落 ModelRoutingDecision 表(管理员 dashboard 可审计),并 yield routing 事件给前端。
router/model-router.service.ts:61,89-121,552 · messages.service.ts:1414-1471
CC:单 provider(Anthropic),模型由用户 /model 手选,无路由层。这是 FFAI 的 G10 核心差异化。
调模型:候选链 + IR 编码 + SSE 消费
buildStreamCandidates 组 [primary, ...fallbacks](剔 mock、剔不可服务组合);请求经厂商中立 Normalized IR 编码成 Anthropic Messages 或 OpenAI Chat Completions wire 格式,SSE 逐行解码回 IR chunk。45s stall watchdog:距上个业务 chunk 超时(心跳「:」刻意不续命)→ reader.cancel() + 抛 stream_stalled。
messages.service.ts:2933,1733-1745 · protocols/sse-line-reader.ts:30-100 · protocols/protocol-client.ts:25
CC 站 7:官方 SDK 单厂商,90s idle watchdog + 30s stall 日志。CC 不用 SDK 的 BetaMessageStream(O(n²)),FFAI 干脆整个 SSE 解析自研(要伺候 4 种 wire 方言)。
流式出流:text_delta 边收边 yield
每个 IR chunk 立即转 SSE 事件推给前端:text_delta(打字机)、tool_args_delta(Canvas 工具的 content 字段流式预览——artifact 在右栏「长」出来)、routing/compact_warning/context_usage。前端 EventSource 消费,React 渲染。
messages.service.ts:386-420,1756-1788
CC 站 8:StreamingMarkdown + Ink 帧 diff 只重画变化的终端单元格。同一个「边流边渲染」思想,画布从终端换成浏览器。
tool_use → 治理流水线(唯一收口)
凡工具调用必经 ToolRegistry.invoke(零绕过,I-21):zod 校验 → IAM 授权 hook → destructive 二次确认(返 PERMISSION_REQUIRED 中断 turn)→ 幂等去重(写工具 Redis 120s dedup)→ 执行 → 异常三档分类 → 出口脱敏(sk-/AKIA/PEM/JWT/连接串掩码,开关 import 时冻结防注入关闭)→ 超 50000 字符落盘换 preview。
tools/tool-registry.service.ts:175-275 · tools/governance/*
CC 站 9:Zod → PreToolUse hook → 权限弹窗三道关。CC 的「权限」是问用户;FFAI 的「治理」是问制度——RBAC、幂等、脱敏、审计都是无人值守也必须成立的关。
四轨执行:进程内 / MCP / Docker / 桌面 RPC
service: 直接调 NestJS 业务方法(继承 RBAC+DataScope);mcp: 官方 SDK callTool(stdio env 剥离宿主凭据);cli:/沙箱轨进 per-user Docker 容器(network=none + cgroup 2g/2cpu/512pids);client: 经 WS 双向 RPC 派发到用户桌面(30s 超时,本地层可独立拒绝 I-28)。读工具并发批、写工具串行——与 CC 完全同款调度。
messages.service.ts:266-283,2326-2340 · services/mcp-manager.service.ts:219 · sandbox/session/docker-sandbox-environment.ts:390 · desktop/desktop-bridge.gateway.ts:178
CC 站 10:所有工具都跑本机(spawn 进程 / Seatbelt/bwrap 沙箱),只读并发 10 个。FFAI 把「本机」拆成了四个不同信任级的执行域。
结果回填,回到第 5 站
tool_result 按原序 yield + 以 role:'tool' 追加进 loopMessages,iter += 1; continue。上限 MAX_ITER=100;每 25 iter 复查配额;iter 末跑 microCompact(折叠旧的可再生工具输出,保护最近 3 轮 + cache 前缀)。
messages.service.ts:1541-1551,2346-2469 · engine/query-engine.service.ts:125-171
CC 站 11:state = next; continue。CC 无迭代上限(靠 maxTurns 参数),FFAI 硬编码 100 兜底。
收尾:落库 → 审计链 → 成本 → done
无 tool_use 的一轮是终点:fireStop hook(可注入 prompt 逼模型续推——CC Stop hook exit 2 的同款语义)→ 落 ASSISTANT_TEXT + TURN_DONE 消息 → trajectory 哈希链记 TURN_DONE → recordOutcome(回填路由决策真实 cost/latency)+ quota.recordUsage(org+user 双计)→ yield done,前端收流。
messages.service.ts:1953-1994,2472-2537 · trajectory/trajectory.service.ts:52
CC 站 12:Stop hooks → transcript 落盘 → 成本汇总。FFAI 多出的是「审计哈希链」和「路由学习数据」两笔账。
02TAOR 主循环:runTurnStream
整个产品的心脏。2947 行的 messages.service.ts 里是一个 while(true),每次迭代 = 一次「调模型 + 执行工具」的回合。
2.1 调用栈对照
── FFAI ──────────────────────────────────────────────────
POST /agent/messages(/stream)
└─ MessagesController.postMessageStream() [messages.controller.ts:124] SSE 壳 + 心跳
└─ runTurnStream() [messages.service.ts:1024] AsyncGenerator,真正的 while(true)
├─ QueryEngine(只做 compaction) [engine/query-engine.service.ts]
└─ runTurn() = 同步 wrapper 内部 drain 同一个 generator [:965,Teams/cron 用]
── Claude Code ───────────────────────────────────────────
用户输入
└─ QueryEngine.submitMessage() [QueryEngine.ts:209] 会话经理
└─ query() [query.ts:219] 薄壳生成器
└─ queryLoop() [query.ts:241] while(true)
结构同构:都是 async generator 流式吐消息、外壳可换(CC:REPL/headless/SDK;FFAI:SSE/同步 runTurn 给 Teams 和 cron)。历史趣闻:FFAI 曾计划让 QueryEngine 承载 TAOR(对齐 CC 命名),但那份 runStreaming 骨架从未接线,2026-07-02 作为零调用伪代码删除——主循环事实上长在 messages.service。CC 的分层更干净(会话经理/干活分离),FFAI 的主循环和会话管理耦合在一个 2947 行的 service 里,这是已知的结构债。
2.2 一轮迭代的完整步骤
| 阶段 | 做什么 | 位置(messages.service.ts) |
|---|---|---|
| A 取消检查 | signal.aborted → finalStopReason='cancelled' break | :1542-1545 |
| B 配额复查 | 每 25 iter 一次 quota.assertAllowed | :1548-1551 |
| C 占用观测 | calculateTokenUsageInfo → yield context_usage(四档) | :1564-1577 |
| D 自动压缩 | ≥ autoCompactThreshold 触发;失败 3 次熔断;压完 reattach 工作内存(mode/skills/persona/最近 5 文件) | :1581-1703 |
| E 调模型 | 候选链逐个 invokeStream(signal 透传);只在未流出任何 chunk 时才切下一候选 | :1724-1836 |
| F 流式解析 | text_delta 累积+yield;tool_call_delta 拼 args + Canvas 预览;stop 取 stopReason/usage | :1756-1795 |
| G max_tokens 抢救 | stopReason='max_tokens' 且未试过 → 摘要+丢最老 4 条 → continue 重试一次 | :1891-1950 |
| H 终点判断 | 非 tool_use → fireStop(hook 可续推)→ 落 ASSISTANT_TEXT → break | :1953-1994 |
| I 执行工具 | 先串行 yield TOOL_USE 卡片 → 读写分批并发执行 → 按原序 yield TOOL_RESULT + 回填 | :2014-2438 |
| J halt 分支 | PERMISSION_REQUIRED / pendingQuestion(ask_user) → break 等用户 | :2441-2458 |
| K microcompact | iter 末折叠旧工具输出(白名单 9 类可再生工具) | :2460-2469 |
终止条件汇总:end_turn(无 tool_use)/ MAX_ITER=100 / cancelled / permission_required / ask_user / turn 硬超时。ask_user 是 FFAI 特有的第一类终态——CC 的 AskUserQuestion 是阻塞式弹窗(循环原地等),FFAI 的 ask_user 直接结束 turn、把问题作为事件推给前端/Teams 卡片,用户回答开启新 turn。这是「终端同步交互」与「Web/IM 异步交互」的形态差异。
2.3 取消、硬超时与多副本
- 取消广播:CC 是单进程,ESC → abortController.abort() 就完了。FFAI 部署多副本(pm2 cluster),取消要跨进程:
TurnCancelBroker本地 Map 命中直接 abort,miss 则 publish Redis 频道agent:turn:cancel广播其他副本;跨 org 取消被CrossOrgCancelError拒绝。services/turn-cancel-broker.service.ts:82-108 - 端到端穿透(I-40):signal 透传到 LLM 流(4 家 provider)、web_fetch/web_search(AbortSignal.any 合并)、cli 容器(abort → container.kill())、MCP(RequestOptions.signal)。CC 同款设计(signal 传 API + 所有工具),两边都补了「合成收尾」——CC 给未执行工具补 is_error tool_result 保消息序列合法,FFAI 把 partial text 落库标
partial:true。 - turn 硬超时:
FFAI_AGENT_TURN_HARD_TIMEOUT_MS默认 15 分钟,setTimeout 到期 abort 复用取消清理路径——防「idle watchdog 每次都差一点不触发但累积挂一小时」。CC 无此层(终端用户自己会按 ESC)。messages.service.ts:83-90,1067-1076 - 无 session 级互斥锁:并发写靠
@@unique([sessionId,sequence])+ P2002 jitter 重试(最多 24 次)原子分配 sequence,而非 Redis 锁。messages.service.ts:625-661
03上下文组装与压缩
FFAI 的 compaction 设计了 5 层,实装 4 层;CC 是 3 级机制。两边的 auto-compact 阈值常量逐字相同。
3.1 压缩层级对照
| FFAI(engine/query-engine.service.ts) | Claude Code | 对比 |
|---|---|---|
| L1 budget trim:硬 token 预算 + 保护当前 ask(超专属预算 31250 token 才 truncateMiddle):222-269 | 工具结果预算裁剪(query.ts:379) | 同思路;FFAI 多了「当前输入超长直接拒绝不静默截断」的 I-24 前置门 |
| L2 单条 tool_result 上限 2000 token 中间截断 :199-209 | maxResultSizeChars 30000 字符落盘 | CC 落盘给路径可 Read 回;FFAI 的落盘在治理 stage 9(50000 字符),L2 是喂模型前的第二道 |
| L3 semantic summary:超 8000 token 让 qwen-turbo(便宜小模型)压 9 段摘要 :128-189 | auto-compact:让主对话同一个模型写 analysis+summary 9 小节(可复用缓存前缀) | 摘要小节结构同款(9 段);CC 用主模型换缓存命中,FFAI 用便宜模型换成本——多 provider 才有的选项 |
| L4 pin working set:未实现(YAGNI 注释):110 | 压缩后重注入读过的文件(最多 5 个/50K) | FFAI 用「post-compact reattach 工作内存」(mode+skills+persona+最近 5 文件,PER_FILE 20K)实现了等价物 :1626-1657 |
| L5 sliding window 默认保最近 20 对 :211-220 | compact 边界切断旧历史 | 等价:都保证「摘要 + 近期」 |
| micro iter 末折叠 9 类可再生工具输出,保护最近 3 轮 + cache 前缀 2 条 :125-171 | microcompact:清 Read/Bash/Grep 等可再生结果 + cache_edits 服务端增量删 | 白名单思路一致;CC 多一个 API 侧原生 context_management(服务端 KV 缓存编辑),FFAI 没有(多厂商没这个 API) |
FFAI:autoCompactThreshold = contextWindow − 20000(摘要预留)− 13000(缓冲),老 session 每小时再让 2K(最多 10K);连续失败 3 次熔断。CC:window − 20000 − 13000,同样 3 次熔断。常量逐字对齐——Active Context 里明写「对齐 CC autoCompact.ts」。三层 env 开关(FFAI_DISABLE_COMPACT / FFAI_DISABLE_AUTO_COMPACT / 默认全开)也是照 CC 的 autoCompact 开关语义做的。
3.2 CLAUDE.md 的对应物是什么
CC 的分层记忆(Managed → ~/.claude → 项目逐级向上)在 FFAI 里被拆成了结构化的几段,全部进 system prompt:
Persona 指令 + 项目上下文
AgentSession 挂 personaId/projectId,persona instructions 注入 system prompt;项目工具白名单过滤工具集。
个人记忆三件套(E26)
USER.md(8KB)+ MEMORY.md(16KB)+ memory/*.md 单条文件——照 CC memory 目录 + Hermes 移植,详见第 7 章。
Org 记忆(DB)+ OrganizationAgentSettings
组织共享记忆按 usage 衰减选材注入(200 行/25KB——与 CC memdir 双重截断常量一致);org 级 allowedPermissionModes 白名单收紧。
04工具系统:四轨 + 装饰器注册
85 个 @AgentTool 装饰器(35 个文件)+ MCP 动态注册。一个工具 = 元数据 + 执行 + 可用性 + 治理标志,与 CC 的 Tool 对象同构。
4.1 ToolDescriptor vs CC Tool 接口
| 关注点 | FFAI ToolDescriptor(tools/tool.types.ts) | CC Tool(Tool.ts:362-695) |
|---|---|---|
| 身份 | name(OpenAI 最严正则)/ aliases / searchHint / userFacingName :12-124 | name / aliases / searchHint |
| 输入 | 扁平 inputSchema 或 inputJSONSchema 或 zodSchema(优先级递增):35-144 | Zod v4 单一真值 |
| 行为标志 | readOnly / concurrencySafe / writeAction(CI 强制显式声明,I-18) / destructive / idempotent / controlTool :65-116 | isReadOnly / isConcurrencySafe / isDestructive(默认 fail-closed) |
| 可见性 | availability{surface[] / permissions[] / requiredCapabilities[]} + exposure 三态(direct/deferred/model_only,借 Codex):49-107 | 被 deny 的工具在模型看到前剔除;shouldDefer 延迟加载 |
| 结果 | maxResultSizeChars(默认 50000 落盘)/ getToolResultText / outputFields :87-139 | maxResultSizeChars 30000 / mapToolResultToToolResultBlockParam |
| UI | renderingHint + streamingContentField(Canvas 流式长出) | renderToolUseMessage / renderToolResultMessage(终端卡片) |
CC 是静态注册表(tools.ts 编译期 feature() 摇树 + 运行期条件展开);FFAI 是 @AgentTool 方法装饰器写 reflect-metadata + AgentToolsModule.forFeature([Service]) 显式收集(static Set 绕开 NestJS @Global 多实例坑)+ bootstrap 统一 adapt 注册、重名 fail-fast。registry/agent-tool.decorator.ts:40 · agent-tool-bootstrap.service.ts:55。业务模块(合同/表单/审批/采购/M365)用一个装饰器就把方法暴露给 agent——这是「agent 长在业务系统里」的接线方式,CC 无对应(CC 的业务接入走 MCP)。
4.2 四轨执行器(CC 只有一轨:本机)
| 轨 | 跑在哪 | 代表工具 | 安全边界 |
|---|---|---|---|
service: | 后端进程内 | artifact_create/patch、knowledge_query、web_search/fetch、form_*、approval_*、po_*、m365_*、memory、CronCreate、delegate_task…(最大轨) | 继承 RBAC + DataScope,orgId/userId 从 ctx 取、不接受 LLM 传入 |
mcp: | 后端 → MCP server(stdio/SSE) | 运行时按 agentMcpServer 表动态注册 mcp:<server>:<tool> | stdio env 剥离宿主凭据(sanitizeEnvVars)+ 治理收口 + writeAction 保守置 true |
cli:/沙箱 | Docker 容器 | cli_exec(git.log/kubectl.get/adp.cli 白名单 argv 模板)、cloud_code_exec、code_session_exec、sql_read_query | per-user 容器 network=none + capDrop ALL + cgroup 2g/2cpu/512pids;主机零执行(I-33) |
client: | 用户桌面(Electron) | file_read/write/edit/list、fs_glob/grep、clipboard_*、notify_push、shell_exec(11 个,服务端 invoke 一律拒绝) | WS 双向 RPC + 本地强制层独立校验(scope+realpath+授权弹窗,I-28);shell.exec 在能力协商白名单里被服务端剔除 |
4.3 治理流水线:ToolRegistry.invoke 的 11 个 stage
这是 FFAI 相对 CC 最大的结构增量——所有工具调用(业务/MCP/子 agent/桥接)必经这一条收口,不存在旁路(I-21,Hermes 踩过「裸 dispatch 绕过治理」的坑后焊死):
stage 0 解析工具(NotFound throw)
stage 1 zod 入参校验(bootstrap adapter 内,失败返 INPUT_INVALID 给模型自修)
stage 3 PreHook(占位 → E20 S5)
stage 4 IAM 授权 hook(AIToolGrant,flag 控 fail-open 渐进;LOCKED_SET 4 项恒放行)
stage 4b destructive 二次确认 → 返 PERMISSION_REQUIRED(不执行,turn 中断等用户)
stage 5 幂等去重(写且非幂等:Redis 120s,key 含 org+user+turnId+sha256(入参))
stage 7 执行(dispatchKey→桌面 WS / 否则 tool.invoke)+ 异常三档分类
(Forbidden→security / BadRequest→business / 其余→system,真实 message 都回模型)
stage 8 出口脱敏 redactSensitiveText/redactDeep(sk-/ghp_/AKIA/PEM/JWT/DB 连接串,
force 模式,REDACT_ENABLED import 时冻结——prompt injection 关不掉)
stage 9 超 50_000 字符 → 全文落 org 隔离 blob,LLM 只拿 <persisted-output> preview
CC 的对应链是 toolExecution.ts 的三道关(Zod → PreToolUse hook → canUseTool 权限),错误同样包成 <tool_use_error> 喂回模型自修——两边连这个 XML 标签的用法都一致。CC 没有的:IAM 按人授权、幂等去重、出口脱敏、审计三档。原因还是信任边界:CC 的输出只回到机主自己的终端,FFAI 的输出会进多租户 DB、进审计链、可能被复述给别人。
4.4 四个代表性工具(对照 CC 的四个)
cli_exec —— 对照 BashTool:不是「跑什么都行的 shell」,是白名单 argv 模板
- CC BashTool:模型给任意命令 → 权限规则匹配(prefix/exact/wildcard + 复合命令拆分 + 环境变量剥离)→ 本机 spawn(可选 Seatbelt/bwrap)。安全边界 = 权限提示系统(问机主)。
- FFAI cli_exec:模型只能选白名单命令 + 填参数(
['git','log','--oneline','-n','{count}'],count 1-1000、path 正则防注入),服务端校验后进 Docker(git 轨 network=none、128m、30s、readonly)。安全边界 = 容器 + 模板(不问任何人)。tools/cli-executor.types.ts:100-163 - 任意命令的口子是
code_session_exec(per-user 持久容器:suspend=docker stop 保卷、resume ~1s、暖池 32、idle 10min suspend、30 天 GC 保卷)——「谁控制命令谁进容器」(I-32)。 - 反面教材:
psql.query曾是白名单轨的一员,2026-07-02 安全审计发现 SELECT-only 只有注释没有代码(任意 SQL +COPY TO PROGRAMRCE 面)→ 移除,终态做成sql_read_query:服务端 replica allowlist +BEGIN READ ONLY+ 文本层 fail-closed 拒非 SELECT——三层强制只读(I-45)。
file_edit(client 轨)—— 对照 FileEditTool:同款「oldString 必须唯一」,校验位置搬到了用户机器
CC FileEditTool 的三重校验(必须先 Read / mtime 时间戳 / oldString 唯一性)在服务端做;FFAI 的 file_edit 是 client 工具,descriptor 声明 destructive + oldString 须唯一,但真正的强制在桌面壳层:scope 收敛(默认 ~/FFAI Workspace/)+ realpath 防符号链接 + per-capability OS 授权弹窗——「服务器已鉴权所以本地直通」被明令禁止(I-28),威胁模型是「服务器 loop 被 prompt injection 时,本地层是最后防线」。CC 不需要这层,因为 CC 的「服务器」和「本地」是同一台机器。
delegate_task —— 对照 AgentTool:CC 同步递归,FFAI 异步 spawn + 轮询
- CC AgentTool:
for await (query({...}))递归调同一个主循环,父循环原地等子 agent 流完。 - FFAI delegate_task:立即返回 taskId,后台
runChildAgentInBackground(建独立 child session),LLM 用 TaskGet/TaskOutput 轮询、TaskStop 真 abort。并发 capFFAI_MAX_CONCURRENT_CHILDREN=3(对齐 Hermes),生命周期总量 20,child 硬超时 10min,结果 tail 截 8000 字符防父 context 被日志污染。services/sessions.service.ts:142-376 - 隔离设计两边一致:child 独立 session/abortController、结束清理、父 close 遍历 abort children;差异是 CC 子 agent 砍 CLAUDE.md 省 token,FFAI 子 agent 走独立 persona + 工具子集。
- coordinator mode(SetCoordinatorMode)注入 system prompt 让 LLM 同轮 fan-out 多个 delegate_task——2026-07-03 改异步后「真并行」才首次成立。
artifact_create —— CC 无进程内对应(harness 的 Artifact 是产品级能力)
13 种类型(MARKDOWN/HTML/CODE/MERMAID/SVG/IMAGE/FORM/APPROVAL…);streamingContentField:'content' 让前端在工具参数还在流式拼接时就把 artifact「长」出来;版本链 append-only(version+parentArtifactId 自引用,回滚=新建拷贝不删历史,I-11);MARKDOWN/HTML/CODE 自动存盘 ~/files/ 防丢失,显式 sourcePath 走 file-first(文件是单一事实源,HEAD 从文件 hydrate,D58)。artifact/artifact.service.ts:56,264,596-704
05权限、沙箱与审计
CC 的问题是「模型想做 X,允许吗?→ 问用户」;FFAI 的问题是「这个员工的这个 agent 想做 X,制度允许吗?→ 查 RBAC/配额/审计,实在要问才问」。
5.1 协作模式两轴(直接借 CC)
FFAI 明确选了 CC 的两轴正交而非 Codex 的一维 4 模式:planMode(OFF/REQUIRED,REQUIRED 时 writeAction 工具全不可见)× permissionMode(DEFAULT / ACCEPT_EDITS / BYPASS / READ_ONLY / ASK_EVERY_TIME),session 级字段 + org 级 allowedPermissionModes 白名单(admin 可禁 BYPASS)。切换走 EnterPlanMode/ExitPlanMode/SetPermissionMode 工具(controlTool 不受自身 mode 过滤——否则切不回去)。tools/tool-registry.service.ts:502-520 · prisma platform_ai.prisma:57-58
CC:canUseTool 返回 Promise → 待确认项 push 进 React state 队列 → 终端弹对话框 → 用户点允许 resolve Promise → 循环原地继续。FFAI:destructive 确认返回 PERMISSION_REQUIRED errorCode → turn 直接结束,事件推给前端/Teams,确认后是新 turn。同一个「写动作前要 ask」,同步会话 vs 异步多端的两种解法。
5.2 双层授权门(CC 无对应)
- RBAC 权限码门(恒 fail-closed,I-41):声明
availability.permissions的工具(agent_tools:web/cli/cloud_exec/sql_read…),ctx.permissions 缺省即不可见。#1887 修的正是旧 fail-open 让主 chat 流绕过全部权限门的洞;配套 seed:web/knowledge 授全员 Employee,cli/cloud_exec 仅 Administrator。 - IAM 逐工具 grant/revoke 门(flag 控 fail-open 渐进,I-22):AIToolGrant 按角色/用户 grant∪revoke,allow 姿态只拦显式 revoke——让授权机制先安全上线再运营收紧。LOCKED_SET 4 项(sessions_send 等)任何姿态恒放行——源自 openclaw「admin 误关 sessions_send,Teams 机器人连『无权限』都发不出」的一年踩坑。
5.3 沙箱对照
| 维度 | Claude Code | FFAI |
|---|---|---|
| 形态 | 本机进程 + 可选 macOS Seatbelt / Linux bwrap;源码注释直言「真正的安全边界是权限提示系统」 | per-user Docker 容器:network=none、capDrop ALL、no-new-privileges、cgroup fail-safe 默认(2g/2cpu/512pids/禁 swap)——「永不信任 LLM 自我约束,约束由容器机制强制」(I-33) |
| 生命周期 | 命令级:每次 spawn 新进程(cwd 持久是 pwd 回读技巧) | 用户级持久:suspend=docker stop(RAM/CPU=0 只占磁盘)、resume ~1s、暖池 32、idle 10min suspend、30 天 GC 销容器保卷(I-34「持久 ≠ 运行」) |
| 凭据隔离 | MCP 子进程继承用户完整 env(机主自己的机器) | MCP stdio env 剥离(BLOCKED:ANTHROPIC/OPENAI/AWS/GH token 等 pattern + 通配 *_SECRET;strict 白名单 PATH/HOME/LANG…)sandbox/backend/sanitize-env-vars.ts:2-36 |
| SSRF | 不设防(本机工具) | 三处:图片 ingress 拒收裸远端 URL(I-38)、hook webhook assertPublicHost + redirect:manual 防 DNS rebind、web fetch bounded reader 5MiB 封顶(I-39) |
5.4 审计:HMAC 哈希链(CC 完全没有的一层)
每个关键事件(TURN_STARTED / ROUTING_DECIDED / TOOL_CALL / STREAM_STALLED… 17 类)append-only 落 agent_trajectory_events,eventHash = HMAC-SHA256(macKey, prevHash + canonicalJson(payload)),macKey 从 KMS root HKDF 派生。用 HMAC 不用裸 sha256:裸哈希链持 DB 写权限者可改 payload 顺链重算蒙混,HMAC 把「能改数据」与「能伪造审计」解耦(对齐 CloudTrail);verifyChain 不做 legacy 回退防 downgrade。trajectory/trajectory.service.ts:42-137 · GET /agent/trajectory/verify/:sessionId。CC 的 transcript 是本地 JSONL——可读、可回放,但无防篡改语义(也不需要:机主改自己的日志没有审计意义)。
06模型侧:Provider / IR / 路由 / 配额
CC 是「一家厂商 × 四条认证路径」;FFAI 是「五家厂商 × 一个中立 IR × 两个协议编解码器 × 一个路由器」。
6.1 Provider 与协议层
Provider 层(5 家 + mock)
OpenAIProvider gpt-5.5/5.4/5/5-mini/4o… openai.provider.ts:29
AnthropicProvider claude-opus-4-7/sonnet/haiku anthropic.provider.ts:25
GeminiProvider (走 Google 的 OpenAI 兼容端点) gemini.provider.ts:28
QwenProvider (走 dashscope 兼容端点) qwen.provider.ts:28
MockProvider mock-echo/fast/strong mock.provider.ts:23
│ 每家静态声明 14 项 ProviderCapabilities(vision/audio/pdf/thinking/caching…)
▼
Normalized IR(厂商中立,protocols/types.ts)
NormalizedRequest / NormalizedMessage / ContentPart(text|image|audio|document)
ProviderStreamChunk: text_delta · tool_call_start/args_delta/done · thinking_* · usage · stop
│
▼ 仅 2 个 ProtocolClient 承载全部 5 家
OpenAIProtocolClient → OpenAI / Qwen / Gemini(+DeepSeek/Moonshot 预留)
AnthropicProtocolClient → Anthropic(+Bedrock/Vertex 预留)
共用 sse-line-reader(45s stall watchdog)+ retry.ts(5 次指数退避 ±25% jitter)
- 出网合规门:openai/anthropic/gemini 的 isAvailable() 要求 API key 且
FFAI_LLM_EGRESS_COMPLIANCE_CLEARED='true';qwen(国内)只查 key。CC 无此概念。 - 错误归一化:13 个 NormalizedErrorCode(rate_limit/overloaded/auth_failed/context_overflow/stream_stalled…),HTTP 分类器 429→rate_limit、529→overloaded、400 body 关键词升级 context_overflow/content_filter。CC 的 withRetry 做同样的分类,但不需要归一化——只有一家的错误格式。
- 重试对照:FFAI 5 次 / base 500ms / 2^n / ±25% jitter / Retry-After 优先 / background 遇 overloaded 直接 bail / 连续 3 次 overloaded 触发降级 hook。CC 10 次 / 500ms / 2^n / 0-25% jitter / Retry-After 优先 / 后台请求 529 放弃 / 连续 3 次 529 触发模型 fallback。连「后台请求不重试防容量雪崩」和「3 次过载换模型」这两个细节都对齐了。流式都遵守「已 yield 过就不再重试」(流不幂等)。
- failover 形态:CC 是同厂商换模型(FallbackTriggeredError → fallbackModel);FFAI 是跨厂商候选链(RoutingDecision.fallbacks),只在 pre-first-chunk 降级、mid-stream 不重放,mock 候选被剔除防「真故障返假输出」。messages.service.ts:2933-2945,1729-1836
诚实的实现缺口(写文档时的代码核验发现):① ProviderRequest.effort 注释声称映射 Anthropic thinking.budget_tokens,但 anthropic-encoder 只读 thinking.budgetTokens,effort 在 Anthropic 侧是死字段;② RoutingRequest 的「历史信号」(recentSuccessRate/recentP50Latency)声明了但决策链未消费;③ LLM-routed 策略(B 策略)代码在但主链停用(交互路径 +3.7s 性价比差,#876)。
6.2 ModelRouter:CC 没有的一整层(G10)
三层 scope 规则匹配
并发拉 PROJECT → USER → ORG 三层 ModelRoutingRule(priority 升序),pattern AND 语义匹配(标量等值 / 数组 includes / 数值 >=<)。PROJECT/USER 命中记 SCOPE_OVERRIDE,ORG 记 RULE。
router/model-router.service.ts:89-121 · router/pattern-matcher.ts:9
default tier + 轻 turn 降档(D64)
无规则命中 → openai 可用则 gpt-5.5;「轻 turn」(无媒体 + 上下文 ≤1500 token + 非 code/reasoning)自动降 provider small 档(gpt-5-mini/haiku),fallback 补 medium 升档兜底——保守不误降。capability 过滤:hasImage→visionInput 等硬过滤。
model-router.service.ts:352-386,552-555,598
决策落库 + 学习回路
每次决策(含 BLOCKED)写 ModelRoutingDecision(request/decision/reasoning/matchSource),turn 结束 recordOutcome 回填真实 cost/latency——admin dashboard 可查「上周走了哪些模型/哪条规则/花了多少」。region-aware 模式(env 开)按数据驻留选:CN→qwen、US→anthropic、EU→当前无签约端点只有 mock。
model-router.service.ts:166-226 · router/model-router-region.ts:52-90
6.3 配额与成本(CC cost-tracker 的企业版)
- 双 scope 月度配额:USER + ORG 并查(USER 拒绝优先),三维额度 tokens/costUsd/containerSeconds(沙箱机时单独记账——换模型不减机时所以不入软上限);软上限不拒绝只降级(degradeRequested → costPriority='budget' → 路由选便宜模型),硬上限 403。quota/quota.service.ts:45-218
- 预算告警:hitRatio ≥ softLimitRatio 发 soft、≥1.0 发 hard;Redis setNx 去重(同月同档只发一次),USER 走邮件+Teams 私聊、ORG 走 admin 邮箱。CC 无告警(成本显示在 /status)。
- 计价:MODEL_PRICING 前缀匹配价目表(USD/1M token,4 类 token 累加——input/output/cacheRead/cacheCreation,与 CC calculateUSDCost 同款拆分);未知模型 fallback gpt-4o 量级 + warn 提示补表。sub-agent 成本自动上溯 parent session(CC 的 sidechain 成本也归并主会话——同思路)。quota/model-pricing.ts:33-86 · quota/cost-tracker.service.ts:38-66
07记忆系统:文件化个人记忆 + DB 组织记忆
这一章 FFAI 是 CC 的「学生」——E26 明写移植 Hermes / 对齐 CC memdir,连截断常量都一样;差异全部来自多租户。
| 维度 | Claude Code | FFAI |
|---|---|---|
| 个人记忆载体 | memory 目录:MEMORY.md 索引 + 单文件单事实(frontmatter + [[链接]]) | 工作区三件套:~/USER.md(8KB,偏好+观察两段)+ ~/MEMORY.md(16KB,curator 巩固 SoT)+ ~/memory/*.md 单条(8KB)——唯一事实源=文件,DB 列已 drop(schema 兜底防双写,I-30)services/memory-file.service.ts:27-62 |
| 注入方式 | MEMORY.md 索引进 context,双重截断 200 行 / 25KB | 个人:快照全文注入(轮内冻结 + 写盘即失效的 LRU 快照缓存);组织:按 usage 衰减选材注入,同款 200 行 / 25KB 双重截断services/memories.service.ts:48-51 |
| 选材算法 | 无(索引全塞) | org 记忆 score = (usageCount+1) × 0.5^(ageDays/14天半衰期),被注入即 bump——Codex 式 usage 衰减,暂不上 embedding(5 家头部 3 家记忆召回不用向量) |
| 写入 | 模型直接 Write 文件 | memory 工具 add/replace/remove(Hermes substring 范式,oldText 必须唯一命中);写前 prompt-injection 扫描拒收;memory_search grep 有界(300 条 / 5s 预算,I-39) |
| 巩固 | 无(人工/模型自发) | Curator 每日 03:30 cron:memory/ → MEMORY.md 巩固,铁律「只 archive 不删、pinned 跳过、幂等」 |
| 隐私边界 | 不需要(单用户) | 三层护栏(I-31):共享上下文(Teams 群聊)下 prompt 层不注入 + memory 工具 fail-closed 403 + 通用 file 工具对个人记忆路径 403——少任一层 = 同等泄漏面 |
08扩展生态:同样的机制,收紧的口径
CC 的七种扩展机制收敛为两种本质(注入文本 / 可调工具),FFAI 全盘继承了这个收敛,然后把每个口子按多租户标准收紧。
8.1 总表对照
| 机制 | Claude Code | FFAI | 收紧点 |
|---|---|---|---|
| Hooks | 27 种事件 × 4 后端(shell 命令/LLM/HTTP/agent),exit 2 阻断语义 | 13 种事件(on_turn_start/pre_tool_use/…/stop/notification),DB 表 agent_hooks 装配,仅 http_webhook 真执行(agent_skill 留 P2);PreToolUse/UserPromptSubmit 可 reject,Stop 可续推——阻断语义同款hooks/hooks.types.ts:161 · hooks-loader.service.ts:38 | 禁 command transport(admin 权限 → 后端 RCE 面);webhook 过 SSRF guard + redirect:manual;抛错 fail-soft 只 warn |
| Skills | SKILL.md 三来源(managed/user/project),inline 注入或 fork 子 agent | 4 层 scope:PLATFORM(git 文件)+ ORG/PROJECT/USER(DB),USER>PROJECT>ORG>PLATFORM 同名覆盖且记 shadowed;LLM 经 skill_<name> 工具触发,inline 贴 body($ARGUMENTS 替换)或 fork 真 sub-agent——两种执行方式同款skills/skill-loader.service.ts:143-255 | allowed-tools 白名单激活后过滤 LLM 可见工具集(CC 同有);frontmatter hooks/shell 永久声明性 NOP(I-17/I-23——CC 的内联 shell 执行语义在多租户是后门) |
| Sub-agent | 递归 query(),同步等 | delegate_task 异步 spawn + Task 轮询(见 4.4) | 并发 3 / 总量 20 / 硬超时 10min / tail 8000 字符 |
| MCP | 官方 SDK,5 种 transport,工具名 mcp__server__tool | 官方 SDK,stdio/SSE 两种,工具名 mcp:server:tool,DB 表管理 + admin API | stdio env 剥离宿主凭据;工具经治理收口;writeAction 保守 true;server 删除同步 unregisterPrefix |
| 斜杠命令 / 插件 / SDK | 60+ 内置命令、插件目录、stdin/stdout 控制协议 | 不做(Web 产品无命令行心智;插件 = 多租户审计成本;SDK 位由 REST+SSE 承担) | — |
另有 CC 没有的一个内置 hook:audit.hook 给全部 13 事件挂 priority 10 的审计钩子,writeAction 工具执行一对一 emit 审计事件(destructive→HIGH),输入截断 4000 字符 + 敏感键 REDACT。hooks/audit.hook.ts:69-138
09多端通道:CC 没有的整个 L0
CC 的 surface 是终端(+SDK);FFAI 的 surface 是 Web、Teams、桌面、定时任务——同一个 runTurnStream,四种消费方式。
SSE 流
EventSource 消费 14 种 StreamEvent;15s 心跳;断线靠 lastMessageId 拉取。三栏布局:Chats/Workspace/Agents 侧栏 + 对话 + Canvas。
Bot Framework webhook
入站 @Public + Bot JWT 自验 → 后台跑 turn(webhook 不被 TAOR 拖住);出站默认原生文本(GFM 表格转围栏 + 长文分块),仅 ask_user 发 Adaptive Card 按钮;群聊 isSharedContext fail-closed(个人记忆不注入);proactive 私聊推送靠 conversationRef 捕获。teams/teams-webhook.controller.ts:65-215
Electron WS 桥
user 级常驻 WS(/agent/desktop/bridge),首帧 capability 协商(shell.exec 被服务端白名单剔除)、30s/75s 保活、client 工具 30s RPC、断连按 socket 降级 pending。服务器 loop + 本地强制层(I-28)。
定时任务(E22/E23)
30s 心跳扫描 + updateMany 原子抢占防多实例双投递;skipHistory:true 每次干净起跑(防跨触发历史污染成簇漏投);两段式任务 deliverResult=false 采集段只写工作区不投递;投递失败落 lastError 可见告警不静默(I-26)。配额 per-user 10 / per-org 50。services/crons.service.ts:276-404
CC 侧最接近的对应物:print 模式(headless 消费同一引擎)≈ FFAI 的 runTurn 同步 wrapper;Agent SDK 控制协议 ≈ FFAI 的 REST+SSE 契约;CronCreate/ScheduleWakeup ≈ FFAI 的 AgentCron(但 CC 的 cron 由 harness 宿主执行,FFAI 是产品功能带投递可靠性不变量)。
10逐层对比总表
一屏看完。粗体 = 该侧独有或明显更强。
| 维度 | Claude Code v2.1.88 | FFAI Agent |
|---|---|---|
| 形态 / 信任边界 | 单用户本地 CLI;边界=机主+权限弹窗 | 多组织服务端;边界=RBAC+隔离+审计 |
| 主循环 | query.ts queryLoop(1729 行文件);needsFollowUp 唯一判据 | messages.service runTurnStream(2947 行文件);stop_reason 判据 + ask_user/permission_required 异步终态 + MAX_ITER=100 |
| UI | 自研 Ink fork(React→Yoga→ANSI 帧 diff) | Next.js Web + Canvas artifact 流式长出 + Teams 卡片 |
| Provider | 1 家 ×4 认证路径,官方 SDK | 5 家 × 中立 IR × 2 协议编解码 + 出网合规门 |
| 模型选择 | 用户 /model 手选 + 529 fallback | ModelRouter:三层 scope 规则 + 轻 turn 降档 + 决策审计落库 |
| 流健壮性 | 90s idle watchdog + 30s stall 日志 + 非流式降级 | 45s stall watchdog(心跳不续命)+ pre-stream 跨厂商 failover + 15min turn 硬超时 |
| 重试 | 10 次指数退避;429 订阅不重试;529×3→fallback | 5 次指数退避 ±25% jitter;background bail;overloaded×3→降级 hook(同款设计语言) |
| Prompt cache | 4 断点精细管理 + 全局 scope 静态段 + sticky beta latch | 靠前缀稳定性(日期到日、快照冻结);无显式断点管理(多厂商 API 不一致) |
| 压缩 | 3 级:auto-compact(主模型摘要)+ microcompact + API 侧 context_management | 5 层设计 4 层实装:L3 用便宜模型(qwen-turbo)写摘要 + microcompact;阈值常量与 CC 逐字一致 |
| 工具注册 | 静态注册表 + 编译期摇树 | @AgentTool 装饰器 ×85 + forFeature 收集(业务模块一行接入) |
| 工具执行域 | 1 轨(本机,可选 OS 沙箱) | 4 轨:进程内 / MCP / Docker / 桌面 RPC |
| 工具治理 | Zod → hook → 权限弹窗;tool_use_error 反馈闭环 | 11-stage 收口流水线:+IAM +幂等 +出口脱敏 +审计;同款 tool_use_error |
| Bash/执行 | 任意命令 + 规则匹配 + 拆复合命令防绕过 | 白名单 argv 模板(cli_exec)+ per-user 持久容器(code_session_exec);主机零执行 |
| 子 agent | 同步递归 query();父批准不泄漏给子 | 异步 spawn + Task 轮询;并发 3/总量 20/超时 10min/tail 8000 |
| 记忆 | memory 目录 + MEMORY.md 索引(200 行/25KB) | 同构文件三件套 + usage 衰减选材 + curator cron + 共享上下文三层隐私护栏(截断常量同款) |
| Hooks | 27 事件 ×4 后端(含本机 shell) | 13 事件 ×1 后端(http_webhook+SSRF guard);禁 command |
| 审计 | 本地 transcript JSONL(无防篡改) | HMAC-SHA256 哈希链 + verify API(17 类事件) |
| 配额/成本 | 会话成本统计 + OTel | org/user 双 scope 月度配额(含容器机时)+ 软限降级 + 预算告警 |
| 多端 | 终端 + print + SDK + IDE 扩展 | Web SSE + Teams Bot + Electron WS + cron(同一引擎四消费) |
| 消息持久化 | 本地 JSONL;resume 恢复 | PG 树形消息(append-only 编辑分支 + activeLeaf 指针)+ blob 分层(64KB 阈值 MinIO + org 隔离去重) |
一句话总结:FFAI 的内核(TAOR 循环 / 压缩阈值 / 重试语言 / 记忆截断 / tool_use_error 闭环 / 两轴协作模式)是对 CC 的有意识临摹——设计文档里每一处都标了出处;FFAI 的增量全部长在 CC 不需要的地方:多厂商(IR+路由)、多租户(RBAC+隔离+配额)、多端(SSE/Teams/WS/cron)、可审计(HMAC 链)。反过来,CC 在单机体验上的深度(Ink 渲染管线、prompt cache 断点精算、权限规则语法、bash 防绕过)是 FFAI 形态不需要、也确实没有做的。
11文件索引速查
按「我想看 X」检索,路径相对 backend/src/modules/agent/。
| 我想看… | 去这里 |
|---|---|
| HTTP/SSE 入口 · 心跳 · 取消端点 | controllers/messages.controller.ts:89,124,205 |
| 主循环 · failover · 上下文组装 | services/messages.service.ts:1024(runTurnStream)· :2541(buildSystemContent)· :2933(候选链) |
| 压缩五层 · 阈值 · microcompact | engine/query-engine.service.ts:75-269 · :173(阈值)· :125(micro) |
| 取消广播 · turn 硬超时 | services/turn-cancel-broker.service.ts:82 · messages.service.ts:83,1067 |
| 工具接口 / 注册 / 装饰器 | tools/tool.types.ts:12-146 · registry/agent-tool.decorator.ts:40 · registry/agent-tool-bootstrap.service.ts:55 |
| 治理流水线 · 脱敏 · 幂等 · 落盘 | tools/tool-registry.service.ts:175-275 · tools/governance/* |
| RBAC 权限门 · IAM hook | tools/tool-registry.service.ts:583-627 · tools/iam-authorization-hook.service.ts:63-106 |
| CLI 白名单 · Docker 底座 · 会话沙箱 · 暖池/GC | tools/cli-executor.types.ts:100 · tools/docker-orchestrator.service.ts:118 · sandbox/session/docker-sandbox-environment.ts:390 · sandbox-orchestrator.service.ts:78 / sandbox-session-reaper.service.ts:50 |
| sql_read_query 三层只读 | tools/postgres-read-query.service.ts:46,116,197 |
| Client 工具 · 桌面 WS 桥 | client/client-tools.service.ts:31-239 · desktop/desktop-bridge.gateway.ts:96,178,352 |
| MCP 管理 · env 剥离 | services/mcp-manager.service.ts:109,177,219 · sandbox/backend/sanitize-env-vars.ts:2 |
| Provider ×5 · capabilities | providers/{openai,anthropic,gemini,qwen,mock}.provider.ts |
| IR 类型 · 协议编解码 · SSE 读流 · 重试 | protocols/types.ts:17,93,179 · protocols/{openai,anthropic}/ · protocols/sse-line-reader.ts:30 · protocols/retry.ts:39 |
| ModelRouter · pattern · region · tier | router/model-router.service.ts:61,352,461,552 · router/pattern-matcher.ts:9 · router/model-router-region.ts:52 |
| 配额 · 成本 · 价目 · 告警 | quota/quota.service.ts:45 · cost-tracker.service.ts:38 · model-pricing.ts:33 · budget-alert.service.ts:96 |
| 个人记忆 · 快照缓存 · curator · org 记忆 | services/memory-file.service.ts:55,148,494 · memory-snapshot-cache.service.ts · memory-curator.service.ts · memories.service.ts:93,182 |
| Hooks 事件/registry/loader/webhook 执行器 | hooks/hooks.types.ts:161 · hooks.registry.ts:111-197 · hooks-loader.service.ts:38 · http-hook-executor.ts:67 |
| Skills 加载 · scope 冲突 · 注入 | skills/skill-loader.service.ts:66,143,187,241 |
| 子 agent · 并发 registry · scratchpad | services/sessions.service.ts:142,259 · subagent/subagent-inflight-registry.ts:16 · scratchpad/scratchpad.service.ts:23 |
| 审计哈希链 · verify | trajectory/trajectory.service.ts:52,88,131 · controllers/trajectory.controller.ts:27 |
| Teams webhook · 卡片 · proactive | teams/teams-webhook.controller.ts:65,126,355,388 · teams/teams-proactive.service.ts |
| cron 心跳 · 原子抢占 · 两段式 | services/crons.service.ts:276,306,358 |
| Artifact 类型/版本/自动存盘 | artifact/artifact.service.ts:56,264,596,663 |
| 工作区 ~/ 路由 · manifest · binding 三层 | storage/storage.service.ts:73,143,190,579 · controllers/workspace.controller.ts:32 |
| Prisma schema(session/message/blob/trajectory/hook/skill) | backend/prisma/schema/platform_ai.prisma:44,249,397,590,1160,1401 |
| 设计事实源 | docs/modules/agent/:02-architecture.md(六层+四轨+G 原则)· invariants.md(I-1~I-47)· decisions.md · references/claude-code-reference.md |