状态:讨论草案。本文描述 Baton 的长期演进方向,不改变当前 v1“统一会话与跨 Harness 上下文接力”的实施范围。本文统一使用
Harness表示 Codex、Claude Code 等 智能执行环境。支撑这些能力的 v2 内核目标见 Baton v2;当前已经用 用户驱动的 Harness submit 落下首个 Intent / Attempt / Receipt 可靠投递切片,并用 BatonSessionsession_history落下首个 ContextSource / Snapshot / Receipt / Epoch 切片。 Plugin 的 Package / Instance / Binding / Controller 详细模型以 Baton Plugin 设计为准。
devloop 当前管理以 PR/MR 为粒度的开发小闭环:
开发 → lint/test → commit/push → PR/MR
它还可以参与更大的需求闭环:
需求 → 开发 → 部署 → review/eval → 修复 → 验收
但需求闭环只是众多 loop 中的一种。用户也可能组装发布闭环、事故处置闭环、代码迁移闭环, 甚至完全不接需求系统。因此 Baton 不应把 Requirement、Work、PR/MR 或 Deployment 固化为 核心对象;这些对象及其领域语义应由扩展提供。
真正通用的问题是:
- 如何把多个智能执行环境和外部能力接成一条可恢复的 loop;
- 如何让用户输入、外部事件和时间共同驱动下一步;
- 如何把跨步骤状态持续展示给人,并按需交给 agent;
- 如何让副作用、智能委托、审批和失败恢复走同一条可追踪路径。
因此 Baton 的目标定位是:
Baton 是可扩展的 Loop Engineering 控制面:编排 Harness 与 Plugin,维护事件、状态、 上下文和调度,使用户能够组合并运行不同领域的工作闭环。
Loop 不以“先写完一份完整 Task Contract,再用 Cron 执行”为前提。完整描述业务判断、例外和 完成条件的成本可能接近直接写代码,而且探索中的思路本来就会持续变化。Baton 允许 Plugin 用 普通代码消费 Baton 事实、表达领域 Resource 和 reconcile,再把已经稳定、适合自动化的动作 逐步交给控制面:
Loop ≈ resource(spec + status) + reconcile
▲
change / result / requeueAfter
Observe → Recommend → Manual approval → Scoped automation
spec 保存人认可的 Contract,status 保存 Controller 的当前观测;Resource、Harness 结果、
Controller Resource/cron Sources 和 requeueAfter 只负责提示“应该重新检查”。首期 Controller 可更新自有 status,并返回一段由人
审核、编辑后交给 Harness 的文本,不要求业务一开始就被穷举成 DSL。详细契约见
Baton Plugin 设计。
Plugin 不一定是 loop。一个“观察已完成 turn,分析问题更适合哪个 Harness,并给用户推荐输入” 的 Plugin,可以只消费 Baton 的只读 Baton-owned Resource 并产生 Proposal;只有需要保存 desired / observed state、长期推进外部状态时,才需要自有 Resource。
┌───────────────────────────┐
用户 ── Slash Command ──────▶│ │
用户 ── Composer Intent ────▶│ │
时间 ── RequeueAfter ───────▶│ Baton │
│ │
│ Event Ledger / Router │
│ Plugin Manager │
│ Reconcile Queue / Timer │
│ Board / Context Composer │
│ BatonSession / Policy │
└─────────┬─────────┬───────┘
│ │
未来受控 Harness 调用 │ │ Plugin 能力
▼ ▼
┌─────────────┐ ┌──────────────┐
│ Harnesses │ │ Plugins │
│ Codex │ │ reqloop │ marketplace
│ Claude Code │ │ other loops │
│ + devloop │ │ / utilities │
└──────┬──────┘ └──────┬───────┘
│ events │ events/results
└───────▶ Baton ◀┘
Baton 拥有控制面,而不拥有各领域:
- 接收、持久化、去重并路由事件;
- 从 Event Ledger 派生只读 Baton-owned Resource,供 Plugin 以 level-based 方式观察 Baton 行为;
- 装载 Plugin,注册 Command 与 Controller;
- 持久化 Resource、合并 reconcile key 并恢复
nextReconcileAt; - 将用户确认的
proposed-inputPlugin Output 作为普通 Input 委托给合适的 Harness; - 从 Resource 与协作事实生成 Board 视图;
- 根据目标 Harness、session 和 turn 组装 ContextBundle;
- 保存意图、决策、执行与回执,使 loop 可追踪、可恢复。
Baton core 只依赖稳定的控制面信封和能力协议,不理解“需求是否完成”“部署是否健康” 或“review 是否通过”等领域判断。
Harness 是 Codex、Claude Code 等智能执行环境的统一称呼,负责:
- agent session 的 open、submit、cancel、resume;
- message、tool、approval、usage 等原生执行事件的归一;
- 按能力接收 Baton 组装的 context;
- 执行需要代码理解、推理和工具调用的智能工作。
Harness 是专门的执行协议,不是任意 Plugin 都要实现的共同接口。一个 Plugin 包可以交付 Harness 实现,但运行时仍按 Harness 契约注册和管理。
Plugin 是 Baton 唯一的通用扩展、安装和运行单元。一个 Plugin 可以封装完整 loop,也可以提供 能独立使用的领域能力或用户本地自动化;划分依据是能力是否能独立演进和复用,而不是它连接了 几个外部系统。
运行时可把 Plugin Manager / Controller 理解成 Baton 内部对象的消费者:Manager 把 Event Ledger 派生成稳定、只读的 Baton-owned Resource,Controller 重新读取最新 snapshot 并产出 Plugin Output。当前 Output 是给用户的 Proposal;未来可以是受 Baton 管控的 Harness work request,但不能是直接 Adapter 调用。
本文未加限定的 Plugin 均指 Baton Plugin。Codex、Claude Code 等 Harness 也有自己的
Plugin 机制,两者处于不同扩展层:
- Harness Plugin:运行在单个 Harness 内,约束或扩展当前 agent loop;
- Baton Plugin:运行在 Harness 之上的控制面,观察和推进跨 session、跨系统的 loop。
devloop 属于 Harness Plugin:它规范 agent 在代码仓内完成开发、lint/test、commit 和 PR/MR, 而不是由 Baton Plugin Manager 装载的控制面能力。
外部系统不再对应另一种顶层运行角色。Plugin 用内部 Connector 隔离 Meego、Teambition 和不同
部署平台,并由 Resource Controller 在已授权 spec 范围内调用;Connector 不进入 Baton
manifest、host 或公共 Plugin API。这样安装、配置和运行都围绕同一个 PluginInstance,不再
为外部系统恢复一套平级身份。
reqloop 作为独立 Marketplace 提供 Requirement Loop,保持可配置、可禁用、可独立升级的 Plugin 边界。Baton core 不依赖 reqloop 的 Requirement、Deployment 等领域类型。
chat-tui 只消费展示快照并产生 intent。它可以提供 Board、命令选择器和状态栏等通用交互, 但不理解 Requirement、devloop、Deployment 或 Review 的领域语义。
需要区分不可变交付物和带配置的运行实例:
PluginPackage
└── PluginInstance
└── PluginBinding
├── Command[]
└── Controller[]
同一个包可以有多个实例,例如 deploy@dev、deploy@prod、
requirement@company-a。Resource、Event 和 Board 条目都引用 pluginInstanceId,
避免把账号、环境和安装包身份混在一起。
Package 是不可变交付物,Instance 是 BatonSession 中的配置身份,Binding 拥有当前进程中的 注册和关闭生命周期。Plugin manifest 声明能力与 Connector 权限;声明只是让能力 提前可见,不等于获得执行权限。完整模型以 Baton Plugin 设计 为准。
BatonPlugin
├── commands 创建、选择或修改 Resource
└── controllers
├── resourceType apiVersion + kind
├── reconcile
├── sources[] 外部 Resource 发现与固定 cron resync
└── present? 决定 Resource 是否显示到 Board
当前运行时已有 Plugin Command、Resource Controller 与 Controller Resource/cron Sources;Controller
既可管理 Plugin 自有 kind,也可观察 Baton 预先注册的只读 kind。manifest
declaration 校验随能力审阅入口补齐。Reconcile 是两种 Controller 共用的处理语义,不要求 Plugin
必须先创建可写 Resource。外部对象发现和固定周期观察使用 Controller 的 sources,一次性动态
重查使用 requeueAfter;Action 等到无法表达成 desired state 的独立命令出现后再引入。Plugin 可以在一个内聚的 loop 内部
组合多个外部系统;跨 Plugin 编排统一回到 Baton,不直接互调。
Codex 和 Claude Code 的 Plugin 主要扩展当前 Harness 内正在运行的 agent,例如提供 skill、 tool、hook 和 command。Baton Plugin 位于 Harness 之上的控制面,长期可以额外开放一项能力: 根据 Resource 状态请求启动、恢复或继续一个或多个 Harness。
Codex / Claude Code Plugin
→ 扩展当前 agent 能做什么
Baton Plugin
→ 观察 loop
→ 返回 proposed-input 或持久 interaction Plugin Output 给人审核
→ 未来请求 Baton 主动调度 Harness
→ Baton 选择/恢复 Harness,组装 context 并调度 turn
这项能力当前只保留长期方向,不进入首期 Plugin API,也不提前命名独立的顶层 Intent。只有真实
工作区证明某些步骤必须在无人输入时主动唤醒 Harness,才扩展 Resource/Reconcile 契约。届时
Baton 放开的仍是受控调度能力,而不是 HarnessAdapter/Harness 的裸句柄;路由、权限、成本、
并发、取消、上下文交付和结果持久化继续由 Baton 负责。
Plugin 可以注册 /requirements 等 slash command。命令 handler 操作 Resource,而不是直接
控制 TUI:
- 列表、表单和选择项由 Baton 渲染;
- 选中或输入需求后创建、恢复或修改 Resource;
- Board 由 Controller presentation 生成,Context 由对应 Resource Context source 提供;
- 需要智能判断时由 Controller 返回
kind: "proposed-input"的 Plugin Output,用户提交后 进入普通 Input 路径。 - 需要用户作出由原 Resource 消费的领域决定时返回
kind: "interaction";Baton 先持久化回答, 再重新 reconcile 同一 Resource。
这使“查看需求并放入 Board”成为 Plugin 能力,而不是 Requirement 进入 Baton core 的理由。
Harness、Plugin、Baton 和用户都可以产生事件。事件来源显式表达:
type EventScope =
{ type: "session"; batonSessionId: string };
type EventSource =
| { type: "baton" }
| { type: "harness"; harnessTargetId: string }
| { type: "plugin"; pluginInstanceId: string }
| { type: "user" };
type EventEnvelope = {
v: 3;
eventId: string;
scope: EventScope;
ts: string;
seq: number;
source: EventSource;
parentEventId?: string;
// harness / harnessTargetId / harnessSessionId / turnId 是可选执行坐标
kind: string;
payload: unknown;
raw?: unknown;
};eventId 是事实的稳定身份;scope 表示它归属哪个权威 ledger;source 表示谁对事实负责。
三者分别回答“哪条事实 / 属于哪里 / 谁报告”,都不等同于 payload 中的行为主体,也不替代
harnessTargetId、turnId 等执行坐标。Event 只写入 BatonSession ledger。fork 复制同一段
逻辑历史时保留领域对象 id;Event envelope 因进入 child ledger 而重新签发 event id 并更换
session scope。
Event 本身就是持久、可重放的事实,不再为它另造平行名字。wake、文件通知和 projection invalidation 是可合并的 signal:它们只提示消费者读取权威状态,不通过 EventSink 冒充已经发生的事实,也不能直接驱动 reducer。
领域事件使用 namespace,例如:
requirement.updated
harness.delivery.ready
deployment.completed
review.changes_requested
Baton core 只理解 event id、scope、source、subject/resource reference、时间、
parentEventId、payload schema 和 provenance 等稳定信封字段,不解释领域 payload。
各来源通过自己的宿主入口报告 Event 草稿,宿主在可信边界盖上不可伪造的 source:
source emit draft
→ Baton stamp source + append + dedupe
→ ack source
→ reduce / project / enqueue reconcile key
→ Plugin Controller / Policy
先持久化再分发。emit 成功只表示 Baton 已可靠接收事实;Controller 仍要重新读取 Resource 和
外部状态。高频 message/tool delta 可以进入 session ledger 和界面状态,但默认不逐条触发
Plugin,避免回调风暴。
对 Plugin 而言,Event 不是必须逐条处理的 callback 参数。Plugin Manager 先把选定的内部事实
转换成有稳定 type、identity 和 revision 的只读 Baton-owned Resource,再把 key 放入 workqueue;
Controller 执行时读取最新 snapshot。首个 baton.dev/v1alpha1, Kind=Turn 来自
_baton_turn_summary,启动 replay 与 live append 使用同一 Resource index 和队列。用户和 Plugin 都不能
修改 Baton-owned Resource;需要 desired state 时使用 Resource。
Resource type 位于同一所有权注册表:Baton 启动时先注册
baton.dev/v1alpha1, Kind=Turn,它是从 Event Ledger 派生的只读类型;Plugin 随后声明自己的
apiVersion + kind。Plugin 可以为 Baton type 注册 Controller 进行观察,但不能重新声明或
修改它;不同 Plugin 也不能声明同一 type。同一 Plugin 的多个 Instance 复用同一 type 定义,
各自的 Resource 数据仍按 namespace(即 pluginInstanceId)隔离。
Plugin 创建的 Resource 用 spec 保存人认可的 Contract,用 status 保存 Controller 的观测。
Baton-owned Resource 更新、Resource 创建或 spec 更新、启动恢复和计时到期都映射为
pluginInstanceId + apiVersion + kind + name,Baton 合并同一 key 的重复唤醒:
同一 Controller 可以管理多份同 kind Resource,因此一个 BatonSession 可以同时承载多个活跃
loop run。Baton 不定义跨业务的 phase 或 metadata.lifecycle;Plugin 在自己的 status
中表达业务阶段,Controller 的 present(resource) 决定每份 Resource 是否进入 Board。
Baton-owned Resource update / Resource change / startup / schedule or timer due
│
▼
keyed reconcile queue
│ same key coalesces
▼
reconcile(BatonSnapshot, latest Resource)
│
├── patch owned status through ResourceClient
├── call owned Connector when authorized
└── return { output?, requeueAfterMs? }
触发原因只是 wake hint,不是必须逐条执行的业务命令。同一 Resource 不并发 reconcile;
metadata.generation 随 spec 变化,status.observedGeneration 表示 status 基于哪版 Contract。
Controller 可以调用自己 Plugin 的 Connector,使实际状态靠近已授权 spec;不确定外部写入使用
稳定 operation key,并在重试前重新观察。
首期 ReconcileResult 只有 output? 和 requeueAfterMs?。proposed-input 供人审核、编辑
或丢弃,提交后才成为普通 Input;interaction 请求一个持久的单选或自由文本决定,回答先进入
Event Ledger,再作为当前 Resource 的 Snapshot 输入触发下一次 reconcile。Resource 的
requeueAfterMs 换算成 Baton 内部 control 中持久化的 nextReconcileAt,进程重启后恢复;
它不是 Plugin 可见的 Resource metadata,也不推进 resourceVersion。Baton-owned Resource 的
due time 只存在于进程队列,重启时由 ledger replay 再次 enqueue。错误都由 Baton 退避重试,
空结果等待新事实。
resource Source 把初始发现、文件监听、webhook channel 等外部 signal 映射为所属 Controller 的 Resource input/key;Source ready 后才执行 initial reconcile。无法表达成 desired state 的 独立命令出现后再增加 Action。关闭 TUI 后仍要求实时推进时,再让 daemon 复用同一 Resource store 和 reconcile queue。
一期 BoardView 直接从 Resource 和带 provenance 的协作事实生成,不先建设一份可独立
演化的 Board 数据库。长期出现跨 Resource、跨 owner 的独立整理需求后,再引入持久化、可查询的
BoardState。Board 不只是 UI,也不是某个 Plugin 的私有状态;它是 Baton、Plugin 与多个
Harness 交换协作信息的公共平面:
当前最小切片由 Controller 的 present() 落地:Plugin 把每份
Resource 派生为至多一个条目,Baton 补齐 owner、Resource reference 和最终身份,再展示到
chat-tui 的可选右侧 Sidecar。present 返回 undefined 时该 Resource 不显示;全部隐藏时不渲染
Sidecar。Plugin 可随 presentation 返回 priority 决定同一 Plugin Instance、同一 Resource
Type 内的次序;Baton 对每组只展示前 5 个。这条链路没有另建
可写 BoardState,仍以 Resource 为事实来源。
可以把 Board 理解成办案团队的“案件板”:不同参与者把线索、进展、结论、待核实项和关系放到 同一个可见空间,其他参与者据此整理认知并决定下一步。它是一种信息交互、整理和展示方式, 但不是系统唯一的通信或存储方式。
- 向用户展示目标、进度、结果、blocker 和待处理事项;
- 作为 ContextComposer 为目标 Harness 选择和编译 context 的主要来源;
- 允许 Plugin 展示结构化信息,并读取指定 scope 的 snapshot 参与 Reconcile;
- Baton 同时驱动多个 Harness 时,承载各 Harness 可共享的目标、进度、交付物和交接状态。
Baton core 不拥有 Requirement、Deployment 等领域对象。Plugin 从 Resource 生成领域条目; Harness 的进度和结果则由 adapter 先归一成事件,再关联 Resource 并展示进 Board:
BoardItem
├── stable key / owner
├── scope
├── resourceRef?
├── structured payload / schemaRef?
├── title / status / detail
├── facets / presentation hints?
├── provenance
└── freshness / ttl?
Board 不是所有参与者共同修改的一份全局 JSON。每个 contributor 只能 upsert 或撤销自己
namespace 下的 key;跨 Plugin、跨 Harness 的关联通过 resourceRef、领域 ID 和派生项表达。
BoardState 维护 revision,读方用 BoardSnapshot 获得一致视图,避免并行 Harness 用
last-write-wins 相互覆盖。Plugin 禁用后可以撤销自己的临时 contribution,同时保留已经进入
事件历史的事实。Baton 也可以根据正典事件产生有 provenance 的派生项,例如未决审批、等待原因、
调度失败、context 水位和跨 Harness catch-up。
“可行动事项”和“状态事实”可以作为默认 facet,帮助 UI 区分“系统建议下一步做什么”和 “已经发生或正在发生什么”,但它们不是 Board 的封闭类型,也不规定固定布局。TUI 可以使用 分区、卡片分组、筛选视图、统一时间线或其他形式。
Plugin 读取的是带 revision 的结构化 BoardSnapshot,不能解析 BoardView 的展示文本。
Board 变化可以 enqueue 相关 Resource 供 Plugin 重新评估,但 Controller 仍要重新读取 Resource
和外部状态,不能把 Board repaint 当成业务命令。Board 是共享协作状态,不取代 Event Ledger、
Resource 或外部系统作为各自事实的真相源。
Event routing、Plugin 私有状态、Harness 原生 session 和 Resource lookup 仍各自存在。Board 适合放可共享、可归属、可整理的信息或受控引用;secret、大体积原始材料和只对单次 turn 有效的 内容不必进入 Board,可以在 ContextComposer 交付时通过对应 Resource 的 Context source 按需补充。
申请用户同意不是 Board 的独立职责。Baton 应提供统一的 Permission Gate:Harness tool 继续
按执行请求授权;Plugin 的敏感外部变化则在相应 desired state 写入 spec 前完成授权。Board
只是决策的展示和追踪入口,真正的策略与回执属于 Policy/Permission。
Baton 按 requester + operation + permissionScope 保存授权策略,其中 requester 可以是
HarnessTarget、PluginInstance 或 Baton。首期至少提供:
- Manual:敏感 spec 变化或 Harness tool 执行前请求用户同意;
- Always Accept:同一作用域内自动同意,仍保留 spec revision、Interaction 或 Harness receipt 等对应审计历史。
默认使用 Manual。授权粒度必须是 Plugin 声明的具体操作,而不是“一次信任整个 Plugin”。 生产环境部署和关闭需求可以保持 Manual,同时让低风险的查询、dev 部署或 review 自动执行。 Plugin 版本新增操作、扩大作用域或提高风险时必须重新询问,不能继承旧授权静默扩权。
理想状态是用户不必参与每一步,但不能把全自动作为初始默认。随着用户对 Baton 和某个 Plugin 的结果建立信任,可以逐项从 Manual 调整为 Always Accept。这与 Harness 对 tool 调用的权限 请示是同一个模型:不同 requester 提出受控操作,用户决定哪些操作以后可以自动通过。
Interaction { kind: permission }
├── interactionId
├── requester: HarnessTarget / PluginInstance / Baton
└── permission payload: operation / permissionScope / risk / subject references / options
PermissionPolicy
└── manual / always-accept
interaction.resolved
└── allow / deny + decidedBy + policyRef
Controller 持有 Interaction identity 和 opened/resolved 生命周期。Harness 的 tool approval 已归一到该模型,Plugin 不另造一套“Board 审批”。Permission 状态可以展示进 Board,方便用户 统一查看,但 BoardItem 本身不是授权凭证;对 Plugin 而言,授权后的 spec revision 才是 Controller 可以收敛的 desired state,实际结果进入 status。
Baton / Plugin / Harness Events
│
BoardState
┌──────────┼───────────┐
▼ ▼ ▼
BoardView BoardSnapshot ContextComposer
面向用户 面向 Plugin 面向 Harness
│
ContextBundle
│
BatonSession 记录
BoardView、BoardSnapshot 和 ContextBundle 是同一协作状态面向不同消费者的视图。ContextBundle 针对某个 Harness、session 和 turn 按 scope、受众、freshness 和预算编译;不能把整个 Board 文本无差别塞进 prompt。大体积证据优先在 Board 保存 resource reference,由 Harness 按需读取。
必须区分三个动作:
Board 更新 ≠ Context 已交付 ≠ Harness 被唤醒。
Board 变化先更新展示视图;ContextComposer 只在用户提交 proposed-input Output,或未来调度准备续跑时
编译所需增量;是否创建 turn 首期只由用户 Input 决定。
BatonSession 记录 context 的 assemble 和 delivery receipt,包括 contribution、来源水位、 摘要、目标 Harness 与实际 transport。Harness adapter 按 capability 选择:
- 独立 context sync;
- turn 级 side channel;
- 受预算约束地 prepend 到下一次 prompt;
- 大体积内容通过 resource、MCP 或 CLI lookup 按需读取。
仍然坚持只通过 Harness 支持的通道注入,不写 Codex、Claude Code 等原生 session 文件。
Requirement Loop 用来验证 Baton 的通用抽象,但不是 Baton 内建流程。默认只需新增一个
reqloop Plugin,由它拥有这条 loop 的领域模型、推进策略和完成条件;不必预先把 Requirement、
Deploy、Review、Repair 和 Completion 拆成五个 Plugin。
reqloop 将 Requirement、Repository、PullRequest、Deployment、Evaluation 等抽象为自己的领域 概念,并用内部 Connector 适配具体平台。Baton 只看到 reqloop 注册的 command 和 Resource Controller;schema、reconcile、Board presentation 与可选 Context source 都收在对应 Resource 下。详细设计 见 reqloop 领域设计。
- 用户安装、启用并配置 reqloop,通过
/requirements创建或恢复 Requirement Resource; Requirement、验收条件和完成策略进入spec。 - RequirementController 返回“根据需求完成开发并提交 PR”的
proposed-inputOutput;用户原样提交、编辑后 提交或丢弃。提交后才成为普通 Input,因此仍是 user-driven turn。 - 目标 Harness 内安装的 devloop 规范 agent 完成开发、lint/test、commit 和 PR/MR;它使用 Codex/Claude Code 自己的 skill、hook、command 和权限机制,不注册为 Baton Plugin。
- MR 可交付时,devloop 产生结构化 DevelopmentOutcome,经 Harness adapter 或窄化的 Baton
event bridge 归一为带 Requirement Resource reference 的
harness.delivery.ready;Controller 将 PR 等实际交付物写入status。无法自动关联时,Controller 返回interaction让用户选择 Requirement 或拒绝关联。 spec要求 review 时,Controller 调用 EvaluationConnector,并通过requeueAfter轮询长耗时 结果;收到 changes requested 后返回包含 review 意见的修复proposed-inputOutput。- 部署、review 和修复满足 completion policy 后,Controller 返回
interaction询问是否关闭; 用户确认后,Controller 在已授权范围内调用 Connector 完成收尾并更新 conditions。
reqloop 可以在自己的 package 内实现 Meego/Teambition、部署平台和 review 平台 Connector, 因为这些适配共同服务于同一条 loop。devloop 仍有独立产品和 standalone 价值,但它安装在 Harness 内,不占用 Baton Plugin 身份。如果未来部署或 review 能力被 release loop、事故处置 loop 等多个场景复用,再将对应能力提取为独立 Baton Plugin;跨 Plugin 协作仍通过 Baton 的 Resource 与 Event 边界,不直接调用内部接口。
这条链路展示了 Plugin 的本质:它通过 Resource spec/status 和 Reconcile 交付一条完整 loop;
Baton 提供持久化、定时唤醒、Board presentation、Resource Context source 和 Harness Input 运行时。
devloop 负责规范 Harness 内部的 agent 开发循环:
- repo、branch、component、worktree 与 PR/MR 生命周期;
- 约束 agent 执行 lint/test、commit/push、code review 和开发期 guard;
- 通过 Harness 原生的 skill、hook、command 和 permission 影响 agent 行为;
- 在交付条件满足时产生结构化 DevelopmentOutcome。
它不是 Baton Plugin,不向 Baton 注册 Command 或 Controller。单独使用 Codex/Claude Code 时,devloop 仍能完成 PR/MR 小闭环; 运行在 Baton 驱动的 Harness 内时,Baton 只观察由 Harness 边界归一后的开发事件。
这要求在 Harness 边界定义稳定的 DevelopmentOutcome,而不是让 Baton 解析
.devloop/*.json、Harness transcript 文案或某个宿主的私有 hook payload:
devloop(Harness Plugin)
→ Harness-native signal / narrow event bridge
→ Harness adapter / Baton ingress
→ harness.delivery.ready / harness.development.blocked
→ Baton event ledger / Board / reqloop
具体 transport 取决于 Harness 能力:可以是原生 custom event、hook 回调或受控的 baton emit
入口。无论哪种方式,都只是结构化事件出口,不使 devloop 获得 Baton PluginInstance 身份。
之前 notify 没有稳定跑起来,主要不是领域事件判断错误,而是宿主侧缺少持久历史、常驻调度和 可靠唤醒。迁移方向是:
- 留在 devloop:agent loop 约束,以及交付、阻塞等开发结果的判定;
- 交给 Harness 边界:通过 adapter 或窄化 event bridge,把宿主内信号归一成 HarnessEvent;
- 移交 Baton:事件持久化、Resource、reconcile queue、Controller Resource/cron Sources、
requeueAfter、session 路由、Board 与 Harness 调度; - 逐步退休:依赖单一宿主的易失 notifier、waiter re-arm 和 agent 手工建立唤醒的步骤。
Baton TUI 打开时可以直接 reconcile;关闭 TUI 后仍要求实时推进时,再把相同 Resource/Event host 放进 daemon,而不是另造一套 notify 协议。
- manifest 声明稳定 plugin id、版本、components、配置 schema 和所需权限;
- PluginPackage 版本目录只读,可写状态进入独立的 PluginInstance data 目录;
- secret 由 Baton 管理并按 capability 注入,不进入事件 payload、日志或 ReconcileResult;
- Plugin 仅在当前 BatonSession 对应的 Project 已被用户信任后装载;
- 安装 Plugin 不等于自动授权其 Connector 权限或敏感 spec 变化;
- Controller 有 timeout、取消、失败策略和 provenance;未来主动 Harness 调用沿用现有可靠 Input 投递约束;
- Plugin 禁用时撤销 Binding 和 due timer,并保留 Resource 与可审计历史;
- Baton 负责控制面一致性,Plugin 失败不能破坏 event ledger 或其他 Plugin 的状态。
Plugin API 可以参考 Codex 和 Claude Code 的打包、权限和 Hook 经验,但不复制任一宿主的私有 事件协议:
代码、持久格式和 Plugin 模型统一使用 Harness 边界,不再引入额外的外部系统运行角色。
演进分成两条相互依赖、但不能混成一张 feature list 的路径:
v2 内核可靠性
scope / placement
→ fact / signal
→ intent / attempt / receipt
→ context snapshot / delivery
│
▼
Loop 产品能力
DevelopmentOutcome
→ Baton-owned Resource / Resource(spec / status)
→ Reconcile(BatonSnapshot, Resource) + PluginOutput + RequeueAfter
→ 未来受控 Harness 调用
→ daemon
- 作用域与执行位置分离:BatonSession 同时收口正典逻辑历史与 session-scoped Plugin 数据,HarnessTarget 和不可变 HarnessLaunchSnapshot 只表达执行位置与当时配置;Project 只负责按 cwd 组织和发现 BatonSession。
- Event、Interaction 与 Signal 收敛:Event v3 使用稳定
eventId + scope + source; permission/question/hook trust 共用Interaction opened/resolved生命周期;wake、文件通知和 projection invalidation 只作为可合并的 signal。signal 触发权威读取,不能直接改变状态。 - 可靠工作投递:先让现有用户驱动的 Harness submit 进入 Attempt ledger,以已持久化
Input 作为上游,验证先记账再投递、
uncertain和恢复语义;未来 Controller 主动调用 Harness 时复用这条路径,不向 Plugin 暴露 Harness 进程或 SDK 句柄。 - 上下文可对账:建立有 owner/key 的 ContextSource、ContextSnapshot、ContextEpoch 和
ContextDeliveryReceipt;明确 Board 更新、context 交付和 Harness 唤醒是三个独立转换。
当前已用
session_history打通 Snapshot → 三种 transport → Receipt → Epoch;Board / Plugin / Resource source 和独立 Context Attempt 仍按真实调度需求增量加入。
这四步的目标模型和恢复流程见 Baton v2。它们可以逐项叠加在 v1 之上,不要求 一次替换 BatonSession ledger 或 Adapter 协议。
- devloop 在 Harness 内提供稳定 DevelopmentOutcome;Harness 边界归一成 Baton Event;
- 建立 PluginPackage/PluginInstance/PluginBinding,以及 Resource 通用信封与存储;
- 将
_baton_turn_summary转换为只读baton.dev/v1alpha1, Kind=Turn,让 Plugin 从启动 replay 和 live append 感知 Baton 内部事实; - 建立同 key 不并发的 reconcile queue,将 Resource 的
requeueAfter持久化为内部 schedule,并让 Controller Resource/cron Sources 进入同一队列; - 接通 Board presentation、Resource Context source、
proposed-input和interactionOutput,用 reqloop + 安装了 devloop 的 Harness 跑通用户审核文本后驱动的 Requirement Loop; - 真实场景无法由 Resource/cron Sources、desired state 或当前进程覆盖时,再依次引入 Action 或 daemon;
- 真实工作区证明必须由 Controller 主动续跑 Harness 时,再扩展受控调用;它复用既有
Input/Attempt 投递与路由,不伪装成易失的
monitor Input。
reqloop 的 Requirement、Repository、PullRequest 等 Resource 及其完成条件始终归 reqloop。
Baton core 只提供通用 Resource 信封、ResourceRef、Event、Input/Attempt/Receipt 和 projection
原语,不提前抽象一个所有领域都必须采用的通用 LoopRun。
每一步都应保持 BatonSession 的 session 事件流是 Harness 会话历史真相源,并明确它与全局 Plugin/Event ledger 的关联,避免形成两份可独立修改的历史。
- Baton core 不内建 Requirement、Work、Deployment、Review 等领域对象和状态机。
- Plugin 是唯一通用扩展单元;Harness 是专门的智能执行协议。
- Connector 等领域适配抽象只能是 Plugin 内部概念,不进入 Baton 公共协议。
- Baton Plugin 与 Harness Plugin 是两层扩展机制;devloop 属于后者,不注册进 Baton Plugin host。
- Resource 用
spec表达用户认可的 Contract,用status表达 Controller 观测; generation / observedGeneration 显式表示收敛水位。 - Baton-owned Resource 从 Event Ledger 只读派生且可重放;用户和 Plugin 都不能修改,也不 另建持久真相。
- Controller 返回
PluginOutput(kind: "proposed-input" | "interaction")和requeueAfterMs;提交文本的 owner 仍是用户,Interaction 决议归请求它的 Resource。 - 未来若实现主动 Harness 调用,Plugin 也不能直接持有 Harness 进程或 SDK 句柄,必须复用 Baton Input/Attempt 投递路径。
- Event 先持久化,再更新 Baton-owned Resource 并触发 Reconcile;触发本身不是必须执行一次的命令。
- Board 是带 owner、scope、revision 和 provenance 的共享协作读模型,不是领域真相源或全局 可变字典。
- Plugin 只能修改自己的 Resource status;Board presentation 只能从 Resource 派生。Controller 可以调用自己的 Connector 收敛已授权 spec,但不能扩大权限或 scope。Plugin 可发 session-scoped、非持久化 Toast 作为状态迁移或显式操作的瞬时反馈,不能用它代替 Resource / Board。
- Board 更新、Context 交付和 Harness 唤醒是三个独立状态转换。
- Harness tool 与敏感 spec 更新共用 Baton Permission 语义;Board 只展示决策,不持有授权。
- 时间触发不自动获得副作用权限;Controller 只能收敛已经授权的 spec。
- Context 只通过 Harness 支持的通道交付,不修改其原生 session 文件。
- Event 是可重放事实;wake 与 invalidation signal 只提示读取权威状态,不能直接驱动 reducer 或形成第二份真相源。
- 外部 Connector 写入使用稳定 operation key;无法确认是否生效时先观察后重试。Harness
投递无法确认是否接收时进入
uncertain并对账。 - Project、BatonSession 与 HarnessTarget 分别承担会话组织、loop/历史归属和执行位置,不能 继续由 cwd、Harness 名称或隐式“当前值”混用。
- Baton core 不内建通用 LoopRun;具体 run、checkpoint 和完成条件由领域 Plugin 拥有。
- Controller resource Source 负责初始发现、实时订阅和目标 Resource materialize;cron
Source 表达固定周期 resync,
requeueAfter表达单个 Resource 动态决定的一次性重查。 三者最终都只 enqueue 同一 keyed reconcile;Source 不更新 status 或产生 Output,只在 Binding 活跃时运行,关闭 TUI 后的准时执行留给 daemon。
- Plugin/Event 全局 ledger 与 BatonSession
session.jsonl如何关联而不形成双真相? - 用户级 PluginPackage、BatonSession 内 PluginInstance 与项目级默认配置应如何继承?
- Resource schema version、reconcile key、队列合并和启动补扫策略如何定义?
- PermissionPolicy 的 scope 继承与覆盖规则如何跨 requester、operation、project、环境和 session 表达?
- 哪些真实场景足以触发 Controller 主动调用 Harness?实现后如何与用户 turn 协调 admit、 排队、steer 或取消?
- Board scope 与 resource reference 如何设计,才能支持多 repo 和多个并行 loop?
- Resource 的 identity、spec/status schema、status patch 冲突和 migration 如何定义?
- resource Source 的可靠性或实时性达到什么指标后,应由 Baton daemon 承载?
- 不同 Harness 分别用 custom event、hook 还是
baton emit传递 DevelopmentOutcome,才能 兼顾原生能力、可靠性和最小耦合? - Harness 对“已接收工作”的权威边界分别是什么,哪些 Harness 能原生查询并收敛
uncertainAttempt?