Agentes ACP vinculados a hilos
Visión general
Este plan define cómo OpenClaw debería soportar agentes de codificación ACP en canales con capacidad de hilos (Discord primero) con ciclo de vida y recuperación a nivel de producción.
Documento relacionado:
Experiencia de usuario objetivo:
- Un usuario crea o enfoca una sesión ACP en un hilo
- Los mensajes del usuario en ese hilo se enrutan a la sesión ACP vinculada
- La salida del agente se transmite de vuelta a la misma identidad de hilo
- La sesión puede ser persistente o de un solo uso con controles de limpieza explícitos
Resumen de decisiones
La recomendación a largo plazo es una arquitectura híbrida:
- El core de OpenClaw posee las responsabilidades del plano de control ACP
- Identidad y metadatos de sesión
- Decisiones de vinculación de hilos y enrutamiento
- Invariantes de entrega y supresión de duplicados
- Semántica de limpieza de ciclo de vida y recuperación
- El backend del runtime ACP es pluggable
- El primer backend es un servicio de plugin respaldado por acpx
- El runtime gestiona transporte ACP, encolamiento, cancelación, reconexión
OpenClaw no debería reimplementar los internos de transporte ACP en core. OpenClaw no debería depender de una ruta de intercepción puramente de plugin para el enrutamiento.
Arquitectura norte (objetivo ideal)
Tratar ACP como un plano de control de primera clase en OpenClaw, con adaptadores de runtime pluggables.
Invariantes no negociables:
- Cada vinculación de hilo ACP referencia un registro de sesión ACP válido
- Cada sesión ACP tiene un estado de ciclo de vida explícito (
creating,idle,running,cancelling,closed,error) - Cada ejecución ACP tiene un estado de ejecución explícito (
queued,running,completed,failed,cancelled) - Spawn, vinculación y encolamiento inicial son atómicos
- Los reintentos de comandos son idempotentes (sin ejecuciones duplicadas ni salidas duplicadas de Discord)
- La salida de canal en hilos vinculados es una proyección de eventos de ejecución ACP, nunca efectos secundarios ad-hoc
Modelo de propiedad a largo plazo:
AcpSessionManageres el único escritor y orquestador ACP- El manager vive en el proceso gateway primero; puede moverse a un sidecar dedicado después detrás de la misma interfaz
- Por clave de sesión ACP, el manager posee un actor en memoria (ejecución serializada de comandos)
- Los adaptadores (
acpx, futuros backends) son solo implementaciones de transporte/runtime
Modelo de persistencia a largo plazo:
- Mover el estado del plano de control ACP a una base de datos SQLite dedicada (modo WAL) bajo el directorio de estado de OpenClaw
- Mantener
SessionEntry.acpcomo proyección de compatibilidad durante la migración, no como fuente de verdad - Almacenar eventos ACP en modo append-only para soportar replay, recuperación ante fallos y entrega determinista
Estrategia de entrega (puente al objetivo ideal)
- Puente a corto plazo
- Mantener la mecánica actual de vinculación de hilos y la superficie de configuración ACP existente
- Corregir bugs de brecha de metadatos y enrutar turnos ACP a través de una única rama ACP del core
- Agregar claves de idempotencia y verificaciones de enrutamiento fail-closed inmediatamente
- Transición a largo plazo
- Mover la fuente de verdad ACP a BD del plano de control + actores
- Hacer la entrega de hilos vinculados puramente basada en proyección de eventos
- Eliminar comportamiento de fallback legacy que depende de metadatos oportunistas de session-entry
Por qué no solo plugins
Los hooks de plugins actuales no son suficientes para el enrutamiento de sesiones ACP de extremo a extremo sin cambios en core.
- El enrutamiento entrante desde la vinculación de hilo se resuelve a una clave de sesión en el dispatch del core primero
- Los hooks de mensajes son fire-and-forget y no pueden cortocircuitar la ruta de respuesta principal
- Los comandos de plugins son buenos para operaciones de control pero no para reemplazar el flujo de dispatch por turno del core
Resultado:
- El runtime ACP puede ser pluginizado
- La rama de enrutamiento ACP debe existir en core
Base existente a reutilizar
Ya implementado y debe permanecer canónico:
- El objetivo de vinculación de hilo soporta
subagentyacp - La sobreescritura de enrutamiento de hilo entrante se resuelve por vinculación antes del dispatch normal
- Identidad de hilo saliente vía webhook en la entrega de respuestas
- Flujo de
/focusy/unfocuscon compatibilidad de objetivo ACP - Almacén de vinculación persistente con restauración al inicio
- Ciclo de vida de desvinculación en archive, delete, unfocus, reset y delete
Este plan extiende esa base en lugar de reemplazarla.
Arquitectura
Modelo de límites
Core (debe estar en el core de OpenClaw):
- Rama de dispatch en modo sesión ACP en la pipeline de respuesta
- Arbitraje de entrega para evitar duplicación padre + hilo
- Persistencia del plano de control ACP (con proyección de compatibilidad
SessionEntry.acpdurante la migración) - Semántica de desvinculación de ciclo de vida y desconexión de runtime vinculada a reset/delete de sesión
Backend de plugin (implementación acpx):
- Supervisión del worker del runtime ACP
- Invocación del proceso acpx y parsing de eventos
- Manejadores de comandos ACP (
/acp ...) y UX del operador - Valores por defecto de configuración y diagnósticos específicos del backend
Modelo de propiedad del runtime
- Un proceso gateway posee el estado de orquestación ACP
- La ejecución ACP se ejecuta en procesos hijos supervisados vía backend acpx
- La estrategia de procesos es de larga duración por clave de sesión ACP activa, no por mensaje
Esto evita el costo de inicio en cada prompt y mantiene la semántica de cancelación y reconexión confiable.
Contrato del runtime core
Agregar un contrato de runtime ACP del core para que el código de enrutamiento no dependa de detalles de CLI y pueda cambiar de backends sin cambiar la lógica de 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 }>;
}
Este es un documento extenso. Para ver la especificación completa, consulta la versión en inglés.