08 口袋速背卡片
用法:面试当天早上、或进场前 10 分钟过一遍。只背这一篇,不要翻别的。 全部结论都基于项目源码核对,数字可直接报。
一、一句话定位
MewCode / EasyCoding:Go 从零手写的终端 AI 编程助手(对标 Claude Code)。单二进制、Anthropic + OpenAI 双协议、多轮 ReAct 闭环、五层权限防御、MCP 工具生态、两层上下文压缩、JSONL 会话存档、流式 TUI。 「从零」= 不依赖 Agent 框架(无 LangChain / Eino);协议解析、MCP、TUI 三处用了官方 SDK。
二、数字底牌(必须报准)
| 项 | 值 |
|---|---|
| 规模 | 101 个非测试文件 / 14,082 行;17 个测试文件 / 3,902 行;合计 17,984 行 |
| 结构 | 18 个 internal 包 + cmd/mewcode + cmd/smoke |
| 迭代上限 | 25 轮(maxIterations)← 不是 10! |
| 幻觉熔断 | 连续 3 轮「整轮全未知工具」(maxUnknownRun) |
| 内置工具 | 6 个:read_file / write_file / edit_file / bash / glob / grep |
| 工具超时 | Agent 统一 30s;bash 自身默认 120s;bash 环境变量只留 4 个 |
| 权限 | 5 层 / 4 档模式 / 10 条黑名单正则 / 3 层 YAML 规则 |
| 规则语法 | =精确 / ~正则 / !取反 / 缺省 glob;层内 deny 优先;层间 local > project > user |
| MCP 命名空间 | mcp__<server>__<tool>(双下划线) |
| MCP 超时 | 连接 30s / 调用 30s;关闭兜底 5s |
| 压缩阈值 | ContextWindow − 20000(SummaryReserve) − 13000(AutoSafetyMargin) = 窗口的 83.5% |
| 第一层阈值 | 单条工具结果 50KB;单条 tool 消息聚合 200KB |
| 摘要熔断 | 连续 3 次失败跳闸 |
| 摘要 PTL 重试 | 前 3 次各丢 1 组;之后按 20% 丢 |
| 保留近期原文 | ≥ 10,000 token 且 ≥ 5 条消息 |
| 恢复段 | 最近 5 个文件 × 每文件 5,000 token |
| Token 估算 | 3.5 字符/token + 真实 usage 锚点 |
| 会话清理 | 启动时 goroutine 清理 30 天以上会话 |
| Plan 提醒 | 每 4 轮一次完整版(第 1/5/9/13/17/21/25 轮) |
| 系统提示模块 | 固定 7 个(优先级 10–70)+ 可选 3 个(80/90/100) |
| 会话恢复阈值 | 估算 > ContextWindow − 8000 时立刻压缩 |
三、五层权限(背这条链)
① 黑名单(10 条正则,仅命令类,不可配置,Bypass 也拦)
↓ 未命中
② 沙箱(仅文件类工具;先 EvalSymlinks 再比 root+sep;不存在则回退最近祖先)
↓ 未命中
③ 规则引擎(local > project > user,就近命中即返回;层内 deny 优先)
↓ 未命中
④ 模式兜底(只产 Allow/Ask,绝不产 Deny)
Default: 读写 Allow / 写 Ask / 执行 Ask
AcceptEdits: 写 Allow / 执行 Ask
Plan: 仅只读工具可见(同 default 兜底)
Bypass: 全 Allow(①② 仍生效)
↓ 判 Ask
⑤ 人在回路(Respond chan 缓冲 1;三选一:本次/永久/拒绝;永久 → 写 settings.local.yaml)顺序不可换的本质:前两层不可协商(用户改不了),后三层可协商(用户配置决定)。
- 黑名单必须在规则前 → 否则
Bash(rm -rf *)能放行rm -rf / - 黑名单必须在模式前 → 否则 Bypass 等于关掉所有安全
- 沙箱必须在规则前 → 否则
Write(/etc/**)能写系统文件
关键边界:Engine.Check() 只做前四层,第五层由 agent 包编排(包注释原话)。
四、ReAct 循环(背这 5 条停止条件)
| 停止条件 | 触发 |
|---|---|
| ① 自然完成 | 本轮 len(calls) == 0 |
| ② 连续幻觉 | unknownRun >= 3(整轮全未知才计数,中途命中即清零) |
| ③ 迭代上限 | 25 轮 |
| ④ 用户取消 | ctx.Done() |
| ⑤ 请求出错 | sErr != nil(PTL 会先走紧急压缩重试一次) |
5 条路径全部调 ensureAssistantTail —— 历史末尾必须是 assistant,否则下次请求 Anthropic 400。
保序分批并发算法:
游标 i 扫过 calls:
只读 → 吃入连续区间 [i, j)
① hook + 权限逐个检查(被拒记 preDenied,不阻塞并发)
② 按序 emit 所有 Start(UI 显示「都在跑」)
③ 被拒项预填错误结果
④ WaitGroup 并发执行(写 results[idx],不同索引 → 无需锁)
⑤ 按序 emit 所有 End + PostToolUse
有副作用 → 单个串行,走完整 Ask 流程五、压缩两层(背这个顺序)
每轮循环开头 → ManageContext
├─ 自动路径:先 layer1(零成本)→ 重估 token → 超阈值才 layer2
├─ 手动 /compact:跳过 layer1 和阈值,直接摘要(不计熔断)
└─ 紧急(收到 PTL):强制 layer1 + 无条件摘要(不计熔断),只重试一次
Layer1(不打 LLM):单条 > 50KB 或聚合 > 200KB → 落盘 + 20 行/2048 字节预览
+ 账本 ContentReplacementState:决策一次不可翻转(保缓存、稳视野)
Layer2(LLM 摘要):两阶段 prompt(analysis 丢弃 + summary 保留,9 个小节)
+ 三段恢复(最近 5 个文件 / 当前工具集 / 「不要凭摘要猜」边界提示)
+ 保留近期原文(≥10K token 且 ≥5 条)
+ PTL 重试丢组 + 连续 3 次失败熔断为什么两层:第一层零成本无损、第二层有成本有损 → 先用便宜的,不够再用贵的。 为什么 Layer1 要账本:同一份历史在不同轮次内容跳变会破坏 Prompt Cache,也让模型视野悄悄变化。
六、高频问答一句话版(20 条)
| 问 | 一句话答 |
|---|---|
| Provider 接口为什么只有 3 个方法 | 接口应贴着唯一调用方的真实需求长;加 CountTokens 会有实现退化成「假装不支持」 |
| 流式为什么用 channel | 取消语义统一 + 无缓冲天然背压;代价是 ~30 个 emit 调用点都要处理失败分支 |
ToolCall.Input 为什么用 json.RawMessage | 免双重编解码 + 不丢数字精度 + 参数错误集中到工具层报 |
| Prompt Cache 怎么做 | 系统提示拆 Stable(打断点)/ Environment(不打);模块空则跳过,避免多一个字节 |
| 两家工具结果格式差异 | Anthropic 塞进一条 user 消息的 tool_result block;OpenAI 每个结果一条 tool 消息 |
| 扩展思考何时关 | 历史含工具交互就关(保守),避免 400;代价是只有第一轮有 thinking |
| 只读并发怎么保证权限不退化 | 只读工具在模式兜底里恒 Allow,永不触发 Ask → 权限检查可以同步跑完 |
| 工具被拒为什么不中断 | 拒绝原因是给模型的有效信息,它会自己换路径重试 |
ensureAssistantTail 干什么 | 异常退出时补 assistant 尾消息,否则下一次请求 400 |
| 沙箱怎么防软链接逃逸 | EvalSymlinks 解析后比对;root 本身也要解析(macOS /tmp→/private/tmp) |
| 路径前缀比较的坑 | 必须比 root+sep,否则 /proj 会匹配上 /project2 |
| 模式为什么只产 Allow/Ask | 拒绝权只留给不可协商层和用户显式规则;模式是档位不是否决 |
| 「永久允许」会泛化吗 | 不会——escapeGlob 转义 *?[];否则 rm -rf build/* 会变成无限授权 |
人在回路 Respond 为什么缓冲 1 | TUI 在 UI 线程同步发,无缓冲会阻塞 UI;缓冲 1 又保证「一次请求一次应答」 |
| MCP 命名空间为什么双下划线 | 单下划线在 server 名或工具名含 _ 时无法反解 |
MCP 只读为什么严格只信 readOnlyHint | 它同时影响并发安全和权限放行,声明错了后果严重;默认不可信 |
| 为什么用 JSONL 不用 SQLite | 追加写天然崩溃安全 + 零依赖 + tail -f 可观测;查询需求几乎为零 |
| 为什么每次写都 fsync | 会话历史就是用户成果;1–10ms 对一轮几秒的对话无感 |
| Token 估算为什么锚点+增量 | 误差不累积,每轮被真实 usage 重新校准;CacheRead 也要算进窗口占用 |
| 压缩会破坏 Prompt Cache 吗 | 会,重写历史前缀全失效;所以阈值留 33K 余量尽量晚触发,且 Layer1 带账本保稳定 |
七、⚠️ 必须主动纠正 / 主动暴露的 6 件事
| # | 内容 | 一句话 |
|---|---|---|
| 1 | 迭代上限 | 代码是 25 轮,简历写的 10 轮是抄了未同步的 README,以代码为准 |
| 2 | 「从零手写」 | 指不依赖 Agent 框架;协议/MCP/TUI 用官方 SDK,核心逻辑全手写 |
| 3 | 沙箱对 bash 无效 | 沙箱是参数级不是进程级;现在靠「bash 默认 Ask」兜底 |
| 4 | MCP 通配规则失效 | 规则语法不支持通配工具名,mcp__github__* 被当成字面工具名 |
| 5 | compact/session/llm 无单测 | 恰好是简历第 1、5 条;这三块核心是纯函数,是取舍失误 |
| 6 | 记忆自动更新不可达 | SetProvider 无调用点 → UpdateAsync 恒早退;「功能写了 ≠ 功能能跑」 |
八、三个收尾金句
- 「简历和代码不一致时,以代码为准。」
- 「我知道我的边界在哪 —— 沙箱停在参数层、安全配置是 fail-open、测试覆盖是错位的,这些不是『不知道怎么做』,而是『当时判断错了投入方向』。」
- 「功能写了 ≠ 功能能跑。」
九、最后 30 秒
- 说 25 轮,不说 10 轮
- 说
mcp__server__tool(双下划线) - 权限先讲顺序,再讲前两层不可协商
- 被质疑时先讲事实、再讲判断,不辩解
- 不会就说不会,然后推测 + 说清哪里可能错
出声念三遍 01-项目口述稿-多版本.md 的三分钟版,比再看一遍题库更有用。