Zum Inhalt springen

OpenClaw Architektur: Pi SDK nahtlos integrieren

OpenClaw nutzt das pi SDK, um einen AI Coding Agent direkt in seine Messaging Gateway Architektur einzubetten. Anstatt pi als Subprozess zu starten oder den RPC-Modus zu verwenden, importiert und instanziiert OpenClaw die AgentSession von pi direkt über createAgentSession(). Dieser eingebettete Ansatz bietet dir:

  • Volle Kontrolle über den Session-Lifecycle und das Event-Handling
  • Injection von Custom Tools (Messaging, Sandbox, kanalspezifische Aktionen)
  • Anpassung des System Prompts pro Kanal/Kontext
  • Session-Persistenz mit Unterstützung für Branching/Compaction
  • Rotation von Multi-Account Auth-Profilen mit Failover
  • Provider-agnostisches Model-Switching
{
"@mariozechner/pi-agent-core": "0.61.1",
"@mariozechner/pi-ai": "0.61.1",
"@mariozechner/pi-coding-agent": "0.61.1",
"@mariozechner/pi-tui": "0.61.1"
}
PaketZweck
pi-aiCore LLM Abstraktionen: Model, streamSimple, Message-Typen, Provider-APIs
pi-agent-coreAgent-Loop, Tool-Ausführung, AgentMessage Typen
pi-coding-agentHigh-Level SDK: createAgentSession, SessionManager, AuthStorage, ModelRegistry, integrierte Tools
pi-tuiTerminal UI Komponenten (genutzt im lokalen TUI-Modus von OpenClaw)
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
│ └── ...
└── ...

Kanalspezifische Message Action Runtimes befinden sich jetzt in den Plugin-eigenen Extension-Verzeichnissen statt unter src/agents/tools, zum Beispiel:

  • die Discord Plugin Action Runtime Dateien
  • die Slack Plugin Action Runtime Datei
  • die Telegram Plugin Action Runtime Datei
  • die WhatsApp Plugin Action Runtime Datei

Der Haupteinstiegspunkt ist runEmbeddedPiAgent() in 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);
},
});

Innerhalb von runEmbeddedAttempt() (aufgerufen durch runEmbeddedPiAgent()) wird das pi SDK verwendet:

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);

subscribeEmbeddedPiSession() abonniert die Events der AgentSession von 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,
});

Folgende Events werden verarbeitet:

  • message_start / message_end / message_update (Streaming von Text/Thinking)
  • tool_execution_start / tool_execution_update / tool_execution_end
  • turn_start / turn_end
  • agent_start / agent_end
  • auto_compaction_start / auto_compaction_end

Nach dem Setup wird die Session gepromptet:

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

Das SDK übernimmt den kompletten Agent-Loop: Senden an das LLM, Ausführen von Tool-Calls und Streaming der Antworten.

Die Image-Injection ist Prompt-lokal: OpenClaw lädt Image-Referenzen aus dem aktuellen Prompt und übergibt sie via images nur für diesen Turn. Es findet kein erneutes Scannen älterer History-Turns statt, um Image-Payloads erneut zu injizieren.

Die Tool-Pipeline von OpenClaw folgt einem klaren Ablauf, um sicherzustellen, dass jeder Befehl sicher und im richtigen Kontext ausgeführt wird:

  1. Base Tools: pi’s codingTools (read, bash, edit, write)
  2. Custom Replacements: OpenClaw ersetzt bash durch exec/process und passt read/edit/write für die Sandbox an.
  3. OpenClaw Tools: messaging, browser, canvas, sessions, cron, gateway, etc.
  4. Channel Tools: Spezifische Action-Tools für Discord, Telegram, Slack oder WhatsApp.
  5. Policy Filtering: Tools werden nach Profil, Provider, Agent, Gruppe und Sandbox-Policies gefiltert.
  6. Schema Normalization: Schemas werden bereinigt, um Eigenheiten von Gemini oder OpenAI abzufangen.
  7. AbortSignal Wrapping: Tools werden so verpackt, dass sie Abort-Signale respektieren.

Die AgentTool-Klasse von pi-agent-core nutzt eine andere execute-Signatur als die ToolDefinition von pi-coding-agent. Der Adapter in pi-tool-definition-adapter.ts schlägt hier die Brücke:

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);
},
}));
}

Die Funktion splitSdkTools() übergibt alle Tools über customTools:

export function splitSdkTools(options: { tools: AnyAgentTool[]; sandboxEnabled: boolean }) {
return {
builtInTools: [], // Empty. We override everything
customTools: toToolDefinitions(options.tools),
};
}

Das stellt sicher, dass die Policy-Filterung von OpenClaw, die Sandbox-Integration, das erweiterte Toolset und die Provider-Konsistenz über alle Plattformen hinweg erhalten bleiben.

Der System Prompt wird in buildAgentSystemPrompt() (system-prompt.ts) generiert. Er setzt einen vollständigen Prompt aus verschiedenen Sektionen zusammen. Dazu gehören Tooling, Tool Call Style, Safety-Leitplanken, die OpenClaw CLI-Referenz, Skills, Docs, Workspace, Sandbox, Messaging, Reply Tags, Voice, Silent Replies, Heartbeats und Runtime-Metadaten. Falls aktiviert, kommen Memory und Reactions sowie optionale Kontext-Dateien hinzu. Für Subagents werden diese Sektionen gekürzt, um einen minimalen Prompt-Modus zu nutzen.

Nachdem eine Session erstellt wurde, wird der Prompt über applySystemPromptOverrideToSession() angewendet:

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

Sessions werden als JSONL-Dateien mit einer Baumstruktur gespeichert (Verknüpfung über id/parentId). Der SessionManager von pi kümmert sich um die Speicherung:

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

OpenClaw erweitert dies mit guardSessionManager(), um die Sicherheit der Tool-Ergebnisse zu gewährleisten.

Die Datei session-manager-cache.ts speichert Instanzen des SessionManagers zwischen, damit Dateien nicht ständig neu eingelesen werden müssen:

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

Mit limitHistoryTurns() wird der Konversationsverlauf gekürzt. Dabei wird unterschieden, ob es sich um einen DM (Direktnachricht) oder einen Gruppenchat handelt.

Wenn der Kontext überläuft, wird automatisch eine Kompaktierung ausgelöst. Für die manuelle Kompaktierung ist compactEmbeddedPiSessionDirect() zuständig:

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

OpenClaw verwaltet einen Speicher für Auth-Profile, der mehrere API-Keys pro Provider enthalten kann:

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

Wenn Fehler auftreten, rotieren die Profile automatisch, wobei ein Cooldown-Tracking erfolgt:

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

Die Auflösung des passenden Modells erfolgt so:

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);

Falls konfiguriert, löst ein FailoverError den Wechsel auf ein Ersatzmodell aus:

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

AI Setup Assistant

OpenClaw lädt custom Pi-Extensions, um spezialisiertes Verhalten zu ermöglichen:

src/agents/pi-hooks/compaction-safeguard.ts fügt Guardrails zur Compaction hinzu. Das beinhaltet ein adaptives Token-Budgeting sowie Zusammenfassungen von Tool-Fehlern und Dateioperationen:

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

src/agents/pi-hooks/context-pruning.ts setzt Context Pruning um, das auf der Cache-TTL basiert:

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

EmbeddedBlockChunker verwaltet das Streaming von Text in diskrete Antwort-Blöcke:

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

Der Streaming-Output wird verarbeitet, um <think>/<thinking>-Blöcke zu entfernen und <final>-Inhalte zu extrahieren:

const stripBlockTags = (text: string, state: { thinking: boolean; final: boolean }) => {
// Strip <think>...</think> content
// If enforceFinalTag, only return <final>...</final> content
};

Reply Directives wie [[media:url]], [[voice]] oder [[reply:id]] werden geparst und extrahiert:

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

In pi-embedded-helpers.ts werden Fehler klassifiziert, damit du sie gezielt abfangen kannst. Das ist der beste Weg, um auf spezifische Probleme wie Context-Überschreitungen oder Rate-Limits zu reagieren:

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" | ...

Falls ein Thinking Level nicht unterstützt wird, solltest du einen Fallback implementieren. So stellst du sicher, dass dein Workflow nicht unterbrochen wird, sondern mit einer alternativen Stufe fortgesetzt wird:

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

Wenn du den Sandbox-Modus aktivierst, werden Tools und Pfade automatisch eingeschränkt. Das ist eine wichtige Sicherheitsmaßnahme, um die Ausführung von Code zu isolieren:

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
}

Sobald sandboxRoot aktiv ist, gelten folgende Regeln für deine Umgebung:

  • Du verwendest isolierte Tools für read/edit/write Operationen.
  • Alle Exec-Befehle werden direkt innerhalb des Containers ausgeführt.
  • Der Browser greift auf eine spezielle Bridge-URL zu.
  • Der Zugriff auf das lokale Dateisystem wird strikt unterbunden.

Jeder Provider hat seine eigenen Eigenheiten. Damit du dich nicht mit den Details der verschiedenen APIs herumschlagen musst, übernimmt OpenClaw die notwendigen Anpassungen im Hintergrund.

  • Refusal Magic String Scrubbing sowie Turn-Validierung für aufeinanderfolgende Rollen
  • Kompatibilität für Claude Code Parameter
  • Fixes für die Turn-Reihenfolge (applyGoogleTurnOrderingFix) und Tool-Schema Sanitization (sanitizeToolsForGoogle)
  • Session-History Sanitization (sanitizeSessionHistory) sowie API-spezifische Bereinigungen
  • apply_patch Tool für Codex-Modelle
  • Handling von Thinking-Level Downgrades

OpenClaw bietet dir einen lokalen TUI-Modus, der pi-tui Komponenten direkt nutzt:

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

Das ermöglicht dir eine interaktive Terminal-Erfahrung, die dem nativen Modus von pi entspricht.

AspektPi CLIOpenClaw Embedded
Aufrufpi Command / RPCSDK via createAgentSession()
ToolsStandard Coding-ToolsEigene OpenClaw Tool-Suite
System-PromptAGENTS.md + PromptsDynamisch pro Channel/Kontext
Session-Speicher~/.pi/agent/sessions/~/.openclaw/agents/<agentId>/sessions/ (oder $OPENCLAW_STATE_DIR/agents/<agentId>/sessions/)
AuthEinzelne ZugangsdatenMulti-Profile mit Rotation
ExtensionsVon Disk geladenProgrammatisch + Disk-Pfade
Event-HandlingTUI-RenderingCallback-basiert (onBlockReply, etc.)

Hier sind einige Bereiche, die wir eventuell noch einmal überarbeiten müssen:

  1. Anpassung der Tool-Signaturen: Aktuell findet eine manuelle Anpassung zwischen den Signaturen von pi-agent-core und pi-coding-agent statt.
  2. Session-Manager-Wrapper: guardSessionManager sorgt zwar für Sicherheit, erhöht aber die Komplexität des Codes.
  3. Laden von Extensions: Wir könnten den ResourceLoader von pi direkter nutzen.
  4. Komplexität der Streaming-Handler: Die Funktion subscribeEmbeddedPiSession ist mittlerweile sehr umfangreich geworden.
  5. Provider-Eigenheiten: Es gibt viele Provider-spezifische Codepfade, die pi potenziell selbst handhaben könnte.

Die Pi-Integration wird durch diese Test-Suites abgedeckt:

  • 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

Live/Opt-in:

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

Aktuelle Run-Commands findest du unter Pi Development Workflow.

OpenClaw

OpenClaw Expert

Noch festgefahren?

Wenn diese Seite nicht hilft, frage OpenClaw Expert nach Schritt-fuer-Schritt-Loesungen.