Pi 통합 아키텍처

이 문서는 OpenClaw가 pi-coding-agent와 그 형제 패키지(pi-ai, pi-agent-core, pi-tui)를 통합하여 AI 에이전트 기능을 구현하는 방식을 설명합니다.

개요

OpenClaw는 Pi SDK를 사용해 AI 코딩 에이전트를 메시징 게이트웨이 아키텍처에 임베드합니다. Pi를 서브프로세스로 실행하거나 RPC 모드를 쓰는 대신, Pi의 AgentSessioncreateAgentSession()으로 직접 임포트하고 인스턴스화합니다. 이 임베디드 방식을 통해 다음을 얻을 수 있습니다.

  • 세션 라이프사이클과 이벤트 처리에 대한 완전한 제어
  • 커스텀 도구 주입 (메시징, 샌드박스, 채널별 액션)
  • 채널/컨텍스트별 시스템 프롬프트 커스터마이징
  • 브랜칭/압축을 지원하는 세션 영속성
  • 페일오버가 가능한 멀티 계정 인증 프로필 교체
  • 공급자 무관 모델 전환

패키지 의존성

{
  "@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"
}
패키지용도
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          # pi-embedded-runner/에서 재내보내기
├── pi-embedded-runner/
│   ├── run.ts                     # 메인 진입점: runEmbeddedPiAgent()
│   ├── run/
│   │   ├── attempt.ts             # 세션 설정을 포함한 단일 시도 로직
│   │   ├── params.ts              # RunEmbeddedPiAgentParams 타입
│   │   ├── payloads.ts            # 실행 결과에서 응답 페이로드 빌드
│   │   ├── images.ts              # 비전 모델 이미지 주입
│   │   └── types.ts               # EmbeddedRunAttemptResult
│   ├── abort.ts                   # 중단 오류 감지
│   ├── cache-ttl.ts               # 컨텍스트 정리를 위한 캐시 TTL 추적
│   ├── compact.ts                 # 수동/자동 압축 로직
│   ├── extensions.ts              # 임베디드 실행을 위한 Pi 확장 로드
│   ├── extra-params.ts            # 공급자별 스트림 파라미터
│   ├── google.ts                  # Google/Gemini 턴 순서 수정
│   ├── history.ts                 # 히스토리 제한 (DM vs 그룹)
│   ├── lanes.ts                   # 세션/전역 명령 레인
│   ├── logger.ts                  # 서브시스템 로거
│   ├── model.ts                   # ModelRegistry를 통한 모델 해석
│   ├── runs.ts                    # 활성 실행 추적, 중단, 큐
│   ├── sandbox-info.ts            # 시스템 프롬프트용 샌드박스 정보
│   ├── session-manager-cache.ts   # SessionManager 인스턴스 캐싱
│   ├── session-manager-init.ts    # 세션 파일 초기화
│   ├── system-prompt.ts           # 시스템 프롬프트 빌더
│   ├── tool-split.ts              # 도구를 builtIn vs custom으로 분할
│   ├── types.ts                   # EmbeddedPiAgentMeta, EmbeddedPiRunResult
│   └── utils.ts                   # ThinkLevel 매핑, 오류 설명
├── pi-embedded-subscribe.ts       # 세션 이벤트 구독/디스패치
├── pi-embedded-subscribe.types.ts # SubscribeEmbeddedPiSessionParams
├── pi-embedded-subscribe.handlers.ts # 이벤트 핸들러 팩토리
├── pi-embedded-subscribe.handlers.lifecycle.ts
├── pi-embedded-subscribe.handlers.types.ts
├── pi-embedded-block-chunker.ts   # 스트리밍 블록 응답 청킹
├── pi-embedded-messaging.ts       # 메시징 도구 전송 추적
├── pi-embedded-helpers.ts         # 오류 분류, 턴 검증
├── pi-embedded-helpers/           # 헬퍼 모듈
├── pi-embedded-utils.ts           # 포맷팅 유틸리티
├── pi-tools.ts                    # createOpenClawCodingTools()
├── pi-tools.abort.ts              # 도구용 AbortSignal 래핑
├── pi-tools.policy.ts             # 도구 허용/차단 정책
├── pi-tools.read.ts               # Read 도구 커스터마이징
├── pi-tools.schema.ts             # 도구 스키마 정규화
├── pi-tools.types.ts              # AnyAgentTool 타입 별칭
├── pi-tool-definition-adapter.ts  # AgentTool -> ToolDefinition 어댑터
├── pi-settings.ts                 # 설정 재정의
├── pi-extensions/                 # 커스텀 Pi 확장
│   ├── compaction-safeguard.ts    # 세이프가드 확장
│   ├── compaction-safeguard-runtime.ts
│   ├── context-pruning.ts         # 캐시 TTL 컨텍스트 정리 확장
│   └── context-pruning/
├── model-auth.ts                  # 인증 프로필 해석
├── auth-profiles.ts               # 프로필 저장소, 쿨다운, 페일오버
├── model-selection.ts             # 기본 모델 해석
├── models-config.ts               # models.json 생성
├── model-catalog.ts               # 모델 카탈로그 캐시
├── context-window-guard.ts        # 컨텍스트 윈도우 검증
├── failover-error.ts              # FailoverError 클래스
├── defaults.ts                    # DEFAULT_PROVIDER, DEFAULT_MODEL
├── system-prompt.ts               # buildAgentSystemPrompt()
├── system-prompt-params.ts        # 시스템 프롬프트 파라미터 해석
├── system-prompt-report.ts        # 디버그 리포트 생성
├── tool-summaries.ts              # 도구 설명 요약
├── tool-policy.ts                 # 도구 정책 해석
├── transcript-policy.ts           # 트랜스크립트 검증 정책
├── skills.ts                      # 스킬 스냅샷/프롬프트 빌드
├── skills/                        # 스킬 서브시스템
├── sandbox.ts                     # 샌드박스 컨텍스트 해석
├── sandbox/                       # 샌드박스 서브시스템
├── channel-tools.ts               # 채널별 도구 주입
├── openclaw-tools.ts              # OpenClaw 전용 도구
├── bash-tools.ts                  # exec/process 도구
├── apply-patch.ts                 # apply_patch 도구 (OpenAI)
├── tools/                         # 개별 도구 구현
│   ├── 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
└── ...

핵심 통합 흐름

1. 임베디드 에이전트 실행

메인 진입점은 pi-embedded-runner/run.tsrunEmbeddedPiAgent()입니다.

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. 세션 생성

runEmbeddedAttempt()(runEmbeddedPiAgent()에서 호출) 내부에서 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);

3. 이벤트 구독

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
  • auto_compaction_start / auto_compaction_end

4. 프롬프팅

설정이 완료되면 세션에 프롬프트를 보냅니다.

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

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

이미지 주입은 프롬프트 단위입니다. OpenClaw는 현재 프롬프트의 이미지 참조를 로드하여 해당 턴에만 images로 전달하며, 과거 히스토리 턴을 다시 스캔하여 이미지를 재주입하지 않습니다.

도구 아키텍처

도구 파이프라인

  1. 기본 도구: Pi의 codingTools (read, bash, edit, write)
  2. 커스텀 대체: OpenClaw가 bash를 exec/process로 대체하고, 샌드박스용 read/edit/write를 커스터마이징
  3. OpenClaw 도구: 메시징, 브라우저, 캔버스, 세션, cron, 게이트웨이 등
  4. 채널 도구: Discord/Telegram/Slack/WhatsApp별 액션 도구
  5. 정책 필터링: 프로필, 공급자, 에이전트, 그룹, 샌드박스 정책에 따라 도구 필터링
  6. 스키마 정규화: Gemini/OpenAI 특이사항에 맞게 스키마 정리
  7. AbortSignal 래핑: 중단 신호를 존중하도록 도구 래핑

도구 정의 어댑터

pi-agent-core의 AgentTool은 pi-coding-agent의 ToolDefinitionexecute 시그니처가 다릅니다. 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 시그니처와 pi-agent-core 시그니처가 다름
      return await tool.execute(toolCallId, params, signal, onUpdate);
    },
  }));
}

도구 분할 전략

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

export function splitSdkTools(options: { tools: AnyAgentTool[]; sandboxEnabled: boolean }) {
  return {
    builtInTools: [], // 비어 있음. 모두 재정의
    customTools: toToolDefinitions(options.tools),
  };
}

이렇게 하면 OpenClaw의 정책 필터링, 샌드박스 통합, 확장 도구 세트가 공급자 전체에서 일관성을 유지합니다.

시스템 프롬프트 구성

시스템 프롬프트는 system-prompt.tsbuildAgentSystemPrompt()에서 조립됩니다. 도구, 도구 호출 스타일, 안전 가드레일, OpenClaw CLI 레퍼런스, 스킬, 문서, 워크스페이스, 샌드박스, 메시징, 응답 태그, 음성, 무음 응답, 하트비트, 런타임 메타데이터 섹션을 포함하며, 활성화 시 메모리와 리액션도 포함합니다. 선택적 컨텍스트 파일과 추가 시스템 프롬프트 내용도 넣을 수 있습니다. 서브에이전트가 사용하는 최소 프롬프트 모드에서는 섹션을 줄입니다.

프롬프트는 세션 생성 후 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 vs 그룹)에 따라 대화 히스토리를 정리합니다.

압축

자동 압축은 컨텍스트 오버플로우 시 트리거됩니다. 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,
);

// Pi의 ModelRegistry와 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),
  });
}

Pi 확장

OpenClaw는 특수한 동작을 위해 커스텀 Pi 확장을 로드합니다.

압축 세이프가드

src/agents/pi-extensions/compaction-safeguard.ts는 적응형 토큰 예산 할당과 도구 실패 및 파일 작업 요약을 포함한 압축 가드레일을 추가합니다.

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

컨텍스트 정리

src/agents/pi-extensions/context-pruning.ts는 캐시 TTL 기반 컨텍스트 정리를 구현합니다.

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

스트리밍 및 블록 응답

블록 청킹

EmbeddedBlockChunker가 스트리밍 텍스트를 개별 응답 블록으로 관리합니다.

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

Thinking/Final 태그 제거

스트리밍 출력은 <think>/<thinking> 블록을 제거하고 <final> 콘텐츠를 추출하는 처리를 거칩니다.

const stripBlockTags = (text: string, state: { thinking: boolean; final: boolean }) => {
  // <think>...</think> 콘텐츠 제거
  // enforceFinalTag인 경우 <final>...</final> 콘텐츠만 반환
};

응답 디렉티브

[[media:url]], [[voice]], [[reply:id]] 같은 응답 디렉티브가 파싱되어 추출됩니다.

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

오류 처리

오류 분류

pi-embedded-helpers.ts가 적절한 처리를 위해 오류를 분류합니다.

isContextOverflowError(errorText)     // 컨텍스트가 너무 큼
isCompactionFailureError(errorText)   // 압축 실패
isAuthAssistantError(lastAssistant)   // 인증 실패
isRateLimitAssistantError(...)        // 속도 제한
isFailoverAssistantError(...)         // 페일오버 필요
classifyFailoverReason(errorText)     // "auth" | "rate_limit" | "quota" | "timeout" | ...

Thinking 레벨 폴백

지원되지 않는 thinking 레벨은 대체됩니다.

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

샌드박스 통합

샌드박스 모드가 활성화되면 도구와 경로가 제한됩니다.

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

if (sandboxRoot) {
  // 샌드박스된 read/edit/write 도구 사용
  // exec은 컨테이너에서 실행
  // 브라우저는 브릿지 URL 사용
}

공급자별 처리

Anthropic

  • Refusal 매직 스트링 스크러빙
  • 연속 역할에 대한 턴 검증
  • Claude Code 파라미터 호환성

Google/Gemini

  • 턴 순서 수정 (applyGoogleTurnOrderingFix)
  • 도구 스키마 세정 (sanitizeToolsForGoogle)
  • 세션 히스토리 세정 (sanitizeSessionHistory)

OpenAI

  • Codex 모델용 apply_patch 도구
  • Thinking 레벨 다운그레이드 처리

TUI 통합

OpenClaw에는 pi-tui 컴포넌트를 직접 사용하는 로컬 TUI 모드도 있습니다.

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

Pi의 네이티브 모드와 유사한 인터랙티브 터미널 경험을 제공합니다.

Pi CLI와의 주요 차이점

항목Pi CLIOpenClaw 임베디드
호출 방식pi 명령 / RPCSDK의 createAgentSession()
도구기본 코딩 도구커스텀 OpenClaw 도구 세트
시스템 프롬프트AGENTS.md + prompts채널/컨텍스트별 동적 생성
세션 저장소~/.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 통합 테스트 커버리지 범위:

  • 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

라이브/옵트인:

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

현재 실행 명령어는 Pi 개발 워크플로우를 참고하세요.