ACP Thread Bound Agents
Обзор
Этот план определяет, как OpenClaw должен поддерживать ACP-агенты в каналах с поддержкой потоков (в первую очередь Discord) с производственным жизненным циклом и восстановлением. Связанный документ:
Целевой пользовательский опыт:
- пользователь создает или фокусирует ACP-сессию в потоке
- сообщения пользователя в этом потоке маршрутизируются в привязанную ACP-сессию
- вывод агента стримится обратно в ту же персону потока
- сессия может быть постоянной или одноразовой с явными элементами управления очисткой
Краткое изложение решений
Долгосрочная рекомендация — гибридная архитектура:
- Ядро 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) - создание, привязка и первоначальная постановка в очередь являются атомарными
- повторные попытки команд идемпотентны (без дублирования запусков или дублирования вывода в Discord)
- вывод в привязанном потоке является проекцией событий запуска ACP, а не побочными эффектами ad-hoc
Долгосрочная модель владения:
AcpSessionManager— единственный писатель и оркестратор ACP- менеджер сначала находится в процессе шлюза; позже может быть перемещен в выделенный сайдкар за тем же интерфейсом
- на каждый ключ сессии ACP менеджер владеет одним актором в памяти (сериализованное выполнение команд)
- адаптеры (
acpx, будущие бэкенды) — это только реализации транспорта/среды выполнения
Долгосрочная модель персистентности:
- переместить состояние плоскости управления ACP в выделенное хранилище SQLite (режим WAL) в каталоге состояния OpenClaw
- сохранить
SessionEntry.acpкак проекцию для совместимости во время миграции, а не как источник истины - хранить события ACP только для добавления, чтобы поддерживать воспроизведение, восстановление после сбоев и детерминированную доставку
Стратегия доставки (мост к конечной цели)
- краткосрочный мост
- сохранить текущую механику привязки потоков и существующую конфигурационную поверхность ACP
- исправить баги с пробелами в метаданных и маршрутизировать ходы ACP через единую ветку ACP в ядре
- немедленно добавить ключи идемпотентности и проверки маршрутизации с отказом при закрытии
- долгосрочный переход
- переместить источник истины ACP в БД плоскости управления + акторы
- сделать доставку в привязанный поток чисто основанной на проекции событий
- удалить устаревшее поведение fallback, зависящее от оппортунистических метаданных session-entry
Почему не только плагин
Текущие хуки плагинов недостаточны для сквозной маршрутизации сессий ACP без изменений в ядре.
- входящая маршрутизация из привязки потока сначала разрешается в ключ сессии в основном диспетчере ядра
- хуки сообщений являются fire-and-forget и не могут прервать основной путь ответа
- команды плагинов хороши для операций управления, но не для замены основного потока диспетчеризации на ход
Результат:
- среда выполнения ACP может быть плагинизирована
- ветка маршрутизации ACP должна существовать в ядре
Существующий фундамент для повторного использования
Уже реализовано и должно оставаться каноническим:
- цель привязки потока поддерживает
subagentиacp - переопределение входящей маршрутизации потока разрешается привязкой перед обычной диспетчеризацией
- идентичность исходящего потока через вебхук в доставке ответа
- поток
/focusи/unfocusс совместимостью цели ACP - постоянное хранилище привязок с восстановлением при запуске
- жизненный цикл отвязки при архивации, удалении, снятии фокуса, сбросе и удалении
Этот план расширяет этот фундамент, а не заменяет его.
Архитектура
Модель границ
Ядро (должно быть в ядре OpenClaw):
- ветка диспетчеризации режима сессии ACP в конвейере ответов
- арбитраж доставки для избежания дублирования родительского канала и потока
- персистентность плоскости управления ACP (с проекцией совместимости
SessionEntry.acpво время миграции) - семантика отвязки жизненного цикла и отсоединения среды выполнения, привязанная к сбросу/удалению сессии
Бэкенд плагина (реализация acpx):
- супервизия рабочих процессов среды выполнения ACP
- вызов процесса acpx и парсинг событий
- обработчики команд ACP (
/acp ...) и UX оператора - значения по умолчанию и диагностика, специфичные для бэкенда
Модель владения средой выполнения
- один процесс шлюза владеет состоянием оркестрации ACP
- выполнение ACP запускается в дочерних процессах под супервизией через бэкенд acpx
- стратегия процесса — долгоживущая на активный ключ сессии ACP, а не на сообщение
Это позволяет избежать затрат на запуск при каждом промпте и сохраняет семантику отмены и переподключения надежной.
Контракт среды выполнения ядра
Добавить контракт среды выполнения ACP в ядро, чтобы код маршрутизации не зависел от деталей CLI и мог переключать бэкенды без изменения логики диспетчеризации:
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, unique(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как проекцию для совместимости во время миграции - идентификаторы процессов и сокеты остаются только в памяти
- устойчивый жизненный цикл и статус выполнения живут в БД ACP, а не в общем JSON сессии
- если владелец среды выполнения умирает, шлюз перегидратирует из БД ACP и возобновит работу с контрольных точек
Маршрутизация и доставка
Входящая:
- сохранить текущий поиск привязки потока как первый шаг маршрутизации
- если привязанная цель — сессия 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
Необходимые границы транзакций:
- транзакция создания
- создать строку сессии ACP
- создать/обновить строку привязки потока ACP
- поставить в очередь строку начального запуска
- транзакция закрытия
- пометить сессию закрытой
- удалить/истечь строки привязок
- записать финальное событие закрытия
- транзакция отмены
- пометить целевой запуск отменяемым/отмененным с ключом идемпотентности
Частичный успех через эти границы не допускается.
Модель акторов на сессию
AcpSessionManager запускает один актор на ключ сессии ACP:
- почтовый ящик актора сериализует побочные эффекты
submit,cancel,closeиstream - актор владеет гидратацией хендла среды выполнения и жизненным циклом процесса адаптера среды выполнения для этой сессии
- актор записывает события запуска по порядку (
seq) до любой доставки в Discord - актор обновляет контрольные точки доставки после успешной отправки
Это устраняет гонки между ходами и предотвращает дублирование или вывод в неправильном порядке.
Идемпотентность и проекция доставки
Все внешние действия ACP должны нести ключи идемпотентности:
- ключ идемпотентности создания
- ключ идемпотентности промпта/управления
- ключ идемпотентности отмены
- ключ идемпотентности закрытия
Правила доставки:
- сообщения Discord выводятся из
acp_eventsплюсacp_delivery_checkpoint - повторные попытки возобновляются с контрольной точки без повторной отправки уже доставленных чанков
- финальная эмиссия ответа — ровно один раз за запуск из логики проекции
Восстановление и самовосстановление
При запуске шлюза:
- загрузить нетерминальные сессии ACP (
creating,idle,running,cancelling,error) - пересоздать акторов лениво при первом входящем событии или активно в пределах настроенного лимита
- согласовать любые
runningзапуски, пропустившие heartbeat, и пометитьfailedили восстановить через адаптер
При входящем сообщении в поток Discord:
- если привязка существует, но сессия ACP отсутствует, завершиться с ошибкой с явным сообщением об устаревшей привязке
- опционально автоматически отвязать устаревшую привязку после безопасной для оператора валидации
- никогда не маршрутизировать устаревшие привязки ACP в обычный путь LLM молча
Жизненный цикл и безопасность
Поддерживаемые операции:
- отменить текущий запуск:
/acp cancel - отвязать поток:
/unfocus - закрыть сессию ACP:
/acp close - автоматическое закрытие простаивающих сессий по эффективному TTL
Политика TTL:
- эффективный TTL — минимум из
- глобального/сессионного TTL
- TTL привязки потока Discord
- TTL владельца среды выполнения ACP
Элементы управления безопасностью:
- разрешительный список ACP-агентов по имени
- ограничение корней рабочего пространства для сессий ACP
- разрешительный список переменных окружения для передачи
- максимальное количество одновременных сессий ACP на аккаунт и глобально
- ограниченный откат перезапуска при сбоях среды выполнения
Конфигурационная поверхность
Ключи ядра:
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
Ключи плагина/бэкенда (раздел плагина acpx):
- переопределения команды/пути бэкенда
- разрешительный список переменных окружения бэкенда
- предустановки бэкенда на агента
- таймауты запуска/остановки бэкенда
- максимальное количество активных запусков на сессию для бэкенда
Спецификация реализации
Модули плоскости управления (новые)
Добавить выделенные модули плоскости управления ACP в ядро:
src/acp/control-plane/manager.ts- владеет акторами ACP, переходами жизненного цикла, сериализацией команд
src/acp/control-plane/store.ts- управление схемой SQLite, транзакции, вспомогательные запросы
src/acp/control-plane/events.ts- типизированные определения событий ACP и сериализация
src/acp/control-plane/checkpoint.ts- устойчивые контрольные точки доставки и курсоры воспроизведения
src/acp/control-plane/idempotency.ts- резервирование ключей идемпотентности и воспроизведение ответов
src/acp/control-plane/recovery.ts- согласование при загрузке и план перегидратации акторов
Мостовые модули совместимости:
src/acp/runtime/session-meta.ts- временно остается для проекции в
SessionEntry.acp - должен перестать быть источником истины после перехода
- временно остается для проекции в
Необходимые инварианты (должны быть обеспечены в коде)
- создание сессии ACP и привязка потока атомарны (одна транзакция)
- не более одного активного запуска на актор сессии ACP одновременно
seqсобытия строго возрастает на запуск- контрольная точка доставки никогда не продвигается дальше последнего зафиксированного события
- воспроизведение идемпотентности возвращает предыдущий успешный результат для дублирующихся ключей команд
- устаревшие/отсутствующие метаданные ACP не могут маршрутизироваться в обычный путь ответа, не относящийся к ACP
Точки касания ядра
Файлы ядра для изменения:
src/auto-reply/reply/dispatch-from-config.ts- ветка ACP вызывает
AcpSessionManager.submitи доставку проекции событий - удалить прямой fallback ACP, обходящий инварианты плоскости управления
- ветка ACP вызывает
src/auto-reply/reply/inbound-context.ts(или ближайшая граница нормализованного контекста)- предоставить нормализованные ключи маршрутизации и семена идемпотентности для плоскости управления ACP
src/config/sessions/types.ts- сохранить
SessionEntry.acpкак поле совместимости только для проекции
- сохранить
src/gateway/server-methods/sessions.ts- сброс/удаление/архивация должны вызывать путь транзакции закрытия/отвязки менеджера ACP
src/infra/outbound/bound-delivery-router.ts- обеспечить поведение назначения с отказом при закрытии для ходов привязанной сессии ACP
src/discord/monitor/thread-bindings.ts- добавить вспомогательные функции валидации устаревших привязок ACP, подключенные к поискам плоскости управления
src/auto-reply/reply/commands-acp.ts- маршрутизировать создание/отмену/закрытие/управление через API менеджера ACP
src/agents/acp-spawn.ts- прекратить ad-hoc запись метаданных; вызывать транзакцию создания менеджера ACP
src/plugin-sdk/**и мост среды выполнения плагина- предоставить регистрацию бэкенда ACP и семантику здоровья чисто
Файлы ядра, которые явно не заменяются:
src/discord/monitor/message-handler.preflight.ts- сохранить поведение переопределения привязки потока как канонический разрешитель ключа сессии
API реестра среды выполнения ACP
Добавить модуль реестра в ядро:
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 соединяются строгим контрактом команд:
- идентификатор бэкенда:
acpx - идентификатор сервиса плагина:
acpx-runtime - кодирование хендла среды выполнения:
runtimeSessionName = acpx:v1:<base64url(json)> - закодированные поля:
name(именованная сессия acpx; используетsessionKeyOpenClaw)agent(команда агента acpx)cwd(корень рабочего пространства сессии)mode(persistent | oneshot)
Сопоставление команд:
- обеспечить сессию:
acpx --format json --json-strict --cwd <cwd> <agent> sessions ensure --name <name>
- ход промпта:
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 потребляет события ndjson из
acpx --format json --json-strict text=>text_delta/outputthought=>text_delta/thoughttool_call=>tool_calldone=>doneerror=>error
Патч схемы сессии
Патч SessionEntry в src/config/sessions/types.ts:
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+ источник истины SQLite ACP) - фаза B: основное чтение из SQLite ACP, fallback-чтение из устаревшего
SessionEntry.acp - фаза C: команда миграции заполняет отсутствующие строки ACP из валидных устаревших записей
- фаза D: удалить fallback-чтение и оставить проекцию опциональной только для UX
- устаревшие поля (
cliSessionIds,claudeCliSessionId) остаются нетронутыми
Контракт ошибок
Добавить стабильные коды ошибок ACP и сообщения для пользователей:
ACP_BACKEND_MISSING- сообщение:
Бэкенд среды выполнения ACP не настроен. Установите и включите плагин среды выполнения acpx.
- сообщение:
ACP_BACKEND_UNAVAILABLE- сообщение:
Бэкенд среды выполнения ACP в настоящее время недоступен. Попробуйте снова через мгновение.
- сообщение:
ACP_SESSION_INIT_FAILED- сообщение:
Не удалось инициализировать среду выполнения сессии ACP.
- сообщение:
ACP_TURN_FAILED- сообщение:
Ход ACP завершился неудачно до завершения.
- сообщение:
Правила:
- возвращать понятное для пользователя сообщение в потоке
- логировать детальную ошибку бэкенда/системы только в журналах среды выполнения
- никогда не переходить молча на обычный путь LLM, когда маршрутизация ACP была явно выбрана
Арбитраж дублирующейся доставки
Единое правило маршрутизации для ходов с привязкой ACP:
- если для целевой сессии ACP и контекста инициатора существует активная привязка потока, доставлять только в этот привязанный поток
- не отправлять также в родительский канал для того же хода
- если выбор привязанного назначения неоднозначен, завершиться с ошибкой (без неявного fallback на родительский канал)
- если активной привязки не существует, использовать обычное поведение назначения сессии
Наблюдаемость и операционная готовность
Необходимые метрики:
- количество успешных/неудачных созданий ACP по бэкенду и коду ошибки
- процентили задержки запуска ACP (ожидание в очереди, время хода среды выполнения, время проекции доставки)
- количество перезапусков актора ACP и причина перезапуска
- количество обнаружений устаревших привязок
- частота попаданий воспроизведения идемпотентности
- счетчики повторных попыток доставки Discord и ограничений скорости
Необходимые логи:
- структурированные логи с ключами
sessionKey,runId,backend,threadId,idempotencyKey - явные логи переходов состояний для конечных автоматов сессии и запуска
- логи команд адаптера с безопасными для редактирования аргументами и сводкой выхода
Необходимая диагностика:
/acp sessionsвключает состояние, активный запуск, последнюю ошибку и статус привязки/acp doctor(или эквивалент) проверяет регистрацию бэкенда, здоровье хранилища и устаревшие привязки
Приоритет конфигурации и эффективные значения
Приоритет включения ACP:
- переопределение аккаунта:
channels.discord.accounts.<id>.threadBindings.spawnAcpSessions - переопределение канала:
channels.discord.threadBindings.spawnAcpSessions - глобальный выключатель ACP:
acp.enabled - выключатель диспетчеризации:
acp.dispatch.enabled - доступность бэкенда: зарегистрированный бэкенд для
acp.backend
Поведение авто-включения:
- когда ACP настроен (
acp.enabled=true,acp.dispatch.enabled=trueилиacp.backend=acpx), авто-включение плагина помечаетplugins.entries.acpx.enabled=true, если не запрещено или явно не отключено
Эффективное значение TTL:
min(сессионный ttl, ttl привязки потока discord, ttl среды выполнения acp)
Карта тестов
Модульные тесты:
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или ближайшие тесты хранилища сессий (персистентность метаданных ACP)
Интеграционные тесты:
src/discord/monitor/reply-delivery.test.ts(поведение цели доставки привязанного ACP)src/discord/monitor/message-handler.preflight*.test.ts(непрерывность маршрутизации ключа сессии привязанного ACP)- тесты среды выполнения плагина acpx в пакете бэкенда (регистрация/запуск/остановка сервиса + нормализация событий)
E2e тесты шлюза:
src/gateway/server.sessions.gateway-server-sessions-a.e2e.test.ts(расширить покрытие жизненного цикла сброса/удаления ACP)- e2e тест цикла хода в потоке ACP для создания, сообщения, стриминга, отмены, снятия фокуса, восстановления после перезапуска
Защита развертывания
Добавить независимый выключатель диспетчеризации ACP:
acp.dispatch.enabledпо умолчаниюfalseдля первого релиза- когда отключено:
- команды управления созданием/фокусировкой ACP все еще могут привязывать сессии
- путь диспетчеризации ACP не активируется
- пользователь получает явное сообщение, что диспетчеризация ACP отключена политикой
- после валидации canary значение по умолчанию может быть изменено на
trueв более позднем релизе
План команд и UX
Новые команды
/acp spawn <agent-id> [--mode persistent|oneshot] [--thread auto|here|off]: создает новую ACP сессию с указанным агентом