为什么需要会话追踪
用 AI Agent 一段时间后,你会遇到一个问题:换了运行环境,Agent 不知道上次聊到哪里。
更具体地说:
- Codex 有
CODEX_THREAD_ID,opencode 有 session ID,但两者互不相通 - 同一个项目可能在两个环境下交替使用,每次都从头开始
- 无法判断上次会话是正常结束还是异常中断
需要一层薄薄的抽象,让 Agent 在任何环境下都能回答一个问题:这个项目上次的会话状态是什么?
答案就是 track-agent-sessions。
设计:一个确定性的会话索引
核心文件是 .agent-sessions/session-history.json,存放在被追踪的项目根目录下:
{
"sessions": [
{
"id": "20260804-1641-codex",
"runtime": "codex",
"native_session_id": "019fcbef-8dcd-7f33-a174-12d...",
"started": "2026-08-04T16:41:42Z",
"ended": null,
"status": "active",
"focus": "登记验证"
}
],
"latest_session": "20260804-1641-codex",
"history_limit": 20
}
每条记录包含运行时标识、原生会话 ID、状态和时间戳。latest_session 指向最近登记的会话,Agent 不需要遍历全表就知道当前焦点在哪里。
生命周期管理
四个核心操作:
开始 → track-agent-sessions start --status active
验证 → track-agent-sessions report
结束 → track-agent-sessions end --native-id <id>
恢复 → track-agent-sessions start (检测到残留 active → 标记 interrupted + 新开 active)
中断恢复的逻辑:如果索引中已有 active 记录但原生 session ID 不同,说明上次会话未正常结束。此时不覆盖旧记录,而是将其标记为 interrupted,再新建一条 active 记录。两条记录都保留,等人工确认。
有界索引:默认保留最近 20 条记录。超出时移除最旧的终态记录(ended / interrupted),不删除 active 记录,不删除 Agent 原生 transcript。
消费者项目如何接入
Skill 的安装方式是向目标项目注入一段 AGENTS.md 指令:
## Agent session tracking
On the first user instruction of every new agent session:
1. Register this session: track-agent-sessions start --status active
2. Report: current session ID + full history
When user explicitly requests to end:
1. End this session: track-agent-sessions end --native-id <id>
Agent 读取这段指令后自主调用脚本,无需人工每次操作。
消费者项目不直接修改 Skill 源码。源码和消费者副本完全隔离:src/track-agent-sessions/ 是唯一源码,消费者下的 .agents/skills/track-agent-sessions/ 是安装副本。修复必须先在源码完成、跑通测试、再重新安装。
安装验证协议
Skill 需要在不同运行时下行为一致,设计了一套验证协议:
1. 登记验证 → Agent 登记会话,报告 ID + 状态 + 文件路径
2. 结束验证 → Agent 结束同一会话,确认状态变为 ended
3. 跨环境验证 → 在另一个运行环境下重复,确认索引兼容
4. 消费者验证 → 在独立子项目中安装,确认不影响已安装副本
每次验证时 Agent 只报告结果,不修改源代码。如果验证失败,回到源码修改、跑测试、重新安装。
关键设计取舍
为什么不用数据库? 文件系统是 Agent 最自然的操作对象。JSON 文件可读、可 grep、可版本控制(.gitignore),没有额外依赖。
为什么项目级而非全局? 同一 Skill 可以安装在不同项目里,每个项目有独立的会话记录。项目级隔离比全局单例更干净。
为什么限制 20 条? Agent 只需要最近的历史来判断当前状态。完整 transcript 由各运行时自行管理。20 条是”够用且不膨胀”的平衡点。
为什么区分 interrupted 和 ended? 异常中断和正常结束在语义上完全不同。标记为 interrupted 的记录提醒用户:这里可能需要人工确认。
从 Problem 到 Skill
这个 Skill 的诞生路径本身就是 Praxis 工作流的一个实例:
问题: Agent 会话记录缺少原生 ID
→ 调查:Codex 和 opencode 各自有原生 ID 格式
→ 方案:以 runtime + 原生 ID 为唯一标识,JSON 文件做索引
→ 实现:track-agent-sessions skill
→ 验证:Codex 和 opencode 各跑 3 轮安装验收
→ 认知:机器事实源与人类投影分离
一个问题 → 一个可复用 Skill → 一条可复用的认知模型。