跳转到内容

Preview Bundle 协议与 Trajectory 系统

学习目标

  • 理解 preview-bundle/v1 协议的实现
  • 掌握三层持久化模型的工程实现
  • 了解 Session 文件锁定的 _locked_save_json 模式
  • 掌握 trajectory.json 的 append-only 设计与回放机制

前置知识

本章涉及 Agent 工作流 Preview 的通用模式,建议先阅读:

下文直接聚焦 CLI-Anything 的具体实现。


项目实践

preview_bundle.py 协议实现

preview_bundle.py(~500 行)是所有 Harness 共享的 Preview Bundle 协议实现。

协议版本:preview-bundle/v1

Bundle 目录结构

bundle_003/
├── manifest.json # 元数据:时间戳、命令、源指纹
├── summary.json # 摘要:命令描述、关键属性
├── render.png # 渲染产物
└── artifacts/ # 额外产物

三层持久化实现

对象格式可变性用途
bundle_dir目录不可变每次 capture 的快照
session.jsonJSON 文件可变Live Preview 的当前头
trajectory.jsonJSON 数组文件追加式命令 → Preview 的完整历史

关键设计_bundle_dir 不是永久的构建身份。它只是单点快照路径,会随源指纹变化而改变。稳定的回放身份锚定在 session_dir + trajectory.json

Publish 流程

每条 trajectory 条目包含:

  • 命令描述
  • 时间戳
  • Bundle 路径
  • 源指纹(源文件哈希)
  • 发布原因(mutation / capture / start)

Session 文件锁定(源码验证 guides/session-locking.md

并发写入 session JSON 可能导致损坏。CLI-Anything 使用排他文件锁定:

# 伪代码:_locked_save_json 模式
import fcntl
def _locked_save_json(path: str, data: dict):
with open(path, "r+") as f:
fcntl.flock(f, fcntl.LOCK_EX) # 排他锁
f.seek(0)
f.truncate() # 截断旧内容
json.dump(data, f, indent=2) # 写入新内容
fcntl.flock(f, fcntl.LOCK_UN) # 释放锁

关键点

  • 使用 "r+" 模式打开(需要文件已存在)
  • ftruncate() 在锁内执行——防止其他进程在截断和写入之间读取到空文件
  • 整个操作在锁内完成

preview live status --json 紧凑设计

为了避免 Agent 每次状态探测都读取整个 trajectory.json(可能包含数百条条目),preview live status --json 返回紧凑的 trajectory_summary

{
"active": true,
"session_dir": "/path/to/session_001",
"trajectory_summary": {
"total_commands": 42,
"latest": {
"command": "chassis build",
"bundle": "bundle_007",
"timestamp": "2026-05-31T10:45:00Z"
}
}
}

Agent-Cheap 设计:Agent 可以高频调用此命令而不会浪费 Token——返回的是摘要而非完整历史。

回放机制

trajectory.json 的 append-only 性质使完整回放成为可能:

  1. 读取 trajectory.json 的所有条目
  2. 按时间顺序重放每个命令
  3. 每个命令对应一个 Bundle,可以查看中间状态
  4. 完整重现 Agent 的工作轨迹

这对于调试、教学展示和审计都有价值。

Preview 协议的 CLI 表面

Harness 如果支持 Preview,应至少暴露:

命令用途
preview recipes列出可用 Preview 配方
preview capture新鲜渲染/导出,发布新 Bundle
preview latest返回最新已有 Bundle
preview diff发布不可变对比 Bundle
preview live start/push/status/stopLive Preview 生命周期

Producer/Consumer 协议回顾

CLI-Anything 严格分离角色:

  • Producercli-anything-<software> preview ...):渲染、发布
  • Consumercli-hub previews ...):读取、展示

Consumer 命令:

  • cli-hub previews inspect <session> — 查看 Bundle 内容
  • cli-hub previews html <session> — 生成 HTML 展示
  • cli-hub previews watch <session> — 实时监控新 Bundle
  • cli-hub previews open <session> — 在浏览器中打开

问题与规避

问题规避策略
并发写入损坏 session.json_locked_save_json 排他文件锁
_bundle_dir 被当作永久身份文档强调稳定回放锚定 session_dir + trajectory.json
trajectory.json 增长过大trajectory_summary 提供紧凑摘要
源指纹变化后旧 Bundle 引用失效源指纹变化时强制重新 capture
Bundle 目录名冲突自动递增编号(bundle_001, bundle_002, …)

设计取舍

Append-only vs 可编辑 Trajectory

选择:trajectory.json 只追加,不修改。

原因

  • 完整的历史审计链
  • 回放的可重复性
  • 避免历史被篡改导致不一致

代价:文件随时间增长。但 trajectory.json 是 JSON 数组,可以压缩或归档旧条目。

flock vs 其他锁定机制

选择fcntl.flock() 排他文件锁。

原因

  • POSIX 标准,Linux/macOS 广泛支持
  • 比临时文件+原子替换更简单
  • 不需要外部锁服务

代价:Windows 不支持 fcntl。CLI-Anything 主要在 Linux 环境运行(CI 和服务器),Windows 兼容性通过 Git for Windows 的 bash 层处理。

参考来源

  • 源码验证:preview_bundle.py(~500 行)
  • 源码验证:guides/session-locking.md
  • 源码验证:guides/preview-methodology.md
  • 源码验证:FreeCAD/Blender Harness 中的 preview 命令