Posts for: #Learning

opencode memory 实现与最佳实践

注:文章由 codex 整理。

背景

这里的 memory 不是操作系统内存,也不是模型推理时的 KV cache,而是 coding agent 在多轮、多 session、多项目之间保存和复用上下文的能力。

截至这次分析的源码快照,opencode 并没有内置一个类似 mem0mnemopi 或向量数据库的长期记忆系统。它的 memory 更接近三层组合:

  1. 规则型长期记忆AGENTS.md、全局 AGENTS.mdopencode.json 里的 instructions。这些内容每轮都会进入 system prompt,是最稳定、最可审计的记忆。
  2. 会话内压缩记忆compaction agent 把长会话压缩成摘要,并保留最近若干 turn,让当前 session 可以继续推进。
  3. 上下文型临时记忆:用户用 @file、MCP resource、skill、subagent 等方式临时补充上下文,主要服务当前任务。

所以分析 opencode memory 时,关键不是找一个 MemoryBackend,而是看它如何把规则文件、配置、压缩摘要和临时上下文组装进模型请求。

本文基于:

核心结论

opencode 的 memory 设计偏“显式上下文工程(context engineering)”,而不是“自动记住一切”。

这带来一个很重要的实践判断:应该把长期稳定的项目知识写进规则文件,把当前任务过程交给 session history 和 compaction,把低置信度、临时性、可能过期的信息留在对话里,而不是沉淀到 AGENTS.md。

可以这样理解:

层次 opencode 机制 适合保存什么 不适合保存什么
长期项目记忆 项目 AGENTS.md 架构边界、测试命令、代码风格、危险操作约束 临时任务进度、一次性 debug 现象
长期个人记忆 ~/.config/opencode/AGENTS.md 个人偏好、默认工作方式、输出习惯 某个项目的私有约定
可复用规则片段 opencode.jsoninstructions 多文件规范、共享团队标准、monorepo 分包规则 需要模型自己递归解析的隐式引用
会话内记忆 compaction 当前 session 的目标、决策、已完成动作、未解决问题 跨 session 的长期知识
临时上下文 @file、MCP、skills、subagents 本轮要读的文件、外部资料、专项流程 需要每次都遵守的硬规则

实现总览

一轮模型调用前,SessionPrompt 会把几类 system prompt 片段拼起来:

Loop Engineering 最佳实践

背景

注:文章由 codex 整理。

Loop engineering 讨论的不是普通代码里的 for/while,也不是 agent harness 内部已经存在的 observe -> act -> observe 工具调用循环,而是人类交给 agent harness 的外部循环规格(loop specification)

arXiv 论文 Stop Hand-Holding Your Coding Agent: Engineering the Loops that Replace Step-by-Step Prompting 给出的核心定义是:loop specification 是一个有边界、可复用的 artifact,包含 triggergoalverificationstopping rulememory,由人类交给 Claude Code、Codex 等 agent harness,让 agent 在无需人类逐步提示的情况下追踪目标、执行、检查并停止。

这篇论文的价值在于把社区里比较口号化的说法收束成一个工程问题:

  • prompt engineering 问的是:这一轮怎么问?
  • context engineering 问的是:agent 应该知道什么?
  • harness engineering 问的是:agent 能在什么环境里行动?
  • loop engineering 问的是:如何设计一个系统,让 agent 能发现工作、执行工作、验证结果、记录状态,并知道什么时候停?

所以它不是“prompt engineering 已死”,而是 prompt 之外多了一层控制系统。一个 loop 的底层仍然会使用 prompt,但关键杠杆从“写一句更聪明的话”变成“设计一个带反馈、验证和刹车的闭环”。

oh-my-pi memory 系统实现分析

注:文章由 codex 整理。

背景

oh-my-pi 的 memory 系统不是一个单一模块,而是一层 MemoryBackend 抽象下面挂了几种不同的实现。它要解决的问题是:Agent 的上下文窗口是短期的,但用户偏好、项目约定、设计决策、踩坑经验应该跨 session 保留下来,并能在未来对话中重新进入模型上下文。

从实现上看,oh-my-pi 把 memory 拆成三个问题:

  1. 记忆写到哪里:本地 Markdown 摘要、本地 SQLite、还是远端 Hindsight 服务。
  2. 什么时候写入:工具显式 retain,还是每 N 轮自动保留 transcript。
  3. 什么时候读出:启动时注入摘要,首轮 prompt 前自动 recall,或者由模型主动调用 recall / reflect

总体架构

memory backend 的公共接口定义在:

packages/coding-agent/src/memory-backend/types.ts
packages/coding-agent/src/memory-backend/resolve.ts

resolveMemoryBackend(settings) 根据 memory.backend 选择具体实现:

memory.backend = off        -> offBackend
memory.backend = local      -> localBackend
memory.backend = hindsight  -> hindsightBackend
memory.backend = mnemopi    -> mnemopiBackend

MemoryBackend 的关键 hook 是:

  • start():session 启动时建立运行时状态、订阅 session event。
  • buildDeveloperInstructions():构造要注入系统提示词的 memory 文本。
  • beforeAgentStartPrompt():在当前 turn 生成前追加一次性 recall 结果,保证首轮就能吃到记忆。
  • enqueue():强制触发 retain / consolidation。
  • clear():清理当前 backend 的持久化状态。
  • status() / search() / save() / stats() / diagnose():给 UI、slash command、extension 使用。

三种真实 backend 的定位不同:

Claude Code auto-mode 实现分析

注:文章由 codex 整理。

背景

本文分析的是 chengzhycn/claude-code 在提交 4b9d30f7953273e567a18eb819f4eddd45fcc877 上的源码实现。

源码入口:

  • 仓库:https://github.com/chengzhycn/claude-code/tree/4b9d30f7953273e567a18eb819f4eddd45fcc877
  • 权限模式定义:src/utils/permissions/PermissionMode.ts
  • auto-mode 状态切换:src/utils/permissions/permissionSetup.ts
  • 工具权限主流程:src/utils/permissions/permissions.ts
  • auto-mode classifier:src/utils/permissions/yoloClassifier.ts
  • Bash 权限判定:src/tools/BashTool/bashPermissions.ts
  • Bash 只读命令判定:src/tools/BashTool/readOnlyValidation.ts
  • Bash 注入/语法安全检查:src/tools/BashTool/bashSecurity.ts

核心问题是:Claude Code 怎么在“不每一步都问用户”和“不直接 bypass permissions”之间做折中。源码里的 auto-mode 不是一个简单的全局 allow,而是一套组合机制:

  1. 进入 auto-mode 时先检查 feature gate 和 opt-in。
  2. 临时剥离会绕过 classifier 的危险 allow rule。
  3. 工具调用先走原有本地权限系统。
  4. 对本地系统仍然需要 ask 的动作,再交给 auto-mode classifier 判断 allow/block。
  5. classifier 不可用、输出不可解析或 transcript 太长时,按场景 fail closed 或回退到人工 permission prompt。

模型

auto-mode 可以理解成一个“permission prompt 的自动代理”,而不是新的沙箱。它的安全性来自三层:

工具自己的权限检查
  -> 本地静态安全规则:路径、只读命令、deny/ask/allow rule、shell 注入模式
  -> auto-mode 快速路径:acceptEdits 可允许的操作、安全工具 allowlist
  -> auto-mode classifier:读取 transcript + 当前 action,输出 allow/block

这意味着:

HTTP2 流量控制

核心问题

注:文章由 codex 整理。

HTTP/2 的一个核心收益是 multiplexing:多个 request/response 可以复用同一条 TCP connection,并且不同 stream 的 frame 可以交错发送。这个能力解决了 HTTP/1.x 依赖多连接并发的问题,但也引入了一个新问题:如果某个接收方、某个 stream 或某个应用层消费者处理不过来,发送方应该怎样被限制,而不至于把整个连接的内存打爆?

HTTP/2 flow control 解决的就是这个问题。它不是拥塞控制(congestion control),也不是优先级调度(prioritization),而是一个接收方驱动的背压机制:

  • TCP 拥塞控制关心网络路径能承受多少数据。
  • HTTP/2 flow control 关心接收端应用和协议栈愿意接收多少 DATA payload。
  • HTTP/2 priority 关心多个 stream 竞争发送机会时先发谁。

可以把它理解为:TCP 管路决定“路上能跑多少车”,HTTP/2 flow control 决定“每个收货口和总仓库还能接多少货”。

基本模型

HTTP/2 的流量控制基于 credit/window:

  1. 接收方声明自己还能接收多少字节。
  2. 发送方发送 DATA frame 时消耗窗口。
  3. 接收方消费掉数据、释放缓冲后,通过 WINDOW_UPDATE 把窗口加回来。
  4. 如果窗口耗尽,发送方必须暂停发送受流控约束的数据。

这里有两个层级的窗口:

窗口 作用范围 用来限制什么
stream flow-control window 单个 stream 某个 request/response body 的发送量
connection flow-control window 整条 HTTP/2 connection 所有 stream 的 DATA frame 总发送量

发送一个 DATA frame 之前,发送方必须同时检查两个窗口:frame payload 长度不能超过 stream window,也不能超过 connection window。发送后,这两个窗口都会按 payload 长度扣减。注意,HTTP/2 frame header 的 9 个字节不计入流控窗口。