TrendShift 딥다이브 · 2026-07-15 · Daily #10

modiqo/waggle 딥다이브
— "붙여넣기 대신 30바이트 토큰"으로 배우는 에이전트 핸드오프의 참조 계층

waggle은 AI 에이전트끼리 작업을 넘길 때(handoff), 긴 컨텍스트를 통째로 복사·붙여넣기 하는 대신 아티팩트(문서·코드·분석 결과)를 가리키는 짧은 토큰 하나만 넘기게 해 주는 MCP 네이티브 "참조(reference) 계층"이다. 오케스트레이터가 아티팩트를 mint(발행)하면 8자짜리 토큰이 나오고, 넘겨받은 서브에이전트는 그 토큰을 resolve(해석)해 자기 모델·자기 하네스에 맞게 재단된 버전을 돌려받는다. 누가 읽었는지, 어느 버전인지, 어디서 도달 가능한지가 전부 추적된다. 기술적으로는 순수 Rust로 쓰인 Sans-I/O 코어 + 이벤트 소싱 + SQLite/Cloudflare 엣지 저장소로, "에이전트 하네스 시대의 참조 계층"을 표방한다. 이 문서는 README를 넘어 token.rs·resolve.rs·matcher.rs까지 소스를 뜯어 "여기서 무엇을 배울 수 있는가"를 정리한다. (저장소: modiqo/waggle · Rust 2021 · 15 crate · 27,473줄 · 테스트 146개 · MIT OR Apache-2.0 · v0.5.3 · ★703 · TrendShift Daily #10)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 지금 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 · 파일 구조 해부
  6. 학습 포인트
  7. 시스템 · 실행 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

이름부터 뜯어보기 — "waggle" = 꿀벌의 흔들춤(waggle dance), 즉 "먹이가 어디 있는지 좌표를 전달하는 신호"

waggle"에이전트가 다른 에이전트에게 일을 넘길 때 쓰는, 아티팩트를 가리키는 짧은 참조 토큰 시스템"이다. 이름은 꿀벌의 흔들춤에서 왔다 — 벌 한 마리가 몸을 흔들어 "먹이가 저 방향 저 거리에 있다"는 좌표만 전달하면, 다른 벌들이 그 좌표로 직접 찾아간다. 꽃가루를 등에 지고 와서 하나하나 나눠 주는 게 아니다. waggle이 하려는 일이 정확히 이것이다 — 내용물(9,000토큰짜리 분석 문서)을 통째로 넘기는 대신, "그건 이 토큰이야"라는 좌표만 넘긴다.

핵심 동작은 딱 두 단어다. 아티팩트를 mint(발행)하면 b2uQyZUC 같은 8자짜리 토큰이 나온다. 이 토큰을 받은 쪽이 resolve(해석)하면, 자기 상황(모델 종류·하네스·다룰 수 있는 형식)에 맞게 재단된 버전을 돌려받는다. 그 사이에 "누가 읽었나 / 어느 부분을 읽었나 / 어느 버전인가 / 수락됐나 거부됐나"가 전부 추가만 되는 로그(append-only log)에 남는다.

핵심 용어
handoff (핸드오프) · agent harness (에이전트 하네스)
핸드오프는 한 에이전트가 다른 에이전트/서브에이전트에게 작업과 맥락을 넘겨주는 순간이다. Claude Code가 서브에이전트를 4개 띄워 병렬로 일 시키거나, Codex가 다른 도구에 위임할 때가 전부 핸드오프다. 하네스는 그 에이전트들을 실제로 돌리고 조율하는 실행 껍데기(오케스트레이터)를 말한다. waggle은 특정 하네스 안에 들어가는 게 아니라, 하네스들 "사이"에 놓이는 중립 지대를 노린다 — Claude Code가 발행한 토큰을 Codex가 해석할 수 있어야 하므로.
핵심 용어
MCP (Model Context Protocol) · MCP-native
MCP는 AI 에이전트가 외부 도구·데이터에 연결되는 표준 규격이다. waggle이 MCP-native라는 건, 에이전트가 waggle을 하나의 MCP 서버로 붙여서 mint·resolve 같은 걸 "도구(tool)"로 호출한다는 뜻이다. 흥미롭게도 waggle은 MCP SDK를 쓰지 않고 JSON-RPC 프로토콜을 직접 구현했다(프로토콜 버전 2024-11-05, 서버 이름 waggled). 덕분에 무거운 의존성 없이 네이티브 데몬에서도, WASM 엣지에서도 같은 코드가 돈다.
한 문장 비유

"에이전트 세계의 URL 단축기 + 택배 송장 — 짐을 부치지 않고 송장 번호만 넘기면, 받는 사람이 자기에게 맞는 크기로 꺼내 보고, 배송 이력까지 남는다"

긴 URL을 bit.ly/x8Kd로 줄이듯, waggle은 9,000토큰짜리 분석 문서를 b2uQyZUC 8글자로 줄인다. 하지만 단순 단축기가 아니다 — 이 송장을 누가 조회했는지, 어느 칸을 열어 봤는지, 물건이 바뀌진 않았는지(버전)가 전부 기록되는 택배 추적 시스템이 붙어 있다.

게다가 받는 사람이 대형 트럭이면 팔레트째, 오토바이 퀵이면 작은 봉투로 — 받는 쪽 사정에 맞춰 같은 물건을 다른 포장으로 내준다. 이 "가리키기 + 추적 + 상황별 재단"의 3종 세트가 그냥 파일 경로(/tmp/analysis.md)를 넘기는 것과의 결정적 차이다(§2).

2왜 지금 주목받는가

TrendShift Daily #10 · 생성 7일 만에 ★703 — "멀티에이전트의 진짜 병목은 핸드오프였다"는 문제의식

2026년 TrendShift 상위권은 온통 에이전트·스킬·오케스트레이션이다. 그 흐름의 "다음 단계"를 정확히 겨눈 게 waggle이다. 에이전트를 여러 개 돌리는 법(오케스트레이션)은 많아졌는데, 정작 그 에이전트들이 서로 일을 넘기는 이음매는 여전히 "컨텍스트를 복붙하고 기도하기"에 머물러 있다는 것 — waggle은 바로 그 빈칸을 노린다. README가 인용하는 숫자가 문제의 크기를 말해 준다.

수치의미 (README 인용)
~15배 토큰멀티에이전트 시스템은 단일 챗 대비 약 15배의 토큰을 태운다. "핸드오프마다 컨텍스트를 잃고 다시 채우기" 때문.
~37% 실패멀티에이전트 실패의 약 37%가 바로 이 이음매(seam), 즉 넘겨줄 때 발생한다. 성능이 아니라 "전달"에서 깨진다.
★703 / 7일저장소 생성 2026-07-08, 분석 시점 겨우 7일 차인데 별 703개. 매우 가파른 상승.

주목 포인트 1 — "경쟁자는 다른 프로토콜이 아니라, 그냥 파일 경로다"라는 정직함

waggle의 README에서 가장 인상적인 건 스스로 한계를 인정하는 태도다. "네 서브에이전트들이 같은 파일시스템을 공유하고 짧은 작업이면, 그냥 /tmp/analysis.md 경로를 넘겨라. waggle은 오버헤드일 뿐"이라고 못 박는다. 실제 자체 벤치에서도 경로 방식이 90%, waggle이 96%로 격차가 크지 않다고 공개한다. 이 정직함이 오히려 "그럼 언제 필요한가"를 또렷하게 만든다.

주목 포인트 2 — 파일 경로가 답하지 못하는 세 가지 질문

경로(path)도 30바이트짜리 참조다. 크기는 같다. 하지만 경로는 아래 셋을 전혀 답하지 못한다 — waggle이 존재하는 이유가 정확히 이 셋이다.

┌────────────────────────────────────────────────────────────────┐ │ "/tmp/analysis.md" (경로) vs "b2uQyZUC" (waggle 토큰) │ │ │ │ Q1. 읽혔나? 어느 부분을? │ │ 경로: cat/grep 해도 흔적 0. 누가 봤는지 모름 │ │ waggle: 읽기가 토큰을 거치므로 "이 영역 읽음" 영수증 발급 │ │ │ │ Q2. 어느 버전인가? │ │ 경로: 가변 바이트를 가리킴 → 파일이 바뀌면 조용히 틀림 │ │ waggle: 발행 시점 콘텐츠 스냅샷(불변) + supersede/revoke │ │ │ │ Q3. 어디서 도달 가능한가? │ │ 경로: file:///... 는 다른 컨테이너/엣지에선 무의미 │ │ waggle: 같은 토큰이 로컬/머신간/엣지 3반경에서 불변 해석 │ └────────────────────────────────────────────────────────────────┘

정리하면 waggle의 판매 포인트는 "파일을 대신 옮겨 준다"가 아니다. 감사(누가 뭘 읽었나)·버전 관리·도달 범위 — 이 세 가지 회계(accounting) 기능이 핵심이다. 짧은 로컬 작업엔 과하고, "여러 하네스에 걸친 장시간 멀티에이전트 파이프라인"에서 값을 한다.

주목 포인트 3 — 하네스 "밖"에 있어야 한다는 설계 철학

waggle은 Claude Code나 Codex 같은 특정 하네스 안에 들어가지 않는다. 일부러 밖에 선다. Claude Code가 발행(mint)한 토큰을 Codex가 해석(resolve)하고, 그 영수증이 둘 다 살아남는 중립 기반(neutral substrate)이 되려는 것. 하네스 안에 넣는 순간 그 경계에서 죽어 버리기 때문이다. "특정 도구에 묶이지 않는 공용 참조 계층"이라는 포지셔닝이 트렌딩의 큰 이유다.

중요 · 오해 방지 — "30바이트 토큰"의 진실
토큰은 정보를 "인코딩"하지 않는다. 서버의 명세를 "가리킬" 뿐이다

"~30바이트 토큰에 attribution(출처·부모·변형)을 담는다"고 오해하기 쉽다. 아니다. 실제 토큰은 8자짜리 무작위 base58 문자열(b2uQyZUC)이고, 출처·버전·변형 같은 실제 데이터는 전부 서버(로컬 데몬)의 AttributionManifest에 저장된다. 토큰은 그걸 찾아가는 열쇠일 뿐 — URL 단축 코드와 똑같은 구조다.

그럼 "30바이트"는 뭔가? 서브에이전트가 실제로 받는 핸드오프 한 줄, 즉 resolve b2uQyZUC via waggle for your working context의 길이를 가리키는 수사적 표현이다(코드 주석에도 "~30 bytes"로 명시). 메모리상 Token 구조체는 정확히 24바이트다. 문서의 마케팅 프레이밍과 코드의 진실을 구분하는 게 이 저장소를 정확히 이해하는 첫걸음이다.

3기술 스택 전체 지도

첫 번째 반전 — 이건 TypeScript/Python이 아니라 순수 Rust 프로젝트다 (package.json 없음)

MCP 도구라고 하면 대부분 Node.js/Python을 떠올리지만, waggle은 전부 Rust다. 15개 crate로 나뉜 Cargo 워크스페이스(약 27,473줄)이고, 무거운 부분은 Cloudflare Workers(WASM)로 컴파일된다. 이 "Rust + Sans-I/O + WASM 엣지" 조합 자체가 배울 거리다.

레이어내용
언어 / 에디션Rust 2021, 최소 버전 1.85, 워크스페이스 버전 0.5.3. rust-toolchain.toml로 버전 고정.
패키지 매니저Cargo(워크스페이스, resolver = "2"). 배포는 cargo-dist가 5개 플랫폼 prebuilt 바이너리를 자동 생성.
핵심 의존성serde/serde_json(직렬화), rusqlite(SQLite 내장 bundled), sha2(콘텐츠 주소화), ed25519-dalek(서명), tokio(async 런타임), clap(CLI), regex, qrcodegen.
MCP 구현SDK 미사용 — MCP 와이어를 직접 구현. 줄 단위 구분(newline-delimited) JSON-RPC 2.0, 프로토콜 2024-11-05, 서버 이름 waggled.
저장 백엔드기본 SQLite(WAL 모드 + synchronous=FULL), ~/.waggle/waggle.db + 옆에 blob 저장. Store 트레잇 추상화라 교체 가능: memory / sqlite / fs-jsonl / cloudflare.
엣지 런타임Cloudflare Workers(WASM32), 테넌트당 Durable Object 1개, D1/R2 사용. wrangler로 배포.
feature-gated기본 꺼짐: code-lens(tree-sitter로 Rust/Python/TS/Go 심볼 추출) · doc-extract(순수 Rust PDF 텍스트 추출). 엣지 WASM 빌드에선 제외돼 가볍게 유지.
검증 도구proptest(속성 기반 테스트) · criterion(벤치) · loom(동시성 모델 체킹) · Miniflare(엣지 대조 테스트). 테스트 146개.

계층 구조 — "순수 도메인 코어"를 중심으로 껍질이 감싼다

waggle 스택의 진짜 핵심은 언어가 아니라 배치 방식이다. 시간·난수·저장 같은 "부작용"을 전부 바깥으로 밀어낸 순수 도메인 코어(waggle-core)를 가운데 두고, 그 바깥을 MCP·CLI·저장소·엣지가 얇게 감싼다.

바깥 껍질 (부작용 담당: I/O·시계·난수·네트워크) ┌─────────────────────────────────────────────────────────┐ │ waggle-cli waggle-mcp edge-worker(WASM) │ │ (clap 명령) (JSON-RPC 서버) (Cloudflare Workers) │ │ └──────────────┬───────────────────┘ │ │ waggle-store (트레잇) │ │ memory / sqlite / fs-jsonl / cloudflare │ │ │ │ │ ┌──────────────────▼──────────────────┐ │ │ │ waggle-core (Sans-I/O) │ ← 순수함수만 │ │ │ mint() resolve() matcher() event() │ 시계·난수도 │ │ │ 토큰·명세·이벤트·재구성·신뢰·서명 │ 파라미터로 │ │ └──────────────────────────────────────┘ │ │ waggle-ops (단일 명령 카탈로그) → MCP스키마·CLI·문서 동시생성 │ └─────────────────────────────────────────────────────────┘
핵심 용어 · 이 저장소의 심장
Sans-I/O (산스-아이오) 아키텍처
"I/O 없는" 설계란, 핵심 로직 코드에서 파일 읽기·시계·난수·네트워크를 일절 직접 하지 않고 전부 함수 인자로 받는 방식이다. 예컨대 mint(spec, opts, entropy, now)처럼 "지금 시각(now)"과 "난수원(entropy)"조차 밖에서 주입한다. 이렇게 하면 같은 코드가 네이티브 데몬에서도, 브라우저급 WASM 엣지에서도 그대로 실행되고, 테스트에서 시각·난수를 고정해 완전히 결정론적으로(deterministic) 검증할 수 있다. waggle-coreCargo.toml 설명이 이를 못 박는다: "시계도, I/O도, 저장소도 없다 — 모든 부작용이 파라미터다. wasm32로 변경 없이 컴파일된다."

4아키텍처 심화 분석

발행(mint) → 핸드오프 → 해석(resolve) → 심문(read/search) → 기록(record)의 생애주기, 그리고 그걸 떠받치는 코드

4-1. 전체 데이터 흐름 — 토큰 하나의 일생

waggle을 이해하는 가장 빠른 길은 토큰 하나가 태어나서 소비되기까지를 따라가는 것이다. 오케스트레이터가 아티팩트를 mint하면, 그 순간의 콘텐츠가 불변 스냅샷으로 굳고 명세(manifest)가 저장된다. 반환값의 첫 줄이 바로 서브에이전트에게 넘길 핸드오프 한 줄이다.

[MINT · 발행] MintSpec(target, sharer, channel, +변형/부모/콘텐츠/계약…) └─> mint(spec, opts, entropy, now) ← 순수함수 └─> AttributionManifest { token, 불변코어, variants, version=1 } ├─ (호스트) 서명키 있으면 Ed25519 서명 └─ store.append(Mint{manifest}) ← 내구성 쓰기(WAL 커밋) 반환: token + "resolve <T> via waggle for your working context" [HANDOFF · 넘기기] 오케스트레이터 → 서브에이전트 프롬프트에 그 "한 줄"만. 아티팩트 본문(9,000토큰)은 안 감. [RESOLVE · 해석] token + ResolverContext(model_family, harness, modalities, posture) + now └─> store.manifest(token) ← 조회 └─> resolve(manifest, ctx, now) ← 순수·전체·결정론적 ├─ disposition(now): Active / Expired / Superseded / Revoked └─ select_variant(...) ← 봉인된(sealed) 매처가 "그 소비자용" 버전 선택 반환: Resolution { disposition, variant(projection), revalidate_after } [INTERROGATE · 심문] read / search / query — 예산(byte budget) 안에서 필요한 조각만 [RECORD · 기록] record --stage run → 이벤트(내용 없는 카운트) append [FUNNEL · 집계] impression→resolve→run→repeat 단계별 카운트 fold [REACH · 도달] 로컬 데몬 → 데몬 간 federation → 엣지(로그 replay로 이주)
핵심 용어
AttributionManifest (귀속 명세) · content-addressed snapshot
토큰이 실제로 가리키는 서버 측 데이터 뭉치다. 세 구획으로 나뉜다: ① 불변 코어(발행 시 고정, 서명 대상 — 대상 URL·발행자·채널·발행시각·부모 계보·콘텐츠 스냅샷·변형 목록), ② 변형(variants), ③ 가변 버전 구획(캠페인·라벨은 나중에 바뀔 수 있고, revoke/supersede/expire 같은 생애주기 변화는 버전을 올린다). 콘텐츠가 content-addressed(내용을 sha2 해시로 주소화)라, 원본 파일이 나중에 바뀌어도 토큰이 가리키는 스냅샷은 그대로다 — "어느 버전을 봤나"가 무너지지 않는다.

4-2. 토큰 포맷 — token.rs 실제 코드

토큰은 비트를 채워 넣은 인코딩 blob이 아니라 무작위로 뽑은 이름표다. 비트코인이 쓰는 base58 알파벳(헷갈리는 0 O I l 제외)에서 거부 표집(rejection sampling)으로 뽑아, 모든 글자가 똑같은 확률로 나오게 한다(편향 제거). 메모리에선 힙을 전혀 안 쓰는 24바이트 고정 구조체다.

// crates/waggle-core/src/token.rs
// 비트코인 base58: 58글자, 0·O·I·l 없음 (소리내 읽고 타이핑하니까)
pub const TOKEN_ALPHABET: &[u8; 58] =
    b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
// modulo 편향 없이 쓸 수 있는 최대 바이트값: 232 = 58 * 4
const REJECTION_BOUND: u8 = 232;

/// waggle 토큰: 인라인, Copy, 총 24바이트, 힙 0
pub struct Token { len: u8, buf: [u8; 23] }

pub fn generate(len: usize, entropy: &mut impl Entropy)
    -> Result<Self, TokenError> {
    // 232 이상 바이트는 버림 → 모든 심볼 등확률 (편향 0)
    for &b in &pool {
        if b < REJECTION_BOUND {
            buf[filled] = TOKEN_ALPHABET[(b % 58) as usize];
            filled += 1;
            if filled == len { break; }
        }
    }
}

기본 길이 8자면 네임스페이스는 5881.3 × 1014(약 130조)라 충돌이 사실상 없고, 열거 불가능(non-enumerable) — 남의 토큰을 순서대로 찍어 맞힐 수 없다. 비공개(권한) 토큰은 16자(약 94비트)를 쓴다. 테스트가 size_of::<Token>() == 24를 못 박아, 맵에서 토큰 두 개가 캐시라인 하나에 들어갈 만큼 작다.

4-3. MCP 도구 표면 — 에이전트가 호출하는 11개 동사

에이전트가 waggle에 대고 쓰는 도구는 정확히 11개다. 전부 "동사"로, 하나의 명령 카탈로그(waggle-ops)에서 자동 생성된다. 이 표가 waggle의 기능 전체라고 봐도 된다.

도구종류하는 일
mint내구성 쓰기아티팩트 → 귀속 토큰 발행. 응답 첫 줄이 넘길 핸드오프 라인.
resolve읽기토큰 → 내 상황에 맞게 재단된 버전. 신뢰 전에도 안전하게 조회.
record느슨한 쓰기단계 보고(run/repeat/assess/accepted/rejected…). 내용 없는 카운트, 추가만 됨.
mutate내구성 쓰기생애주기(revoke/supersede/expire, CAS 필요) + 라벨/캠페인. 부모 revoke = 자식 tombstone.
funnel읽기토큰의 단계별 카운트(노출→해석→실행→반복) + 결과(수락/거부/분쟁) + 계보 롤업.
read느슨한 쓰기콘텐츠를 외과적으로: 줄 범위 / 마크다운 섹션 / 코드 심볼 / JSON 포인터. 전체 안 줌(기본 4096바이트).
search느슨한 쓰기토큰 콘텐츠 안을 정규식 grep(줄번호+주변 맥락). 매치만 이동, 원본은 그대로.
query읽기명세/퍼널/계보를 경로로 슬라이스. 예산 넘으면 형태만+더 깊은 경로 안내.
find읽기이름으로 후보 검색. 이름은 절대 스스로 resolve 안 됨(추측 금지).
coverage읽기폴더/계약 영수증: 읽은 파일/전체, 안 읽은 파일을 이름으로 지목, 충족 판정.
map읽기오리엔테이션. 인자 없으면 전체 지도, 토큰 주면 현재+앞/뒤 경로(항상 live 계산).

CLI 전용 5개(init·serve·daemon·edge·identity)는 에이전트가 아니라 사람이 설정·운영할 때 쓴다. 도구가 읽기 / 느슨한 쓰기 / 내구성 쓰기로 분류된 게 눈에 띈다 — 내구성 쓰기(mint·mutate)만 반드시 디스크 커밋을 보장하고, 기록(record)은 느슨하게 처리해 속도를 낸다.

4-4. 해석 + 봉인된 매처 — resolve.rs / matcher.rs

resolve의 묘미는 "같은 토큰이라도 받는 쪽에 따라 다른 버전을 준다"는 점이다. 소비자가 자기 상황(모델 종류·하네스·다룰 형식·태도)을 제시하면, 매처가 그중 가장 잘 맞는 변형(variant)을 골라 준다. 먼저 폐기·취소 여부(disposition)를 보고, 살아 있으면 변형을 고른다.

// crates/waggle-core/src/resolve.rs
pub fn resolve<'m>(manifest: &'m AttributionManifest,
                  ctx: &ResolverContext, now: Timestamp) -> Resolution<'m> {
    let disposition = manifest.disposition(now);
    let variant = match disposition {
        Disposition::Revoked { .. } => None,          // 취소되면 아무것도 안 줌
        _ => select_variant(&manifest.variants, ctx), // 봉인된 매처
    };
    Resolution { disposition, variant, as_of: now,
                 revalidate_after: now.plus_ms(DEFAULT_REVALIDATE_MS) }
}
// crates/waggle-core/src/matcher.rs — "가장 구체적인" 변형이 이긴다
pub fn select_variant<'m>(variants: &'m [Variant], ctx: &ResolverContext)
    -> Option<Selected<'m>> {
    let mut best: Option<(u8, Selected)> = None;
    for (i, variant) in variants.iter().enumerate() {
        if !matches(&variant.match_expr, ctx) { continue; }
        let spec = variant.match_expr.specificity(); // 제약된 차원 0..=4
        let better = match &best {
            None => true,
            Some((best_spec, _)) => spec > *best_spec,  // 동점이면 선언 순서가 이김
        };
        if better { best = Some((spec, Selected { index: i as u8, variant })); }
    }
    best.map(|(_, s)| s)
}
핵심 용어 · 신뢰의 근거
sealed matcher (봉인된 매처) · deterministic adaptivity
매처가 봉인됐다는 건, 매칭 로직을 사용자가 갈아끼울 훅·플러그인·설정이 아예 없다는 뜻이다. 4개 차원(model_family·harness·modalities·posture)에서 제약을 더 많이 건 변형이 승리하고, 동점이면 먼저 선언된 게 이긴다. 왜 이렇게 딱딱하게? "같은 상황이면 언제나 같은 버전을 준다(deterministic)"는 약속을 지키려면, 매칭이 예측 불가능하게 커스터마이즈되면 안 되기 때문이다. 표현력을 늘리고 싶으면 알고리즘을 바꾸는 게 아니라 차원을 추가해야 한다 — 눈에 보이고 버전 관리되는 변화로만.

4-5. 응답 봉투(Envelope) — "코드 안에서 다음 수를 가르친다"

waggle의 모든 도구 응답은 Envelope { result, next, hint, stats } 형태로 감싸진다. 여기서 next가 특별하다 — 그냥 결과만 주는 게 아니라, "이제 이걸 호출하면 된다"는 실행 가능한 다음 호출을 스키마까지 맞춰 함께 준다. 에러가 나면 고치는 법을 hint로 짚어 준다.

// mint 성공 응답 (crates/waggle-mcp/src/handlers.rs)
Envelope::ok(
  json!({
    "token": token.as_str(),
    "handoff": "resolve b2uQyZUC via waggle for your working context",
    "variants": view.manifest.variants.len(),
  }),
  next,   // ← 다음에 부를 만한 호출 3개를 스키마째로 (in-band teaching)
)

이유는 명확하다 — 규칙 파일(CLAUDE.md)에 적힌 사용법은 낡지만, 응답 봉투는 낡을 수 없다. 에이전트가 매 응답마다 "지금 할 수 있는 다음 행동"을 정확한 형식으로 받으니, 별도 설명 없이도 올바르게 이어 쓴다. 이 "인-밴드 교육(in-band teaching)"이 waggle이 여러 하네스에서 매끄럽게 작동하는 비결이다.

4-6. 단일 명령 카탈로그 — 한 테이블에서 네 개가 태어난다

MCP 도구 스키마, clap CLI, map 네비게이션, COMMANDS.md 문서 — 이 넷이 전부 하나의 테이블(waggle-ops::OPERATIONS)에서 생성된다. 넷이 어긋나면(drift) 패리티 테스트가 빌드를 실패시킨다. "문서와 실제가 따로 노는" 고질병을 구조로 막은 것 — 이 저장소에서 가장 배울 만한 엔지니어링 습관 중 하나다.

한눈 정리 · 아키텍처가 지키는 4대 약속
Sans-I/O 코어 · 결정론적 적응 · 이벤트 소싱 · 단일 카탈로그

Sans-I/O: 코어는 부작용 0 → 네이티브·WASM 공용 + 결정론적 테스트. ② 결정론적 적응: 같은 상황 → 같은 버전(봉인된 매처). ③ 이벤트 소싱: 로그가 진실, 카운터는 캐시, 재생하면 똑같이 복원(CI가 이걸 property로 검증). ④ 단일 카탈로그: 도구·CLI·문서가 한 소스에서 나와 절대 어긋나지 않음. 네 가지 모두 "믿을 수 있는 참조 계층"이라는 목표에 직결된다.

5디렉토리 · 파일 구조 해부

15개 crate가 "순수 코어 → 저장소 → 표면(MCP/CLI) → 엣지"로 겹겹이, 관심사가 폴더로 분리된다

폴더 구조 자체가 Sans-I/O 철학의 지도다. 가운데 waggle-core(부작용 없는 도메인)를 두고, 저장 백엔드를 crate별로 갈아끼울 수 있게 나눴으며, MCP·CLI·엣지가 그 위에 얇게 얹힌다.

waggle/ ├── crates/ ★ 15개 Rust crate (워크스페이스 코어) │ ├── waggle-core/ ★★ Sans-I/O 도메인: token·manifest·mint· │ │ resolve·matcher·event·fold·reconstruct·서명 │ ├── waggle-ops/ ★ 단일 명령 카탈로그(OPERATIONS 테이블) │ ├── waggle-mcp/ ★ MCP 표면: JSON-RPC 와이어·핸들러·envelope· │ │ content(read/search)·query·lineage·map │ ├── waggle-cli/ `waggle` 바이너리: serve·daemon·edge·identity·init │ ├── waggle-store/ Store 트레잇 + 에러 + memory 구현 + 적합성 스위트 │ ├── waggle-store-sqlite/ SQLite 백엔드(WAL·blob·crash_matrix·loom 테스트) │ ├── waggle-store-fs-jsonl/ 파일시스템 JSONL 백엔드 │ ├── waggle-store-cloudflare/ D1/DO/R2 백엔드(엣지용) │ ├── waggle-lens-code/ tree-sitter 심볼 추출 (code-lens feature) │ ├── waggle-tree/ 디렉토리 통째 mint: bloom·trigram·검색 │ ├── waggle-social/ OpenGraph·QR·채널 — 소셜 언펄 │ ├── waggle-agent/ 에이전트 연동 │ ├── waggle-tmux/ `waggle-tmux` 바이너리: 멀티하네스 스위치보드 │ └── waggle/ umbrella re-export crate ├── edge-worker/ Cloudflare Worker(WASM): routes·hive(DurableObject) │ └── wrangler.toml ├── bench/ 벤치 하네스: driver·cost_model·tokenizer·determinism ├── spec/ waggle-spec.md + vectors/ (적합성 벡터) ├── docs/ design/(설계문서) + guide/(튜토리얼) + WHY.md ├── paper/ LaTeX 학술 논문(waggle.tex) ├── xtask/ cargo xtask: gen-docs, 적합성 벡터 생성 ├── scripts/demo.sh 전체 생애주기 5막 데모 (just demo) ├── COMMANDS.md 카탈로그에서 생성된 인벤토리 (직접 편집 금지) ├── Cargo.toml / Cargo.lock 워크스페이스 매니페스트 ├── dist-workspace.toml cargo-dist 배포 설정 ├── rust-toolchain.toml 1.85 고정 └── LICENSE-APACHE / LICENSE-MIT 듀얼 라이선스
경로역할
crates/waggle-core/심장. 부작용 0의 순수 도메인. 여기만 이해하면 waggle의 개념이 전부 잡힌다.
crates/waggle-ops/도구·CLI·문서가 태어나는 단일 진실원. drift를 패리티 테스트로 차단.
crates/waggle-mcp/에이전트가 붙는 창구. JSON-RPC를 직접 구현하고 응답을 Envelope로 감쌈.
waggle-store-*같은 트레잇의 4개 구현(memory/sqlite/fs-jsonl/cloudflare). 로컬은 SQLite, 엣지는 D1으로 자연스럽게 이주.
waggle-tmux/여러 하네스(Claude Code·Codex…)를 tmux로 나란히 띄워 보는 스위치보드 별도 바이너리.
spec/ · paper/적합성 벡터(구현이 스펙과 일치하는지 대조용) + 학술 논문까지 갖춘, 스펙 우선 프로젝트.
scripts/demo.sh발행→자식 발행→map/resolve/query→퍼널 영수증→오래된 CAS 거부 후 supersede의 5막 실연. 실습 시나리오로 그대로 쓸 수 있음.

6학습 포인트

이 저장소에서 "에이전트 도구"가 아니라 "믿을 수 있는 분산 시스템 만들기"로 가져갈 것들

배울 것 1 — Sans-I/O로 "테스트 가능 + 이식 가능"을 동시에 얻기

부작용(시계·난수·I/O)을 전부 인자로 밀어내면 두 마리 토끼가 잡힌다: 테스트에서 완전히 결정론적(시각 고정 → 재현 100%)이고, 같은 코드가 네이티브와 WASM 양쪽에서 돈다. Rust가 아니어도 어떤 언어에서든 적용할 수 있는 설계 원칙이다. "핵심 로직은 순수 함수로, 부작용은 가장자리로."

배울 것 2 — 이벤트 소싱 + 재구성 보장

waggle은 카운터를 직접 갱신하지 않는다. 추가만 되는 이벤트 로그가 진실이고, 퍼널 카운트는 그걸 접어(fold) 만든 캐시일 뿐이다. 그리고 "로그를 처음부터 재생하면 캐시와 똑같아야 한다"를 CI의 property 테스트로 강제한다. 감사·복구·시간여행이 공짜로 따라오는, 금융·분산 시스템의 정석 패턴을 작은 코드로 볼 수 있다.

배울 것 3 — "봉인된 알고리즘"으로 신뢰를 보장하기

매처를 일부러 확장 불가능하게 봉인해서 "같은 상황 → 같은 결과"라는 약속을 지킨다. 커스터마이즈 훅을 열어 두면 편하지만, 그 순간 결정론이 깨진다. 유연성을 포기해서 신뢰를 사는 트레이드오프 — API를 설계할 때 곱씹어 볼 만한 결정이다.

배울 것 4 — 서명과 CAS로 무결성·동시성 다루기

Ed25519 분리 서명을 "불변 코어"에만 걸어서, 나중에 라벨을 바꿔도 서명이 깨지지 않게 했다(canonical_core_bytes()). 생애주기 변경은 낙관적 동시성 제어(CAS, expected_version)로, 중복 발행은 MintNonce 유니크 제약으로 막는다. "누가 위조했나 / 동시에 고치면 어쩌나"를 다루는 실전 기법 세트다.

배울 것 5 — 인-밴드 교육(Envelope)과 단일 카탈로그

모든 응답에 "다음에 할 수 있는 것"을 스키마째 실어 보내고(next), 도구·CLI·문서를 한 소스에서 생성해 어긋남을 원천 봉쇄한다. "문서는 낡지만 응답 봉투는 안 낡는다"는 통찰은, 사람이든 에이전트든 쓰는 API를 만들 때 그대로 통한다.

배울 것 6 — 트리 규모 참조 (디렉토리 통째 mint)

파일 수천 개짜리 폴더도 Bloom 필터 + 트라이그램 인덱스로 노드 몇 개로 압축해 mint하고, search가 명세만 보고 서브트리를 쳐낸다(prune). 큰 데이터를 "전부 로드하지 않고 필요한 조각만 여는" 인덱싱 기법을 실물로 볼 수 있다.

한 줄 정리 · 재사용 가능한 교훈
"waggle은 벌의 흔들춤을 소프트웨어로 옮긴 것 — 짐이 아니라 좌표를 넘긴다"

이 저장소가 Rust·MCP를 몰라도 남기는 교훈은 분명하다. 전달의 비용이 실행의 비용을 넘어설 때, 답은 "내용을 가리키고, 접근을 추적하고, 상황에 맞게 재단하는 참조 계층"이다. 그리고 그 계층은 Sans-I/O·이벤트 소싱·봉인된 결정론으로 믿을 수 있게 만들어야 한다.

7시스템 · 실행 요구사항

노트북 한 대면 충분 — 무거운 GPU·클라우드가 필요 없는, 가벼운 로컬 우선 도구

waggle은 모델을 돌리는 도구가 아니라 메타데이터를 다루는 도구라, 요구사항이 가볍다. Rust로 컴파일된 단일 바이너리 + SQLite 파일 하나가 전부다. 성능 수치가 이를 잘 보여 준다.

항목요구/수치
런타임단일 네이티브 바이너리(waggle). 별도 서버·DB 설치 불필요 — SQLite가 crate에 bundled.
저장 공간~/.waggle/waggle.db(WAL) + blob. 메타데이터라 매우 작음.
빌드 요구소스 빌드 시 Rust 1.85+. 하지만 prebuilt 바이너리를 쓰면 Rust 툴체인 불필요.
플랫폼macOS(arm64/x86_64), Linux(arm64/x86_64), Windows(x86_64) 5종 prebuilt.
해석 속도캐시 적중 resolve 39 나노초, 내구성 append 39µs, 100만 이벤트 퍼널 접기 334µs.
엣지 속도Cloudflare Worker→Durable Object 전 경로 resolve p50 1.2 밀리초.
엣지(선택)Cloudflare 계정 + wrangler. 안 써도 로컬만으로 완전 동작.
한눈 요약 — "무겁지 않다"

흔한 오해와 달리 waggle은 추론(inference)을 하지 않는다. 토큰·명세·이벤트라는 가벼운 메타데이터만 다루므로, 오래된 노트북에서도 나노초~마이크로초 단위로 응답한다. GPU도, 상시 켜 둘 클라우드도 필요 없다 — 필요할 때 데몬이 뜨고, 여러 하네스가 그 데몬 하나를 공유한다.

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

설치는 한 줄 — 이후 난이도별로 "발행→해석→추적→변형→엣지"까지 올라간다

먼저 설치. Rust 툴체인 없이 prebuilt로도, crates.io로도 된다. 붙일 하네스에 MCP 서버로 등록만 하면 끝이다.

# 설치 (셋 중 하나)
cargo install waggle-cli
# 또는 prebuilt (Rust 불필요):
curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/modiqo/waggle/releases/latest/download/waggle-cli-installer.sh | sh
# 또는 Homebrew:
brew install modiqo/homebrew-tap/waggle-cli

# Claude Code에 MCP 서버로 등록
claude mcp add waggle -- waggle serve --stdio
waggle init      # 에이전트가 일할 각 repo에서
실습 1 · 난이도 ★☆☆☆☆

demo.sh로 생애주기 5막 눈으로 보기 — 개념 감 잡기

저장소를 클론하고 just demo(또는 scripts/demo.sh)를 실행한다. 오케스트레이터가 아티팩트를 mint → 자식 에이전트가 또 mint → 작성자가 map/resolve/query로 조각만 읽기 → 퍼널 영수증 확인 → 오래된 버전으로 고치려다 CAS에 거부당한 뒤 supersede까지, 토큰 하나의 일생을 터미널에서 그대로 본다. 각 단계 출력의 next 필드를 유심히 볼 것.

실습 2 · 난이도 ★★☆☆☆

내 손으로 mint → resolve → funnel — 핵심 3동사 체득

아무 마크다운 파일을 mint해 토큰을 받고, 그 토큰을 서로 다른 ResolverContext(예: 큰 모델 vs 작은 모델)로 resolve다른 버전이 나오는지 확인한다. 그다음 record --stage run을 몇 번 날리고 funnel로 카운트가 오르는지 본다. "내용 없는 카운트"가 어떻게 감사 로그가 되는지를 체감하는 게 목표.

실습 3 · 난이도 ★★★☆☆

변형(variant) 규칙 직접 짜 보기 — 봉인된 매처 이해

발행 시 여러 variant를 달고, 각각의 match_expr에 서로 다른 차원 제약(model_family / harness / modalities / posture)을 건다. 그런 다음 다양한 컨텍스트로 resolve해서 "가장 구체적인 변형이 이기고, 동점이면 선언 순서가 이긴다"는 규칙을 직접 재현한다. matcher.rsspecificity()가 어떻게 계산되는지 소스로 확인.

실습 4 · 난이도 ★★★★☆

이벤트 로그 재생 = 카운터 property 테스트 재현 — 이벤트 소싱

waggle-corereconstruct()FunnelFold를 읽고, "이벤트 로그를 처음부터 접으면 현재 퍼널 카운트와 정확히 같아야 한다"는 replay-equivalence를 작은 테스트로 직접 써 본다. proptest로 무작위 이벤트 시퀀스를 생성해 이 불변식이 항상 성립하는지 확인하면, 이벤트 소싱의 핵심 보장을 손으로 익히게 된다.

실습 5 · 난이도 ★★★★★

Cloudflare 엣지에 push하고 같은 토큰 해석하기 — 도달 범위

npx wrangler deploy로 Worker를 올리고 waggle edge push로 기록+스냅샷을 복제한 뒤, waggle edge smoke로 엣지에서 mint→resolve→funnel 왕복을 검증한다. 원본 파일은 노트북에 남아 있는데 엣지에서 같은 토큰이 해석되는 걸 확인하면, "3반경 도달"이 무슨 뜻인지 완전히 이해된다. Miniflare 대조 테스트(edge == SQLite 바이트 동일)도 함께 읽어 볼 것.

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

waggle을 정복하며 곁들여 익히면 좋은 것들 — 주차별 5단계
주차주제 · 목표
1주차Rust 기초 + Cargo 워크스페이스. 소유권·트레잇·Result/Option·라이프타임('m)을 익히고, 여러 crate로 나뉜 워크스페이스를 빌드·테스트해 본다. 목표: waggle-core 코드를 읽을 수 있게.
2주차MCP(Model Context Protocol) + JSON-RPC. MCP 규격과 tools/resources 개념, 줄 단위 JSON-RPC 2.0 와이어를 익힌다. waggle-mcp가 SDK 없이 이걸 어떻게 직접 짜는지 대조.
3주차이벤트 소싱 + CQRS. "로그가 진실, 뷰는 파생"이라는 패턴, fold/reconstruct, 낙관적 동시성(CAS)을 개념부터 학습. waggle의 FunnelFold·reconstruct()가 교보재.
4주차Sans-I/O 설계 + 결정론적 테스트. 부작용을 가장자리로 미는 설계, proptest 속성 테스트, loom 동시성 모델 체킹을 실습. 시각·난수 주입으로 재현 가능한 테스트 짜기.
5주차Cloudflare Workers + WASM 엣지 + Ed25519. worker crate로 WASM 배포, Durable Object로 상태 관리, D1/R2 사용법. ed25519-dalek으로 분리 서명·검증까지. "같은 코어를 엣지로 이주"의 실체 완성.

10핵심 키워드 사전

이 문서와 저장소에서 반복되는 용어 빠른 참조
용어
mint / resolve아티팩트를 발행해 토큰을 얻는 것 / 토큰을 해석해 내 상황용 버전을 받는 것. waggle의 두 핵심 동사.
handoff한 에이전트가 다른 에이전트에게 작업·맥락을 넘기는 순간. waggle이 최적화하는 지점.
harness에이전트들을 실제로 돌리고 조율하는 실행 껍데기(Claude Code, Codex 등).
MCPModel Context Protocol. 에이전트-도구 연결 표준. waggle은 MCP 서버로 붙는다.
AttributionManifest토큰이 가리키는 서버 측 명세. 출처·부모·콘텐츠 스냅샷·변형·버전을 담음.
variant / projection같은 아티팩트의 상황별 버전. resolve가 소비자 컨텍스트에 맞는 하나를 골라 준다.
Sans-I/O시계·난수·I/O를 코드에서 직접 안 하고 인자로 받는 설계. 이식성+결정론적 테스트.
event sourcing추가만 되는 이벤트 로그를 진실로 두고, 카운터/뷰는 그걸 접어(fold) 만든 캐시로 보는 패턴.
sealed matcher확장 훅이 없는 봉인된 매칭기. "같은 상황 → 같은 결과" 결정론을 보장.
CAS (expected_version)낙관적 동시성 제어. 예상 버전이 안 맞으면 쓰기를 거부해 덮어쓰기 사고를 막음.
base58 (Bitcoin)토큰 문자셋. 헷갈리는 0 O I l을 뺀 58글자. 8자면 약 130조 네임스페이스.
Envelope / next모든 도구 응답을 감싸는 봉투. next가 "다음에 할 수 있는 호출"을 스키마째 제공(인-밴드 교육).
Durable ObjectCloudflare의 상태 있는 엣지 객체. waggle은 테넌트당 하나로 엣지 저장을 처리.

11참고 링크

공식 소스 우선 — 버전·수치는 클론한 소스(v0.5.3) 기준

공식
· GitHub 저장소: github.com/modiqo/waggle (Rust · MIT OR Apache-2.0 · ★703)
· 홈페이지: waggle.sh
· TrendShift 카드: trendshift.io/repositories/82635 (Daily #10)
· crates.io: crates.io/crates/waggle-cli

저장소 안에서 꼭 읽을 것
· docs/WHY.md — 왜 파일 경로로는 부족한가(문제 정의)
· spec/waggle-spec.md + spec/vectors/ — 규격과 적합성 벡터
· scripts/demo.sh — 생애주기 5막 실연
· crates/waggle-core/src/ — token · resolve · matcher (개념의 심장)
· benches/PERF.md — 성능 수치 근거

곁들여 학습
· Model Context Protocol 규격 (MCP tools/resources)
· 이벤트 소싱 · CQRS 패턴 (Martin Fowler 등)
· Sans-I/O 설계 철학 (Python sans-io 운동 → 언어 불문 적용)
· Cloudflare Workers · Durable Objects · D1 문서
· Ed25519 서명 · content-addressed storage(내용 주소화) 개념

참고 · 문서와 실제의 차이 (소스 정독으로 발견)
수치·버전은 "클론한 최신 소스"가 우선

① README는 "v0.4.0 on crates.io"라 적혀 있지만 실제 릴리스는 v0.5.3까지 나왔다 — 문서가 살짝 뒤처짐. ② "~30바이트 토큰"은 마케팅 프레이밍이고, 코드상 토큰은 8자 base58 문자열(메모리 24바이트 구조체)이 서버 명세를 가리킬 뿐 정보를 인코딩하지 않는다(§2 참조). ③ 이 문서의 수치는 분석 시점 HEAD(2026-07-14) 소스 기준이며, 트렌딩 특성상 지표는 계속 바뀐다.