按被问到的概率排序。每张卡的结构是:先给一句能立住的判断,再给结构化展开,最后给一个能证明你真读过源码的细节。
别把这些当稿子背。面试官真正在测的是「你有没有自己的判断」,所以每张卡的第一句话(判断)才是核心,后面的展开只是支撑。
如果你只能记住一样东西,记住第一句。
判断:智能体系统的复杂度不在「循环」本身,而在循环之外的四件事:上下文治理、工具调度、权限边界、错误恢复。
展开:标准的六层分层是 —— 入口层 / 会话层 / 智能体循环层 / 工具执行层 / 供应商适配层 / 持久化层。
关键是循环层和工具执行层必须分开:循环层是有状态的状态机(它要跨轮次记住「我压缩过几次」「我切换过备用模型没有」),工具执行层是无状态的调度器(给一批工具调用,还一批执行结果,做完就忘)。
揉在一起的直接后果是:错误恢复逻辑没法单独测试 —— 你想测「压缩失败后会不会正确重试」,就必须先造一堆假工具。这是很多自建智能体项目后期极难维护的根本原因。
细节:我对比读过 Claude Code 和 Hermes 的源码。Hermes 的代码量是 Claude Code 的 3.7 倍(191 万行对 51 万行),但两者的核心循环体量几乎相同。那 3.7 倍的差距全在外围 —— Hermes 的 22 个聊天平台适配器、十几种模型供应商适配、插件系统。这说明智能体的能力密度集中在很小的一块代码里,其余都是集成工程量。
判断:摘要压缩是最后一级,前面还有三级更便宜的。上来就答摘要,说明没做过。
展开:一条成本递增的阶梯 ——
顺序很重要:如果第 3 级已经降到阈值以下,第 4 级就直接跳过 —— 从而保住细粒度上下文,而不是把它变成一坨摘要。Claude Code 源码里有一句注释就是讲这个:「keep granular context instead of a single summary」。
细节(这个是杀手锏):缓存冷热应该成为压缩策略的输入。
提示词缓存是前缀匹配的 —— 改动上下文中段,从那里往后的缓存全部失效。所以缓存热的时候,改本地数据会让整段缓存作废,省下的 token 还不如重建缓存贵;Claude Code 的做法是发一条 cache_edits 指令让服务端在缓存内部删除,本地一个字不改。
反过来,如果距上次响应已经超过缓存有效期、缓存已经凉了,那前缀反正要全部重新处理 —— 这时候正是大刀阔斧清理旧内容的最佳时机。
同一个目标,两条完全相反的路。大多数自建智能体根本没有「缓存现在是热还是凉」这个概念,所以策略只有一套,在两种场景下各错一半。而判断冷热只需要一个时间戳,成本为零。
判断:最有效的防护不是「拦住危险命令」,而是「那个危险工具根本不在模型能看到的清单里」。
展开:四层,从外到内 ——
--dangerously-skip-permissions 也照样拦得住。顺序就是设计。细节:「命令」和「数据」必须区分。
朴素的 if "rm -rf /" in cmd 会拦掉 git commit -m "fix: block rm -rf / spellings" —— 这是 Hermes 真实修过的 bug,用户没法提交一条说明文字里含这段字符的代码提交。
正确做法是只在命令位置匹配(行首 / ;&&|| 分隔符后 / $( 子 shell 后 / sudo 包装后),并对引号内容做遮蔽。但双引号里的 $(...) 不能遮蔽,因为 shell 真的会执行它;而遇到 sh -c / eval 这类 shell 载体时,遮蔽整个失效,因为那里引号内容就是代码。源码里一句话总结:「quoting is not a bypass」。
最后补一句判断力:误报率过高的安全措施等于没有安全措施 —— 用户会直接把它整个关掉。
判断:并发安全性应该由工具自己声明,调度器不认识任何具体工具。
展开:
is_concurrency_safe(参数) 必须是接收参数的 —— 同一个 Bash 工具,跑 ls 安全、跑 rm 不安全。安全性取决于这次要做什么,不取决于工具类型。所以它是一个方法而不是一个静态标记。细节:两级中止作用域。
一批并行的 Bash 命令里有一个失败了(比如编译报错),其他几个跑完毫无意义 —— 但如果用同一个全局中止开关去停它们,整个轮次就结束了,模型收不到错误信息,也就没法重试。
Claude Code 的做法是创建一个父控制器的子控制器:批内失败时中止子控制器,兄弟子进程立刻死掉省资源;父控制器不动,所以本轮不结束,模型正常收到错误并重试。
任何有「批内失败」概念的并发执行器,都应该有一个可以独立触发的子作用域。
判断:真正的无限循环几乎都不来自主流程,而来自两条恢复逻辑互相触发。
展开:
细节:Claude Code 源码里留了一条事故记录。有人在「结束钩子」分支里把 hasAttemptedReactiveCompact(是否已尝试反应式压缩)这个幂等锁重置成了 false,结果形成死循环:压缩 → 还是超 → 报错 → 结束钩子判定不合格要求重试 → 又去压缩 → …… 烧掉了几千次 API 调用。
请注意这个循环的形状:它不是一条路径自己转圈,而是两条恢复路径互相触发。压缩路径和钩子路径各自看起来都有终止条件,但组合起来就成了死循环。
还有一个相关的:上下文超长导致的失败明确不走结束钩子。因为那个钩子会往上下文里注入反馈内容,越注入越超 —— 源码里管这叫 death spiral(死亡螺旋)。失败路径必须能识别「这一类失败不该触发常规的质量重试机制」。
判断:子智能体的第一性原理是上下文隔离,不是并行。并行只是副产品。
展开:要读 20 个文件才能得出一个结论时,自己读会让那 20 个文件的内容永久占用主上下文、每一轮都重发一遍;派子智能体去读,它的上下文用完即弃,主智能体只收到一句结论。40,000 token 换成 15 token。
细节:扇出时的缓存策略。
N 个子智能体如果能让请求前缀达到字节级一致,后 N−1 个全是缓存命中,输入成本降到 1/3 以下。Claude Code 为此付出了四个看起来不优雅的代价:
· 给子智能体一堆用不到的工具(因为工具定义是前缀的一部分)
· 传父智能体已渲染好的系统提示词字节,而不是重新生成(重新生成可能因为特性开关的冷热变化而产生不同字节)
· 用完全相同的占位内容填充所有工具结果
· 把递归分叉的守卫从接口层下移到调用层(因为从工具池里移除会改变工具定义,破坏缓存)
只让最后一个文本块携带各自不同的指令。这是很具体、可量化的架构决策。
判断:先分清三种记忆,别一股脑塞进向量数据库。
CLAUDE.md 就是这个定位。两个容易漏的工程点:
trust_score 加 record_feedback(fact_id, helpful):有用的上浮,误导过人的沉底。没有这个机制,一条早期的错误记忆会永久污染后续所有会话 —— 比如智能体误以为「这个项目用 npm」,实际用的是 pnpm,之后每次都会先执行错的命令。细节:Hermes 的全息记忆用 SHA-256 确定性生成原子向量,而不是用神经网络生成 embedding。
好处是:同一个词在任何机器、任何 Python 版本上编码结果完全一致 —— 彻底避开了「换了 embedding 模型就要重算全库」这个运维噩梦,而且向量可以直接存进数据库的二进制字段跨机器同步。
代价是:它是词袋级的符号组合,没有语义泛化能力("docker" 和 "container" 相似度接近 0),所以必须配合词法检索使用,而不是替代它。这个权衡本身就很值得讨论。
判断:智能体的成本大头是输入 token,不是输出。因为每一轮都要重发整个历史。所以优化的核心是提示词缓存的命中率。
四个手段:
细节:还有一个很小但很实在的例子 —— backfillObservableInput(回填可观测输入)。
工具有时需要给日志、钩子、开发工具包补一些派生字段,但绝不能改那个要发回 API 的原始参数对象,因为字节变了缓存就没了。Claude Code 的做法是只修改一个克隆副本,而且只有当补充操作真的新增了字段时才产生克隆 —— 如果只是覆写了已有字段,连克隆都不做,因为那会改变对话记录的序列化结果、破坏测试固件的哈希。
这个级别的克制程度,能说明「保护缓存」在这个系统里是一等公民约束。
这些问题能同时展示你的深度,也能帮你判断这家公司值不值得去:
你手上现在有两套真实系统的完整源代码。
面试里最强的表达不是「我读过 Claude Code 的源码」,而是「我遇到 X 问题时参考了它的 Y 做法,因为它的约束和我们的类似 / 不类似」。
所以最好的下一步是:挑一个你自己的小项目,把第 11 章的骨架实际写一遍。哪怕只实现主循环 + 工具并发分区 + 两级上下文治理,你在面试里能讲的东西会立刻变得具体十倍 —— 因为你会有自己踩坑的故事,而故事比知识更有说服力。
文档里任何看不懂或想深挖的地方,选中那段文字点「提问」就行。术语忘了含义就翻下一章的术语表。