Pi Coding Agent 이해하기: 터미널 우선 AI 코딩 워크플로우를 위한 최소한의 확장 가능한 아키텍처
요약
Pi Coding Agent는 계층화된 TypeScript 툴킷 기반의 확장 가능한 코딩 에이전트 아키텍처를 소개합니다. 제공자 액세스, 런타임, 워크플로우를 분리하여 사용자가 커스텀 워크플로우를 구축하거나 SDK로 임베딩할 수 있는 유연성을 제공합니다.
핵심 포인트
- 계층화된 TypeScript 패키지로 구성된 모듈형 아키텍처
- 컨텍스트 엔지니어링을 위한 전용 파일 및 확장 훅 제공
- 대화형, 헤드리스(JSONL RPC), SDK 임베딩 등 다양한 실행 모드 지원
- 내장 샌드박스 부재 및 엄격한 RPC 규칙 등 설계상 트레이드오프 존재
요약 (TL;DR)
- Pi Coding Agent는 폐쇄적인 코딩 어시스턴트 제품이 아니라, 계층화된 TypeScript 툴킷으로 구축되었습니다.
- 이 아키텍처는 제공자 액세스 (provider access), 에이전트 런타임 (agent runtime), 코딩 워크플로우 (coding workflow), 그리고 터미널 UI를 별도의 패키지로 분리합니다.
AGENTS.md,SYSTEM.md,APPEND_SYSTEM.md, 기술 (skills), 그리고 확장 훅 (extension hooks)을 통해 컨텍스트 엔지니어링 (Context engineering)을 일급 기능 (first-class feature)으로 제공합니다.- Pi는 대화형으로 실행하거나, JSONL RPC를 통해 헤드리스 (headless)로 실행하거나, 동일한 기본 런타임을 사용하는 SDK를 통해 임베딩 (embedded)할 수 있습니다.
- 이러한 유연성에는 트레이드오프 (tradeoffs)가 따릅니다: 내장된 샌드박스 (sandbox)가 없으며, 엄격한 RPC 프레이밍 (framing) 규칙이 적용되고, 확장 개발자는 신뢰 (trust) 및 압축 (compaction) 동작을 이해해야 합니다.
서론
대부분의 코딩 에이전트들은 완성된 제품으로서 자신을 제시합니다. 즉, 사용자가 이를 설치하고, 명령어를 배우며, 제작자가 정해둔 경계 안에서 작업하게 됩니다. 내장된 워크플로우가 사용자의 요구 사항과 일치한다면 이는 괜찮을 수 있습니다. 하지만 프롬프트 (prompts)가 조립되는 방식, 도구 (tools)가 등록되는 방식, 세션이 요약되는 방식, 또는 에이전트가 사용자의 자체 애플리케이션 내에 임베딩되는 방식을 변경하고 싶을 때는 한계에 부딪히게 됩니다.
Pi Coding Agent는 다른 길을 택합니다.
Earendil Works의 공식 Pi 홈페이지, 문서 및 저장소(repository)를 바탕으로 볼 때, Pi는 고정된 최종 사용자 제품이라기보다 코딩 지향적 런타임을 갖춘 최소한의 에이전트 하네스 (agent harness)로 이해하는 것이 더 적절합니다. 유용한 기본값들을 제공하지만, 이 아키텍처는 사용자가 워크플로우의 큰 부분을 교체하거나 확장하기를 원할 수 있음을 전제로 합니다. 이 프로젝트는 계획형 워크플로우 (plan-like workflows), 추가 명령어, 그리고 기타 상위 수준의 기능과 같은 고급 동작들을 코어 (core)에 하드코딩하는 대신, 확장 기능 (extensions)이나 패키지 (packages)에 담길 수 있는 요소로 명시적으로 배치합니다.
이러한 설계 선택은 AI 툴링 (tooling)을 구축하는 엔지니어들에게 매우 중요합니다. 이는 유지보수성 (maintainability), 이식성 (portability), 그리고 시스템이 터미널, IDE 래퍼 (wrappers), 자동화 파이프라인 (automation pipelines), 또는 내부 개발자 플랫폼 (internal developer platforms)에 얼마나 쉽게 적응할 수 있는지에 영향을 미칩니다.
이 글에서는 Pi가 어떻게 구조화되어 있는지, 왜 계층화(layering)가 중요한지, 컨텍스트 파이프라인(context pipeline)이 어떻게 작동하는지, 그리고 확장 기능(extensions), RPC 모드, 또는 SDK 임베딩(embedding)을 사용하기 시작할 때 어떤 트레이드오프(tradeoffs)가 발생하는지 살펴보겠습니다.
Pi가 해결하려는 문제
코딩 에이전트(coding agent)는 한 번에 여러 가지 작업을 수행해야 합니다:
- 하나 이상의 모델 제공자(model providers)와 통신합니다.
- 도구 호출(tool calls) 및 상태(state)를 포함하는 에이전트 루프(agent loop)를 유지합니다.
- 파일 시스템 액세스(filesystem access), 셸 실행(shell execution), 세션 히스토리(session history), 컨텍스트 제한(context limits)과 같은 코딩 특화된 사항들을 관리합니다.
- 사용자 인터페이스(user interface) 또는 통합 접점(integration surface)을 제공합니다.
많은 도구들이 이 네 가지를 하나의 밀접하게 결합된(tightly coupled) 애플리케이션 내부에서 모두 해결합니다. 이는 초기 경험을 단순하게 만들 수는 있지만, 종종 커스터마이징(customization) 비용을 높게 만듭니다. 만약 프롬프트 구성(prompt composition)이나 세션 요약(session summarization)을 변경하고 싶다면, 결국 프로젝트를 포크(fork)하거나 내부적인 가정(internal assumptions)에 반하여 작업해야 할 수도 있습니다.
Pi의 아키텍처는 책임을 여러 계층(layers)으로 분리함으로써 이 문제를 해결합니다.
Pi 스택: 하나의 모놀리스(monolith) 대신 네 개의 계층
저장소(repository)의 README에 따르면, Pi는 다음과 같이 구별되는 패키지들을 가진 모노레포(monorepo)로 구성되어 있습니다:
@earendil-works/pi-ai@earendil-works/pi-agent-core@earendil-works/pi-coding-agent@earendil-works/pi-tui
이러한 패키지 분리는 시스템을 이해하는 가장 명확한 방법입니다.
계층 1: pi-ai
이것은 제공자 추상화 계층(provider abstraction layer)입니다. 이 계층의 역할은 여러 모델 제공자에 걸쳐 통일된 인터페이스를 제시하는 것입니다.
이 계층이 존재하는 이유:
- 에이전트 루프가 특정 제공자의 SDK에 직접 의존해서는 안 됩니다.
- 제공자를 전환할 때 코딩 런타임(coding runtime)을 다시 작성할 필요가 없어야 합니다.
- 프론트엔드(frontends)와 확장 시스템(extension systems)은 가능한 한 제공자에 구애받지 않아야(provider-agnostic) 합니다.
이는 표준적이면서도 중요한 결정입니다. 만약 제공자별 세부 사항이 상위 계층으로 유출되면, 전체 시스템을 테스트하고 발전시키기가 더 어려워집니다.
계층 2: pi-agent-core
이것은 도구 호출(tool calling) 및 상태 관리(state management)를 포함하여, 핵심 에이전트 동작을 위한 런타임 계층(runtime layer)입니다.
이것이 중요한 이유:
- 도구 실행 (Tool execution)은 런타임의 관심사이지, 터미널 UI의 관심사가 아닙니다.
- 루프 내의 상태 전이 (State transitions)는 CLI 모드와 임베디드 (embedded) 모드 모두에서 재사용 가능해야 합니다.
- 헤드리스 (Headless) 통합 방식도 대화형 (interactive) 방식과 동일한 에이전트 동작을 얻어야 합니다.
아키텍처 관점에서, 이 부분은 Pi가 "단순한 CLI"에 머물지 않도록 만드는 핵심 요소입니다.
Layer 3: pi-coding-agent
이곳은 Pi가 일반적인 에이전트 하네스 (agent harness)를 넘어 코딩 에이전트 (coding agent)가 되는 지점입니다.
이 계층에는 다음이 포함됩니다:
- 코딩 워크플로우 (coding workflow) 동작
- 세션 (sessions) 및 지속성 (persistence)
- 내장된 파일 및 셸 (shell) 도구
- 압축 (compaction) 및 요약 (summarization)
- 확장 기능 (extensions)
- 기술 (skills)
- 모드별 런타임 어셈블리 (mode-specific runtime assembly)
이 패키지는 프로젝트의 운영 중심지입니다. 하위 수준의 런타임 및 상위 수준의 UI와 분리된 상태를 유지하면서도, 대부분의 사용자가 "Pi"라고 생각하는 로직을 담고 있습니다.
Layer 4: pi-tui
이것은 터미널 UI (terminal UI) 계층입니다.
이것이 별도의 패키지로 존재한다는 점은 중요한데, 이는 사용자 인터페이스가 에이전트 그 자체는 아니라는 점을 시사하기 때문입니다. 동일한 런타임이 서로 다른 프런트엔드 (frontend)를 지원할 수 있습니다.
이는 Pi의 가장 강력한 아키텍처 결정 중 하나인 프런트엔드/런타임 분리 (frontend/runtime separation)로 직접 연결됩니다.
하나의 런타임, 다중 모드
공식 문서에서는 네 가지 주요 사용 모드를 설명합니다:
- 대화형 (interactive)
- 출력/JSON (print/JSON)
- RPC
- SDK 임베딩 (SDK embedding)
즉, 터미널이 주요 경험일지라도 Pi는 터미널 인터페이스에 종속되지 않습니다.
대화형 모드 (Interactive mode)
이것은 대부분의 사람들이 처음 시작하게 될 사용자 대상 CLI 워크플로우입니다. 런타임을 터미널 UI 및 내장 명령과 결합합니다.
출력 및 JSON 모드 (Print and JSON modes)
이 모드들은 장기적인 대화형 세션 없이 구조화된 출력을 원하는 자동화 또는 간단한 스크립팅에 유용합니다.
RPC 모드 (RPC mode)
RPC 모드는 stdin/stdout을 통한 JSONL 프로토콜로 Pi를 노출합니다. 이 모드는 핵심 런타임을 재구현하지 않고도 IDE 통합, 에디터 플러그인, 서비스 래퍼 (service wrappers)를 가능하게 만드는 모드입니다.
예시:
pi --mode rpc [options]
{"id": "req-1", "type": "prompt", "message": "Hello, world!"}
이는 서브프로세스 임베딩 (subprocess embedding)이 다른 언어로 작성되었거나 다른 환경에서 실행되는 도구들을 위한 가장 쉬운 통합 경로인 경우가 많기 때문에 매우 강력한 설계 선택입니다.
SDK 모드 (SDK mode)
Node.js 및 TypeScript 애플리케이션의 경우, Pi는 SDK를 통해 프로세스 내에 임베딩 (embedded in-process)될 수 있습니다.
import {
type CreateAgentSessionRuntimeFactory,
createAgentSessionFromServices,
...
이 코드 스니펫은 서비스 (services), 세션 관리자 (session manager), 런타임 생성 (runtime creation), 그리고 그 위에 동작하는 모드 러너 (mode runner)로 이어지는 분해 과정을 명확하게 보여줍니다.
핵심 런타임 흐름: 프롬프트, 도구, 영속성, 압축
AI 에이전트 (AI agents)에게 아키텍처란 제약 조건 하에서의 워크플로우 (workflow) 그 자체입니다. Pi의 런타임 (runtime)은 다음과 같은 루프를 따르는 것으로 보입니다:
- 시작 컨텍스트 (startup context) 및 신뢰 민감형 설정 (trust-sensitive configuration) 로드.
- 시스템 프롬프트 (system prompt) 및 작업 컨텍스트 (working context) 조립.
- 모델 호출 (model call) 전 확장 훅 (extension hooks) 실행.
- 프로바이더 (provider) 요청 전송.
- 가능한 도구 호출 (tool calls)을 포함한 모델 출력 수신.
- 도구 호출을 실행하고 결과 첨부.
- 어시스턴트 (assistant)가 완료될 때까지 반복.
- 세션 엔트리 (session entries) 영속화 (persist).
- 토큰 압박 (token pressure)이 증가하면 오래된 컨텍스트 압축 (compact).
흥미로운 점은 이 파이프라인 (pipeline)이 완전히 하드코딩 (hardcoded)되어 있지 않다는 것입니다. 확장 시스템 (extension system)을 통해 여러 단계를 가로챌 (intercept) 수 있습니다.
확장 훅은 루프를 관찰 가능하고 조정 가능하게 만듭니다
확장 문서에는 시작, 프로바이더 요청, 도구 호출, 압축, 트리 탐색 (tree navigation), 그리고 종료를 둘러싼 생명주기 이벤트 (lifecycle events)가 설명되어 있습니다. 원문 자료에서 언급된 예시는 다음과 같습니다:
session_startbefore_agent_starttool_callbefore_provider_requestafter_provider_responsesession_before_compactsession_compactsession_before_treesession_treesession_shutdown
컨텍스트 엔지니어링 (Context engineering)은 아키텍처에 내장되어 있습니다
많은 에이전트 시스템이 프롬프트 엔지니어링 (prompt engineering)을 설정 파일에 붙여넣는 텍스트로 취급합니다. 반면 Pi는 이를 인프라 (infrastructure)로 취급합니다.
문서와 홈페이지에 따르면, Pi는 다음 항목들을 로드할 수 있습니다:
- 사용자/전역(user/global) 및 프로젝트 디렉토리의
AGENTS.md및CLAUDE.md - 기본 시스템 프롬프트 (system prompt)를 대체하기 위한
SYSTEM.md - 프롬프트에 내용을 추가하기 위한
APPEND_SYSTEM.md - 필요할 때 로드되는 기술 (skills)
- 프롬프트 템플릿 (prompt templates)
- 확장 기능 (extension)에서 제공하는 프롬프트 수정 사항
- 프로젝트 신뢰 상태 (project trust state)
이는 단순한 편의 기능이 아닙니다. 시스템이 운영되는 방식을 변화시킵니다.
온디맨드 기술 (on-demand skills)이 중요한 이유
기술 (skills)은 프롬프트에 항상 포함되는 대신, 필요할 때만 로드됩니다. 이는 컨텍스트 윈도우 (context windows)와 프롬프트 캐시 (prompt caches)가 비대해지는 것을 방지하는 데 도움이 됩니다.
이는 실용적인 절충안 (tradeoff)입니다:
- 항상 로드되는 지침 (instructions)은 더 단순합니다.
- 온디맨드 로딩 (on-demand loading)은 더 효율적이며 더 세밀한 제어를 제공합니다.
Pi는 두 번째 옵션을 선택했으며, 이는 Pi의 더 넓은 설계 철학인 '최소한의 기본 코어, 런타임 (runtime)에서의 동적 동작'에 부합합니다.
확장 기능을 통한 프롬프트 커스터마이징 (Prompt customization)
Pi는 또한 확장 기능이 모델 실행 전에 조립된 시스템 프롬프트를 수정할 수 있도록 허용합니다.
export default function promptCustomizer(pi: ExtensionAPI) {
pi.on("before_agent_start", async (event) => {
const { systemPrompt, systemPromptOptions } = event;
...
이는 Pi의 철학을 보여주는 강력한 사례입니다. 프롬프트 구성 (prompt composition)은 단순히 파일을 로드하는 단계가 아니라, 런타임 (runtime)의 일부이며 수정에 열려 있습니다.
세션 (Sessions), JSONL 지속성 (persistence), 그리고 브랜칭 (branching)
Pi는 세션을 JSONL 형식으로 저장하며 /resume, /new, /tree, /fork, /clone과 같은 명령어를 지원합니다.
이러한 조합은 세션 모델이 평면적인 기록 (flat transcript)이 아님을 의미합니다. 사용자가 대안적인 경로를 탐색할 수 있는 브랜칭 워크플로우 (branching workflows)를 지원합니다.
JSONL이 합리적인 선택인 이유
JSONL은 다음과 같은 이유로 에이전트 세션 저장에 실용적인 형식입니다:
- 추가 (append)에 용이함
- 검사 (inspect)가 쉬움
- 라인 단위로 처리하기 쉬움
- 이벤트 형태의 이력 (event-like histories)에 편리함
터미널 우선 (terminal-first) 도구의 경우, 무거운 데이터베이스를 요구하는 것보다 이 방식이 종종 더 적합합니다.
브랜칭이 컨텍스트 스토리를 바꾸는 방식
소스 자료에 따르면, 브랜치 전환 시 브랜치 요약 (branch summarization)이 사용되는데, 이는 포기된 브랜치의 컨텍스트를 새로운 브랜치의 작업 컨텍스트 (working context)에 주입하기 위함입니다.
이는 브랜칭이 단순한 UI 기능이 아니기 때문에 중요합니다. 브랜칭은 메모리와 연속성에 영향을 미칩니다.
Pi는 또한 전체 히스토리 (full history)와 인메모리 작업 컨텍스트 (in-memory working context)를 구분합니다. 압축 (Compaction)은 후자에 영향을 미치며, 근본적인 저장된 세션 히스토리에는 영향을 주지 않습니다. 이는 동작을 디버깅하거나 이전 항목에 의존하는 확장 기능 (extensions)을 작성할 때 중요한 운영 세부 사항입니다.
압축은 단순한 토큰 트리밍이 아닙니다
대부분의 에이전트 시스템은 컨텍스트 윈도우 (context windows)가 유한하기 때문에 결국 요약 (summarization)이 필요하게 됩니다. Pi는 압축을 내부적인 장부 정리로 숨기기보다는 가시적인 아키텍처 기능으로 노출합니다.
문서에서는 두 가지 요약 메커니즘을 설명합니다:
- 자동/수동 압축 (auto/manual compaction)
- 브랜치 요약 (branch summarization)
또한 컷포인트 (cut-point) 규칙도 정의합니다. 예를 들어, 도구 결과 (tool results)는 반드시 해당 도구 호출 (tool calls)에 붙어 있어야 하므로, 유효한 압축 경계는 제한됩니다.
이것이 바로 확장 기능 제작자들이 알아야 할 구현 세부 사항입니다. 만약 당신의 확장 기능이 히스토리를 어디서든 분할할 수 있다고 가정한다면, 도구 호출의 일관성 (tool-call coherence)을 깨뜨릴 수 있습니다.
Pi는 심지어 훅 (hooks)을 통해 사용자 정의 압축 로직을 허용합니다.
pi.on("session_before_compact", async (event, ctx) => {
const { preparation, branchEntries, customInstructions, signal } = event;
...
이로 인해 압축은 단순한 구현 세부 사항이 아닌, 정책 표면 (policy surface)이 됩니다.
사용자 정의 가능한 압축의 트레이드오프 (Tradeoffs)
이러한 유연성은 유용하지만, 확장 기능 제작자의 부담을 증가시킵니다.
다음 사항들을 이해해야 합니다:
firstKeptEntryIdtokensBefore- 직렬화 및 잘려나간 도구 출력 (serialized and truncated tool outputs)
- 유효한 컷포인트 (valid cut points)
- 반복되는 압축이 이전의 유지된 경계와 어떻게 연관되는지
이러한 세부 사항을 무시하면, 요약이 기술적으로는 유효할지 몰라도 운영상 오해를 불러일으킬 수 있습니다.
확장 기능은 Pi 설계의 진정한 중심입니다
Pi의 홈페이지에는 일부 내장 기능(built-in features)을 건너뛰며, 사용자가 확장 기능(extensions)이나 패키지(packages)를 통해 이를 추가할 것을 명시적으로 요구한다고 나와 있습니다. 이는 이 프로젝트에서 가장 이례적이면서도 중요한 측면 중 하나입니다.
동적 도구 등록 (Dynamic tool registration)
도구(Tools)는 컴파일 타임(compile time)에 고정되지 않습니다. 확장 기능은 세션 시작 시점에 도구를 등록할 수 있습니다.
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
...
이는 Pi의 워크플로우 표면(workflow surface)이 단순히 설정(configured)되는 것이 아니라, 확장(extended)되도록 의도되었음을 보여주는 명확한 신호입니다.
확장 기능이 변경할 수 있는 것들
제공된 자료에 따르면, 확장 기능은 다음 사항들에 영향을 미칠 수 있습니다:
- 명령 (commands)
- 도구 (tools)
- 프로바이더 요청/응답 처리 (provider request/response handling)
- 프롬프트 조립 (prompt assembly)
- 압축 동작 (compaction behavior)
- 트리 탐색 동작 (tree navigation behavior)
- UI 상호작용 (UI interactions)
- 세션 생명주기(session lifecycle) 주변의 워크플로우 로직
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기