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)
waggle은 "에이전트가 다른 에이전트에게 일을 넘길 때 쓰는, 아티팩트를 가리키는 짧은 참조 토큰 시스템"이다. 이름은 꿀벌의 흔들춤에서 왔다 — 벌 한 마리가 몸을 흔들어 "먹이가 저 방향 저 거리에 있다"는 좌표만 전달하면, 다른 벌들이 그 좌표로 직접 찾아간다. 꽃가루를 등에 지고 와서 하나하나 나눠 주는 게 아니다. waggle이 하려는 일이 정확히 이것이다 — 내용물(9,000토큰짜리 분석 문서)을 통째로 넘기는 대신, "그건 이 토큰이야"라는 좌표만 넘긴다.
핵심 동작은 딱 두 단어다. 아티팩트를 mint(발행)하면 b2uQyZUC 같은 8자짜리 토큰이 나온다. 이 토큰을 받은 쪽이 resolve(해석)하면, 자기 상황(모델 종류·하네스·다룰 수 있는 형식)에 맞게 재단된 버전을 돌려받는다. 그 사이에 "누가 읽었나 / 어느 부분을 읽었나 / 어느 버전인가 / 수락됐나 거부됐나"가 전부 추가만 되는 로그(append-only log)에 남는다.
mint·resolve 같은 걸 "도구(tool)"로 호출한다는 뜻이다. 흥미롭게도 waggle은 MCP SDK를 쓰지 않고 JSON-RPC 프로토콜을 직접 구현했다(프로토콜 버전 2024-11-05, 서버 이름 waggled). 덕분에 무거운 의존성 없이 네이티브 데몬에서도, WASM 엣지에서도 같은 코드가 돈다.긴 URL을 bit.ly/x8Kd로 줄이듯, waggle은 9,000토큰짜리 분석 문서를 b2uQyZUC 8글자로 줄인다. 하지만 단순 단축기가 아니다 — 이 송장을 누가 조회했는지, 어느 칸을 열어 봤는지, 물건이 바뀌진 않았는지(버전)가 전부 기록되는 택배 추적 시스템이 붙어 있다.
게다가 받는 사람이 대형 트럭이면 팔레트째, 오토바이 퀵이면 작은 봉투로 — 받는 쪽 사정에 맞춰 같은 물건을 다른 포장으로 내준다. 이 "가리키기 + 추적 + 상황별 재단"의 3종 세트가 그냥 파일 경로(/tmp/analysis.md)를 넘기는 것과의 결정적 차이다(§2).
2026년 TrendShift 상위권은 온통 에이전트·스킬·오케스트레이션이다. 그 흐름의 "다음 단계"를 정확히 겨눈 게 waggle이다. 에이전트를 여러 개 돌리는 법(오케스트레이션)은 많아졌는데, 정작 그 에이전트들이 서로 일을 넘기는 이음매는 여전히 "컨텍스트를 복붙하고 기도하기"에 머물러 있다는 것 — waggle은 바로 그 빈칸을 노린다. README가 인용하는 숫자가 문제의 크기를 말해 준다.
| 수치 | 의미 (README 인용) |
|---|---|
| ~15배 토큰 | 멀티에이전트 시스템은 단일 챗 대비 약 15배의 토큰을 태운다. "핸드오프마다 컨텍스트를 잃고 다시 채우기" 때문. |
| ~37% 실패 | 멀티에이전트 실패의 약 37%가 바로 이 이음매(seam), 즉 넘겨줄 때 발생한다. 성능이 아니라 "전달"에서 깨진다. |
| ★703 / 7일 | 저장소 생성 2026-07-08, 분석 시점 겨우 7일 차인데 별 703개. 매우 가파른 상승. |
waggle의 README에서 가장 인상적인 건 스스로 한계를 인정하는 태도다. "네 서브에이전트들이 같은 파일시스템을 공유하고 짧은 작업이면, 그냥 /tmp/analysis.md 경로를 넘겨라. waggle은 오버헤드일 뿐"이라고 못 박는다. 실제 자체 벤치에서도 경로 방식이 90%, waggle이 96%로 격차가 크지 않다고 공개한다. 이 정직함이 오히려 "그럼 언제 필요한가"를 또렷하게 만든다.
경로(path)도 30바이트짜리 참조다. 크기는 같다. 하지만 경로는 아래 셋을 전혀 답하지 못한다 — waggle이 존재하는 이유가 정확히 이 셋이다.
정리하면 waggle의 판매 포인트는 "파일을 대신 옮겨 준다"가 아니다. 감사(누가 뭘 읽었나)·버전 관리·도달 범위 — 이 세 가지 회계(accounting) 기능이 핵심이다. 짧은 로컬 작업엔 과하고, "여러 하네스에 걸친 장시간 멀티에이전트 파이프라인"에서 값을 한다.
waggle은 Claude Code나 Codex 같은 특정 하네스 안에 들어가지 않는다. 일부러 밖에 선다. Claude Code가 발행(mint)한 토큰을 Codex가 해석(resolve)하고, 그 영수증이 둘 다 살아남는 중립 기반(neutral substrate)이 되려는 것. 하네스 안에 넣는 순간 그 경계에서 죽어 버리기 때문이다. "특정 도구에 묶이지 않는 공용 참조 계층"이라는 포지셔닝이 트렌딩의 큰 이유다.
"~30바이트 토큰에 attribution(출처·부모·변형)을 담는다"고 오해하기 쉽다. 아니다. 실제 토큰은 8자짜리 무작위 base58 문자열(b2uQyZUC)이고, 출처·버전·변형 같은 실제 데이터는 전부 서버(로컬 데몬)의 AttributionManifest에 저장된다. 토큰은 그걸 찾아가는 열쇠일 뿐 — URL 단축 코드와 똑같은 구조다.
그럼 "30바이트"는 뭔가? 서브에이전트가 실제로 받는 핸드오프 한 줄, 즉 resolve b2uQyZUC via waggle for your working context의 길이를 가리키는 수사적 표현이다(코드 주석에도 "~30 bytes"로 명시). 메모리상 Token 구조체는 정확히 24바이트다. 문서의 마케팅 프레이밍과 코드의 진실을 구분하는 게 이 저장소를 정확히 이해하는 첫걸음이다.
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·저장소·엣지가 얇게 감싼다.
mint(spec, opts, entropy, now)처럼 "지금 시각(now)"과 "난수원(entropy)"조차 밖에서 주입한다. 이렇게 하면 같은 코드가 네이티브 데몬에서도, 브라우저급 WASM 엣지에서도 그대로 실행되고, 테스트에서 시각·난수를 고정해 완전히 결정론적으로(deterministic) 검증할 수 있다. waggle-core의 Cargo.toml 설명이 이를 못 박는다: "시계도, I/O도, 저장소도 없다 — 모든 부작용이 파라미터다. wasm32로 변경 없이 컴파일된다."waggle을 이해하는 가장 빠른 길은 토큰 하나가 태어나서 소비되기까지를 따라가는 것이다. 오케스트레이터가 아티팩트를 mint하면, 그 순간의 콘텐츠가 불변 스냅샷으로 굳고 명세(manifest)가 저장된다. 반환값의 첫 줄이 바로 서브에이전트에게 넘길 핸드오프 한 줄이다.
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자면 네임스페이스는 588 ≈ 1.3 × 1014(약 130조)라 충돌이 사실상 없고, 열거 불가능(non-enumerable) — 남의 토큰을 순서대로 찍어 맞힐 수 없다. 비공개(권한) 토큰은 16자(약 94비트)를 쓴다. 테스트가 size_of::<Token>() == 24를 못 박아, 맵에서 토큰 두 개가 캐시라인 하나에 들어갈 만큼 작다.
에이전트가 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)은 느슨하게 처리해 속도를 낸다.
resolve.rs / matcher.rsresolve의 묘미는 "같은 토큰이라도 받는 쪽에 따라 다른 버전을 준다"는 점이다. 소비자가 자기 상황(모델 종류·하네스·다룰 형식·태도)을 제시하면, 매처가 그중 가장 잘 맞는 변형(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)
}
model_family·harness·modalities·posture)에서 제약을 더 많이 건 변형이 승리하고, 동점이면 먼저 선언된 게 이긴다. 왜 이렇게 딱딱하게? "같은 상황이면 언제나 같은 버전을 준다(deterministic)"는 약속을 지키려면, 매칭이 예측 불가능하게 커스터마이즈되면 안 되기 때문이다. 표현력을 늘리고 싶으면 알고리즘을 바꾸는 게 아니라 차원을 추가해야 한다 — 눈에 보이고 버전 관리되는 변화로만.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이 여러 하네스에서 매끄럽게 작동하는 비결이다.
MCP 도구 스키마, clap CLI, map 네비게이션, COMMANDS.md 문서 — 이 넷이 전부 하나의 테이블(waggle-ops::OPERATIONS)에서 생성된다. 넷이 어긋나면(drift) 패리티 테스트가 빌드를 실패시킨다. "문서와 실제가 따로 노는" 고질병을 구조로 막은 것 — 이 저장소에서 가장 배울 만한 엔지니어링 습관 중 하나다.
① Sans-I/O: 코어는 부작용 0 → 네이티브·WASM 공용 + 결정론적 테스트. ② 결정론적 적응: 같은 상황 → 같은 버전(봉인된 매처). ③ 이벤트 소싱: 로그가 진실, 카운터는 캐시, 재생하면 똑같이 복원(CI가 이걸 property로 검증). ④ 단일 카탈로그: 도구·CLI·문서가 한 소스에서 나와 절대 어긋나지 않음. 네 가지 모두 "믿을 수 있는 참조 계층"이라는 목표에 직결된다.
폴더 구조 자체가 Sans-I/O 철학의 지도다. 가운데 waggle-core(부작용 없는 도메인)를 두고, 저장 백엔드를 crate별로 갈아끼울 수 있게 나눴으며, MCP·CLI·엣지가 그 위에 얇게 얹힌다.
| 경로 | 역할 |
|---|---|
| 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막 실연. 실습 시나리오로 그대로 쓸 수 있음. |
부작용(시계·난수·I/O)을 전부 인자로 밀어내면 두 마리 토끼가 잡힌다: 테스트에서 완전히 결정론적(시각 고정 → 재현 100%)이고, 같은 코드가 네이티브와 WASM 양쪽에서 돈다. Rust가 아니어도 어떤 언어에서든 적용할 수 있는 설계 원칙이다. "핵심 로직은 순수 함수로, 부작용은 가장자리로."
waggle은 카운터를 직접 갱신하지 않는다. 추가만 되는 이벤트 로그가 진실이고, 퍼널 카운트는 그걸 접어(fold) 만든 캐시일 뿐이다. 그리고 "로그를 처음부터 재생하면 캐시와 똑같아야 한다"를 CI의 property 테스트로 강제한다. 감사·복구·시간여행이 공짜로 따라오는, 금융·분산 시스템의 정석 패턴을 작은 코드로 볼 수 있다.
매처를 일부러 확장 불가능하게 봉인해서 "같은 상황 → 같은 결과"라는 약속을 지킨다. 커스터마이즈 훅을 열어 두면 편하지만, 그 순간 결정론이 깨진다. 유연성을 포기해서 신뢰를 사는 트레이드오프 — API를 설계할 때 곱씹어 볼 만한 결정이다.
Ed25519 분리 서명을 "불변 코어"에만 걸어서, 나중에 라벨을 바꿔도 서명이 깨지지 않게 했다(canonical_core_bytes()). 생애주기 변경은 낙관적 동시성 제어(CAS, expected_version)로, 중복 발행은 MintNonce 유니크 제약으로 막는다. "누가 위조했나 / 동시에 고치면 어쩌나"를 다루는 실전 기법 세트다.
모든 응답에 "다음에 할 수 있는 것"을 스키마째 실어 보내고(next), 도구·CLI·문서를 한 소스에서 생성해 어긋남을 원천 봉쇄한다. "문서는 낡지만 응답 봉투는 안 낡는다"는 통찰은, 사람이든 에이전트든 쓰는 API를 만들 때 그대로 통한다.
파일 수천 개짜리 폴더도 Bloom 필터 + 트라이그램 인덱스로 노드 몇 개로 압축해 mint하고, search가 명세만 보고 서브트리를 쳐낸다(prune). 큰 데이터를 "전부 로드하지 않고 필요한 조각만 여는" 인덱싱 기법을 실물로 볼 수 있다.
이 저장소가 Rust·MCP를 몰라도 남기는 교훈은 분명하다. 전달의 비용이 실행의 비용을 넘어설 때, 답은 "내용을 가리키고, 접근을 추적하고, 상황에 맞게 재단하는 참조 계층"이다. 그리고 그 계층은 Sans-I/O·이벤트 소싱·봉인된 결정론으로 믿을 수 있게 만들어야 한다.
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도, 상시 켜 둘 클라우드도 필요 없다 — 필요할 때 데몬이 뜨고, 여러 하네스가 그 데몬 하나를 공유한다.
먼저 설치. 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에서
저장소를 클론하고 just demo(또는 scripts/demo.sh)를 실행한다. 오케스트레이터가 아티팩트를 mint → 자식 에이전트가 또 mint → 작성자가 map/resolve/query로 조각만 읽기 → 퍼널 영수증 확인 → 오래된 버전으로 고치려다 CAS에 거부당한 뒤 supersede까지, 토큰 하나의 일생을 터미널에서 그대로 본다. 각 단계 출력의 next 필드를 유심히 볼 것.
아무 마크다운 파일을 mint해 토큰을 받고, 그 토큰을 서로 다른 ResolverContext(예: 큰 모델 vs 작은 모델)로 resolve해 다른 버전이 나오는지 확인한다. 그다음 record --stage run을 몇 번 날리고 funnel로 카운트가 오르는지 본다. "내용 없는 카운트"가 어떻게 감사 로그가 되는지를 체감하는 게 목표.
발행 시 여러 variant를 달고, 각각의 match_expr에 서로 다른 차원 제약(model_family / harness / modalities / posture)을 건다. 그런 다음 다양한 컨텍스트로 resolve해서 "가장 구체적인 변형이 이기고, 동점이면 선언 순서가 이긴다"는 규칙을 직접 재현한다. matcher.rs의 specificity()가 어떻게 계산되는지 소스로 확인.
waggle-core의 reconstruct()와 FunnelFold를 읽고, "이벤트 로그를 처음부터 접으면 현재 퍼널 카운트와 정확히 같아야 한다"는 replay-equivalence를 작은 테스트로 직접 써 본다. proptest로 무작위 이벤트 시퀀스를 생성해 이 불변식이 항상 성립하는지 확인하면, 이벤트 소싱의 핵심 보장을 손으로 익히게 된다.
npx wrangler deploy로 Worker를 올리고 waggle edge push로 기록+스냅샷을 복제한 뒤, waggle edge smoke로 엣지에서 mint→resolve→funnel 왕복을 검증한다. 원본 파일은 노트북에 남아 있는데 엣지에서 같은 토큰이 해석되는 걸 확인하면, "3반경 도달"이 무슨 뜻인지 완전히 이해된다. Miniflare 대조 테스트(edge == SQLite 바이트 동일)도 함께 읽어 볼 것.
| 주차 | 주제 · 목표 |
|---|---|
| 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으로 분리 서명·검증까지. "같은 코어를 엣지로 이주"의 실체 완성. |
| 용어 | 뜻 |
|---|---|
| mint / resolve | 아티팩트를 발행해 토큰을 얻는 것 / 토큰을 해석해 내 상황용 버전을 받는 것. waggle의 두 핵심 동사. |
| handoff | 한 에이전트가 다른 에이전트에게 작업·맥락을 넘기는 순간. waggle이 최적화하는 지점. |
| harness | 에이전트들을 실제로 돌리고 조율하는 실행 껍데기(Claude Code, Codex 등). |
| MCP | Model 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 Object | Cloudflare의 상태 있는 엣지 객체. waggle은 테넌트당 하나로 엣지 저장을 처리. |
공식
· 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) 소스 기준이며, 트렌딩 특성상 지표는 계속 바뀐다.