Lesson 07 · 记忆

状态写到磁盘,活过压缩

「State that survives compression — because it's outside the conversation.」

⏱ 약 10 분 · 📝 3 개 인터랙티브 컴포넌트 · 🧑‍💻 기반 shareAI-lab · s07_task_system.py

TodoWrite 和 Task 有什么区别?

s03 的 TodoManager 也能列任务——但它存在内存里。s06 讲的 auto_compact 一触发,messages[] 被替换成 summary,TodoManager 的状态就跟着没了(因为它是 in-memory)。

s07 的 Task 不一样:每个 task 是一个 .tasks/task_42.json 文件。context 被压也好、进程重启也好、agent 被换掉也好——只要磁盘文件还在,任务就还在

# .tasks/task_12.json
{
  "id": 12,
  "subject": "Refactor auth middleware",
  "description": "Extract JWT logic to shared module",
  "status": "pending",
  "blockedBy": [8, 11],   # 必须先完成 #8 和 #11
  "owner": ""
}
选择规则:临时待办(做完这次就忘)用 todo;持久任务(跨会话保留、有依赖)用 task

blockedBy · 依赖图的脾气

blockedBy 是一个 task id 列表——必须这些都 completed 之后,当前 task 才「可执行」。

s07 的实现有一个精巧的细节:完成一个 task 时,它会自动从所有其他 task 的 blockedBy 列表里移除自己

def _clear_dependency(self, completed_id: int):
    # Scan every task, remove completed_id from their blockedBy
    for f in self.dir.glob("task_*.json"):
        task = json.loads(f.read_text())
        if completed_id in task.get("blockedBy", []):
            task["blockedBy"].remove(completed_id)
            self._save(task)

这样 agent 不需要维护一张独立的 "什么 task unblocked 了" 表——只要遍历 .tasks/,找 status=="pending" and not blockedBy 的就是现在可执行的。

依赖图交互

下面这个 widget 给你一个 5 个 task 的依赖图。点 "complete",看 blockedBy 自动更新,哪些 task 变成「可执行」(绿色高亮)。

经过压缩还能活吗?

s06 讲过 auto_compact 会把 messages[] 换成一条 summary。但 task 不受影响,因为它在磁盘。测试一下:让 agent 做了 5 个 task,压缩掉对话后,它扫一下 .tasks/,原地能继续。

Interactive

Widget 1 · Dependency Graph · 点 complete 看依赖更新

5 个 task 的拓扑关系。点任意一个 pending 的 task 上的按钮改它的状态,观察 blockedBy 数组和「下一个可执行」列表的变化。

Task 列表(.tasks/ 目录)
当前可执行(status=pending, blockedBy=[])
依赖关系图
Interactive

Widget 2 · Compression Survival · task 活过 auto_compact

让 agent 建 3 个 task,然后触发 auto_compact(messages 清空),再恢复——看 task 还在不在。

.tasks/ 目录
messages[]
Interactive

Widget 3 · Dependency Chain · 给你一张图,答哪些 task 现在能动

5 个 task 的依赖关系。回答在给定状态下,下一步可以动手的是哪些(可能多选)。

答对 0 / 4