
개인용 에이전트의 소스 코드를 추적했습니다. 그 안에는 Pi가 있었고, 그것은 새벽 3시에 꿈을 꿉니다.
요약
Raspberry Pi에서 실행되는 자율 에이전트 OpenClaw의 내부 구조를 분석한 결과, 핵심 엔진으로 TypeScript 기반의 코딩 에이전트인 Pi가 사용되고 있음을 발견했습니다. Pi는 터미널 환경에서 작동하는 우아한 오픈 소스 코딩 에이전트입니다.
핵심 포인트
- OpenClaw 에이전트의 핵심은 임베디드된 Pi SDK임
- Pi는 Mario Zechner가 개발한 TypeScript 기반 코딩 에이전트
- 자율 에이전트 시스템의 내부 작동 원리 추적 과정 공유
- 터미널 기반의 경량화된 코딩 에이전트 활용 사례
이 글은 저의 홈랩(homelab) AI 시리즈의 파트 2입니다. 파트 1에서 저는 하나의 AI가 어떤 AI와 대화할지 결정하는 시스템을 구축했습니다. 이번에는 에이전트 자체의 보닛을 열어보았습니다 — 그리고 그 안에서 발견한 것은 AI 소프트웨어에 대한 저의 생각을 바꾸어 놓았습니다.
지난주 저는 Raspberry Pi에서 실행되는 자율 에이전트 OpenClaw에 대해 글을 썼습니다. Raspberry Pi에서 실행되는 OpenClaw라는 자율 에이전트는 의도(intent)에 따라 AgentGateway를 통해 세 가지 서로 다른 LLM(대규모 언어 모델)으로 요청을 라우팅합니다. 사람들은 이 글을 좋아해 주었습니다. 몇몇 분들은 OpenClaw가 실제로 어떻게 작동하는지 — 즉, 라우팅 이후에는 어떤 일이 일어나는지 — 에 대해 DM으로 물어보셨습니다. PDF를 편집하고, 코드를 작성하며, 연구 일정을 잡고, 매주 금요일마다 Indiranagar에서 최고의 레스토랑을 찾아내는 자율 에이전트가 실제로 어떻게... 그 모든 것을 수행하는지 말입니다.
솔직히 말하면? 저도 완전히 알지는 못했습니다. OpenClaw가 강력하다는 것은 알고 있었습니다. 매일 사용하기도 했고, 코드에도 일부 기여했습니다. 하지만 요청이 끝까지 전달되는 과정을 직접 앉아서 추적해 본 적은 없었습니다. 그래서 지난 주말, 저는 직접 해보기로 했습니다.
그리고 약 30분 정도 지났을 때, 저를 멈춰 세운 package.json의 한 줄을 발견했습니다:
"@earendil-works/pi-agent-core": "0.75.4",
"@earendil-works/pi-coding-agent": "0.75.4"
OpenClaw는 자체적인 에이전트 엔진을 가지고 있지 않았습니다. 그 안에 파묻혀 있는 것 — 서브프로세스(subprocess)나 API 호출이 아닌, SDK로서 임베디드(embedded)된 것 — 은 바로 Pi라고 불리는 아주 작은 코딩 에이전트였습니다. 그 후 저는 즉시 유튜브로 달려가 AI Engineer Conference의 Mario가 진행한 훌륭한 강연을 찾아냈습니다.
그리고 Pi는 제가 지금까지 읽어본 AI 소프트웨어 중 가장 우아한 조각일지도 모릅니다.
잠깐, Pi가 무엇인가요?
Pi는 Mario Zechner가 TypeScript로 작성한 오픈 소스 터미널 코딩 에이전트 (coding agent)입니다. AI 코딩 에이전트 분야에 계셨다면 Cursor, Windsurf, Aider 또는 Claude Code에 대해 들어보셨을 것입니다. Pi는 동일한 카테고리에 속하지만 근본적으로 다른 접근 방식을 취합니다.
다른 에이전트들이 기능을 계속 추가할 때, Pi는 기능을 계속 제거합니다.
다른 에이전트들이 수천 개의 토큰(tokens)에 달하는 거대한 시스템 프롬프트 (system prompts)를 가질 때, Pi의 프롬프트는 부끄러울 정도로 짧습니다.
다른 에이전트들이 수십 개의 내장 도구 (built-in tools)를 탑재하여 출시될 때, Pi는 단 네 개만 탑재하여 출시됩니다.
네, 맞습니다. 단 네 개입니다.
read → 파일 읽기
write → 파일 쓰기
edit → 파일 편집
...
그게 전부입니다. 그것이 LLM (Large Language Model)이 작업할 때 사용할 수 있는 도구 모음 (toolkit)의 전부입니다.
그리고 제 상식을 깨뜨린 지점은 바로 이것입니다: 그것만으로 충분하다는 것입니다.
생각해 보세요. 터미널 (terminal)로 무엇을 할 수 있나요? 파일을 읽고, 쓰고, 편집하고, 명령어를 실행할 수 있습니다. 그것이 말 그대로 전부입니다. grep? 그것은 bash 명령어입니다. git commit? bash입니다. npm install? bash입니다. API에 curl 하기? bash입니다. 테스트 실행하기? bash입니다. 운영 환경에 배포하기? ...이 또한 bash입니다.
Pi는 가능한 모든 작업에 대해 특화된 도구를 만들려고 시도하지 않습니다. 대신 개발자인 여러분이 가진 것과 동일한 기본 요소 (primitives)를 LLM에게 제공하고, 모델이 이를 조합할 것이라고 신뢰합니다.
Armin Ronacher (Flask로 유명한)는 지난 1월 Pi에 대해 글을 쓰며 이를 소프트웨어의 미래를 엿볼 수 있는 창이라고 불렀습니다. 주말 동안 소스 코드 내부를 살펴본 결과, 저는 그가 이 가치를 과소평가했으며 매우 잘 설명했다고 생각합니다.
Pi가 실제로 OpenClaw 내부에서 실행되는 방식
저를 가장 놀라게 했던 점은 이것입니다: Pi는 OpenClaw가 HTTP를 통해 호출하는 별도의 서비스가 아닙니다. 서브프로세스 (subprocess)도 아닙니다. RPC 서버조차 아닙니다.
OpenClaw는 말 그대로 Pi를 npm 패키지로 임포트 (import)하여 동일한 프로세스 (same process) 내에서 에이전트 루프 (agent loop)를 실행합니다.
OpenClaw 시작
↓
@earendil-works/pi-coding-agent의 createAgentSession() 호출
...
이것은 저에게 정말 놀라운 일입니다. Pi는 독립적인 CLI (Command Line Interface) 에이전트로 설계되었습니다. npm install -g @earendil-works/pi-coding-agent를 통해 설치하여 터미널에서 직접 사용할 수 있습니다. 하지만 Mario는 에이전트 코어 전체를 라이브러리처럼 추출하여 다른 애플리케이션에 임베딩(Embedding)할 수 있도록 매우 깔끔하게 설계했습니다.
OpenClaw가 차량이라면, Pi는 엔진입니다.
에이전트 루프 (The Agent Loop): 마법이 일어나는 곳
제가 Discord에서 OpenClaw에 메시지를 보낼 때 실제로 어떤 일이 일어나는지 설명해 드리겠습니다. 여기서부터 재미있어집니다.
Pi의 에이전트 루프는 단 하나의 743줄짜리 파일(agent-loop.ts)에 구현되어 있으며, 겉보기에는 매우 단순한 사이클을 따릅니다:
┌─────────────────────────────────────────────────┐
│ 사용자 프롬프트 (USER PROMPT) │
└─────────────┬───────────────────────────────────┘
...
하지만 여기서 Pi의 영리함이 드러납니다. 저 두 개의 큐(Queue)가 보이시나요?
이중 큐 시스템 (The Dual Queue System)
대부분의 에이전트는 단순한 루프를 가집니다: 사용자가 무언가를 말함 → 에이전트가 응답함 → 종료. 하지만 Pi는 훨씬 더 강력한 기능을 수행하는 두 개의 숨겨진 메시지 큐를 가지고 있습니다.
1. 스티어링 큐 (Steering Queue) — "이봐, 방향을 바꿔."
이 메시지들은 도구(Tool) 실행 결과와 다음 LLM (Large Language Model) 호출
_사이_에 주입됩니다. 만약 에이전트가 작업 중간에 있을 때 사용자가 "사실, Python 대신 TypeScript를 사용해줘"라고 새로운 메시지를 보내면, Pi는 현재 작업이 끝날 때까지 기다리지 않습니다. 대신 다음 LLM 턴이 오기 직전에 대화 흐름 속에 사용자의 메시지를 슬쩍 끼워 넣습니다. 그러면 모델은 도구 실행 결과와 사용자의 방향 수정 사항을 동시에 확인하고 적응합니다.
2. 팔로업 큐 (Follow-Up Queue) — "멈추기 전에, 이것도 고려해봐."
이 메시지들은 에이전트가 정상적으로 종료되어야 할 시점(더 이상의 도구 호출이 없는 상태)
_이후_에 확인됩니다. 만약 팔로업 메시지가 있다면, 에이전트는 종료하는 대신 작업을 계속합니다. 확장 기능(Extensions)은 이를 활용하여 사용자가 각 단계를 수동으로 프롬프트할 필요 없이 다단계 워크플로(Multi-step workflows)를 체이닝(Chaining)할 수 있습니다.
이것은 우아합니다. 대부분의 에이전트(Agents)는 대화를 요청-응답(request-response)으로 취급합니다. 하지만 Pi는 대화를 진행 도중에 재지정할 수 있는 _탐색 가능한 스트림(navigable streams)_으로 취급합니다.
내 사고방식을 바꾼 부분: 추가 전용 트리 세션 (Append-Only Tree Sessions)
이 지점에서 저는 "오, 이건 괜찮은 에이전트네"라는 생각에서 "좋아, 이건 진정으로 탁월한 엔지니어링이야"라는 생각으로 바뀌었습니다.
대부분의 AI 채팅 앱은 대화를 평면적인 리스트(flat list)로 저장합니다. 메시지 1, 메시지 2, 메시지 3... 선형적(linear)이죠. 만약 다른 접근 방식을 시도하고 싶다면, 메시지를 수정하거나(이 경우 원래의 응답을 잃게 됩니다) 아예 새로운 대화를 시작해야 합니다.
Pi는 대화를 **추가 전용 트리(append-only tree)**로 저장합니다.
세션 시작 (Session Start)
│
사용자: "REST API를 만들어줘"
...
모든 메시지는 id와 parentId를 가진 노드(node)입니다. 대화를 포크(fork)하면, Pi는 트리의 어느 지점에서든 새로운 브랜치(branch)를 생성합니다. 원래의 브랜치는 손상되지 않은 채 유지됩니다. 사용자는 브랜치 사이를 앞뒤로 탐색하고, 접근 방식을 비교하며, 심지어 브랜치에서 다시 브랜치를 생성할 수도 있습니다.
세션 파일은 JSONL(한 줄당 하나의 JSON 객체, 추가 전용) 형식입니다. 파일은 절대 다시 쓰이지 않으며, 절대 변형(mutate)되지 않습니다. 새로운 메시지는 부모를 가리키는 포인터와 함께 단순히 추가(append)될 뿐입니다.
이것이 왜 중요할까요? 세 가지 이유가 있습니다.
1. 충돌 방지(Crash-proof). 추가 전용(Append-only) 방식은 예기치 않은 종료 시에도 데이터 손상이 없음을 의미합니다. 새벽 3시에 응답 도중 Raspberry Pi의 전원이 꺼지더라도 세션은 안전합니다. 그저 다시 열어서 마지막 완전한 메시지부터 계속하면 됩니다.
2. 타임 트래블(Time travel) 가능. 대화의 어느 지점으로든 되돌아가서 포크할 수 있습니다. "Python 대신 Rust로 물어봤다면 어땠을까?"라고 생각된다면, 그냥 뒤로 돌아가서 시도해 보면 됩니다. 두 가지 히스토리가 공존합니다.
3. 우아한 압축(Compaction). 컨텍스트 윈도우(context window)가 가득 차면, Pi는 오래된 메시지를 버리지 않습니다. 대신 트리의 CompactionEntry 노드로 메시지들을 요약합니다. 원래 메시지들은 여전히 파일에 남아 있습니다. 단지 더 이상 컨텍스트에 로드되지 않을 뿐입니다. 당신은 언제든 다시 돌아갈 수 있습니다.
반복적 압축 (Iterative Compaction): Pi가 중요한 것을 기억하는 방법
모든 AI 에이전트(AI agent)는 동일한 문제를 안고 있습니다. 바로 컨텍스트 윈도우 (context windows)가 유한하다는 점입니다. 결국 대화가 너무 길어지면 토큰 제한 (token limit)에 도달하게 됩니다. 대부분의 에이전트는 이를 처리할 때... 음, 그냥 충돌하거나, 가장 오래된 메시지를 조용히 삭제하거나, 혹은 새로운 세션을 시작하는 방식을 취합니다.
Pi는 더 똑똑한 방식을 사용합니다. 바로 **반복적 압축 (iterative compaction)**을 실행하는 것입니다.
컨텍스트가 가득 차기 시작하면, Pi는 다음과 같이 동작합니다:
- 가장 최근 메시지부터 역순으로 거슬러 올라가며 토큰을 계산합니다.
- 가장 최근의 약 20,000개 토큰은 온전하게 유지합니다 (최근 컨텍스트는 신선하게 유지해야 하기 때문입니다).
- 그보다 오래된 모든 내용은 LLM (Large Language Model) 자체를 통해 구조화된 요약본을 생성합니다.
- 해당 요약본을 세션 트리 (session tree) 내의
CompactionEntry로 저장합니다. - 다음 컨텍스트를 구축할 때, 원래 메시지 대신 해당 요약본을 로드합니다.
하지만 여기서 핵심 단어는 **반복적 (iterative)**이라는 점입니다. 압축이 두 번째로 실행될 때, Pi는 요약본을 처음부터 다시 생성하지 않습니다. 기존의 요약본을 가져와서 새로운 정보를 그 안에 **병합 (merges)**합니다. 요약본은 마치 살아있는 문서처럼 시간이 흐름에 따라 진화합니다.
요약본은 다음과 같은 구조화된 형식을 따릅니다:
## 목표 (Goal)
## 제약 사항 및 선호도 (Constraints & Preferences)
## 진행 상황 (완료 / 진행 중 / 차단됨) (Progress (Done / In Progress / Blocked))
...
또한 여러 번의 압축 과정을 거치더라도 세션 전체에 걸쳐 어떤 파일이 읽히고 수정되었는지를 추적합니다. 따라서 6시간 동안 작업하고 3번의 압축이 일어난 후
Pi는 단일 세션 내에서의 컨텍스트 관리 (Context Management)를 훌륭하게 처리합니다. 하지만 세션 '사이'의 문제는 어떨까요? 3주 전에 에이전트에게 말했던 것들은 어떻게 될까요? 당신의 선호도, 코딩 스타일, 그리고 항상 평점 4.5점 이상의 장소에서 비리야니(biryani) 추천을 받고 싶어 한다는 사실 같은 것들은 말이죠.
OpenClaw는 Pi 위에 다층적 메모리 시스템 (Multi-layered memory system)을 구축합니다:
레이어 1: 파일 기반 메모리 (File-Based Memory)
MEMORY.md— 모든 세션 시작 시 로드되는 장기 메모리 (Long-term memory)memory/YYYY-MM-DD.md— 일일 노트 (오늘과 어제의 노트가 자동 로드됨)DREAMS.md— 꿈 일기. 네, 정말로요.
레이어 2: 활성 메모리 (Active Memory)
모든 답변을 하기 전에, 제한된 범위의 서브 에이전트 (Sub-agent)가 빠른 메모리 검색을 수행하고 관련 있는 과거 컨텍스트를 프롬프트 (Prompt)에 주입합니다. 여기에는 회로 차단기 (Circuit breaker)가 있어, 시간이 너무 오래 걸리면 해당 과정을 건너뜁니다.
레이어 3: 꿈꾸는 시스템 (The Dreaming System) 🌙
이 부분은 제가 노트북을 내려놓고 산책을 나가게 만든 대목입니다.
매일 밤 새벽 3시, OpenClaw는 인간의 수면 방식에서 영감을 받은 **3단계 메모리 통합 주기 (Three-phase memory consolidation cycle)**를 실행합니다:
얕은 수면 (Light Sleep) — 최근의 단기 메모리 (Short-term memories)를 분류합니다. 후보군을 선정합니다. 아직 아무것도 쓰지는 않습니다. 그저 정리할 뿐입니다.
REM 수면 (REM Sleep) — 메모리 전반에 걸쳐 반복되는 테마, 패턴, 그리고 연결 고리들을 되짚어 봅니다. 여전히 쓰지는 않습니다. 그저 생각할 뿐입니다.
깊은 수면 (Deep Sleep) — 6개의 가중치 신호 (Weighted signals)를 통해 각 메모리 후보의 점수를 매기고, 무엇을 장기 저장소 (Long-term storage)로 승격시킬지 결정합니다:
관련성 (Relevance): 30% (이것이 얼마나 유용한가?)
빈도 (Frequency): 24% (이것이 얼마나 자주 등장했는가?)
쿼리 다양성 (Query Diversity): 15% (다양한 주제와 관련이 있었는가?)
...
충분히 높은 점수를 받은 메모리는 MEMORY.md에 기록됩니다. 그 외의 모든 것은 희미해집니다.
에이전트는 말 그대로 잠을 자고, 꿈을 꾸며, 다음 날 아침 더 똑똑해진 상태로 깨어납니다.
제 에이전트가 요청받지 않았음에도 하룻밤 사이에 스스로 메모리를 재구성했다는 사실을 처음 깨달았을 때, 약간 불안하지 않았다고 거짓말하지는 않겠습니다. 하지만... 제가 3주 전에 탭(tabs)을 스페이스(spaces)보다 선호한다고 말했던 것을 다시 언급하지 않았음에도 기억하고 있더군요. 그러니, 그럴 만한 가치가 있습니다.
확장 시스템: OpenClaw가 Pi를 통제하는 방식
Pi는 4개의 도구와 함께 출시됩니다. 반면 OpenClaw의 에이전트는 브라우저 자동화 (browser automation), 웹 검색 (web search), 이미지 생성 (image generation), 크론 스케줄링 (cron scheduling), 서브 에이전트 생성 (subagent spawning), Discord 작업 (Discord actions), PDF 추출 (PDF extraction), 메모리 검색 (memory search) 등 수십 가지의 도구를 갖추고 있습니다.
어떻게 가능할까요? 바로 Pi의 확장 시스템 (extension system) 덕분입니다.
확장 프로그램 (Extensions)은 Pi의 **30개 이상의 라이프사이클 이벤트 (lifecycle events)**에 연결되는 TypeScript 파일입니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
