Saltar al contenido principal

Arquitectura de integración de 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 integrar un agente de codificación con IA en su arquitectura de gateway de mensajería. En lugar de lanzar Pi como un subproceso o usar el modo RPC, OpenClaw importa e instancia directamente AgentSession de Pi mediante 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 prompt del sistema por canal/contexto
  • Persistencia de sesión con soporte para ramificación/Compaction
  • Rotación de perfiles de autenticación multiaccount con failover
  • Cambio de modelo independiente del proveedor

Dependencias de paquetes

{
  "@mariozechner/pi-agent-core": "0.64.0",
  "@mariozechner/pi-ai": "0.64.0",
  "@mariozechner/pi-coding-agent": "0.64.0",
  "@mariozechner/pi-tui": "0.64.0"
}
PaquetePropósito
pi-aiAbstracciones centrales de LLM: Model, streamSimple, tipos de mensajes, APIs de proveedor
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 UI de terminal (usados en el modo TUI local de OpenClaw)

Estructura de archivos

src/agents/
├── pi-embedded-runner.ts          # Re-exports from pi-embedded-runner/
├── pi-embedded-runner/
│   ├── run.ts                     # Main entry: runEmbeddedPiAgent()
│   ├── run/
│   │   ├── attempt.ts             # Single attempt logic with session setup
│   │   ├── params.ts              # RunEmbeddedPiAgentParams type
│   │   ├── payloads.ts            # Build response payloads from run results
│   │   ├── images.ts              # Vision model image injection
│   │   └── types.ts               # EmbeddedRunAttemptResult
│   ├── abort.ts                   # Abort error detection
│   ├── cache-ttl.ts               # Cache TTL tracking for context pruning
│   ├── compact.ts                 # Manual/auto compaction logic
│   ├── extensions.ts              # Load pi extensions for embedded runs
│   ├── extra-params.ts            # Provider-specific stream params
│   ├── google.ts                  # Google/Gemini turn ordering fixes
│   ├── history.ts                 # History limiting (DM vs group)
│   ├── lanes.ts                   # Session/global command lanes
│   ├── logger.ts                  # Subsystem logger
│   ├── model.ts                   # Model resolution via ModelRegistry
│   ├── runs.ts                    # Active run tracking, abort, queue
│   ├── sandbox-info.ts            # Sandbox info for system prompt
│   ├── session-manager-cache.ts   # SessionManager instance caching
│   ├── session-manager-init.ts    # Session file initialization
│   ├── system-prompt.ts           # System prompt builder
│   ├── tool-split.ts              # Split tools into builtIn vs custom
│   ├── types.ts                   # EmbeddedPiAgentMeta, EmbeddedPiRunResult
│   └── utils.ts                   # ThinkLevel mapping, error description
├── pi-embedded-subscribe.ts       # Session event subscription/dispatch
├── pi-embedded-subscribe.types.ts # SubscribeEmbeddedPiSessionParams
├── pi-embedded-subscribe.handlers.ts # Event handler factory
├── pi-embedded-subscribe.handlers.lifecycle.ts
├── pi-embedded-subscribe.handlers.types.ts
├── pi-embedded-block-chunker.ts   # Streaming block reply chunking
├── pi-embedded-messaging.ts       # Messaging tool sent tracking
├── pi-embedded-helpers.ts         # Error classification, turn validation
├── pi-embedded-helpers/           # Helper modules
├── pi-embedded-utils.ts           # Formatting utilities
├── pi-tools.ts                    # createOpenClawCodingTools()
├── pi-tools.abort.ts              # AbortSignal wrapping for tools
├── pi-tools.policy.ts             # Tool allowlist/denylist policy
├── pi-tools.read.ts               # Read tool customizations
├── pi-tools.schema.ts             # Tool schema normalization
├── pi-tools.types.ts              # AnyAgentTool type alias
├── pi-tool-definition-adapter.ts  # AgentTool -> ToolDefinition adapter
├── pi-settings.ts                 # Settings overrides
├── pi-hooks/                      # Custom pi hooks
│   ├── compaction-safeguard.ts    # Safeguard extension
│   ├── compaction-safeguard-runtime.ts
│   ├── context-pruning.ts         # Cache-TTL context pruning extension
│   └── context-pruning/
├── model-auth.ts                  # Auth profile resolution
├── auth-profiles.ts               # Profile store, cooldown, failover
├── model-selection.ts             # Default model resolution
├── models-config.ts               # models.json generation
├── model-catalog.ts               # Model catalog cache
├── context-window-guard.ts        # Context window validation
├── failover-error.ts              # FailoverError class
├── defaults.ts                    # DEFAULT_PROVIDER, DEFAULT_MODEL
├── system-prompt.ts               # buildAgentSystemPrompt()
├── system-prompt-params.ts        # System prompt parameter resolution
├── system-prompt-report.ts        # Debug report generation
├── tool-summaries.ts              # Tool description summaries
├── tool-policy.ts                 # Tool policy resolution
├── transcript-policy.ts           # Transcript validation policy
├── skills.ts                      # Skill snapshot/prompt building
├── skills/                        # Skill subsystem
├── sandbox.ts                     # Sandbox context resolution
├── sandbox/                       # Sandbox subsystem
├── channel-tools.ts               # Channel-specific tool injection
├── openclaw-tools.ts              # OpenClaw-specific tools
├── bash-tools.ts                  # exec/process tools
├── apply-patch.ts                 # apply_patch tool (OpenAI)
├── tools/                         # Individual tool implementations
│   ├── browser-tool.ts
│   ├── canvas-tool.ts
│   ├── cron-tool.ts
│   ├── gateway-tool.ts
│   ├── image-tool.ts
│   ├── message-tool.ts
│   ├── nodes-tool.ts
│   ├── session*.ts
│   ├── web-*.ts
│   └── ...
└── ...
Los runtimes de acciones de mensajes específicos de canal ahora viven en los directorios de extensiones propiedad del Plugin en lugar de bajo src/agents/tools, por ejemplo:
  • los archivos de runtime de acciones del Plugin de Discord
  • el archivo de runtime de acciones del Plugin de Slack
  • el archivo de runtime de acciones del Plugin de Telegram
  • el archivo de runtime de acciones del Plugin de WhatsApp

Flujo principal de integración

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-6",
  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 de 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/thinking)
  • tool_execution_start / tool_execution_update / tool_execution_end
  • turn_start / turn_end
  • agent_start / agent_end
  • compaction_start / 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 ciclo 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 referencias de imágenes del prompt actual y las pasa mediante images solo para ese turno. No vuelve a escanear turnos anteriores del historial para volver a inyectar cargas útiles de imágenes.

Arquitectura de herramientas

Canalización 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 y políticas de sandbox
  6. Normalización de esquemas: esquemas limpiados para particularidades de Gemini/OpenAI
  7. Envoltura con AbortSignal: herramientas envueltas para respetar señales de cancelación

Adaptador de definición de herramientas

El AgentTool de pi-agent-core tiene una firma execute distinta de ToolDefinition de pi-coding-agent. El adaptador en pi-tool-definition-adapter.ts hace este puente:
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) => {
      // pi-coding-agent signature differs from pi-agent-core
      return await tool.execute(toolCallId, params, signal, onUpdate);
    },
  }));
}

Estrategia de división de herramientas

splitSdkTools() pasa todas las herramientas mediante customTools:
export function splitSdkTools(options: { tools: AnyAgentTool[]; sandboxEnabled: boolean }) {
  return {
    builtInTools: [], // Empty. We override everything
    customTools: toToolDefinitions(options.tools),
  };
}
Esto garantiza que el filtrado por políticas de OpenClaw, la integración con sandbox y el conjunto ampliado de herramientas sigan siendo coherentes entre proveedores.

Construcción del prompt del sistema

El prompt del sistema se construye en buildAgentSystemPrompt() (system-prompt.ts). Ensambla un prompt completo con secciones que incluyen Tooling, estilo de llamada de herramientas, barreras de seguridad, referencia de la CLI de OpenClaw, Skills, Docs, Workspace, Sandbox, Mensajería, etiquetas de respuesta, Voz, respuestas silenciosas, Heartbeats, metadatos de runtime, además de Memory y Reactions cuando están activados, y archivos de contexto opcionales y contenido adicional opcional del prompt del sistema. Las secciones se recortan para el modo de prompt mínimo usado por subagentes. El prompt se aplica después de crear la sesión mediante 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 por id/parentId). SessionManager de Pi maneja la persistencia:
const sessionManager = SessionManager.open(params.sessionFile);
OpenClaw envuelve esto con guardSessionManager() para seguridad en los resultados de herramientas.

Caché de sesiones

session-manager-cache.ts almacena en caché instancias de SessionManager para evitar analizar repetidamente archivos:
await prewarmSessionFile(params.sessionFile);
sessionManager = SessionManager.open(params.sessionFile);
trackSessionManagerAccess(params.sessionFile);

Limitación del historial

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

Compaction

La Compaction automática se activa cuando se desborda el contexto. Las firmas comunes de desbordamiento incluyen request_too_large, context length exceeded, input exceeds the maximum number of tokens, input token count exceeds the maximum number of input tokens, input is too long for the model y ollama error: context length exceeded. compactEmbeddedPiSessionDirect() maneja la Compaction 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 varias claves API por proveedor:
const authStore = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
const profileOrder = resolveAuthProfileOrder({ cfg, store: authStore, provider, preferredProfile });
Los perfiles rotan en caso de fallo 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,
);

// Uses pi's ModelRegistry and AuthStorage
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:

Protección de Compaction

src/agents/pi-hooks/compaction-safeguard.ts añade barreras de seguridad a Compaction, incluido presupuesto adaptativo de tokens y 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-hooks/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 por bloques

Fragmentación por bloques

EmbeddedBlockChunker gestiona el streaming de texto en bloques discretos de respuesta:
const blockChunker = blockChunking ? new EmbeddedBlockChunker(blockChunking) : null;

Eliminación de etiquetas Thinking/Final

La salida en streaming se procesa para eliminar bloques <think>/<thinking> y extraer el contenido de <final>:
const stripBlockTags = (text: string, state: { thinking: boolean; final: boolean }) => {
  // Strip <think>...</think> content
  // If enforceFinalTag, only return <final>...</final> content
};

Directivas de respuesta

Las directivas de respuesta como [[media:url]], [[voice]], [[reply:id]] se analizan y extraen:
const { text: cleanedText, mediaUrls, audioAsVoice, replyToId } = consumeReplyDirectives(chunk);

Manejo de errores

Clasificación de errores

pi-embedded-helpers.ts clasifica errores para un manejo adecuado:
isContextOverflowError(errorText)     // Context too large
isCompactionFailureError(errorText)   // Compaction failed
isAuthAssistantError(lastAssistant)   // Auth failure
isRateLimitAssistantError(...)        // Rate limited
isFailoverAssistantError(...)         // Should failover
classifyFailoverReason(errorText)     // "auth" | "rate_limit" | "quota" | "timeout" | ...

Fallback de nivel de thinking

Si un nivel de thinking no es compatible, se aplica un fallback:
const fallbackThinking = pickFallbackThinkingLevel({
  message: errorText,
  attempted: attemptedThinking,
});
if (fallbackThinking) {
  thinkLevel = fallbackThinking;
  continue;
}

Integración con sandbox

Cuando el modo sandbox está activado, las herramientas y rutas quedan restringidas:
const sandbox = await resolveSandboxContext({
  config: params.config,
  sessionKey: sandboxSessionKey,
  workspaceDir: resolvedWorkspace,
});

if (sandboxRoot) {
  // Use sandboxed read/edit/write tools
  // Exec runs in container
  // Browser uses bridge URL
}

Manejo específico por proveedor

Anthropic

  • Limpieza de cadenas mágicas de rechazo
  • Validación de turnos para roles consecutivos
  • Validación estricta en upstream Pi de parámetros de herramientas

Google/Gemini

  • Saneamiento de esquemas de herramientas propiedad del Plugin

OpenAI

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

Integración con TUI

OpenClaw también tiene un modo TUI local que usa directamente componentes de pi-tui:
// src/tui/tui.ts
import { ... } from "@mariozechner/pi-tui";
Esto proporciona la experiencia interactiva de terminal similar al modo nativo de Pi.

Diferencias clave frente a la CLI de Pi

AspectoCLI de PiOpenClaw embebido
Invocacióncomando pi / RPCSDK mediante createAgentSession()
HerramientasHerramientas de codificación predeterminadasSuite de herramientas personalizada de OpenClaw
Prompt del sistemaAGENTS.md + promptsDinámico por canal/contexto
Almacenamiento de sesión~/.pi/agent/sessions/~/.openclaw/agents/<agentId>/sessions/ (o $OPENCLAW_STATE_DIR/agents/<agentId>/sessions/)
AuthCredencial únicaMultiprofile con rotación
ExtensionesCargadas desde discoProgramáticas + rutas en disco
Manejo de eventosRenderizado TUIBasado en callbacks (onBlockReply, etc.)

Consideraciones futuras

Áreas para posible reelaboración:
  1. Alineación de firmas de herramientas: actualmente se adapta entre las firmas de pi-agent-core y pi-coding-agent
  2. Envoltura del gestor de sesiones: guardSessionManager añade seguridad, pero aumenta la complejidad
  3. Carga de extensiones: podría usar ResourceLoader de Pi más directamente
  4. Complejidad del manejador de streaming: subscribeEmbeddedPiSession ha crecido mucho
  5. Particularidades de proveedores: muchos caminos de código específicos por proveedor que Pi podría manejar potencialmente

Pruebas

La cobertura de 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-hooks/**/*.test.ts
En vivo/opt-in:
  • src/agents/pi-embedded-runner-extraparams.live.test.ts (activa OPENCLAW_LIVE_TEST=1)
Para ver los comandos actuales de ejecución, consulta Flujo de desarrollo de Pi.