REPO DEEP DIVE · 2026-07-26 · EXOHARNESS/EXO · AI 에이전트 하네스

exoharness/exo 딥다이브
— 자기 소스코드를 런타임에 고쳐 가며 진화하는 AI 에이전트 하네스

exo는 AI 에이전트를 돌리는 하네스(harness)다. 흔한 에이전트 프레임워크와 다른 점은 딱 하나 — 에이전트가 자기 자신의 소스코드를 읽고, 고치고, 다시 빌드하고, 재시작할 수 있다는 것이다. 프롬프트뿐 아니라 툴, 어댑터, 스케줄러, 심지어 하네스 정책 코드까지 전부 수정 대상이다.

위험하게 들린다. 그래서 exo의 진짜 설계는 "무엇을 못 하게 막을까"가 아니라 "무엇을 절대 못 건드리게 남겨둘까"에 있다. 답은 이벤트 로그 하나다. 에이전트가 자기를 망가뜨려도 append-only 로그와 시크릿·스냅샷은 그대로 남아 있어서, 되감고(rewind) 다시 시도하되 "아까 뭘 시도했다 실패했는지"를 기억한 채 시도할 수 있다. 이게 저자들이 말하는 "재귀적 자기개선(RSI)"의 런타임 지원이다.

코드는 Rust 47,757줄 + TypeScript 12,637줄. Rust 크레이트 4개(신뢰층 exoharness, 정책층 executor, cli, cost)와 TypeScript로 짠 실제 에이전트(examples/exo/)로 나뉜다. 샌드박스 프로바이더 7종, 채팅 어댑터 7종, 대체 실행자(Codex·Claude Code·Cursor SDK) 3종이 이미 붙어 있다.

(저장소 exoharness/exo · 라이선스 MIT(Copyright 2026 Ankur Goyal) · 언어 Rust · 생성 2026-05-20 · ★ 355 · 포크 28 · 이슈 45 · 홈 exoharness.ai · TrendShift 데일리 1위(2026-07-26 기준) · shallow clone 소스 직접 분석)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 지금 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석 — 신뢰층과 정책층을 가른 한 줄
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 설계 교훈 6가지와 솔직한 한계
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

"자기가 사는 집을 리모델링할 수 있는 에이전트" — 단, 등기부등본은 못 건드린다

먼저 하네스라는 말부터 풀자. 요즘 "AI 에이전트"라고 부르는 것은 사실 두 덩어리다. 하나는 모델(GPT·Claude 같은 LLM), 다른 하나는 그 모델을 실제로 일하게 만드는 바깥 껍데기다. 껍데기는 프롬프트를 조립하고, 툴 목록을 넘기고, 모델이 "이 함수 불러줘"라고 하면 진짜로 실행해 주고, 결과를 다시 넣어 준다. 이 껍데기를 하네스라고 부른다.

용어 풀이
하네스 (harness)
원래는 말에 씌우는 마구(馬具)다. 말(모델)의 힘을 실제 일(수레 끌기)로 바꾸는 장치. 소프트웨어에서는 어떤 핵심 부품을 감싸서 실제로 돌아가게 만드는 주변 코드를 뜻한다. Claude Code, Codex CLI, Cursor 같은 것들이 전부 "코딩 에이전트 하네스"다. 모델은 같아도 하네스가 다르면 완전히 다른 도구가 된다는 게 요즘 업계의 공통 인식이다.

거의 모든 하네스는 사람이 손으로 짜서 고정해 놓는다. 프롬프트, 툴 목록, 컨텍스트 관리 방식이 코드에 박혀 있고, 모델은 그 안에서만 움직인다. 에이전트가 "자기개선"이라고 하는 것도 대개 메모리에 사실을 하나 적어 두거나, 스킬 파일을 하나 추가하는 정도다.

exo의 주장은 이렇다. 모델이 계속 똑똑해진다면, 하네스를 사람이 고정해 두는 것 자체가 병목이 된다. 그러니 에이전트에게 자기 하네스의 소스코드를 통째로 넘겨 주고, 고치고 다시 빌드해서 재시작하게 하자. 실제로 canonical 설정에서 exo의 소스 트리는 에이전트 샌드박스 안 /workspace/exo에 마운트되어 있고, 에이전트는 그걸 shell 툴로 직접 편집한다.

핵심 비유

"세입자에게 집 리모델링 권한을 통째로 주되, 등기부등본은 구청 금고에 둔다"

보통 에이전트 프레임워크는 세입자에게 못 하나 박는 것도 허락받게 한다. 벽지를 바꾸는 것(메모리 갱신) 정도만 자유롭다. 안전하지만, 세입자가 아무리 뛰어난 목수여도 집은 그대로다.

exo는 반대다. 벽을 허물든 배관을 새로 깔든 마음대로 하라고 한다. 다만 등기부등본과 공사 이력만은 구청 금고(= exoharness의 이벤트 로그)에 두고, 세입자는 열람만 가능하고 수정은 못 하게 한다. 공사를 망쳐도 "이 집이 원래 어떤 상태였고, 지난주에 뭘 시도했다 실패했는지"가 남아 있으니 되돌릴 수 있다.

이 금고가 없으면 어떻게 되는지가 중요하다. 세입자가 벽을 허물다가 공사 기록 장부까지 태워 버리면, 되돌릴 방법도 없고 같은 실수를 무한 반복하게 된다. exo가 "이벤트 로그만은 절대 못 건드린다"고 못 박은 이유가 딱 이것이다.

그래서 코드베이스가 두 덩어리로 칼같이 갈라져 있다. 이게 exo를 이해하는 데 필요한 사실상 유일한 개념이다.

┌───────────────────────────────────────────────────────────────┐ EXECUTOR (정책층) — 갈아끼울 수 있음 · 에이전트가 수정 가능 프롬프트 조립 · 모델 호출 · 툴 디스패치 · 메모리/압축 정책 crates/executor (Rust 18,591줄) + examples/exo (TS) └───────────────────────────────────────────────────────────────┘ │ Arc<dyn ExoHarness> 트레잇으로만 통신 ▼ ┌───────────────────────────────────────────────────────────────┐ EXOHARNESS (신뢰층) — 영속적 · 최소한 · 기본값에선 수정 금지 정체성(agent/conversation/turn) · append-only 이벤트 로그 아티팩트 · 시크릿(AES-256-GCM) · 샌드박스 생명주기/스냅샷 crates/exoharness (Rust 19,825줄) ※ LLM을 직접 호출하지 않는다 — 그건 '판단'이라 정책층 소관 └───────────────────────────────────────────────────────────────┘

공식 문서의 표현이 정확하다 — "히스토리를 저장하는 놈과 프롬프트를 결정하는 놈이 같으면, 에이전트가 프롬프트를 안전하게 다시 쓰게 둘 수 없다. 잘못된 변경 하나가 히스토리까지 날려 버리기 때문이다."(website/docs-src/concepts/exoharness-and-executor.md)

2왜 지금 주목받는가

두 달 된 저장소가 TrendShift 데일리 1위에 오른 이유 다섯 가지

① 만든 사람이 "AI 평가(eval) 인프라" 쪽 사람이다

LICENSE 파일의 저작권자는 Ankur Goyal이다. LLM 평가·관측 플랫폼 Braintrust의 창업자다. 그 흔적이 의존성에 그대로 남아 있다 — Cargo.tomlbraintrust-sdk-rust, lingua(멀티프로바이더 메시지 포맷), braintrust-llm-router를 git 의존성으로 직접 가져온다.

이게 왜 중요하냐면, exo의 설계가 "에이전트를 만드는 사람"이 아니라 "에이전트를 관찰하고 평가하는 사람"의 관점에서 나왔다는 뜻이기 때문이다. 그래서 아키텍처의 중심에 프롬프트 엔지니어링이 아니라 이벤트 로그와 재현성이 놓여 있다. 실행 트레이싱(execution_tracing.rs)도 처음부터 1급 시민이다.

② "RSI"라는 단어를 진지하게 다시 정의했다

용어 풀이
RSI (Recursive Self Improvement, 재귀적 자기개선)
AI가 AI를 개선해 그 결과로 더 빠르게 AI를 개선하는 것. exo의 docs/RSI.md는 여기에 시비를 건다 — "AI로 GPU 커널을 짜서 AI를 빨리 만든다"는 건 재귀가 아니라 자기촉매(autocatalytic)일 뿐이라는 것이다. 증기기관도, 인터넷도 자기촉매였다. 진짜 재귀는 "완성된 자기 자신"으로 "다음 버전의 완성된 자기 자신"을 만드는 것 — 컴파일러가 자기 자신을 컴파일하는 것(부트스트래핑)처럼.

그리고 여기서 exo가 던지는 통찰이 하나 있다. 프로그래밍 언어의 재귀 함수도 런타임 지원이 있어야 성립한다 — 콜 스택과 스코프가 없으면 재귀 함수는 자기 상태를 유지하지 못한다. 마찬가지로 에이전트의 재귀적 자기개선에도 런타임 지원이 필요하고, exo에서 그 역할을 하는 게 지워지지 않는 이벤트 로그다. 콜 스택은 아니지만 "완전한 실행 이력"이다.

왜 로그가 콜 스택 역할인가

재귀 함수가 콜 스택 없이 돌면 어떻게 되는가? 어디서 왔는지 몰라 같은 곳을 무한히 맴돈다. 자기를 고치는 에이전트도 똑같다. 코드를 고쳤다가 망가뜨리고, 되감고, 또 같은 방식으로 고치고... 로그가 있으면 되감은 뒤에도 "이미 이 방법은 해 봤고 실패했다"를 읽을 수 있다. 되감기(rewind)가 로그까지 지워 버리면 이 학습이 통째로 사라진다.

③ 실행자(executor)를 통째로 갈아끼울 수 있다

exo 위에서 돌아가는 에이전트는 exo 기본 턴 루프를 쓸 수도 있지만, Codex CLI · Claude Code SDK · Cursor SDK를 실행자로 꽂아 쓸 수도 있다. examples/typescript/codex-harness.ts(1,330줄)는 샌드박스 안에서 codex app-server --listen stdio://를 띄우고 그 알림을 exo 이벤트로 변환한다. claude-code-harness.ts는 아예 Claude Agent SDK의 프로세스 spawn을 가로채서 샌드박스 안 바이너리를 대신 띄운다.

"어떤 코딩 에이전트를 쓰느냐"와 "히스토리·시크릿·샌드박스를 누가 소유하느냐"를 분리했다. 실행자를 바꿔도 에이전트의 정체성과 기억은 그대로다. 이건 지금 시장에서 꽤 드문 포지셔닝이다.

④ 설치가 진짜 한 줄이다

curl -fsSL https://raw.githubusercontent.com/exoharness/exo/main/setup.sh -o setup.sh
bash setup.sh

setup.sh가 git·Docker 유무를 확인하고, 없으면 설치를 제안하고, mise로 Node 22.15.0 / pnpm 10.26.2 / Rust 1.95를 버전 고정해 깔고, 빌드하고, API 키를 물어본 뒤 ./exo.sh 명령을 알려 준다. "Rust 프로젝트라 빌드가 험할 것"이라는 진입장벽을 의도적으로 무너뜨렸다.

주의
curl | bash 계열 설치는 원리적으로 신뢰 문제가 있다

이 방식은 원격 스크립트를 내려받아 그대로 실행한다. exo의 setup.sh는 파일로 저장한 뒤 실행하도록(-o setup.shbash setup.sh) 되어 있어 읽어 볼 기회는 준다. 실행 전에 한 번 열어 보는 습관을 권한다. 게다가 이 스크립트는 리눅스에서 usermod -aG docker를 제안하는데, 이는 사실상 root 권한과 동등한 권한 부여다.

⑤ 숫자로 본 현황 — 그리고 그 숫자를 어떻게 읽을 것인가

지표해석
★ 스타355절대값은 작다. 다만 저장소 생성이 2026-05-20이라 두 달 남짓의 수치다
포크28스타 대비 8% — "구경"보다 "직접 돌려 봄"의 비율이 높은 편
열린 이슈45스타 355 대비 상당히 높다. 활발한 개발 초기 단계의 전형
최근 커밋2026-07-25PR #156 — 하루 단위로 움직이는 중
라이선스MIT상업적 이용·수정·재배포 자유. 가장 느슨한 편
숫자 읽는 법
TrendShift "1위"는 스타 개수 1위가 아니다

TrendShift의 순위는 누적 스타가 아니라 최근 증가 모멘텀에 가깝다. 스타 355개짜리 저장소가 스타 10만개짜리보다 위에 올 수 있는 이유가 이것이다. 이 딥다이브에 나온 355·28·45는 2026-07-26 시점 GitHub API 조회값이며, 며칠 뒤면 달라진다.

3기술 스택 전체 지도

Rust 47,757줄 + TypeScript 12,637줄이 어디에 어떻게 쓰였는가

왜 Rust와 TypeScript를 섞었는가

이 조합에는 명확한 역할 분담이 있다. Rust는 "절대 안 죽어야 하는 것" — 이벤트 로그, 시크릿 암호화, 샌드박스 프로세스 관리, 어댑터 감독. TypeScript는 "자주 바뀔 것" — 프롬프트 조립, 툴 정의, 어댑터 프로토콜 구현.

그리고 이게 자기수정 설계와 직결된다. 에이전트가 가장 자주 손댈 부분(프롬프트·툴)은 TypeScript라 재컴파일 없이 파일만 고치면 되고, 신뢰층은 Rust라 고치려면 cargo build를 거쳐야 한다. 수정 난이도 자체가 안전장치다.

크레이트 / 디렉토리줄 수역할
crates/exoharness19,825신뢰층. 데이터 모델·이벤트 로그·시크릿·샌드박스 7종·HTTP 서버/클라이언트
crates/executor18,591정책층. 턴 루프·툴 런타임·어댑터 감독·스케줄러·TS 브리지·RLM
crates/cli8,967exo 명령. 8개 서브커맨드 트리 + rustyline 기반 REPL
crates/cost374LiteLLM 공개 가격 DB를 받아 캐시하고 토큰 비용 계산
examples/exoTS 다수실제로 돌아가는 canonical 에이전트 — 프롬프트·툴 8종·어댑터 7종
examples/typescriptTS 다수대체 하네스 — codex(1,330줄)·claude-code(752)·cursor-sdk(619)·rlm(714) 등
examples/gameboy-agentPy+TSPyBoy 에뮬레이터로 포켓몬을 플레이하는 데모 에이전트

주요 외부 의존성

이름쓰임새
tokio 1.52Rust 비동기 런타임. 전체 I/O의 기반
object_store 0.12저장 추상화. 로컬 파일시스템으로 이벤트/레코드 JSON 저장 (fs·aws·gcp·azure 피처 활성)
uuid 1.18 (v7)모든 ID. 정렬 = 시간순이라는 성질이 로그 설계의 핵심
lingua / braintrust-llm-router멀티 프로바이더 메시지 포맷 + LLM 라우팅 (git 의존성)
actix-web 4.9exoharness를 HTTP 서버로 노출할 때 (exo serve)
aws-sdk-bedrockagentcoreAWS Bedrock AgentCore를 샌드박스 프로바이더로 쓸 때
boa_engine순수 Rust JS 엔진. RLM 하네스의 JS REPL에 사용
clap 4.5 / rustylineCLI 파싱 / 대화형 REPL 라인 편집
sqlx 0.8워크스페이스에 선언은 되어 있으나 exoharness의 기본 백엔드는 DB를 쓰지 않는다 (아래 §4 참조)
tsx / Node 22TypeScript 하네스를 별도 프로세스로 실행
용어 풀이
UUIDv7
UUID의 최신 버전. 앞 48비트가 밀리초 타임스탬프다. 그래서 (1) 문자열로 정렬하면 그게 곧 시간순 정렬이고, (2) ID만 보고 생성 시각을 역산할 수 있고, (3) 중앙 카운터 없이 여러 프로세스가 동시에 만들어도 순서가 보장된다. exo는 이 성질을 극단적으로 활용해 DB 인덱스도, created_at 컬럼도 없이 append-only 로그의 전순서를 구현했다.

4아키텍처 심화 분석 — 신뢰층과 정책층을 가른 한 줄

이 장이 이 문서의 본체다. 여섯 조각으로 나눠 본다

4-1. 데이터 모델 — 대화는 "메시지 목록"이 아니라 "이벤트 로그"다

보통 챗봇을 만들면 대화를 messages: [{role, content}, ...] 배열로 저장한다. exo는 그러지 않는다. 저장하는 건 이벤트(event)이고, 메시지는 그중 한 종류일 뿐이다.

// crates/exoharness/src/types.rs — EventData는 22종 태그드 enum
pub enum EventData {
    ConversationCreated { .. }, ConversationForked { .. },
    SessionStarted { .. },      SessionEnded { .. },
    TurnStarted { .. },         TurnEnded { .. },
    Messages { messages, usage: Option<Box<UsageRecord>> },
    ToolRequested { .. },       ToolResult { .. },
    ArtifactWritten { .. },     SandboxCreated { .. }, ...
    Custom { event_type, payload }   // 탈출구
}

이 설계의 요점은 Custom이라는 탈출구다. 예를 들어 "컨텍스트 압축(compaction)"을 생각해 보자. 보통은 프레임워크가 압축을 1급 개념으로 넣는다. exo는 넣지 않는다. 대신 정책층이 "내가 만든 요약본은 여기에 있다"는 커스텀 이벤트를 로그에 append하면 끝이다. 신뢰층은 압축이 뭔지 알 필요가 없다.

설계 포인트
"로그 ≠ 프롬프트"라는 분리

전체 이벤트 로그가 곧 모델에 보내는 프롬프트인 것은 아니다. 로그는 지워지지 않는 기록이고, 프롬프트는 정책층이 로그로부터 만들어 내는 하나의 뷰(view)다. 그래서 압축·요약·필터링을 아무리 공격적으로 해도 원본은 안전하다. 이 한 문장이 exo 전체 아키텍처를 압축한다.

계층 구조는 Agent → Conversation → Session → Turn 네 단계다.

개념
Agent전역 설정 단위. 여러 대화에 공통으로 적용되는 메타데이터와 시크릿을 소유
Conversation한 에이전트와의 대화 흐름. 멈췄다 재개할 수 있고, 수백만 건이 쌓일 수 있다
Session대화 안에서 살아 있는 한 번의 접속. "지금 켜져 있는 인스턴스"
Turn사용자 입력 한 번. 그 안에서 LLM 호출은 여러 번 일어날 수 있다

Rust 코드에서는 이게 4단계 상속 트레잇으로 나타난다. 재미있는 건 TurnHandle을 따로 둔 점이다 — 진행 중인 턴에서 이벤트를 쓰면 세션/턴 ID가 자동으로 태깅돼서, 나중에 "이 이벤트가 어느 턴 소속이었나"를 잃어버리지 않는다.

// 실제 메서드 이름 (TS 쪽에서는 camelCase로 래핑됨)
trait ConversationHandle: SandboxHandle {
    async fn begin_turn(..)   -> Arc<dyn TurnHandle>;   // 핫패스
    async fn get_events(..)   -> GetEventsResult;        // 커서 스캔
    async fn watch_events(..) -> EventStream;            // 스트리밍 구독
    async fn fork(..)         -> Arc<dyn ConversationHandle>;
}
trait TurnHandle: SnapshotHandle {
    async fn add_events(data: Vec<EventData>) -> AddEventsResult;
    async fn finish() -> EventId;
}

4-2. 저장 계층 — 놀랍게도 데이터베이스가 없다

Cargo.toml에 sqlx·sqlite·postgres가 선언돼 있어서 당연히 DB를 쓸 거라 생각하기 쉽다. 그런데 기본 백엔드(BasicExoHarness)를 열어 보면 DB를 전혀 쓰지 않는다. 이벤트 하나가 파일 하나다.

agents/{agent_id}/record.json agents/{agent_id}/conversations/{conv_id}/events/ 019a3f2c-....json ← UUIDv7 파일명 = 이미 시간순 019a3f2d-....json 019a3f31-....json 정렬 방법: 디렉토리를 list → 파일명 문자열 sort → 끝. (UUIDv7 앞부분이 타임스탬프라 사전순 = 시간순)

그럼 동시성은 어떻게 처리할까? 두 겹이다.

락이 있는데 왜 CAS까지?

락은 "동시에 쓰는 것"만 막는다. 그런데 턴 하나가 진행되는 동안 에이전트는 샌드박스에서 명령을 돌리거나 HTTP를 호출하며 락을 놓고 한참 기다린다. 그 사이에 스케줄러가 태스크를 끝내고 같은 대화에 이벤트를 append할 수 있다. 락만으로는 이 "내가 자는 사이 히스토리가 앞서갔다"를 못 잡는다. CAS가 그걸 잡는다.

은행 창구에 비유하면, 락은 "한 번에 한 명만 창구 이용"이고 CAS는 "당신이 뽑은 번호표가 아직 유효한가"다. 잠깐 화장실 다녀온 사이 순번이 지나갔으면 다시 뽑아야 한다.

4-3. 타임 트래블 — 되감기와 분기

"에이전트의 전체 상태 = 이벤트 로그의 버전"이라는 불변식이 성립하기 때문에, 로그의 임의 지점으로 되감기(rewind)하거나 분기(fork)할 수 있다.

동작의미중요한 점
rewind알려진 정상 상태로 되돌림로그는 append-only라 "그 뒤에 뭘 했는지"는 지워지지 않는다
fork기존 대화에서 새 대화를 가지치기exo conversation fork <agent> <conv> "이름"
snapshot샌드박스 파일시스템 스냅샷스냅샷 ID가 로그에 기록돼서, 로그를 되감으면 파일시스템도 같이 되감긴다
중요한 함정
rewind_sandbox는 "반쪽 롤백"이다

이름 때문에 전부 되돌아갈 것 같지만, rewind_sandbox가 되돌리는 건 샌드박스 파일시스템뿐이다. 대화 이벤트, 아티팩트, 어댑터·스케줄러 레코드, 시크릿은 그대로 남는다. 저장소의 docs/SELF-CONTROL.md가 이 비대칭성을 가장 큰 주의사항으로 명시하고 있다.

그런데 이건 버그가 아니라 의도된 설계다. 파일시스템은 실험 대상이고 로그는 학습의 근거라, 둘이 같이 지워지면 §2의 "무한 루프" 문제가 되살아난다.

4-4. 샌드박스 — 프로바이더 7종과 크로스 클라우드 스냅샷

샌드박스는 에이전트가 명령을 실행하는 격리 환경이다. exo는 공통 트레잇 두 개(ManagedSandboxHandle / ManagedSandboxBackend)로 추상화하고 7종을 구현했다.

프로바이더위치스냅샷
Docker로컬 (docker CLI)docker commitdocker save로 tar 캡처
Apple Container로컬 (container CLI)× 미구현(로드맵으로 명시)
LocalProcess격리 없이 호스트에서 직접× 격리된 FS가 없어 의미 자체가 없음
Daytona클라우드 REST○ + Docker tar도 받아들임
E2B클라우드 (envd Connect API)○ 템플릿 ID 참조
Sprites클라우드 (REST + WebSocket)○ 체크포인트 ID 참조
Vercel / AWS AgentCore클라우드 REST× 미구현
영리한 구현
로컬 Docker 스냅샷이 클라우드로 "순간이동"한다

스냅샷은 보통 만든 백엔드에서만 복원 가능하다(SnapshotKind 태그로 강제). 그런데 딱 하나 예외가 있다 — Daytona 백엔드가 DockerImageTar 스냅샷을 받아들인다. 내부적으로 임시 격리된 레지스트리 자격증명으로 docker login/push를 돌려 이미지를 클라우드로 밀어 넣고 거기서 복원한다. 호스트의 ~/.docker/config.json은 건드리지 않는다.

실용적 의미: 노트북에서 시작한 에이전트 작업 환경을 그대로 클라우드로 승급시킬 수 있다.

영리한 구현
Rust 바이너리 안에 Python 소켓 서버가 문자열로 박혀 있다

Vercel과 AWS AgentCore는 스트리밍 exec API가 없어서 stdin/stdout을 실시간으로 주고받을 수 없다. exo의 해법은 sandbox_provider/process_bridge.rs파이썬 소켓 서버 코드 전문을 Rust 문자열 상수로 넣어 두고, base64로 인코딩해 원격 샌드박스에 설치·실행시킨 뒤 TCP 소켓(127.0.0.1:48765)으로 통신하는 것이다. "API가 없으면 만들어 넣는다"는 접근.

4-5. 시크릿 — 이벤트 스키마에 아예 필드가 없다

자기 코드를 고칠 수 있는 에이전트에게 API 키를 어떻게 안 들키나? exo의 답이 인상적이다.

시크릿은 AES-256-GCM으로 암호화해 저장하고, 마스터 키는 macOS Keychain 또는 ~/.config/exo/master.key(권한 0600)에 둔다. 이 위치는 harness 데이터 루트와도, git 저장소와도 별개다. 에이전트가 소스를 아무리 고쳐도 이미 암호화된 시크릿을 풀 키에 닿지 못한다.

그런데 진짜 핵심은 다른 데 있다. 샌드박스에서 프로세스를 띄울 때 시크릿은 환경변수로 주입된다. 그럼 그게 로그에 남지 않을까? 남지 않는다 — 필터링하는 게 아니라, 이벤트 타입에 env 필드 자체가 없다.

// SandboxProcessStarted 이벤트가 기록하는 것
sandbox_id, process_id, name, command, cwd, mode,
stdin, output, lifecycle, status, provider_state
//  ↑ env 필드가 아예 존재하지 않음 ←←← 이게 격리의 실체
왜 이 방식이 강력한가

"로그를 쓸 때 시크릿을 마스킹한다"는 흔한 접근은 마스킹 로직에 구멍이 하나만 있어도 유출된다. 정규식이 못 잡는 형태, 새로 추가된 필드, 예외 경로... 계속 관리해야 한다.

exo는 그 대신 담을 그릇을 아예 만들지 않았다. 실수로 시크릿을 로그에 넣으려면 먼저 Rust 타입 정의를 고치고 재컴파일해야 한다. 컴파일러가 정책을 강제하는 셈이다.

4-6. 자기수정의 실제 실행 경로 — guardian

이제 하이라이트다. 에이전트가 자기 코드를 고쳤다. 그 다음은?

① 에이전트가 shell 툴로 /workspace/exo 소스 편집 │ ▼ ② guardian_action 툴 호출 ← 임의 셸 명령 아님! allowlist 8종만: status / build / start_services / stop_services / restart_services / restart_adapters / restart_scheduler / restart_all / logs │ ▼ ③ 호스트 측 스크립트 exo-service-guardian 실행 build → cargo build -p exo + scheduler-runner 빌드 → .exo/exo-control.restart 마커 파일 기록 │ ▼ ④ ./exo.sh --control 래퍼가 마커를 보고 자식 프로세스만 재시작 (터미널은 안 닫힘 · .exo 상태는 보존) │ ▼ ⑤ 재시작은 drain 방식: 마커 파일을 러너가 스스로 claim → 진행 중 작업 마치고 종료 → 15초 내 응답 없으면 강제 kill │ ▼ ⑥ .exo/exo-reboot-notice.json → 새 러너가 이를 읽고 대화들을 깨워 "나 돌아왔다"고 알림
설계 포인트
"자기수정 가능"과 "임의 명령 실행 가능"은 다르다

에이전트는 소스코드를 무엇이든 고칠 수 있다. 그런데 빌드하고 재시작하는 통로는 9개 액션짜리 allowlist뿐이다. 게다가 재시작 명령은 bash -lc 'sleep 2; exec ...'2초 지연 후 분리된 프로세스로 실행된다 — 현재 턴이 결과를 기록하고 끝낼 시간을 벌어 주는 것이다. 자기를 재시작하면서도 "재시작했다"는 사실을 로그에 남길 수 있다.

실패하면? 전용 롤백 툴은 없다. 원시 도구를 조합한다 — 코드는 git revert, 파일시스템은 rewind_sandbox, 설치한 툴은 uninstall_agent_tool, 어댑터는 disable_adapter. 문서가 권하는 루틴은 "빌드 전 스냅샷 → 빌드 → status/logs 확인 → 그 다음에 커밋"이다. 참고로 저장소 문서는 "클론된 샌드박스에서 카나리 검증하는 기능은 아직 없다"고 스스로 밝히고 있다.

4-7. 턴 루프와 어댑터·스케줄러

실제 한 턴이 도는 모습은 이렇다.

// crates/executor/src/basic.rs — 라운드 반복의 뼈대
for round in 0u32.. {
    if round > max_tool_round_trips { return; }       // 무한루프 방지
    let messages = materialize_prompt_history(..);        // 로그 → 프롬프트
    let response = complete_model_round(..);              // LLM 호출
    let events   = interpret_model_response(response);    // 툴콜 파싱
    turn.add_events(events).await?;                       // 로그에 기록
    let reqs = collect_tool_requests(&events);
    if reqs.is_empty() { return; }                       // 툴콜 없으면 종료
    turn.add_events(execute_tool_round(..)).await?;       // 툴 실행 후 기록
}

TypeScript 하네스는 어떻게 붙는가. Rust가 JS 엔진을 내장한 게 아니라, node --import tsx runner.ts <하네스경로>Node 자식 프로세스를 띄우고 stdin/stdout 위 NDJSON RPC로 대화한다. 핵심은 권한 분리다 — TypeScript는 "무엇을 호출할지"만 결정하고, 실제 셸·파일시스템 접근은 Rust 호스트가 수행한다.

영리한 구현
툴 목록을 매 라운드 통째로 새로 만든다

보통은 툴 레지스트리를 한 번 만들어 재사용한다. exo의 TS 턴 루프는 라운드마다 빈 레지스트리를 새로 생성해 전부 재등록한다. 이유는 하나 — 에이전트가 턴 도중 install_agent_tool로 만든 툴이 바로 다음 라운드에 보이게 하기 위해서다. 스스로 도구를 만들어 즉시 쓰는 루프가 이 한 줄에서 나온다.

어댑터(adapter)는 외부 채팅 채널과의 장기 연결을 담당하는 호스트 프로세스다. ExoChat·IRC·Discord·Slack·WhatsApp·Signal·agent-cli 7종이 있다. 여기서도 프로토콜은 소켓이 아니라 stdin/stdout 위 줄바꿈 구분 JSON이고, 워커는 별도 프로세스 그룹으로 spawn된다(손자 프로세스까지 정리하려고). 재연결은 지수 백오프(5초 → 최대 300초, 60초 이상 안정되면 리셋)로 프로세스를 통째로 재기동하는 방식이다.

설계 포인트
모델 응답이 자동으로 외부에 나가지 않는다

에이전트가 Slack에 연결돼 있어도, 모델이 생성한 텍스트가 자동으로 슬랙에 전송되지 않는다. send_adapter_message 툴 호출만 아웃박스 파일에 기록되고, 어댑터 런타임이 1초마다 그걸 드레인해서 보낸다. 프롬프트의 표현대로 "외부 side effect는 명시적이고 검사 가능해야 한다"는 원칙이 코드로 구현된 것.

스케줄러는 "매 시간 뉴스 헤드라인 가져오기" 같은 반복 작업을 돌린다. 이름과 달리 진짜 cron이 아니다 — @every 5m이나 */N * * * *만 받아 내부적으로 interval_ms(고정 간격)로 정규화한다. 동시성 제어는 파일 락이 아니라 시간 기반 리스(lease, 기본 10분)라, 워커가 죽어도 리스가 만료되면 다른 워커가 자동으로 집어 간다. 작업이 끝나면 결과 요약으로 대화를 "깨워서"(wakeup) 새 턴을 시작시킨다.

4-8. RLM — 셸이 아니라 JS REPL 안에서 "생각하는" 하네스

crates/executor/src/rlm.rs(808줄)는 좀 별종이다. 코드 안 프롬프트에 "You are a subquery model inside a recursive language model"이라고 적혀 있다. 이 하네스에는 셸도 샌드박스도 없다. 대신 대화 전체를 문자열로 평탄화해 순수 Rust JS 엔진(boa)에 넣고, 툴을 딱 3개만 준다 — repl_execute, subquery, subquery_variable.

Basic 하네스와 RLM 하네스의 차이

Basic/TS 하네스는 행동(act)하는 에이전트다 — 셸을 돌리고 파일을 고친다. RLM은 계산(compute)하는 루프다 — 아주 긴 컨텍스트를 JS 코드로 잘라 내고, 조각마다 서브 모델에게 질문을 던지고, 결과를 다시 변수에 담아 조합한다. globalThis.Final이 설정되면 종료한다.

비유하자면 전자는 현장에 나가는 조사관, 후자는 자료실에서 문서 더미를 프로그래밍으로 분해하는 분석가다.

5디렉토리 구조 해부

파일 290개 · 8.4MB. 어디부터 열어야 하는가
exo/ ├─ setup.sh ← 최초 설치 (mise로 node/pnpm/rust 버전 고정) ├─ Cargo.toml ← 워크스페이스: cli, cost, exoharness, executor, scheduler-runner ├─ mise.toml ← node 22.15.0 / pnpm 10.26.2 / python 3.11 / rust 1.95 │ ├─ crates/ │ ├─ exoharness/ ■ 신뢰층 19,825줄 — 여기는 에이전트가 못 고친다 │ │ ├─ types.rs ← 4단계 트레잇 + EventData 22종 enum │ │ ├─ basic.rs (4,081) ← 기본 백엔드: 파일시스템 + CAS 헤드 체크 │ │ ├─ secrets.rs ← AES-256-GCM + 마스터키 3종 백엔드 │ │ ├─ sandbox.rs (2,184) ← 샌드박스 공통 트레잇 │ │ ├─ sandbox_provider/ ← docker · e2b · daytona · vercel · sprites · │ │ │ aws_agentcore · process_bridge(파이썬 임베드) │ │ ├─ server.rs, http/ ← 같은 트레잇을 HTTP로도 구현 (exo serve) │ │ └─ contract_tests.rs ← 로컬/HTTP 구현에 같은 테스트를 돌려 동치성 강제 │ │ │ ├─ executor/ ■ 정책층 18,591줄 — 갈아끼울 수 있는 부분 │ │ ├─ basic.rs ← 턴 루프 본체 │ │ ├─ typescript.rs ← Node 자식 프로세스 + NDJSON RPC 브리지 │ │ ├─ harness_tool.rs ← 툴 런타임 2종(Basic=shell만 / Exo=전체) │ │ ├─ rlm.rs (808) ← JS REPL 기반 재귀 서브쿼리 하네스 │ │ ├─ adapter/ ← 워커 감독 · 지수 백오프 · 아웃박스 claim/ack │ │ └─ scheduler_*.rs ← 고정 간격 태스크 + 시간 기반 리스 │ │ │ ├─ cli/ (8,967) ← exo 명령 8종 + rustyline REPL/TUI │ └─ cost/ (374) ← LiteLLM 가격 JSON 원격 fetch + 24시간 캐시 │ ├─ examples/exo/ ■ 실제로 돌아가는 canonical 에이전트 (TypeScript) │ ├─ harness.ts ← 매 턴 프롬프트를 조립하는 곳 — 여기부터 읽어라 │ ├─ prompts/me.md ← 에이전트 정체성 27줄 │ ├─ SELF.md ← 에이전트가 자기수정 전에 읽는 지침 │ ├─ guardian-tools.ts ← 빌드/재시작 allowlist │ ├─ sandbox-tools.ts ← 스냅샷/되감기 │ ├─ scheduler-tools.ts ← 반복 태스크 │ ├─ memory-tools.ts ← remember/forget (600자 · 최대 200개) │ ├─ introspection-tools.ts ← 자기 이벤트 로그 조회 (비용 집계 포함) │ ├─ web-tools.ts (799) ← web_search/web_fetch + SSRF 가드 │ ├─ todo-tools.ts ← todowrite │ └─ adapters/ ← protocol.ts + 7종 워커 │ ├─ examples/typescript/ ← 대체 하네스 (codex 1330 · claude-code 752 · │ cursor-sdk 619 · rlm 714 · basic 13줄) ├─ examples/gameboy-agent/ ← PyBoy로 포켓몬 플레이하는 데모 ├─ containers/ ← codex / claude-code / cursor-sdk 샌드박스 이미지 ├─ docs/ ← RSI.md · EXO-BASICS.md · design/ 8종 · spec.md └─ website/docs-src/ ← 공식 문서 원본 (concepts/ 가 가장 잘 쓰였다)
읽는 순서 추천
문서 → 프롬프트 → 조립 코드 → 신뢰층

docs/RSI.md(철학, 60줄) → ② website/docs-src/concepts/exoharness-and-executor.md(핵심 분리) → ③ examples/exo/prompts/me.md(에이전트가 실제로 뭘 지시받는지, 27줄) → ④ examples/exo/harness.ts(프롬프트 조립 실물) → ⑤ crates/exoharness/src/types.rs(데이터 모델). 여기까지가 하루치다.

6학습 포인트 — 설계 교훈 6가지와 솔직한 한계

이 저장소에서 가져갈 만한 것, 그리고 조심할 것

가져갈 만한 설계 교훈

교훈 1

"바꿀 수 있는 것"과 "절대 못 바꾸는 것"을 먼저 가른다

유연성을 늘리는 정석은 보통 "설정 옵션을 추가하는 것"이다. exo는 반대로 갔다 — 불변으로 남길 최소 집합을 먼저 정하고, 나머지 전부를 열어 버렸다. 불변 집합이 작을수록 위에서 진화할 수 있는 공간이 커진다. 이건 에이전트가 아닌 일반 시스템 설계에도 그대로 적용되는 원칙이다.

교훈 2

정책을 "런타임 체크"가 아니라 "타입 정의"로 강제한다

시크릿이 로그에 안 남는 이유가 마스킹 함수가 아니라 이벤트 타입에 env 필드가 없어서라는 것. 정책을 데이터 구조로 옮기면 실수할 경로 자체가 사라진다. "검사"보다 "불가능하게 만들기"가 항상 강하다.

교훈 3

같은 인터페이스의 두 구현에 같은 테스트를 돌린다

contract_tests.rs가 로컬 파일시스템 구현(BasicExoHarness)과 HTTP 클라이언트 구현(HttpExoHarness) 양쪽에 동일한 테스트 함수를 실행한다. "트레잇이 같으니 동작도 같겠지"를 믿지 않고 테스트로 강제한 것. 다중 백엔드를 지원하는 모든 프로젝트가 훔쳐 갈 만한 패턴이다.

교훈 4

재시작을 "즉시 kill"이 아니라 "마커 + drain"으로

guardian이 재시작할 때 프로세스를 바로 죽이지 않는다. .exo/exo-adapters.restart 같은 마커 파일을 쓰면 러너가 그걸 스스로 claim하고, 진행 중 작업을 마친 뒤 종료한다. 15초 안에 안 끝나야 비로소 강제 kill. 게다가 재시작 명령 자체도 2초 지연 실행이라 "내가 나를 재시작한다"는 기록을 남길 시간이 확보된다.

교훈 5

프롬프트 크기는 "요약만 주입, 본문은 툴로 조회"로 억제한다

exo에는 컨텍스트 압축 정책이 없다. 대신 메모리·투두·스킬을 프롬프트에 넣을 때 이름과 한 줄 설명 목록만 넣고, 상세 내용은 에이전트가 필요할 때 툴로 가져가게 한다. 압축 알고리즘을 짜기 전에 이 방식부터 시도해 볼 가치가 있다.

교훈 6

ID 하나로 정렬 인덱스와 타임스탬프를 동시에 없앤다

UUIDv7을 파일명으로 쓰면 ls | sort가 곧 시간순 정렬이고, ID에서 생성 시각을 역산할 수 있다. 결과적으로 DB도 인덱스도 created_at 컬럼도 없이 append-only 로그가 성립한다. 작은 선택이 아키텍처 전체를 단순하게 만든 사례.

솔직한 한계

한계 1
컨텍스트 압축이 없어서 긴 대화의 비용이 선형으로 는다

대화 이력을 매 턴 통째로 replay한다. 롤링 요약도, 오래된 메시지 축약도 없다. "수백만 건이 쌓일 수 있다"는 데이터 모델 설명과 실제 프롬프트 조립 방식 사이에 아직 메워지지 않은 간극이 있다. 물론 설계상 이건 정책층 문제라 사용자(또는 에이전트 자신)가 만들어 넣으면 된다 — 그게 exo의 논리다.

한계 2
기본 저장 백엔드는 단일 프로세스 전용이다

BasicExoHarness는 프로세스 전역 뮤텍스로 쓰기를 직렬화한다. 문서도 "코디네이션된 로컬 런타임 전용"이라고 명시한다. Postgres/SQLite 백엔드는 의존성 선언만 있고 실제 구현이 없다. 여러 머신에서 같은 로그를 공유하는 시나리오는 아직 준비돼 있지 않다(README의 "Ongoing Work"에도 이 항목이 있다).

한계 3
모델 프로바이더 판정이 문자열 매칭에 의존한다

모델명이 claude로 시작하면 Anthropic, base_url에 openrouter.ai가 있으면 OpenRouter, 나머지는 OpenAI 호환 — 이런 하드코딩 분기다. 그래서 Bedrock의 us.anthropic.claude-... 같은 ID는 Anthropic 매칭에서 빠져 OpenAI 경로로 폴백한다(코드 주석에 명시돼 있다).

한계 4
"안전하게"의 범위를 정확히 이해해야 한다

exo의 안전성은 복구 가능성(recoverability)이지 행동 제한(containment)이 아니다. 에이전트는 샌드박스 안에서 임의 명령을 돌릴 수 있고, 어댑터를 통해 외부와 통신하며, 스케줄러로 반복 작업을 건다. 로그가 남고 되감을 수 있을 뿐, "하면 안 되는 일을 못 하게" 막는 장치는 아니다. 프로덕션 데이터나 실계정 자격증명 근처에서 돌릴 물건이 아직 아니다.

한계 5
두 달 된 프로젝트다

2026-05-20 생성, 열린 이슈 45개, README가 직접 "still in the early stages of development"라고 쓴다. 스스로 밝힌 미완 영역도 명확하다 — 자율적 자기정비, 장애 후 복구·이식성, 멀티 에이전트 오케스트레이션 정책, 그리고 GUI 컴퓨터 사용(윈도우 환경 조작) 미지원.

7하드웨어 / 시스템 요구사항

돌려 보려면 무엇이 필요한가
항목요구사항비고
OSmacOS 또는 LinuxWindows는 WSL2 경유 권장(공식 안내 없음)
필수 도구git, Docker없으면 setup.sh가 설치를 제안한다
런타임Node 22.15.0 / pnpm 10.26.2 / Rust 1.95mise가 자동으로 버전 고정 설치
Rust edition2024, rust-version 1.95상당히 최신. 구버전 툴체인으론 빌드 불가
API 키OpenAI 또는 OpenRouter필수. 없으면 에이전트가 아무것도 못 한다
디스크수 GBRust 빌드 산출물 + Docker 이미지 + 스냅샷 tar
GPU불필요모델은 전부 원격 API 호출
첫 빌드 시간수 분Rust 워크스페이스 전체 컴파일
비용 주의
장기 실행 에이전트는 토큰을 계속 쓴다

exo는 "오래 사는 에이전트"를 지향한다. 스케줄러가 매 시간 태스크를 돌리고, 그 결과가 대화를 깨우고, 그때마다 전체 대화 이력이 프롬프트로 재생된다(압축 없음). cost 크레이트와 REPL의 /cost 명령이 괜히 있는 게 아니다. 처음 며칠은 비용을 꼭 지켜보길 권한다.

8직접 해볼 수 있는 실습 과제

난이도순 6단계
실습 1난이도 ★☆☆☆☆

README의 공식 스모크 테스트 돌려 보기

설치 후 이 프롬프트를 그대로 넣어 본다 — "샌드박스에 python3와 curl을 apt-get으로 설치해라(sudo 불필요). 그 다음 매 분 BBC RSS에서 헤드라인을 가져와 이전에 출력하지 않은 새 헤드라인만 출력하는 태스크를 스케줄해라."

이 한 문장이 샌드박스 · 툴 실행 · 스케줄러 · 대화 깨우기를 전부 관통한다. 잘 돌면 설치가 정상이라는 뜻이다.

실습 2난이도 ★★☆☆☆

이벤트 로그를 사람 눈으로 직접 읽어 보기

exo conversation events로 로그를 뽑고, 저장 경로의 events/*.json 파일도 직접 열어 본다. 한 턴이 TurnStarted → Messages → ToolRequested → ToolResult → Messages → TurnEnded로 어떻게 쪼개지는지 확인하는 게 목표다. UUIDv7 파일명을 정렬하면 정말 시간순인지도 눈으로 검증해 보자.

실습 3난이도 ★★☆☆☆

프롬프트 한 줄 바꿔서 성격 바꾸기

examples/exo/prompts/me.md를 열어 정체성 문구를 고치고, guardian_actionbuild → 재시작을 거쳐 반영해 본다. 그 다음 .exo/exo-profile.md(git-ignore되는 로컬 프로필)와의 차이도 확인한다. "어느 파일이 어느 시점에 프롬프트에 들어가는가"를 체감하는 게 핵심.

실습 4난이도 ★★★☆☆

망가뜨렸다가 되감기

에이전트에게 일부러 위험한 작업을 시킨다(예: 샌드박스에서 중요한 패키지 제거). 그 전에 snapshot_sandbox로 스냅샷을 찍어 두고, 망가진 뒤 rewind_sandbox로 되돌린다. 그리고 확인한다 — 파일시스템은 돌아왔는데 대화 로그에는 "망가뜨렸던 기록"이 그대로 남아 있는가? §4-3의 "반쪽 롤백"을 몸으로 이해하는 실습이다.

실습 5난이도 ★★★★☆

나만의 툴 하나 추가하기

examples/exo/의 기존 *-tools.ts 하나를 템플릿 삼아 새 툴 파일을 만들고 harness.ts에 등록한다. 그 다음 같은 툴을 에이전트에게 만들라고 시켜서(install_agent_tool) 비교해 본다. 매 라운드 툴이 재등록되기 때문에 에이전트가 방금 만든 툴을 곧바로 쓸 수 있다는 걸 직접 확인할 수 있다.

실습 6난이도 ★★★★★

실행자를 Codex나 Claude Code로 갈아끼우기

examples/typescript/codex-harness.tscontainers/codex-sandbox/Dockerfile을 읽고, 같은 exoharness 위에서 다른 코딩 에이전트를 실행자로 돌려 본다. 그 다음 같은 대화를 실행자만 바꿔 이어서 진행해 본다. 이게 되면 §1의 "신뢰층/정책층 분리"가 말뿐이 아니라는 걸 확인한 셈이다.

9관련 기술 심화 학습 로드맵

exo를 제대로 소화하려면 어느 방향으로 파고들 것인가

1단계 — 이벤트 소싱(Event Sourcing)

exo의 뼈대는 사실 새로운 발명이 아니라 이벤트 소싱이라는 오래된 패턴이다. "현재 상태를 저장"하는 대신 "상태를 바꾼 사건들을 저장"하고, 현재 상태는 사건을 재생해서 얻는다. 금융 시스템이 오래 써 온 방식이다. Martin Fowler의 Event Sourcing 글과 CQRS 개념을 먼저 보면 exo의 get_events / materialize_prompt_history 짝이 왜 그렇게 생겼는지 바로 이해된다.

2단계 — 신뢰 경계(Trust Boundary)와 능력 기반 보안

"에이전트가 코드를 고칠 수 있는데 시크릿은 못 본다"는 구조는 보안 설계의 고전적 주제다. capability-based security, principle of least privilege, confused deputy problem을 찾아보길 권한다. 특히 guardian의 allowlist 방식이 왜 "임의 셸 실행 + 검증"보다 강한지가 이 개념들로 설명된다.

3단계 — 컨테이너 격리와 스냅샷

docker commit / docker save가 실제로 무엇을 하는지, OverlayFS 레이어가 어떻게 쌓이는지 알면 exo의 스냅샷 크기와 속도 특성이 예측 가능해진다. 더 나아가면 E2B·Daytona 같은 클라우드 개발 샌드박스 서비스들의 CRIU 기반 체크포인트 기술이 있다.

4단계 — 에이전트 하네스 비교

exo 저장소 자체에 docs/coding-agent-harnesses.md가 있다. 그리고 Codex CLI, Claude Code, Cursor의 실제 동작을 exo의 대체 하네스 코드로 역추적해 볼 수 있다 — 각각을 어떻게 붙였는지가 그 도구들의 인터페이스를 드러낸다. 남이 만든 어댑터 코드는 그 대상 시스템의 가장 정직한 문서다.

5단계 — RSI 담론 자체

Rich Sutton의 The Bitter Lesson(exo가 명시적으로 인용한다)을 읽고, 그 다음 "하네스를 사람이 짜는 것도 결국 hand-engineering 아닌가"라는 exo의 주장을 스스로 검증해 보자. 반론도 만만치 않다 — 모델이 자기 하네스를 고칠 만큼 신뢰할 수 있는가, 평가(eval) 없이 개선을 어떻게 측정하는가. 저자가 평가 플랫폼 창업자라는 사실이 여기서 다시 의미를 갖는다.

10핵심 키워드 사전

이 문서에 나온 용어 정리
용어
exoharness / executor
exo를 가르는 두 층. exoharness는 정체성·이벤트 로그·아티팩트·시크릿·샌드박스를 소유하는 신뢰되고 영속적인 층이고, executor는 프롬프트 조립·모델 호출·툴 디스패치·메모리 정책을 담당하는 휘발성이고 교체 가능한 층이다. exoharness는 의도적으로 LLM을 직접 호출하지 않는다.
용어
append-only 이벤트 로그
추가만 되고 수정·삭제는 안 되는 기록. exo에서 "에이전트 상태 = 이 로그의 버전"이라는 불변식이 성립하며, 되감기·분기·시크릿 보호가 전부 여기서 나온다.
용어
CAS (compare-and-swap)
"내가 알던 값이 아직 그대로면 바꾼다"는 원자적 연산. exo는 대화의 latest_event_id를 이 방식으로 검사해, 락을 놓고 기다리는 사이 히스토리가 앞서갔으면 "이 턴은 낡았다"고 거부한다.
용어
rewind / fork / snapshot
rewind=이전 상태로 되돌리기(로그는 안 지워짐), fork=기존 대화에서 새 대화 가지치기, snapshot=샌드박스 파일시스템 시점 저장. 스냅샷 ID가 로그에 기록되므로 로그를 되감으면 파일시스템도 따라온다.
용어
어댑터 (adapter)
ExoChat·IRC·Discord·Slack·WhatsApp·Signal 등 외부 채널과의 장기 연결을 담당하는 호스트 측 장기 실행 프로세스. 툴(단발 함수 호출)과 구분된다. stdin/stdout JSON으로 통신하고, 지수 백오프로 프로세스째 재기동한다.
용어
guardian
샌드박스 바깥에서 일어나야 하는 정비 작업의 통제 창구. 에이전트는 guardian_action 툴로 빌드·상태확인·로그조회·서비스 재시작만 요청할 수 있다(9개 액션 allowlist). 임의 셸 명령은 통과하지 못한다.
용어
drain 재시작
프로세스를 즉시 죽이는 대신 마커 파일을 남겨 두고, 러너가 그것을 스스로 발견해 진행 중 작업을 마친 뒤 종료하게 하는 방식. exo는 15초 유예 후에야 강제 종료한다.
용어
RLM (Recursive Language Model)
exo의 특수 하네스. 셸·샌드박스 없이 순수 JS REPL(boa 엔진) 안에서 긴 컨텍스트를 프로그래밍적으로 분해하고 서브 모델에게 재귀 질의를 던져 답을 조합한다. 툴은 repl_execute·subquery·subquery_variable 3개뿐.
용어
mise
언어 런타임 버전 관리 도구(asdf 후계 격). exo의 setup.sh가 이걸로 Node·pnpm·Rust·Python 버전을 고정해 깔아서, "내 컴퓨터에선 되는데" 문제를 줄인다.
용어
SSRF 가드
Server-Side Request Forgery 방어. exo의 web_fetch는 사설 IP·localhost 접근을 차단하고 DNS 리바인딩까지 재검증한다. 에이전트가 "내부 관리 페이지를 대신 열어 줘" 같은 유도에 넘어가는 걸 막는 장치.

11참고 링크

원문으로 확인하고 싶을 때
대상위치
저장소github.com/exoharness/exo
공식 문서exoharness.ai/docs
설계 철학docs/RSI.md — "A Systems View of Recursive Self Improvement"
개념 요약docs/EXO-BASICS.md
핵심 분리 설명website/docs-src/concepts/exoharness-and-executor.md
데이터 모델website/docs-src/concepts/data-model.md, crates/exoharness/src/types.rs
타임 트래블website/docs-src/concepts/time-travel.md
자기수정 지침examples/exo/SELF.md, examples/exo/docs/SELF-CONTROL.md
에이전트 정체성examples/exo/prompts/me.md (27줄)
프롬프트 조립examples/exo/harness.ts
커뮤니티Discord (README 상단 배지에 초대 링크)