仓库记忆与持久状态 本节摘要:聊天历史是易失的,仓库是持久的。工作台把 Agent 状态存进版本化的文件,让下一次会话、下一个 Agent、下一个审查者都从同一真相之源读。诊断很直接:Agent 结束一次会话,聊天关闭;下次会话打开问从哪开始,模型说「让我看下文件」,读了过期便签,把已经做完的工作重做一遍——或更糟,因为没人告诉它某文件已完成而重写它。工作台的修法是仓库记忆:状态活在仓库里的 JSON 文件中,写在模式之下,原子持久,代码审查里 diff 友好。聊天是瞬态 feed;仓库是真相之源。
本节摘要:聊天历史是易失的,仓库是持久的。工作台把 Agent 状态存进版本化的文件,让下一次会话、下一个 Agent、下一个审查者都从同一真相之源读。诊断很直接:Agent 结束一次会话,聊天关闭;下次会话打开问从哪开始,模型说「让我看下文件」,读了过期便签,把已经做完的工作重做一遍——或更糟,因为没人告诉它某文件已完成而重写它。工作台的修法是仓库记忆:状态活在仓库里的 JSON 文件中,写在模式之下,原子持久,代码审查里 diff 友好。聊天是瞬态 feed;仓库是真相之源。本节定义什么该进仓库记忆(活动任务 id、本次动过的文件、所做假设、开放阻塞、下一步)什么不该(裸聊天转录、token 级推理轨迹、「用户好像沮丧」、采样补全、厂商特定模型 id)——测试是耐久性:三个月后 CI 重跑还有用吗?有则入仓,无则入遥测。核心是模式优先的状态(JSON Schema 即契约):无它则每个 Agent 发明新字段、每个审查者学新形状、每个 CI 脚本得特判旧版本;有它则坏写即拒写。本节用标准库实现
StateManager(load/update/commit + 原子临时文件重命名写)、JSON Schema 子集校验,并给出五条让多智能体 monorepo 存活的生产模式:原子写非可选(Hive bug 案例)、每个非幂等工具调用带幂等键、大工件与状态分离、审计用事件溯源+续跑用快照、模式迁移否则拒载。读完本节,你能为状态文件写 JSON Schema 并在坏写腐蚀工作台前拒绝它。
对应原课程:Phase 14 · Lesson 34 ·
repo-memory-and-state(原英文phases/14-agent-engineering/34-repo-memory-and-state/docs/en.md)。前置:第 32 节(最小工作台)。
阅读完本节,你应当能够:
agent_state.json 与 task_board.json 编写 JSON Schema。Agent 结束一次会话。聊天关闭。下次会话打开,问从哪开始。模型说「让我看下文件」,读了过期便签,重做已经做完的工作。或更糟——它重写一个已完成的文件,因为没人告诉它那文件已完成。
工作台的修法是仓库记忆:状态活在仓库的 JSON 文件里,写在模式之下,原子持久,diff 友好。聊天是瞬态 feed;仓库是真相之源。
| 该进 | 不该进 |
|---|---|
| 活动任务 id | 裸聊天转录 |
| 本次动过的文件 | token 级推理轨迹 |
| Agent 所做假设 | 「用户好像沮丧」 |
| 开放阻塞 | 采样补全 |
| 下一步 | 厂商特定模型 id |
测试是耐久性:三个月后 CI 重跑还有用吗?有则入仓;无则入遥测。
JSON Schema 是契约。没有它,每个 Agent 发明新字段、每个审查者学新形状、每个 CI 脚本得特判旧版本。有它,坏写即拒写。
模式覆盖:
status 值。null)。T-\d{3,})。状态写要挺过部分失败:写临时文件、fsync、覆盖目标重命名。状态文件是真相之源;半个写坏的文件比没有文件还糟。
模式变时,在模式升级旁发布一个迁移脚本。状态文件带 schema_version 字段;管理器拒绝加载它无法迁移的版本的文件。
原课程 code/main.py 实现:
agent_state.schema.json 与 task_board.schema.json。StateManager.load、StateManager.update、StateManager.commit,带原子临时-重命名写。{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["schema_version", "active_task", "touched", "next_action"], "properties": { "schema_version": {"type": "integer", "const": 2}, "active_task": {"type": "string", "pattern": "^T-\\d{3,}$"}, "touched": {"type": "array", "items": {"type": "string"}}, "blockers": {"type": "array"}, "next_action": {"type": "string"} } }
def atomic_write(path, data): d = os.path.dirname(path) fd, tmp = tempfile.mkstemp(dir=d) # 同目录,保证同文件系统→原子 rename with os.fdopen(fd, "w") as f: f.write(data); f.flush(); os.fsync(f.fileno()) os.replace(tmp, path) # POSIX 与 Windows 都原子
class StateManager: def __init__(self, path, schema): self.path, self.schema = path, schema def load(self): state = json.load(open(self.path)) errors = validate(state, self.schema) if errors: raise BadState(errors) # 拒载坏状态 if state["schema_version"] not in MIGRABLE: raise Unmigrable() return state def commit(self, state): errors = validate(state, self.schema) if errors: raise BadState(errors) # 拒写坏状态 atomic_write(self.path, json.dumps(state, indent=2))
运行 python3 code/main.py 会写 workdir/agent_state.json 与 workdir/task_board.json,跨两轮变更它们,每步打印校验后的状态。
四加一条模式,把本节的最小变成多智能体 monorepo 能挺过的东西。
原子临时-重命名不是可选。 一份 2026-03 的 Hive 项目 bug 报告干净地记录了失败模式:state.json 经 write_text() 写,异常被吞。部分写让会话对着损坏状态续跑且无信号。修法恒为:tempfile.mkstemp 在目标同目录、写、fsync、os.replace(POSIX 与 Windows 都原子)。本节的 atomic_write 正是此事。
每个非幂等工具调用带幂等键。 若 Agent 调工具后、检查点结果前崩溃,恢复会重试工具调用。读安全;邮件、DB 插入、文件上传危险。模式:每次工具调用前把调用 ID 记进 pending_calls.jsonl。重试时查 ID;若在,跳过调用用缓存结果。Anthropic 与 LangChain 在 2026 指引里都点了这事;LangGraph 的 checkpointer 持久化待写也是同理。
大工件与状态分离。 别把 CSV、长转录、生成文件存进 agent_state.json。把工件存为单独文件(或上传对象存储),状态里只留路径。检查点保持小而快;工件独立增长。
审计用事件溯源,续跑用快照。 每次变更追加到事件日志(state.events.jsonl);周期性快照到 state.json。续跑读快照,再重放快照时间戳之后的事件。磁盘代价更高,但能逐字重放 Agent 决策——调试长程运行时关键。同 Postgres 内部 WAL 的形状。
模式迁移否则拒载。 schema_version 整数是契约。管理器加载未知版本文件时拒读。在模式升级旁发迁移脚本;tools/migrate_state.py 每次启动幂等运行。
💡 设计要点:仓库记忆的纪律,本质是把数据库工程的硬道理搬进 Agent 状态:契约(模式)、原子性(临时-重命名)、幂等性(幂等键)、审计(事件日志)、迁移(版本+脚本)。这些是任何生产后端几十年的常识;Agent 状态没理由豁免。第 31 节把状态归约到「会话持久化」原语,本节就是那个原语的工程落地。
| 运行时 | 状态持久化 |
|---|---|
| LangGraph checkpointers | 同想法,不同存储;持久化图状态到 SQLite/Postgres/自定义;本节模式是 checkpointer 死掉、你要手读状态时的退路 |
| Letta memory blocks(第 08 节) | 带结构模式的持久块;同纪律限定到长寿命人格 |
| OpenAI Agents SDK 会话存储 | 可插拔后端,模式感知;本节状态文件即本地文件后端 |
原课程 outputs/skill-state-schema.md:生成项目特定的 JSON Schema 对(状态+板)、接好原子写的 Python StateManager、一个迁移脚手架,让下次模式升级不破坏工作台。
last_human_touch 时间戳。人编辑后 5 秒内拒任何 Agent 写。oneOf,让任务可以是建造任务或审查任务(不同必填字段)。schema_version 字段,写 v1→v2 迁移(把 blockers 改名 risks)。StateManager API 不变。pending_calls.jsonl;重试查 ID 跳过用缓存。schema_version 是契约,未知版本拒读,迁移脚本幂等启动运行。下一节,我们做实「启动」这一面——初始化脚本:Agent 会话开始时该跑什么(检查环境、加载状态、播种任务板、校验规则),把「开始干活前」变成一段确定、可审计的代码。