ACP 线程绑定 Agent

概述

本计划定义了 OpenClaw 如何在支持线程的频道(先做 Discord)中支持 ACP 编程 Agent,达到生产级的生命周期管理和故障恢复能力。

相关文档:

目标用户体验:

  • 用户在线程中 spawn 或 focus 一个 ACP 会话
  • 该线程中的用户消息路由到绑定的 ACP 会话
  • Agent 输出以相同的线程身份流式回传
  • 会话可以是持久的或一次性的,提供显式的清理控制

决策摘要

长期建议采用混合架构:

  • OpenClaw 核心负责 ACP 控制平面
    • 会话身份和元数据
    • 线程绑定和路由决策
    • 投递不变量和重复抑制
    • 生命周期清理和恢复语义
  • ACP 运行时后端可插拔
    • 首个后端是基于 acpx 的插件服务
    • 运行时负责 ACP 传输、队列、取消、重连

OpenClaw 不应该在核心中重新实现 ACP 传输内部逻辑。 OpenClaw 不应该仅依赖纯插件拦截路径来做路由。

终极架构(理想方案)

将 ACP 作为 OpenClaw 的一等控制平面,配合可插拔的运行时适配器。

不可妥协的不变量:

  • 每个 ACP 线程绑定都引用一个有效的 ACP 会话记录
  • 每个 ACP 会话都有显式的生命周期状态(creatingidlerunningcancellingclosederror
  • 每次 ACP 运行都有显式的运行状态(queuedrunningcompletedfailedcancelled
  • 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 路由分支必须在核心中存在

可复用的现有基础

已经实现且应保持为规范行为的:

  • 线程绑定目标支持 subagentacp
  • 入站线程路由覆盖在正常 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_sessions
    • session_key (pk)、backendagentmodecwdstatecreated_atupdated_atlast_error
  • acp_runs
    • run_id (pk)、session_key (fk)、staterequester_message_ididempotency_keystarted_atended_aterror_codeerror_message
  • acp_bindings
    • binding_key (pk)、thread_idchannel_idaccount_idsession_key (fk)、expires_atbound_at
  • acp_events
    • event_id (pk)、run_id (fk)、seqkindpayload_jsoncreated_at
  • acp_delivery_checkpoint
    • run_id (pk/fk)、last_event_seqlast_discord_message_idupdated_at
  • acp_idempotency
    • scopeidempotency_keyresult_jsoncreated_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 -> idle
  • running -> cancelling -> idle | error
  • idle -> closed
  • error -> idle | closed

运行状态机:

  • queued -> running -> completed
  • running -> failed | cancelled
  • queued -> cancelled

必需的事务边界:

  • spawn 事务
    • 创建 ACP 会话行
    • 创建/更新 ACP 线程绑定行
    • 入队初始运行行
  • close 事务
    • 标记会话为 closed
    • 删除/过期绑定行
    • 写入最终 close 事件
  • cancel 事务
    • 使用幂等性 key 将目标运行标记为 cancelling/cancelled

这些边界不允许部分成功。

每会话 actor 模型

AcpSessionManager 为每个 ACP session key 运行一个 actor:

  • actor 邮箱串行化 submitcancelclosestream 副作用
  • 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 会话(creatingidlerunningcancellingerror
  • 在首次入站事件时惰性重建 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.enabled
  • acp.dispatch.enabled(独立的 ACP 路由开关)
  • acp.backend(默认 acpx
  • acp.defaultAgent
  • acp.allowedAgents[]
  • acp.maxConcurrentSessions
  • acp.stream.coalesceIdleMs
  • acp.stream.maxChunkChars
  • acp.runtime.ttlMinutes
  • acp.controlPlane.store(默认 sqlite
  • acp.controlPlane.storePath
  • acp.controlPlane.recovery.eagerActors
  • acp.controlPlane.recovery.reconcileRunningAfterMs
  • acp.controlPlane.checkpoint.flushEveryEvents
  • acp.controlPlane.checkpoint.flushEveryMs
  • acp.idempotency.ttlHours
  • channels.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 回退
  • 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 命名会话;使用 OpenClaw sessionKey
    • agent(acpx agent 命令)
    • cwd(会话工作空间根目录)
    • modepersistent | 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/output
  • thought => text_delta/thought
  • tool_call => tool_call
  • done => done
  • error => 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 保留
  • 遗留字段(cliSessionIdsclaudeCliSessionId)保持不变

错误契约

添加稳定的 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 投递重试和速率限制计数器

必需日志:

  • sessionKeyrunIdbackendthreadIdidempotencyKey 索引的结构化日志
  • 会话和运行状态机的显式状态转换日志
  • 带脱敏参数和退出摘要的适配器命令日志

必需诊断:

  • /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=trueacp.dispatch.enabled=trueacp.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_sessionsacp_runsacp_bindingsacp_eventsacp_delivery_checkpointacp_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 适配器(ensureSessionsubmitstreamcancelclose
  • 添加后端健康检查和启动/关闭注册
  • 将 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.ts
    • src/auto-reply/reply/commands-acp/runtime-options.ts
    • src/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 表面(submitcancelclose 等)
  • 提高可测试性,让后端行为更容易审计