ACP 线程绑定 Agent
概述
本计划定义了 OpenClaw 如何在支持线程的频道(先做 Discord)中支持 ACP 编程 Agent,达到生产级的生命周期管理和故障恢复能力。
相关文档:
目标用户体验:
- 用户在线程中 spawn 或 focus 一个 ACP 会话
- 该线程中的用户消息路由到绑定的 ACP 会话
- Agent 输出以相同的线程身份流式回传
- 会话可以是持久的或一次性的,提供显式的清理控制
决策摘要
长期建议采用混合架构:
- OpenClaw 核心负责 ACP 控制平面
- 会话身份和元数据
- 线程绑定和路由决策
- 投递不变量和重复抑制
- 生命周期清理和恢复语义
- ACP 运行时后端可插拔
- 首个后端是基于 acpx 的插件服务
- 运行时负责 ACP 传输、队列、取消、重连
OpenClaw 不应该在核心中重新实现 ACP 传输内部逻辑。 OpenClaw 不应该仅依赖纯插件拦截路径来做路由。
终极架构(理想方案)
将 ACP 作为 OpenClaw 的一等控制平面,配合可插拔的运行时适配器。
不可妥协的不变量:
- 每个 ACP 线程绑定都引用一个有效的 ACP 会话记录
- 每个 ACP 会话都有显式的生命周期状态(
creating、idle、running、cancelling、closed、error) - 每次 ACP 运行都有显式的运行状态(
queued、running、completed、failed、cancelled) - spawn、bind 和初始入队是原子操作
- 命令重试是幂等的(不会产生重复运行或重复的 Discord 输出)
- 绑定线程的频道输出是 ACP 运行事件的投影,绝非临时副作用
长期所有权模型:
AcpSessionManager是唯一的 ACP 写入者和编排者- manager 先在 gateway 进程中运行;后续可以迁移到专用 sidecar,接口不变
- 每个 ACP session key 对应 manager 中的一个内存 actor(串行化命令执行)
- 适配器(
acpx、未来后端)只是传输/运行时实现
长期持久化模型:
- 将 ACP 控制平面状态迁移到 OpenClaw state 目录下的专用 SQLite 存储(WAL 模式)
- 保留
SessionEntry.acp作为迁移期间的兼容投影,不再是权威来源 - 追加写入 ACP 事件以支持回放、崩溃恢复和确定性投递
投递策略(通往理想方案的桥梁)
- 短期桥接
- 保留现有线程绑定机制和现有 ACP 配置界面
- 修复元数据缺失 bug,将 ACP 回合路由到核心中的单一 ACP 分支
- 立即添加幂等性 key 和闭合失败的路由检查
- 长期切换
- 将 ACP 权威来源迁移到控制平面 DB + actor
- 让绑定线程投递变成纯粹的事件投影
- 移除依赖机会性 session-entry 元数据的遗留回退行为
为什么不用纯插件方案
当前的插件钩子不足以在无核心变更的情况下实现端到端的 ACP 会话路由。
- 入站路由先在核心 dispatch 中通过线程绑定解析到 session key
- 消息钩子是发后即忘的,无法短路主回复路径
- 插件命令适合做控制操作,但不适合替代核心的每轮 dispatch 流程
结论:
- ACP 运行时可以插件化
- ACP 路由分支必须在核心中存在
可复用的现有基础
已经实现且应保持为规范行为的:
- 线程绑定目标支持
subagent和acp - 入站线程路由覆盖在正常 dispatch 之前通过绑定解析
- 出站线程身份通过 webhook 在回复投递中实现
/focus和/unfocus流程与 ACP 目标兼容- 持久绑定存储,启动时恢复
- archive、delete、unfocus、reset 和 delete 时的解绑生命周期
本计划扩展这些基础,而非替换。
架构
边界模型
核心(必须在 OpenClaw 核心中):
- 回复管线中的 ACP 会话模式 dispatch 分支
- 投递仲裁,避免父频道 + 线程重复
- ACP 控制平面持久化(迁移期间保留
SessionEntry.acp兼容投影) - 与会话 reset/delete 相关的生命周期解绑和运行时分离语义
插件后端(acpx 实现):
- ACP 运行时 worker 监督
- acpx 进程调用和事件解析
- ACP 命令处理器(
/acp ...)和运维 UX - 后端专属配置默认值和诊断
运行时所有权模型
- 一个 gateway 进程拥有 ACP 编排状态
- ACP 执行在通过 acpx 后端监督的子进程中运行
- 进程策略是每个活跃 ACP session key 一个长期存活的进程,而非每条消息一个
这避免了每次 prompt 的启动开销,保持取消和重连语义可靠。
核心运行时契约
添加核心 ACP 运行时契约,让路由代码不依赖 CLI 细节,可以在不改变 dispatch 逻辑的情况下切换后端:
export type AcpRuntimePromptMode = "prompt" | "steer";
export type AcpRuntimeHandle = {
sessionKey: string;
backend: string;
runtimeSessionName: string;
};
export type AcpRuntimeEvent =
| { type: "text_delta"; stream: "output" | "thought"; text: string }
| { type: "tool_call"; name: string; argumentsText: string }
| { type: "done"; usage?: Record<string, number> }
| { type: "error"; code: string; message: string; retryable?: boolean };
export interface AcpRuntime {
ensureSession(input: {
sessionKey: string;
agent: string;
mode: "persistent" | "oneshot";
cwd?: string;
env?: Record<string, string>;
idempotencyKey: string;
}): Promise<AcpRuntimeHandle>;
submit(input: {
handle: AcpRuntimeHandle;
text: string;
mode: AcpRuntimePromptMode;
idempotencyKey: string;
}): Promise<{ runtimeRunId: string }>;
stream(input: {
handle: AcpRuntimeHandle;
runtimeRunId: string;
onEvent: (event: AcpRuntimeEvent) => Promise<void> | void;
signal?: AbortSignal;
}): Promise<void>;
cancel(input: {
handle: AcpRuntimeHandle;
runtimeRunId?: string;
reason?: string;
idempotencyKey: string;
}): Promise<void>;
close(input: { handle: AcpRuntimeHandle; reason: string; idempotencyKey: string }): Promise<void>;
health?(): Promise<{ ok: boolean; details?: string }>;
}
实现细节:
- 首个后端:作为插件服务发布的
AcpxRuntime - 核心通过注册表解析运行时,当没有可用的 ACP 运行时后端时以显式的运维错误失败
控制平面数据模型和持久化
长期权威来源是专用 ACP SQLite 数据库(WAL 模式),用于事务性更新和崩溃安全恢复:
acp_sessionssession_key(pk)、backend、agent、mode、cwd、state、created_at、updated_at、last_error
acp_runsrun_id(pk)、session_key(fk)、state、requester_message_id、idempotency_key、started_at、ended_at、error_code、error_message
acp_bindingsbinding_key(pk)、thread_id、channel_id、account_id、session_key(fk)、expires_at、bound_at
acp_eventsevent_id(pk)、run_id(fk)、seq、kind、payload_json、created_at
acp_delivery_checkpointrun_id(pk/fk)、last_event_seq、last_discord_message_id、updated_at
acp_idempotencyscope、idempotency_key、result_json、created_at,唯一约束(scope, idempotency_key)
export type AcpSessionMeta = {
backend: string;
agent: string;
runtimeSessionName: string;
mode: "persistent" | "oneshot";
cwd?: string;
state: "idle" | "running" | "error";
lastActivityAt: number;
lastError?: string;
};
存储规则:
- 保留
SessionEntry.acp作为迁移期间的兼容投影 - 进程 id 和 socket 只保存在内存中
- 持久的生命周期和运行状态存在 ACP DB 中,不在通用会话 JSON 中
- 如果运行时所有者挂了,gateway 从 ACP DB 重新水化并从检查点恢复
路由和投递
入站:
- 保持当前线程绑定查找作为第一步路由
- 如果绑定目标是 ACP 会话,路由到 ACP 运行时分支而非
getReplyFromConfig - 显式的
/acp steer命令使用mode: "steer"
出站:
- ACP 事件流规范化为 OpenClaw 回复块
- 投递目标通过现有绑定目标路径解析
- 当绑定线程在该会话回合中活跃时,抑制父频道的完成消息
流式策略:
- 使用合并窗口流式输出部分内容
- 可配置的最小间隔和最大块大小以控制在 Discord 速率限制内
- 完成或失败时总是发出最终消息
状态机和事务边界
会话状态机:
creating -> idle -> running -> idlerunning -> cancelling -> idle | erroridle -> closederror -> idle | closed
运行状态机:
queued -> running -> completedrunning -> failed | cancelledqueued -> cancelled
必需的事务边界:
- spawn 事务
- 创建 ACP 会话行
- 创建/更新 ACP 线程绑定行
- 入队初始运行行
- close 事务
- 标记会话为 closed
- 删除/过期绑定行
- 写入最终 close 事件
- cancel 事务
- 使用幂等性 key 将目标运行标记为 cancelling/cancelled
这些边界不允许部分成功。
每会话 actor 模型
AcpSessionManager 为每个 ACP session key 运行一个 actor:
- actor 邮箱串行化
submit、cancel、close和stream副作用 - actor 拥有运行时 handle 水化和该会话的运行时适配器进程生命周期
- actor 在任何 Discord 投递之前按序(
seq)写入运行事件 - actor 在成功的出站发送后更新投递检查点
这消除了跨回合竞争,防止重复或乱序的线程输出。
幂等性和投递投影
所有外部 ACP 操作必须携带幂等性 key:
- spawn 幂等性 key
- prompt/steer 幂等性 key
- cancel 幂等性 key
- close 幂等性 key
投递规则:
- Discord 消息从
acp_events+acp_delivery_checkpoint派生 - 重试从检查点恢复,不重发已投递的块
- 每次运行的最终回复发送由投影逻辑保证恰好一次
恢复和自愈
gateway 启动时:
- 加载非终态 ACP 会话(
creating、idle、running、cancelling、error) - 在首次入站事件时惰性重建 actor,或在配置上限内积极重建
- 协调任何缺少心跳的
running运行,标记为failed或通过适配器恢复
入站 Discord 线程消息时:
- 如果绑定存在但 ACP 会话缺失,以显式的过期绑定消息闭合失败
- 可选在运维安全验证后自动解绑过期绑定
- 绝不悄悄将过期 ACP 绑定路由到正常 LLM 路径
生命周期和安全
支持的操作:
- 取消当前运行:
/acp cancel - 解绑线程:
/unfocus - 关闭 ACP 会话:
/acp close - 按有效 TTL 自动关闭空闲会话
TTL 策略:
- 有效 TTL 取以下最小值
- 全局/会话 TTL
- Discord 线程绑定 TTL
- ACP 运行时所有者 TTL
安全控制:
- 按名称白名单 ACP agent
- 限制 ACP 会话的工作空间根目录
- 环境变量白名单透传
- 每账号和全局最大并发 ACP 会话数
- 运行时崩溃的有界退避重启
配置界面
核心 key:
acp.enabledacp.dispatch.enabled(独立的 ACP 路由开关)acp.backend(默认acpx)acp.defaultAgentacp.allowedAgents[]acp.maxConcurrentSessionsacp.stream.coalesceIdleMsacp.stream.maxChunkCharsacp.runtime.ttlMinutesacp.controlPlane.store(默认sqlite)acp.controlPlane.storePathacp.controlPlane.recovery.eagerActorsacp.controlPlane.recovery.reconcileRunningAfterMsacp.controlPlane.checkpoint.flushEveryEventsacp.controlPlane.checkpoint.flushEveryMsacp.idempotency.ttlHourschannels.discord.threadBindings.spawnAcpSessions
插件/后端 key(acpx 插件部分):
- 后端命令/路径覆盖
- 后端环境变量白名单
- 后端按 agent 预设
- 后端启动/停止超时
- 后端每会话最大进行中运行数
实现规格
控制平面模块(新增)
在核心中添加专用 ACP 控制平面模块:
src/acp/control-plane/manager.ts- 拥有 ACP actor、生命周期转换、命令串行化
src/acp/control-plane/store.ts- SQLite schema 管理、事务、查询辅助
src/acp/control-plane/events.ts- 类型化 ACP 事件定义和序列化
src/acp/control-plane/checkpoint.ts- 持久投递检查点和回放游标
src/acp/control-plane/idempotency.ts- 幂等性 key 预留和响应回放
src/acp/control-plane/recovery.ts- 启动时协调和 actor 重建计划
兼容性桥接模块:
src/acp/runtime/session-meta.ts- 临时保留用于向
SessionEntry.acp投影 - 迁移切换后必须停止作为权威来源
- 临时保留用于向
必须在代码中强制执行的不变量
- ACP 会话创建和线程绑定是原子的(单事务)
- 每个 ACP 会话 actor 同时最多一个活跃运行
- 事件
seq在每次运行中严格递增 - 投递检查点绝不超过最后提交的事件
- 幂等性回放为重复命令 key 返回之前的成功载荷
- 过期/缺失的 ACP 元数据不能路由到正常的非 ACP 回复路径
核心接触点
需要修改的核心文件:
src/auto-reply/reply/dispatch-from-config.ts- ACP 分支调用
AcpSessionManager.submit和事件投影投递 - 移除绕过控制平面不变量的直接 ACP 回退
- ACP 分支调用
src/auto-reply/reply/inbound-context.ts(或最近的规范化上下文边界)- 为 ACP 控制平面暴露规范化的路由 key 和幂等性种子
src/config/sessions/types.ts- 保留
SessionEntry.acp作为仅投影的兼容字段
- 保留
src/gateway/server-methods/sessions.ts- reset/delete/archive 必须调用 ACP manager 的 close/unbind 事务路径
src/infra/outbound/bound-delivery-router.ts- 对 ACP 绑定会话回合强制闭合失败的目标行为
src/discord/monitor/thread-bindings.ts- 添加连接到控制平面查询的 ACP 过期绑定验证辅助
src/auto-reply/reply/commands-acp.ts- 通过 ACP manager API 路由 spawn/cancel/close/steer
src/agents/acp-spawn.ts- 停止临时元数据写入;调用 ACP manager spawn 事务
src/plugin-sdk/**和插件运行时桥接- 清晰地暴露 ACP 后端注册和健康语义
核心中明确不替换的文件:
src/discord/monitor/message-handler.preflight.ts- 保持线程绑定覆盖行为作为规范的 session-key 解析器
ACP 运行时注册表 API
添加核心注册表模块:
src/acp/runtime/registry.ts
必需 API:
export type AcpRuntimeBackend = {
id: string;
runtime: AcpRuntime;
healthy?: () => boolean;
};
export function registerAcpRuntimeBackend(backend: AcpRuntimeBackend): void;
export function unregisterAcpRuntimeBackend(id: string): void;
export function getAcpRuntimeBackend(id?: string): AcpRuntimeBackend | null;
export function requireAcpRuntimeBackend(id?: string): AcpRuntimeBackend;
行为:
requireAcpRuntimeBackend在不可用时抛出类型化的 ACP 后端缺失错误- 插件服务在
start时注册后端,在stop时注销 - 运行时查找是只读和进程本地的
acpx 运行时插件契约(实现细节)
首个生产后端(extensions/acpx),OpenClaw 和 acpx 通过严格的命令契约连接:
- 后端 id:
acpx - 插件服务 id:
acpx-runtime - 运行时 handle 编码:
runtimeSessionName = acpx:v1:<base64url(json)> - 编码载荷字段:
name(acpx 命名会话;使用 OpenClawsessionKey)agent(acpx agent 命令)cwd(会话工作空间根目录)mode(persistent | oneshot)
命令映射:
- 确保会话:
acpx --format json --json-strict --cwd <cwd> <agent> sessions ensure --name <name>
- prompt 回合:
acpx --format json --json-strict --cwd <cwd> <agent> prompt --session <name> --file -
- 取消:
acpx --format json --json-strict --cwd <cwd> <agent> cancel --session <name>
- 关闭:
acpx --format json --json-strict --cwd <cwd> <agent> sessions close <name>
流式:
- OpenClaw 消费
acpx --format json --json-strict的 ndjson 事件 text=>text_delta/outputthought=>text_delta/thoughttool_call=>tool_calldone=>doneerror=>error
会话 schema 补丁
在 src/config/sessions/types.ts 中补丁 SessionEntry:
type SessionAcpMeta = {
backend: string;
agent: string;
runtimeSessionName: string;
mode: "persistent" | "oneshot";
cwd?: string;
state: "idle" | "running" | "error";
lastActivityAt: number;
lastError?: string;
};
持久化字段:
SessionEntry.acp?: SessionAcpMeta
迁移规则:
- 阶段 A:双写(
acp投影 + ACP SQLite 权威来源) - 阶段 B:主读 ACP SQLite,回退读遗留
SessionEntry.acp - 阶段 C:迁移命令从有效遗留条目回填缺失的 ACP 行
- 阶段 D:移除回退读,投影仅为可选 UX 保留
- 遗留字段(
cliSessionIds、claudeCliSessionId)保持不变
错误契约
添加稳定的 ACP 错误码和面向用户的消息:
ACP_BACKEND_MISSING- 消息:
ACP 运行时后端未配置。请安装并启用 acpx 运行时插件。
- 消息:
ACP_BACKEND_UNAVAILABLE- 消息:
ACP 运行时后端当前不可用。请稍后再试。
- 消息:
ACP_SESSION_INIT_FAILED- 消息:
无法初始化 ACP 会话运行时。
- 消息:
ACP_TURN_FAILED- 消息:
ACP 回合在完成前失败。
- 消息:
规则:
- 在线程中返回可操作的用户安全消息
- 详细的后端/系统错误只记录到运行时日志
- 当 ACP 路由被显式选择时,绝不悄悄回退到正常 LLM 路径
重复投递仲裁
ACP 绑定回合的唯一路由规则:
- 如果目标 ACP 会话和请求者上下文存在活跃的线程绑定,仅投递到该绑定线程
- 同一回合不要同时发送到父频道
- 如果绑定目标选择模糊,以显式错误闭合失败(不隐式回退到父频道)
- 如果不存在活跃绑定,使用正常的会话目标行为
可观测性和运维就绪
必需指标:
- 按后端和错误码的 ACP spawn 成功/失败计数
- ACP 运行延迟百分位(队列等待、运行时回合时间、投递投影时间)
- ACP actor 重启计数和重启原因
- 过期绑定检测计数
- 幂等性回放命中率
- Discord 投递重试和速率限制计数器
必需日志:
- 按
sessionKey、runId、backend、threadId、idempotencyKey索引的结构化日志 - 会话和运行状态机的显式状态转换日志
- 带脱敏参数和退出摘要的适配器命令日志
必需诊断:
/acp sessions包含状态、活跃运行、最后错误和绑定状态/acp doctor(或等效命令)验证后端注册、存储健康和过期绑定
配置优先级和有效值
ACP 启用优先级:
- 账号覆盖:
channels.discord.accounts.<id>.threadBindings.spawnAcpSessions - 频道覆盖:
channels.discord.threadBindings.spawnAcpSessions - 全局 ACP 开关:
acp.enabled - dispatch 开关:
acp.dispatch.enabled - 后端可用性:
acp.backend对应的已注册后端
自动启用行为:
- 当 ACP 已配置(
acp.enabled=true、acp.dispatch.enabled=true或acp.backend=acpx),插件自动启用将plugins.entries.acpx.enabled=true,除非在拒绝列表中或被显式禁用
TTL 有效值:
min(会话 ttl, discord 线程绑定 ttl, acp 运行时 ttl)
测试地图
单元测试:
src/acp/runtime/registry.test.ts(新增)src/auto-reply/reply/dispatch-from-config.acp.test.ts(新增)src/infra/outbound/bound-delivery-router.test.ts(扩展 ACP 闭合失败用例)src/config/sessions/types.test.ts或最近的 session-store 测试(ACP 元数据持久化)
集成测试:
src/discord/monitor/reply-delivery.test.ts(绑定 ACP 投递目标行为)src/discord/monitor/message-handler.preflight*.test.ts(绑定 ACP session-key 路由连续性)- 后端包中的 acpx 插件运行时测试(服务注册/启动/停止 + 事件规范化)
Gateway 端到端测试:
src/gateway/server.sessions.gateway-server-sessions-a.e2e.test.ts(扩展 ACP reset/delete 生命周期覆盖)- ACP 线程回合往返端到端:spawn、消息、流式、取消、unfocus、重启恢复
上线保护
添加独立的 ACP dispatch 开关:
acp.dispatch.enabled首次发布默认false- 禁用时:
- ACP spawn/focus 控制命令仍可绑定会话
- ACP dispatch 路径不激活
- 用户收到显式消息说明 ACP dispatch 被策略禁用
- 金丝雀验证后,后续版本可将默认值翻转为
true
命令和 UX 计划
新命令
/acp spawn <agent-id> [--mode persistent|oneshot] [--thread auto|here|off]/acp cancel [session]/acp steer <instruction>/acp close [session]/acp sessions
现有命令兼容性
/focus <sessionKey>继续支持 ACP 目标/unfocus保持当前语义/session idle和/session max-age替代旧的 TTL 覆盖
分阶段上线
阶段 0 ADR 和 schema 冻结
- 发布 ACP 控制平面所有权和适配器边界的 ADR
- 冻结 DB schema(
acp_sessions、acp_runs、acp_bindings、acp_events、acp_delivery_checkpoint、acp_idempotency) - 定义稳定的 ACP 错误码、事件契约和状态转换守卫
阶段 1 核心中的控制平面基础
- 实现
AcpSessionManager和每会话 actor 运行时 - 实现 ACP SQLite 存储和事务辅助
- 实现幂等性存储和回放辅助
- 实现事件追加 + 投递检查点模块
- 将 spawn/cancel/close API 连接到 manager,带事务保证
阶段 2 核心路由和生命周期集成
- 从 dispatch 管线中将线程绑定的 ACP 回合路由到 ACP manager
- 当 ACP 绑定/会话不变量失败时强制闭合失败路由
- 将 reset/delete/archive/unfocus 生命周期与 ACP close/unbind 事务集成
- 添加过期绑定检测和可选的自动解绑策略
阶段 3 acpx 后端适配器/插件
- 基于运行时契约实现
acpx适配器(ensureSession、submit、stream、cancel、close) - 添加后端健康检查和启动/关闭注册
- 将 acpx ndjson 事件规范化为 ACP 运行时事件
- 强制后端超时、进程监督和重启/退避策略
阶段 4 投递投影和频道 UX(先做 Discord)
- 实现带检查点恢复的事件驱动频道投影(先做 Discord)
- 使用速率限制感知的刷新策略合并流式块
- 保证每次运行的最终完成消息恰好一次
- 发布
/acp spawn、/acp cancel、/acp steer、/acp close、/acp sessions
阶段 5 迁移和切换
- 引入双写到
SessionEntry.acp投影 + ACP SQLite 权威来源 - 添加遗留 ACP 元数据行的迁移工具
- 将读路径翻转为 ACP SQLite 主读
- 移除依赖缺失
SessionEntry.acp的遗留回退路由
阶段 6 加固、SLO 和规模限制
- 强制并发限制(全局/账号/会话)、队列策略和超时预算
- 添加完整遥测、仪表盘和告警阈值
- 混沌测试崩溃恢复和重复投递抑制
- 发布后端故障、DB 损坏和过期绑定修复的运维手册
完整实现清单
- 核心控制平面模块和测试
- DB 迁移和回滚计划
- ACP manager API 在 dispatch 和命令中的集成
- 插件运行时桥接中的适配器注册接口
- acpx 适配器实现和测试
- 支持线程的频道投递投影逻辑,带检查点回放(先做 Discord)
- reset/delete/archive/unfocus 的生命周期钩子
- 过期绑定检测器和面向运维的诊断
- 所有新 ACP key 的配置验证和优先级测试
- 运维文档和故障排除手册
测试计划
单元测试:
- ACP DB 事务边界(spawn/bind/enqueue 原子性、cancel、close)
- ACP 会话和运行的状态机转换守卫
- 所有 ACP 命令的幂等性预留/回放语义
- 每会话 actor 串行化和队列排序
- acpx 事件解析器和块合并器
- 运行时监督器重启和退避策略
- 配置优先级和有效 TTL 计算
- 后端/会话无效时核心 ACP 路由分支选择和闭合失败行为
集成测试:
- 用于确定性流式和取消行为的假 ACP 适配器进程
- ACP manager + dispatch 集成,带事务性持久化
- 线程绑定入站路由到 ACP session key
- 线程绑定出站投递抑制父频道重复
- 检查点回放在投递失败后恢复,从最后事件继续
- ACP 运行时后端的插件服务注册和关闭
Gateway 端到端测试:
- 用线程 spawn ACP,交换多轮 prompt,unfocus
- gateway 重启后保留 ACP DB 和绑定,然后继续同一会话
- 多线程中的并发 ACP 会话无串扰
- 重复命令重试(相同幂等性 key)不创建重复运行或回复
- 过期绑定场景产生显式错误和可选的自动清理行为
风险和缓解
- 过渡期间的重复投递
- 缓解:单一目标解析器和幂等事件检查点
- 高负载下的运行时进程翻搅
- 缓解:每会话的长期存活所有者 + 并发上限 + 退避
- 插件缺失或配置错误
- 缓解:显式的运维错误和闭合失败的 ACP 路由(不隐式回退到正常会话路径)
- subagent 和 ACP 开关之间的配置混乱
- 缓解:显式的 ACP key 和包含有效策略来源的命令反馈
- 控制平面存储损坏或迁移 bug
- 缓解:WAL 模式、备份/恢复钩子、迁移冒烟测试和只读回退诊断
- Actor 死锁或邮箱饥饿
- 缓解:看门狗定时器、actor 健康探测和带拒绝遥测的有界邮箱深度
验收清单
- ACP 会话 spawn 能在支持的频道适配器(当前 Discord)中创建或绑定线程
- 所有线程消息只路由到绑定的 ACP 会话
- ACP 输出以相同的线程身份出现,流式或批量
- 绑定回合不在父频道中产生重复输出
- spawn+bind+初始入队在持久存储中是原子的
- ACP 命令重试是幂等的,不重复运行或输出
- cancel、close、unfocus、archive、reset 和 delete 执行确定性清理
- 崩溃重启保留映射并恢复多轮连续性
- 并发的线程绑定 ACP 会话独立工作
- ACP 后端缺失状态产生清晰可操作的错误
- 过期绑定被检测并显式呈现(带可选的安全自动清理)
- 控制平面指标和诊断对运维人员可用
- 新增的单元、集成和端到端覆盖通过
补遗:当前实现的定向重构(状态)
这些是非阻塞的后续工作,用于在当前功能集落地后保持 ACP 路径的可维护性。
1) 集中 ACP dispatch 策略评估(已完成)
- 通过
src/acp/policy.ts中的共享 ACP 策略辅助实现 - dispatch、ACP 命令生命周期处理器和 ACP spawn 路径现在使用共享策略逻辑
2) 按子命令领域拆分 ACP 命令处理器(已完成)
src/auto-reply/reply/commands-acp.ts现在是一个薄路由器- 子命令行为拆分为:
src/auto-reply/reply/commands-acp/lifecycle.tssrc/auto-reply/reply/commands-acp/runtime-options.tssrc/auto-reply/reply/commands-acp/diagnostics.ts- 共享辅助在
src/auto-reply/reply/commands-acp/shared.ts
3) 按职责拆分 ACP 会话管理器(已完成)
- manager 拆分为:
src/acp/control-plane/manager.ts(公共门面 + 单例)src/acp/control-plane/manager.core.ts(manager 实现)src/acp/control-plane/manager.types.ts(manager 类型/依赖)src/acp/control-plane/manager.utils.ts(规范化 + 辅助函数)
4) 可选的 acpx 运行时适配器清理
extensions/acpx/src/runtime.ts可以拆分为:- 进程执行/监督
- ndjson 事件解析/规范化
- 运行时 API 表面(
submit、cancel、close等) - 提高可测试性,让后端行为更容易审计