Arquitectura de la integración con Pi

Este documento describe cómo OpenClaw se integra con pi-coding-agent y sus paquetes hermanos (pi-ai, pi-agent-core, pi-tui) para potenciar sus capacidades de agente de IA.

Visión general

OpenClaw usa el SDK de Pi para embeber un agente de IA de programación en su arquitectura de gateway de mensajería. En lugar de ejecutar Pi como subproceso o usar modo RPC, OpenClaw importa e instancia directamente el AgentSession de Pi vía createAgentSession(). Este enfoque embebido proporciona:

  • Control total sobre el ciclo de vida de la sesión y el manejo de eventos
  • Inyección de herramientas personalizadas (mensajería, sandbox, acciones específicas del canal)
  • Personalización del system prompt por canal/contexto
  • Persistencia de sesiones con soporte de ramificación/compactación
  • Rotación de perfiles de autenticación multi-cuenta con failover
  • Cambio de modelo agnóstico de proveedor

Dependencias de paquetes

{
  "@mariozechner/pi-agent-core": "0.49.3",
  "@mariozechner/pi-ai": "0.49.3",
  "@mariozechner/pi-coding-agent": "0.49.3",
  "@mariozechner/pi-tui": "0.49.3"
}
PaquetePropósito
pi-aiAbstracciones core de LLM: Model, streamSimple, tipos de mensaje, APIs de proveedores
pi-agent-coreBucle del agente, ejecución de herramientas, tipos AgentMessage
pi-coding-agentSDK de alto nivel: createAgentSession, SessionManager, AuthStorage, ModelRegistry, herramientas integradas
pi-tuiComponentes de interfaz de terminal (usados en el modo TUI local de OpenClaw)

Estructura de archivos

src/agents/
├── pi-embedded-runner.ts          # Re-exporta desde pi-embedded-runner/
├── pi-embedded-runner/
│   ├── run.ts                     # Punto de entrada principal: runEmbeddedPiAgent()
│   ├── run/
│   │   ├── attempt.ts             # Lógica de intento único con configuración de sesión
│   │   ├── params.ts              # Tipo RunEmbeddedPiAgentParams
│   │   ├── payloads.ts            # Construir payloads de respuesta a partir de resultados
│   │   ├── images.ts              # Inyección de imágenes para modelo de visión
│   │   └── types.ts               # EmbeddedRunAttemptResult
│   ├── abort.ts                   # Detección de errores de abort
│   ├── cache-ttl.ts               # Seguimiento de TTL de caché para poda de contexto
│   ├── compact.ts                 # Lógica de compactación manual/automática
│   ├── extensions.ts              # Cargar extensiones Pi para ejecuciones embebidas
│   ├── extra-params.ts            # Parámetros de stream específicos por proveedor
│   ├── google.ts                  # Correcciones de orden de turnos para Google/Gemini
│   ├── history.ts                 # Limitación de historial (DM vs grupo)
│   ├── lanes.ts                   # Lanes de comandos de sesión/global
│   ├── logger.ts                  # Logger de subsistema
│   ├── model.ts                   # Resolución de modelo vía ModelRegistry
│   ├── runs.ts                    # Seguimiento de ejecuciones activas, abort, cola
│   ├── sandbox-info.ts            # Info del sandbox para el system prompt
│   ├── session-manager-cache.ts   # Caché de instancias de SessionManager
│   ├── session-manager-init.ts    # Inicialización de archivos de sesión
│   ├── system-prompt.ts           # Constructor del system prompt
│   ├── tool-split.ts              # Dividir herramientas en builtIn vs custom
│   ├── types.ts                   # EmbeddedPiAgentMeta, EmbeddedPiRunResult
│   └── utils.ts                   # Mapeo de ThinkLevel, descripción de errores
├── pi-embedded-subscribe.ts       # Suscripción/despacho de eventos de sesión
├── pi-embedded-subscribe.types.ts # SubscribeEmbeddedPiSessionParams
├── pi-embedded-subscribe.handlers.ts # Fábrica de manejadores de eventos
├── pi-embedded-subscribe.handlers.lifecycle.ts
├── pi-embedded-subscribe.handlers.types.ts
├── pi-embedded-block-chunker.ts   # Chunking de respuestas en bloque por streaming
├── pi-embedded-messaging.ts       # Seguimiento de envío de herramienta de mensajería
├── pi-embedded-helpers.ts         # Clasificación de errores, validación de turnos
├── pi-embedded-helpers/           # Módulos auxiliares
├── pi-embedded-utils.ts           # Utilidades de formato
├── pi-tools.ts                    # createOpenClawCodingTools()
├── pi-tools.abort.ts              # Wrapping de AbortSignal para herramientas
├── pi-tools.policy.ts             # Política de allowlist/denylist de herramientas
├── pi-tools.read.ts               # Personalizaciones de la herramienta read
├── pi-tools.schema.ts             # Normalización de esquemas de herramientas
├── pi-tools.types.ts              # Alias de tipo AnyAgentTool
├── pi-tool-definition-adapter.ts  # Adaptador AgentTool -> ToolDefinition
├── pi-settings.ts                 # Sobreescrituras de configuración
├── pi-extensions/                 # Extensiones Pi personalizadas
│   ├── compaction-safeguard.ts    # Extensión de salvaguarda
│   ├── compaction-safeguard-runtime.ts
│   ├── context-pruning.ts         # Extensión de poda de contexto por cache-TTL
│   └── context-pruning/
├── model-auth.ts                  # Resolución de perfil de autenticación
├── auth-profiles.ts               # Almacén de perfiles, cooldown, failover
├── model-selection.ts             # Resolución de modelo por defecto
├── models-config.ts               # Generación de models.json
├── model-catalog.ts               # Caché del catálogo de modelos
├── context-window-guard.ts        # Validación de ventana de contexto
├── failover-error.ts              # Clase FailoverError
├── defaults.ts                    # DEFAULT_PROVIDER, DEFAULT_MODEL
├── system-prompt.ts               # buildAgentSystemPrompt()
├── system-prompt-params.ts        # Resolución de parámetros del system prompt
├── system-prompt-report.ts        # Generación de reportes de depuración
├── tool-summaries.ts              # Resúmenes de descripción de herramientas
├── tool-policy.ts                 # Resolución de política de herramientas
├── transcript-policy.ts           # Política de validación de transcripciones
├── skills.ts                      # Snapshot/construcción de prompt de skills
├── skills/                        # Subsistema de skills
├── sandbox.ts                     # Resolución de contexto del sandbox
├── sandbox/                       # Subsistema del sandbox
├── channel-tools.ts               # Inyección de herramientas específicas del canal
├── openclaw-tools.ts              # Herramientas específicas de OpenClaw
├── bash-tools.ts                  # Herramientas exec/process
├── apply-patch.ts                 # Herramienta apply_patch (OpenAI)
├── tools/                         # Implementaciones individuales de herramientas
│   ├── browser-tool.ts
│   ├── canvas-tool.ts
│   ├── cron-tool.ts
│   ├── discord-actions*.ts
│   ├── gateway-tool.ts
│   ├── image-tool.ts
│   ├── message-tool.ts
│   ├── nodes-tool.ts
│   ├── session*.ts
│   ├── slack-actions.ts
│   ├── telegram-actions.ts
│   ├── web-*.ts
│   └── whatsapp-actions.ts
└── ...

Flujo de integración principal

1. Ejecutar un agente embebido

El punto de entrada principal es runEmbeddedPiAgent() en pi-embedded-runner/run.ts:

import { runEmbeddedPiAgent } from "./agents/pi-embedded-runner.js";

const result = await runEmbeddedPiAgent({
  sessionId: "user-123",
  sessionKey: "main:whatsapp:+1234567890",
  sessionFile: "/path/to/session.jsonl",
  workspaceDir: "/path/to/workspace",
  config: openclawConfig,
  prompt: "Hello, how are you?",
  provider: "anthropic",
  model: "claude-sonnet-4-20250514",
  timeoutMs: 120_000,
  runId: "run-abc",
  onBlockReply: async (payload) => {
    await sendToChannel(payload.text, payload.mediaUrls);
  },
});

2. Creación de sesión

Dentro de runEmbeddedAttempt() (llamado por runEmbeddedPiAgent()), se usa el SDK de Pi:

import {
  createAgentSession,
  DefaultResourceLoader,
  SessionManager,
  SettingsManager,
} from "@mariozechner/pi-coding-agent";

const resourceLoader = new DefaultResourceLoader({
  cwd: resolvedWorkspace,
  agentDir,
  settingsManager,
  additionalExtensionPaths,
});
await resourceLoader.reload();

const { session } = await createAgentSession({
  cwd: resolvedWorkspace,
  agentDir,
  authStorage: params.authStorage,
  modelRegistry: params.modelRegistry,
  model: params.model,
  thinkingLevel: mapThinkingLevel(params.thinkLevel),
  tools: builtInTools,
  customTools: allCustomTools,
  sessionManager,
  settingsManager,
  resourceLoader,
});

applySystemPromptOverrideToSession(session, systemPromptOverride);

3. Suscripción a eventos

subscribeEmbeddedPiSession() se suscribe a los eventos del AgentSession de Pi:

const subscription = subscribeEmbeddedPiSession({
  session: activeSession,
  runId: params.runId,
  verboseLevel: params.verboseLevel,
  reasoningMode: params.reasoningLevel,
  toolResultFormat: params.toolResultFormat,
  onToolResult: params.onToolResult,
  onReasoningStream: params.onReasoningStream,
  onBlockReply: params.onBlockReply,
  onPartialReply: params.onPartialReply,
  onAgentEvent: params.onAgentEvent,
});

Los eventos manejados incluyen:

  • message_start / message_end / message_update (streaming de texto/pensamiento)
  • tool_execution_start / tool_execution_update / tool_execution_end
  • turn_start / turn_end
  • agent_start / agent_end
  • auto_compaction_start / auto_compaction_end

4. Prompting

Después de la configuración, se envía el prompt a la sesión:

await session.prompt(effectivePrompt, { images: imageResult.images });

El SDK maneja el bucle completo del agente: envío al LLM, ejecución de llamadas a herramientas y streaming de respuestas.

La inyección de imágenes es local al prompt: OpenClaw carga las referencias de imágenes del prompt actual y las pasa vía images solo para ese turno. No vuelve a escanear turnos anteriores del historial para re-inyectar payloads de imágenes.

Arquitectura de herramientas

Pipeline de herramientas

  1. Herramientas base: codingTools de Pi (read, bash, edit, write)
  2. Reemplazos personalizados: OpenClaw reemplaza bash con exec/process, personaliza read/edit/write para sandbox
  3. Herramientas de OpenClaw: mensajería, navegador, canvas, sesiones, cron, gateway, etc.
  4. Herramientas de canal: herramientas de acción específicas de Discord/Telegram/Slack/WhatsApp
  5. Filtrado por política: herramientas filtradas por perfil, proveedor, agente, grupo, políticas de sandbox
  6. Normalización de esquemas: esquemas limpiados para particularidades de Gemini/OpenAI
  7. Wrapping de AbortSignal: herramientas envueltas para respetar señales de abort

Adaptador de definición de herramientas

El AgentTool de pi-agent-core tiene una firma de execute diferente a la ToolDefinition de pi-coding-agent. El adaptador en pi-tool-definition-adapter.ts conecta ambas:

export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
  return tools.map((tool) => ({
    name: tool.name,
    label: tool.label ?? name,
    description: tool.description ?? "",
    parameters: tool.parameters,
    execute: async (toolCallId, params, onUpdate, _ctx, signal) => {
      // La firma de pi-coding-agent difiere de pi-agent-core
      return await tool.execute(toolCallId, params, signal, onUpdate);
    },
  }));
}

Estrategia de división de herramientas

splitSdkTools() pasa todas las herramientas vía customTools:

export function splitSdkTools(options: { tools: AnyAgentTool[]; sandboxEnabled: boolean }) {
  return {
    builtInTools: [], // Vacío. Sobreescribimos todo
    customTools: toToolDefinitions(options.tools),
  };
}

Esto asegura que el filtrado de políticas, la integración con sandbox y el toolset extendido de OpenClaw se mantengan consistentes entre proveedores.

Construcción del system prompt

El system prompt se construye en buildAgentSystemPrompt() (system-prompt.ts). Ensambla un prompt completo con secciones que incluyen Tooling, Tool Call Style, guardrails de seguridad, referencia del CLI de OpenClaw, Skills, Docs, Workspace, Sandbox, Messaging, Reply Tags, Voice, Silent Replies, Heartbeats, metadata de runtime, más Memory y Reactions cuando están habilitados, y archivos de contexto opcionales y contenido extra del system prompt. Las secciones se recortan para el modo de prompt mínimo usado por subagentes.

El prompt se aplica después de la creación de la sesión vía applySystemPromptOverrideToSession():

const systemPromptOverride = createSystemPromptOverride(appendPrompt);
applySystemPromptOverrideToSession(session, systemPromptOverride);

Gestión de sesiones

Archivos de sesión

Las sesiones son archivos JSONL con estructura de árbol (enlace id/parentId). El SessionManager de Pi maneja la persistencia:

const sessionManager = SessionManager.open(params.sessionFile);

OpenClaw lo envuelve con guardSessionManager() para seguridad de resultados de herramientas.

Caché de sesiones

session-manager-cache.ts cachea instancias de SessionManager para evitar parseos repetidos de archivos:

await prewarmSessionFile(params.sessionFile);
sessionManager = SessionManager.open(params.sessionFile);
trackSessionManagerAccess(params.sessionFile);

Limitación de historial

limitHistoryTurns() recorta el historial de conversación según el tipo de canal (DM vs grupo).

Compactación

La auto-compactación se dispara por desbordamiento de contexto. compactEmbeddedPiSessionDirect() maneja la compactación manual:

const compactResult = await compactEmbeddedPiSessionDirect({
  sessionId, sessionFile, provider, model, ...
});

Autenticación y resolución de modelos

Perfiles de autenticación

OpenClaw mantiene un almacén de perfiles de autenticación con múltiples claves API por proveedor:

const authStore = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
const profileOrder = resolveAuthProfileOrder({ cfg, store: authStore, provider, preferredProfile });

Los perfiles rotan en caso de fallos con seguimiento de cooldown:

await markAuthProfileFailure({ store, profileId, reason, cfg, agentDir });
const rotated = await advanceAuthProfile();

Resolución de modelos

import { resolveModel } from "./pi-embedded-runner/model.js";

const { model, error, authStorage, modelRegistry } = resolveModel(
  provider,
  modelId,
  agentDir,
  config,
);

// Usa el ModelRegistry y AuthStorage de Pi
authStorage.setRuntimeApiKey(model.provider, apiKeyInfo.apiKey);

Failover

FailoverError activa el fallback de modelo cuando está configurado:

if (fallbackConfigured && isFailoverErrorMessage(errorText)) {
  throw new FailoverError(errorText, {
    reason: promptFailoverReason ?? "unknown",
    provider,
    model: modelId,
    profileId,
    status: resolveFailoverStatus(promptFailoverReason),
  });
}

Extensiones de Pi

OpenClaw carga extensiones personalizadas de Pi para comportamientos especializados:

Salvaguarda de compactación

src/agents/pi-extensions/compaction-safeguard.ts añade guardrails a la compactación, incluyendo presupuesto adaptativo de tokens más resúmenes de fallos de herramientas y operaciones de archivos:

if (resolveCompactionMode(params.cfg) === "safeguard") {
  setCompactionSafeguardRuntime(params.sessionManager, { maxHistoryShare });
  paths.push(resolvePiExtensionPath("compaction-safeguard"));
}

Poda de contexto

src/agents/pi-extensions/context-pruning.ts implementa poda de contexto basada en cache-TTL:

if (cfg?.agents?.defaults?.contextPruning?.mode === "cache-ttl") {
  setContextPruningRuntime(params.sessionManager, {
    settings,
    contextWindowTokens,
    isToolPrunable,
    lastCacheTouchAt,
  });
  paths.push(resolvePiExtensionPath("context-pruning"));
}

Streaming y respuestas en bloque

Chunking de bloques

EmbeddedBlockChunker gestiona el streaming de texto en bloques de respuesta discretos:

const blockChunker = blockChunking ? new EmbeddedBlockChunker(blockChunking) : null;

Limpieza de etiquetas thinking/final

La salida de streaming se procesa para eliminar bloques <think>/<thinking> y extraer contenido <final>:

const stripBlockTags = (text: string, state: { thinking: boolean; final: boolean }) => {
  // Eliminar contenido <think>...</think>
  // Si enforceFinalTag, solo devolver contenido <final>...</final>
};

Directivas de respuesta

Las directivas de respuesta como [[media:url]], [[voice]], [[reply:id]] se parsean y extraen:

const { text: cleanedText, mediaUrls, audioAsVoice, replyToId } = consumeReplyDirectives(chunk);

Manejo de errores

Clasificación de errores

pi-embedded-helpers.ts clasifica errores para manejarlos apropiadamente:

isContextOverflowError(errorText)     // Contexto demasiado grande
isCompactionFailureError(errorText)   // Compactación falló
isAuthAssistantError(lastAssistant)   // Fallo de autenticación
isRateLimitAssistantError(...)        // Rate limited
isFailoverAssistantError(...)         // Debe hacer failover
classifyFailoverReason(errorText)     // "auth" | "rate_limit" | "quota" | "timeout" | ...

Fallback de nivel de pensamiento

Si un nivel de pensamiento no es soportado, hace fallback:

const fallbackThinking = pickFallbackThinkingLevel({
  message: errorText,
  attempted: attemptedThinking,
});
if (fallbackThinking) {
  thinkLevel = fallbackThinking;
  continue;
}

Integración con sandbox

Cuando el modo sandbox está habilitado, las herramientas y las rutas se restringen:

const sandbox = await resolveSandboxContext({
  config: params.config,
  sessionKey: sandboxSessionKey,
  workspaceDir: resolvedWorkspace,
});

if (sandboxRoot) {
  // Usar herramientas read/edit/write con sandbox
  // Exec se ejecuta en contenedor
  // Browser usa URL del bridge
}

Manejo específico por proveedor

Anthropic

  • Limpieza de cadena mágica de rechazo
  • Validación de turnos para roles consecutivos
  • Compatibilidad de parámetros con Claude Code

Google/Gemini

  • Correcciones de orden de turnos (applyGoogleTurnOrderingFix)
  • Sanitización de esquemas de herramientas (sanitizeToolsForGoogle)
  • Sanitización del historial de sesión (sanitizeSessionHistory)

OpenAI

  • Herramienta apply_patch para modelos Codex
  • Manejo de degradación del nivel de pensamiento

Integración con TUI

OpenClaw también tiene un modo TUI local que usa componentes de pi-tui directamente:

// src/tui/tui.ts
import { ... } from "@mariozechner/pi-tui";

Esto proporciona la experiencia interactiva de terminal similar al modo nativo de Pi.

Diferencias clave con el CLI de Pi

AspectoCLI de PiOpenClaw embebido
Invocacióncomando pi / RPCSDK vía createAgentSession()
HerramientasHerramientas de coding por defectoSuite de herramientas personalizada de OpenClaw
System promptAGENTS.md + promptsDinámico por canal/contexto
Almacenamiento de sesiones~/.pi/agent/sessions/~/.openclaw/agents/<agentId>/sessions/ (o $OPENCLAW_STATE_DIR/agents/<agentId>/sessions/)
AutenticaciónCredencial únicaMulti-perfil con rotación
ExtensionesCargadas desde discoRutas programáticas + disco
Manejo de eventosRenderizado TUIBasado en callbacks (onBlockReply, etc.)

Consideraciones futuras

Áreas para posible retrabajo:

  1. Alineación de firmas de herramientas: actualmente se adapta entre las firmas de pi-agent-core y pi-coding-agent
  2. Wrapping del session manager: guardSessionManager añade seguridad pero aumenta la complejidad
  3. Carga de extensiones: podría usar el ResourceLoader de Pi más directamente
  4. Complejidad del handler de streaming: subscribeEmbeddedPiSession ha crecido mucho
  5. Particularidades de proveedores: muchos codepaths específicos por proveedor que Pi podría potencialmente manejar

Tests

La cobertura de la integración con Pi abarca estas suites:

  • src/agents/pi-*.test.ts
  • src/agents/pi-auth-json.test.ts
  • src/agents/pi-embedded-*.test.ts
  • src/agents/pi-embedded-helpers*.test.ts
  • src/agents/pi-embedded-runner*.test.ts
  • src/agents/pi-embedded-runner/**/*.test.ts
  • src/agents/pi-embedded-subscribe*.test.ts
  • src/agents/pi-tools*.test.ts
  • src/agents/pi-tool-definition-adapter*.test.ts
  • src/agents/pi-settings.test.ts
  • src/agents/pi-extensions/**/*.test.ts

Live/opt-in:

  • src/agents/pi-embedded-runner-extraparams.live.test.ts (habilitar OPENCLAW_LIVE_TEST=1)

Para los comandos de ejecución actuales, consulta Flujo de trabajo de desarrollo de Pi.