本文定义 Baton 的稳定内核:它解决什么问题、核心对象由谁拥有、依赖如何流动,以及所有 Harness 和 Plugin 都不能绕过的约束。一次用户输入如何到达 Harness、Harness 结果如何返回 用户,统一见 工作流;适配协议见 Harness,长期领域 loop 见 Plugin。
Baton 是 terminal-native 的 Loop Engineering 控制面,也是跨 coding agent 的统一工作区。
用户拥有的不是某个 agent 进程,而是可持久、可恢复、可跨 Harness 接力的 BatonSession。
Codex、Claude Code 等原生会话用于执行与加速恢复,不能成为逻辑历史的唯一载体。
Baton 按三层协作:
- Baton core 负责 Input、Interaction、Event、Context、权限、Harness routing、调度和 Projection,不理解 Requirement、Deployment、Review 等领域语义。
- Baton Plugin 以 Resource 的
spec/status表达长期领域 loop,由 Controller reconcile; 当前只能通过受控 Output 建议下一条 Input 或请求用户决议,不能直接调用 Harness。 - Harness 提供智能执行能力,Adapter 把各家协议归一成稳定契约。devloop 等 Harness Plugin 只约束 Harness 内部的小闭环,不成为 Baton Plugin 的私有执行接口。
chat-tui 位于内核之外:它消费展示快照并产生 intent,不拥有 Session、队列、Harness 或 Plugin 生命周期,也不解释领域语义。
从控制关系看,内核收束为三个协作域:
| 域 | 负责 | 不负责 |
|---|---|---|
| Input | 表达进入 Baton 的刺激;当前主要是用户 prompt、interaction resolution 和 control | 不理解 Harness wire,不决定执行终态 |
| Controller | admission、queue、Attempt、Turn、Interaction、Event 持久化和 Projection | 不实现 Harness 方言,不依赖具体 UI shape |
| Harness | Target、原生 Session、Adapter、Capability 和原生事件归一 | 不拥有用户输入生命周期,不直接向页面投影 |
控制方向是 Input → Controller → Harness;感知方向是
Harness → Controller → Projection → 用户。Input 与 Harness 不直接依赖,只有 Controller
同时理解两侧稳定契约。
| 概念 | 语义与 owner |
|---|---|
| Project | 按 cwd 组织和发现 BatonSession,并承载同 workspace 跨 Session 的 Plugin 私有数据;不拥有 Session 历史 |
| BatonSession | 用户拥有的正典逻辑历史和 session-scoped Plugin 数据;跨 Harness 的唯一时间线 |
| HarnessTarget | Baton 配置、调度和状态查询侧的一份具体执行目标;同一 Harness 可有多个 Target,状态必须按 Target 隔离 |
| HarnessSession | Harness 在某个 Target 内持有的持久原生执行会话;缺失只影响恢复优化,不阻止 BatonSession 继续 |
| HarnessSessionBinding | 当前 BatonSession 到 HarnessSession 的可重建连接;由 Adapter 在 identity 可知时主动发布 |
| HarnessSessionHandle | 进程内调用路由句柄;不能持久化,也不能代替 HarnessSession identity |
| Input | Controller 拥有的待处理刺激;用户 prompt 具有稳定 message/turn identity 和可查询消费状态 |
| Delivery Attempt | 一次已准入 Input 向 Harness 投递的持久记录;先 prepared 再 dispatch,无法证明结果时保留 uncertain |
| Turn | 一段有始有终的 Harness 活动;driven/observed 是发起角色,不影响“必须收口”的契约 |
| Event | append-only 的最小执行事实;Event Ledger 是 Session 执行与感知历史的真相源 |
| Interaction | 需要外部参与者给出结果后才能继续的持久待决对象;Controller 拥有 opened/resolved 生命周期 |
| Context delivery | 有 owner/key 的 ContextSource 被组装为 Snapshot,并向具体 HarnessSession 交付;Receipt 才推进 Epoch |
| Projection | Event reduce 得到的派生展示快照;不是新的事实来源 |
Event.scope 回答事实属于哪条 ledger,Event.source 回答谁报告事实,HarnessTarget、
HarnessSession 和 Turn 则是执行坐标。这些维度正交,不能从 Harness 名、alias 或 wire key
猜测彼此。
Baton 签发的 Event、Interaction、Context Snapshot/Epoch、Session、Turn、Message、Tool Call
和 Attempt 使用带前缀的稳定 ULID。HarnessTarget、PluginInstance 等配置对象使用各自作用域内
的稳定 ID。fork 复制逻辑对象时保留对象 ID,进入 child ledger 的 Event envelope 重新签发
eventId;详细语义见 resume 与 fork。
Baton host process
┌──────────┐ intent ┌──────────────────────────────────────┐
│ chat-tui │─────────▶│ Controller / Session / Event Ledger │
│ │◀─────────│ Context / Interaction / Projection │
└──────────┘ render │ Plugin Manager / Runner supervision │
└──────────────┬───────────────┬─────────┘
│ │
stable contract │ IPC
▼ ▼
Harness Adapter Plugin Runner
│ │
▼ ▼
Harness Connector / domain
host 主线程拥有 stdin、焦点、Controller、SessionStore、Plugin Manager 和展示快照。render、键盘 handler 与同步 getter 只读内存,不执行文件、网络、Git、Package import 或 Plugin 回调。 I/O 使用 async API;三方 Plugin 按活动 Binding 运行在独立 Runner 进程,Harness 进程或 SDK 生命周期由对应 Adapter 持有。
进程边界按故障与 owner 划分,不按页面区域划分。chat-tui 的 composer、timeline、footer、 sidecar 只是 surface 订阅边界,共享一条终端焦点和一条 host event loop。进程关闭按 owner 反向进行:停止接收 intent → cancel/close Harness → 关闭 Plugin Binding/Runner → flush Session → destroy renderer。
内核只有一条双向流水线。这里仅给出拓扑;admission、Context、Attempt、Interaction 和终态的 顺序以 工作流 为准。
一切可恢复、可展示的执行事实都经过:
Event → append → broadcast → reduce → Projection
live、resume 和自愈使用同一条 reduce 路径。Adapter、Controller 或 TUI 都不能再开一条 per-turn callback 或直接改视图状态的旁路。Plugin Resource 和外部系统可以拥有各自领域事实, 但不能冒充 Session Event Ledger。
每个被接受的 Turn 必须恰好逻辑收口一次;正常结束、wire error、子进程退出、transport close 和 cancel 都必须报告或合成终态。物理终态可以重复或迟到,Controller 按 Baton turn ID 幂等 finalize。
内部状态使用封闭词表,Adapter 在边界归一 Harness 的开放值;未知终态不能乐观映射为成功。
原始协议保留在 Event raw,未知通知进入有界诊断而不是静默丢弃。
Harness 差异只能存在于 Adapter、Definition、Inspector 和 Capability。Session、Event Store、
reduce、Projection 与 chat-tui 不出现 if harness === ...。新增 Harness 默认只修改:
src/harness/<harness>/
src/harness/registry.ts
src/harness/ids.ts
只有被多个 Harness 共同印证、且确实改变稳定契约的能力,才可提升为新的 Capability 或内核概念。
用户输入先成为 BatonSession 事实,再尝试 dispatch;Delivery Attempt 先持久化 prepared,
再调用 Adapter。Plugin Output 先持久化,再通知 UI。Context Snapshot 只说明准备送什么,只有
DeliveryReceipt 才证明 transport 已接受。无法证明副作用是否发生时保留 uncertain,不盲目
重投。
Baton core 不内建 Requirement、Deployment、Review 或通用 LoopRun。领域 Plugin 拥有 Resource、
Connector、完成条件和 reconcile;Harness Plugin 拥有 agent 内部开发约束。Plugin 建议的
proposed-input 经用户确认后成为普通 Input,继续走同一 Context、Permission、Attempt 和
Harness routing 主路径。
判断一个新能力落点时依次问:
- 它的事实由谁拥有,生命周期由谁收口?
- 它是单个 Harness 的协议差异,还是多个 Harness 共享的稳定语义?
- 它是否需要持久身份、恢复和对账,还是仅是短寿命 signal/view?
- 是否可以通过现有 Input、Interaction、Event、Context 或 Capability 表达?
- 提升内核后,能否保持新增 Harness 不修改 Session/store/projection/chat-tui?
signal 只提示重新读取权威状态,不能冒充 Event;Board 更新、Context 已交付和 Harness 已被唤醒 是三个独立事实。尚未满足真实场景的自动 Harness Work、多 Harness 并行汇总和主线/草稿收录 留在 Backlog,不提前向 Plugin 暴露 Harness 句柄。
- 工作流 — 用户输入、Harness 执行、事件投影与 Interaction 闭环
- Harness — Target、Session、Adapter、Capability 与扩展契约
- Plugin — Resource/Controller、Runner、Board、Context 与长期 loop
- 审批生命周期 — permission、授权方与 auto-review 回执
- Session Paths、resume 与 fork — 会话分支、恢复与收录
- 日志体系 — Baton、Harness 与 Plugin 的结构化诊断
- Backlog — 有意识暂缓的能力和启动条件