콘텐츠로 이동

OpenClaw Pi SDK 연동: AI 에이전트 임베딩 가이드

OpenClaw는 AI 에이전트 기능을 구현하기 위해 pi SDK를 메시징 Gateway 아키텍처에 통합합니다. pi를 별도의 하위 프로세스로 실행하거나 RPC 모드를 사용하는 대신, OpenClaw는 createAgentSession()을 통해 pi의 AgentSession을 직접 가져와 인스턴스화합니다. 이러한 임베디드 방식은 다음과 같은 이점을 제공합니다.

  • 세션 수명 주기 및 이벤트 처리에 대한 완전한 제어
  • 사용자 지정 도구 주입 (메시징, 샌드박스, 채널별 작업)
  • 채널/컨텍스트별 시스템 프롬프트 사용자 지정
  • 브랜칭 및 압축 기능을 지원하는 세션 지속성
  • 장애 조치(failover)를 포함한 다중 계정 인증 프로필 로테이션
  • 공급자 독립적인 모델 전환
{
"@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"
}
패키지목적
pi-ai핵심 LLM 추상화: Model, streamSimple, 메시지 타입, 공급자 API
pi-agent-core에이전트 루프, 도구 실행, AgentMessage 타입
pi-coding-agent상위 수준 SDK: createAgentSession, SessionManager, AuthStorage, ModelRegistry, 내장 도구
pi-tui터미널 UI 구성 요소 (OpenClaw의 로컬 TUI 모드에서 사용)
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
│ └── ...
└── ...

채널별 메시지 작업 런타임은 이제 src/agents/tools가 아닌 플러그인 소유의 확장 디렉토리에 위치합니다. 예를 들면 다음과 같습니다.

  • Discord 플러그인 작업 런타임 파일
  • Slack 플러그인 작업 런타임 파일
  • Telegram 플러그인 작업 런타임 파일
  • WhatsApp 플러그인 작업 런타임 파일

주요 진입점은 pi-embedded-runner/run.ts에 있는 runEmbeddedPiAgent()입니다.

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

runEmbeddedPiAgent()에 의해 호출되는 runEmbeddedAttempt() 내부에서 pi SDK가 사용됩니다.

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()은 pi의 AgentSession 이벤트를 구독합니다.

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

처리되는 이벤트는 다음과 같습니다.

  • message_start / message_end / message_update (텍스트/사고 과정 스트리밍)
  • tool_execution_start / tool_execution_update / tool_execution_end
  • turn_start / turn_end
  • agent_start / agent_end
  • compaction_start / compaction_end

설정 후 세션에 프롬프트가 전달됩니다.

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

SDK는 LLM으로 전송, 도구 호출 실행, 응답 스트리밍을 포함한 전체 에이전트 루프를 처리합니다.

이미지 주입은 프롬프트 로컬 방식으로 작동합니다. OpenClaw는 현재 프롬프트에서 이미지 참조를 로드하여 해당 턴에만 images를 통해 전달합니다. 이미지 페이로드를 다시 주입하기 위해 이전 기록 턴을 다시 스캔하지는 않습니다.

도구 파이프라인은 여러 단계로 구성되어 작업을 처리합니다.

  1. Base Tools: pi의 codingTools (read, bash, edit, write)
  2. Custom Replacements: OpenClaw는 bash를 exec/process로 대체하며, 샌드박스를 위해 read/edit/write를 커스텀합니다.
  3. OpenClaw Tools: messaging, browser, canvas, sessions, cron, Gateway 등을 포함합니다.
  4. Channel Tools: Discord/Telegram/Slack/WhatsApp 전용 액션 도구입니다.
  5. Policy Filtering: 프로필, 제공자, 에이전트, 그룹, 샌드박스 정책에 따라 도구를 필터링합니다.
  6. Schema Normalization: Gemini/OpenAI의 특성에 맞춰 스키마를 정리합니다.
  7. AbortSignal Wrapping: abort signal을 준수하도록 도구를 래핑합니다.

pi-agent-core의 AgentTool은 pi-coding-agent의 ToolDefinition과 다른 execute 시그니처를 가집니다. pi-tool-definition-adapter.ts의 어댑터가 이를 연결해 줍니다.

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

splitSdkTools()는 모든 도구를 customTools를 통해 전달합니다.

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

이를 통해 OpenClaw의 정책 필터링, 샌드박스 통합, 확장된 도구 세트가 모든 제공자 전반에서 일관되게 유지됩니다.

시스템 프롬프트는 system-prompt.ts의 buildAgentSystemPrompt()에서 생성됩니다. 이 함수는 Tooling, Tool Call Style, Safety guardrails, OpenClaw CLI 참조, Skills, Docs, Workspace, Sandbox, Messaging, Reply Tags, Voice, Silent Replies, Heartbeats, Runtime metadata, 그리고 활성화된 경우 Memory와 Reactions, 선택적 컨텍스트 파일 및 추가 시스템 프롬프트 내용을 포함하는 전체 프롬프트를 조립합니다. 서브 에이전트가 사용하는 최소 프롬프트 모드에서는 섹션이 축소됩니다.

프롬프트는 세션 생성 후 applySystemPromptOverrideToSession()을 통해 적용됩니다.

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

세션은 트리 구조(id/parentId 연결)를 가진 JSONL 파일입니다. pi의 SessionManager가 지속성을 처리합니다.

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

OpenClaw는 도구 결과의 안전성을 위해 이를 guardSessionManager()로 래핑합니다.

session-manager-cache.ts는 반복적인 파일 파싱을 피하기 위해 SessionManager 인스턴스를 캐시합니다.

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

limitHistoryTurns()는 채널 유형(DM 대 그룹)에 따라 대화 기록을 다듬습니다.

컨텍스트 오버플로우가 발생하면 자동 압축이 트리거됩니다. 일반적인 오버플로우 시그니처에는 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, ollama error: context length exceeded 등이 있습니다. compactEmbeddedPiSessionDirect()가 수동 압축을 처리합니다.

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

OpenClaw는 제공자당 여러 API 키를 가진 인증 프로필 저장소를 유지합니다.

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

프로필은 실패 시 쿨다운 추적과 함께 교체됩니다.

await markAuthProfileFailure({ store, profileId, reason, cfg, agentDir });
const rotated = await advanceAuthProfile();
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);

FailoverError는 설정된 경우 모델 폴백을 트리거합니다.

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

OpenClaw는 특정 동작을 수행하기 위해 커스텀 Pi Extension을 로드하여 기능을 확장합니다.

src/agents/pi-hooks/compaction-safeguard.ts 파일은 Compaction 과정에 안전장치를 추가합니다. 여기에는 적응형 토큰 예산 관리와 도구 실패 및 파일 작업 요약 기능이 포함되어 있습니다.

  1. 아래 코드를 사용하여 Compaction 모드가 safeguard로 설정되었는지 확인하고 런타임을 구성합니다.
if (resolveCompactionMode(params.cfg) === "safeguard") {
setCompactionSafeguardRuntime(params.sessionManager, { maxHistoryShare });
paths.push(resolvePiExtensionPath("compaction-safeguard"));
}

src/agents/pi-hooks/context-pruning.ts 파일은 캐시 TTL(Time-To-Live)을 기반으로 컨텍스트를 정리하는 기능을 구현합니다.

  1. 설정 파일에서 컨텍스트 정리 모드가 cache-ttl로 지정된 경우, 세션 관리자를 통해 정리 런타임을 활성화합니다.
if (cfg?.agents?.defaults?.contextPruning?.mode === "cache-ttl") {
setContextPruningRuntime(params.sessionManager, {
settings,
contextWindowTokens,
isToolPrunable,
lastCacheTouchAt,
});
paths.push(resolvePiExtensionPath("context-pruning"));
}

OpenClaw는 스트리밍 데이터와 응답 블록을 효율적으로 처리하여 사용자에게 일관된 경험을 제공합니다.

EmbeddedBlockChunker는 스트리밍되는 텍스트를 개별적인 응답 블록으로 관리하는 역할을 합니다.

  1. 설정에 따라 EmbeddedBlockChunker 인스턴스를 생성하여 텍스트 청킹을 처리합니다.
const blockChunker = blockChunking ? new EmbeddedBlockChunker(blockChunking) : null;

스트리밍 출력 과정에서 <think> 또는 <thinking> 블록을 제거하고 <final> 태그 내부의 콘텐츠만 추출하는 처리가 수행됩니다.

  1. 아래 함수를 사용하여 텍스트 내의 불필요한 태그를 제거하고 상태를 관리합니다.
const stripBlockTags = (text: string, state: { thinking: boolean; final: boolean }) => {
// Strip <think>...</think> content
// If enforceFinalTag, only return <final>...</final> content
};

[[media:url]], [[voice]], [[reply:id]]와 같은 응답 지시어(Reply directives)를 파싱하고 추출하여 적절한 동작을 수행합니다.

  1. consumeReplyDirectives 함수를 호출하여 청크 데이터에서 지시어를 분리하고 정리된 텍스트를 얻습니다.
const { text: cleanedText, mediaUrls, audioAsVoice, replyToId } = consumeReplyDirectives(chunk);

OpenClaw 오류 처리 시스템은 개발자가 직면하는 복잡한 예외 상황을 체계적으로 관리하도록 설계되었습니다. pi-embedded-helpers.ts 파일은 발생한 오류를 적절하게 분류하여 시스템이 올바르게 대응할 수 있도록 돕습니다.

  1. 다음 코드를 사용하여 오류 유형을 분류하고 처리할 수 있습니다.
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" | ...
  1. 만약 특정 thinking level이 지원되지 않는 상황이라면, 시스템은 자동으로 대체 수준을 선택하여 작업을 계속 진행합니다.
const fallbackThinking = pickFallbackThinkingLevel({
message: errorText,
attempted: attemptedThinking,
});
if (fallbackThinking) {
thinkLevel = fallbackThinking;
continue;
}

OpenClaw 샌드박스 통합 기능을 사용하면 격리된 환경에서 안전하게 작업을 수행할 수 있습니다. 샌드박스 모드가 활성화되면 도구와 경로가 엄격하게 제한되어 보안을 유지합니다.

  1. 아래 코드를 통해 샌드박스 컨텍스트를 확인하고 설정할 수 있습니다.
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
}

각 AI 모델 공급자마다 고유한 요구 사항이 있어, OpenClaw는 이를 안정적으로 지원하기 위해 다음과 같은 세부적인 처리를 수행합니다. 각 공급자의 특성에 맞춰 최적화된 데이터 흐름을 보장합니다.

Anthropic 모델을 사용할 때는 모델의 응답 품질과 안정성을 위해 몇 가지 필수적인 전처리가 진행됩니다.

  1. 거절 메시지(Refusal magic string)를 감지하여 불필요한 텍스트를 제거합니다.
  2. 연속된 역할(consecutive roles)이 발생할 경우 이를 검증하고 조정합니다.
  3. 상위 API에서 요구하는 엄격한 Pi 도구 매개변수 유효성 검사를 수행합니다.

Google/Gemini 모델과의 통신에서는 도구 정의의 호환성을 유지하는 것이 중요합니다.

  1. 플러그인이 소유한 도구 스키마(tool schema)가 Gemini의 규격에 맞도록 정제 작업을 거칩니다.

OpenAI 모델을 활용할 때 특정 모델 버전의 제약 사항을 우회하거나 기능을 보완합니다.

  1. Codex 모델을 위해 apply_patch 도구를 적용합니다.
  2. 사고 수준(Thinking level)이 지원되지 않는 환경에서 이를 적절하게 낮추는 처리를 수행합니다.

OpenClaw는 터미널 환경에서 직접 상호작용할 수 있도록 pi-tui 컴포넌트를 활용한 로컬 TUI 모드를 제공합니다.

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

이 기능을 통해 pi의 네이티브 모드와 유사한 대화형 터미널 경험을 바로 누릴 수 있습니다.

OpenClaw를 사용할 때 기존 Pi CLI와 어떤 점이 다른지 이해하는 것은 매우 중요합니다. 아래 표는 두 시스템 간의 핵심적인 기술적 차이를 정리한 것입니다.

구분Pi CLIOpenClaw Embedded
호출 방식pi 명령어 / RPCcreateAgentSession()을 통한 SDK
도구기본 코딩 도구커스텀 OpenClaw 도구 모음
시스템 프롬프트AGENTS.md + 프롬프트채널/컨텍스트별 동적 설정
세션 저장소~/.pi/agent/sessions/~/.openclaw/agents/<agentId>/sessions/ (또는 $OPENCLAW_STATE_DIR/agents/<agentId>/sessions/)
인증단일 자격 증명교체 가능한 다중 프로필
확장 기능디스크에서 로드프로그래밍 방식 + 디스크 경로
이벤트 처리TUI 렌더링콜백 기반 (onBlockReply 등)

현재 시스템에서 개선이 필요한 영역은 다음과 같습니다.

  1. 도구 시그니처 정렬: 현재 pi-agent-core와 pi-coding-agent 시그니처 사이를 조정하는 과정이 필요합니다.
  2. 세션 관리자 래핑: guardSessionManager는 안전성을 높여주지만, 동시에 복잡성을 증가시키는 요인이 됩니다.
  3. 확장 기능 로딩: pi의 ResourceLoader를 더 직접적으로 활용할 수 있는 방법을 검토 중입니다.
  4. 스트리밍 핸들러 복잡성: subscribeEmbeddedPiSession의 규모가 커져 관리가 필요합니다.
  5. 제공자별 특이 사항: pi가 처리할 수 있는 영역임에도 불구하고, 현재는 제공자별로 별도의 코드 경로가 많이 존재합니다.

Pi 통합 테스트는 다음 스위트(suite)를 통해 포괄적으로 진행됩니다.

  • 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

라이브/옵트인 테스트의 경우 다음을 참고하세요:

  • src/agents/pi-embedded-runner-extraparams.live.test.ts (OPENCLAW_LIVE_TEST=1 활성화 필요)

현재 실행 명령어에 대한 자세한 내용은 Pi Development Workflow를 확인해 주세요.

OpenClaw

OpenClaw Expert

아직 막혀 있나요?

이 문서에서 답을 못 찾았다면 OpenClaw Expert에게 바로 물어보세요.