TRENDSHIFT 딥다이브 · 월간 #16 · 2026-06-18

unicity-astrid/sdk-js 딥다이브
— Rust 말고 TypeScript로 AI 에이전트 OS의 '캡슐'을 짜는 SDK

unicity-astrid/sdk-js"Astrid OS(AI 에이전트를 위한 운영체제) 위에서 도는 캡슐(capsule)을, Rust 대신 JavaScript/TypeScript로 작성하게 해 주는" 공식 SDK다. 원래 캡슐은 Rust(sdk-rust)로 짜야 했는데, 이 저장소는 그 JS/TS 동반(companion)이다. 같은 WIT 계약, 같은 wasip2 컴포넌트 출력, 같은 .capsule 묶음 포맷을 내놓기 때문에 — 커널 입장에선 "이 캡슐을 Rust로 짰는지 JS로 짰는지" 구분조차 못 한다. (저장소: unicity-astrid/sdk-js · 언어 TypeScript 5.6+ · 모듈 순수 ESM · 빌드 tsc + esbuild + ComponentizeJS → wasm32-wasip2 컴포넌트 → .capsule · 패키지 @unicity-astrid/sdk · @unicity-astrid/build · 라이선스 MIT OR Apache-2.0 · 제작 Joshua J. Bouw & Unicity Labs · 상태 Alpha · 최신 커밋 2026-06-07)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어/시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

한 문장으로 이 프로젝트가 뭘 하는지부터.

sdk-js = "AI 에이전트 OS의 확장 모듈(캡슐)을, 웹 개발자에게 익숙한 TypeScript로 짜게 해 주는 키트." 배경부터 보자. Astrid OS는 "AI 에이전트를 위한 운영체제"다. 그 안에서 도는 모든 프로그램을 캡슐(capsule)이라 부르는데, 캡슐은 샌드박스(WebAssembly) 안에 갇혀, 허락받은 권한과 정해진 자원 한도 안에서만 도는 격리된 프로세스다. 원래 이 캡슐은 Rust로만 짤 수 있었다(sdk-rust). 이 저장소 sdk-js는 바로 그 JavaScript/TypeScript 판 동반 SDK다.

README가 스스로를 이렇게 정의한다 — "같은 WIT 계약, 같은 wasip2 컴포넌트 출력, 같은 .capsule 묶음 포맷 — 그래서 커널은 이 바이너리를 어떤 언어로 만들었는지 알 수 없다." 그리고 사용 감각을 이렇게 비교한다: "Rust SDK가 std를 상대로 코딩하는 느낌이라면, 이 SDK는 node:fs/promises · WHATWG · Node의 EventEmitter를 상대로 코딩하는 느낌이다. 호스트 ABI는 똑같고, 관용구만 번역했다."

🔌 한 줄 비유

"USB-C 포트(커널)" 하나에, Rust로 만든 기기도 JS로 만든 기기도 똑같이 꽂힌다

Astrid 커널은 표준 USB-C 포트를 하나 정의해 뒀다. 이 포트의 모양(전압·핀 배치)이 바로 WIT 계약(host ABI)이다. 캡슐은 그 포트에 꽂는 기기(USB-C 플러그가 달린 장치)다. 기기 내부를 Rust로 만들었든 JS로 만들었든, 플러그 규격(wasip2 컴포넌트 + WIT 계약)만 맞으면 커널은 차이를 못 느끼고 똑같이 받아들인다.

sdk-js"USB-C 기기를 JS로 만들 수 있게 해 주는 공구 세트"다. 지금까지 USB-C 기기는 Rust 공장에서만 찍어냈는데, 이제 JS 개발자도 같은 규격의 기기를 만들 수 있게 됐다. 포트(커널)는 손댈 필요가 없다.

용어
캡슐 (capsule)
Astrid OS의 "프로세스" 단위. Capsule.toml 매니페스트로 기술되는 격리된 WASM 프로그램이다. 각 캡슐은 ① WebAssembly 샌드박스 안에 갇히고, ② 매니페스트에 적어 명시적으로 허락받은 권한(capability)만 쓰며(파일 읽기·네트워크 등), ③ 정해진 자원 예산 안에서 돌고, ④ 모든 행동이 변조 불가능한 감사 기록(audit chain)에 남는다. "에이전트에게 새 능력(도구)을 붙여 주는 플러그인"이라고 보면 쉽다.
용어
WASM · wasip2 컴포넌트 모델
WebAssembly(WASM)는 브라우저·서버 어디서나 도는 고속 샌드박스 실행 포맷이다(언어 중립). 컴포넌트 모델(Component Model)은 그 위에 "타입이 있는 부품(컴포넌트)"을 정의하는 차세대 표준이고, wasip2(WASI Preview 2)는 그 부품이 바깥세상(파일·시계 등)과 대화하는 인터페이스 규약이다. 캡슐은 결국 wasm32-wasip2 타깃의 컴포넌트 하나로 컴파일된다.
용어
WIT (WebAssembly Interface Types)
컴포넌트끼리 주고받을 함수·타입의 계약을 적는 인터페이스 정의 언어(IDL). "커널이 캡슐에게 제공하는 함수(host import)는 무엇이고, 캡슐이 커널에게 노출하는 함수(export)는 무엇인지"를 .wit 파일에 적어 둔다. Astrid에선 이 WIT가 Rust SDK와 JS SDK가 공유하는 단 하나의 진실원이라, 두 언어가 글자 그대로 같은 타입을 본다(§4·§5에서 상술).

정리하면 이 SDK의 핵심 약속은 셋이다. ① 웹 개발자도 에이전트 OS를 확장할 수 있다 — Rust를 몰라도 캡슐을 짠다. ② 결과물은 Rust 캡슐과 100% 호환되는 .capsule 파일이라, 커널·다른 캡슐과 그대로 맞물린다. ③ 코드는 데코레이터 몇 개(@capsule · @tool · @interceptor)와 익숙한 fs/http/kv API로 짜고, 빌드는 ComponentizeJS가 알아서 WASM 컴포넌트로 바꿔 준다. 대신 치러야 할 값(바이너리 크기 ~11MB)도 분명한데, 그건 §2에서 냉정하게 본다.

2왜 주목받는가

TrendShift 월간 16위 — "에이전트 OS 확장을 Rust 독점에서 풀어 준다"는 한 방.

2026년 들어 GitHub 트렌딩은 온통 "에이전트 스킬 · 에이전트 OS" 물결이다(skills 저장소들, hermes-agent, omnigent…). 그 흐름에서 Astrid는 "에이전트를 위한 진짜 운영체제"를 표방하는데, 그 OS를 확장하는 길이 지금까지 Rust 하나뿐이었다. sdk-js는 그 문을 JS/TS 쪽으로 연다. 주목 포인트를 다섯 가지로 본다.

① Rust 장벽을 없앤다 — 가장 큰 개발자 풀에게 문을 연다

캡슐을 짜려면 Rust + 매크로 + WIT를 알아야 했다. 이건 진입장벽이 높다. sdk-js는 똑같은 일을 전 세계에서 가장 인구가 많은 JS/TS 개발자가 할 수 있게 만든다. "에이전트 OS의 플러그인 생태계"를 키우려면 결국 사람 수가 관건인데, 이 SDK가 그 수문을 연다.

② 얇은 FFI가 아니라 "네이티브 JS 느낌"으로 번역했다

흔한 접근은 Rust API를 그대로 JS로 노출하는 얇은 바인딩(FFI)이다 — 어색하고 Rust 냄새가 난다. sdk-js는 반대로 갔다. 파일은 node:fs/promises 모양으로, HTTP는 fetch 폴리필까지, 에러는 Rust의 Result가 아니라 JS답게 throw로. README의 표현대로 "JS와 싸워 가며 Rust 모양 에러를 붙이는 건 '네이티브 느낌'의 정반대"라서다.

③ 같은 호스트 ABI = 언어를 갈아탈 자유

핵심 설계 결정 하나. Rust SDK와 JS SDK는 완전히 동일한 WIT 호스트 ABI를 쓴다. 그래서 자주 호출돼 성능이 중요한 캡슐은 Rust로, 빨리 만들고 싶은 캡슐은 JS로 — 커널을 전혀 바꾸지 않고 캡슐만 언어 이식할 수 있다. "프로토타입은 JS로, 검증되면 Rust로 다시"라는 현실적 전략이 가능해진다.

④ ComponentizeJS + 표준 데코레이터 — 최신 WASM 기술의 실전 사례

이 저장소는 그 자체로 "JS를 진짜 WASM 컴포넌트로 만드는 법"의 교본이다. Bytecode Alliance의 @bytecodealliance/componentize-js(SpiderMonkey 임베드 StarlingMonkey)로 JS를 wasm32-wasip2 컴포넌트로 굽고, TC39 표준 데코레이터(실험 플래그 없는 stage-3)로 Rust의 절차 매크로를 대체한다. 요즘 WASM·TS 생태계의 최첨단이 한 곳에 모여 있다.

⑤ Unicity / Astrid 생태계 — "에이전트가 경제 주체인 인터넷"

배경 서사도 한몫한다. Unicity는 "자율 에이전트의 인터넷(Autonomous Agentic Internet)"을 표방하는 블록체인 플랫폼이고, Astrid OS는 그 위 에이전트 런타임 계층이다. 에이전트를 1급 경제 주체로 두는 큰 그림 안에서, sdk-js는 "그 세계의 앱을 누구나 만들게 하는 입구" 역할로 묶인다.

캡슐 만드는 방법어떻게sdk-js와의 관계
sdk-rust (Rust)절차 매크로 #[capsule] + Rust로 작성. 바이너리 ~200KB원조. sdk-js가 같은 WIT 계약을 JS로 미러링한다.
sdk-js (JS/TS)데코레이터 @capsule + TS로 작성 → ComponentizeJS이 저장소. 바이너리 ~11MB지만 진입장벽이 낮다.
MCP 서버 흡수기존 MCP 서버를 매니페스트로 감싸 캡슐로Astrid가 재작성 없이 흡수. sdk-js는 "새로 짜는" 길.
날(raw) WASM임의 언어로 wasip2 컴포넌트를 직접 빌드가능하지만 WIT·번들·엔트리를 전부 손수. SDK가 그걸 자동화.
강점
"Rust 전용이던 에이전트 OS 확장"을 익숙한 JS로 — 그리고 결과물은 Rust와 완전 호환

데코레이터 몇 개와 fs/kv/ipc 같은 익숙한 모듈만으로 캡슐을 짜면, ComponentizeJS가 알아서 WASM 컴포넌트로 굽는다. 호스트 ABI가 Rust와 동일해 "잘 나가는 캡슐만 골라 Rust로 재작성"하는 길도 열려 있다. 웹 개발자에게는 새 생태계의 가장 낮은 입구다.

냉정하게 보기
Alpha · 11MB 바이너리 · 단독 빌드 불가 · 누락된 라이선스/테스트 · npm 미발행

상태가 Alpha다 — 핵심 경로(TS→컴포넌트→.capsule→설치)는 "엔드투엔드로 증명"됐지만 여전히 초기다. ② 바이너리가 ~11MB(gzip 3.5MB)로 Rust(~200KB)의 50배다. StarlingMonkey(JS 엔진) 자체를 안에 품기 때문 — 크기 민감한 곳엔 부적합하고, 한 번 띄워 오래 쓰는 데몬형에 어울린다. ③ 이 저장소 혼자로는 캡슐을 완성 못 한다 — 최종 .capsule 패킹과 호스트 구현은 Rust 쪽 astrid-build와 커널이 한다(contracts 서브모듈도 따로 받아야 함). ④ README가 거는 LICENSE 파일들이 정작 저장소에 없고, 테스트 프레임워크도 없으며(npm test는 빈 껍데기), 릴리스는 GitHub 릴리스만, npm publish는 아직 없다.

3기술 스택 전체 지도

루트 package.json(npm workspaces) · 각 패키지의 package.json/tsconfig · 실제 소스 트리에서 확인한 구성.

마음의 준비. 이건 "백엔드+프론트엔드" 웹 앱이 아니라 컴파일 툴체인 + 호스트 바인딩 라이브러리다. 전부 TypeScript(+ 빌드 오케스트레이터는 Node ESM .mjs)이고, npm workspaces로 묶인 모노레포다. 계층은 ⓐ 언어/런타임, ⓑ 빌드 체인, ⓒ 두 패키지, ⓓ 계약·타입, ⓔ 일부러 "안 쓴 것"으로 나뉜다.

ⓐ 언어 · 런타임 — 의외로 엄격하다

항목선택메모
언어TypeScript ^5.6제품 코드 전부 TS. 빌드 스크립트는 Node ESM .mjs.
데코레이터TC39 표준(stage-3)experimentalDecorators가 아니다(tsconfig에서 false). 최신 표준 데코레이터 사용.
Node 엔진>=20 (CI는 22)빌드 오케스트레이터가 Node에서 돈다.
모듈 포맷순수 ESM"type":"module". require 없음, import만.
패키지 매니저npm + npm workspaces잠금파일 package-lock.json, CI는 npm ci. 워크스페이스: packages/*, examples/*.
TS 엄격도strict 초집합noUncheckedIndexedAccess·exactOptionalPropertyTypes·verbatimModuleSyntax 등 강한 옵션 다수. 타깃 ES2022.

ⓑ 빌드 체인 — TS가 WASM 컴포넌트가 되기까지

도구버전역할
tscTS 5.6tsc -b(프로젝트 레퍼런스/composite)로 src/*.tsdist/*.js 컴파일.
esbuild^0.24컴파일된 엔트리 + SDK를 자기완결 ESM 한 덩어리로 번들(astrid:* WIT 지정자는 external 처리).
@bytecodealliance/componentize-js^0.18★ 번들된 JS + WIT를 받아 wasm32-wasip2 컴포넌트로 굽는다. 내부에 StarlingMonkey(SpiderMonkey) JS 엔진 임베드.
(Rust) astrid-build별도 저장소위 결과 .wasmCapsule.toml+wit과 함께 .capsule 아카이브(gzip tar)로 패킹. JS 저장소 밖.
용어
ComponentizeJS · StarlingMonkey
ComponentizeJS는 Bytecode Alliance가 만든 도구로, "평범한 JavaScript를 진짜 WASM 컴포넌트로 바꿔 주는 컴파일러"다. 비결은 JS를 기계어로 번역하는 게 아니라, StarlingMonkey라는 작은 JS 엔진(Firefox의 SpiderMonkey를 WASM용으로 깎은 것)을 결과물 안에 통째로 넣고 그 위에서 내 JS를 돌리는 것. 그래서 결과 바이너리가 ~11MB로 큰 거다 — 엔진을 함께 싣기 때문.

ⓒ 발행되는 두(+1) 패키지

패키지한 줄 역할
@unicity-astrid/sdk캡슐 작성 API. 데코레이터(@capsule/@tool/@interceptor/@command/@install/@upgrade/@run) + 호스트 모듈(fs·net·http·kv·ipc·process·identity·approval·uplink·elicit·capabilities 등).
@unicity-astrid/build빌드 오케스트레이터. tsc + esbuild + ComponentizeJS를 순서대로 돌려 컴포넌트를 굽는다. 직접 호출 안 하고 Rust astrid-build가 부른다.
@unicity-astrid/sdk/contractsWIT에서 자동 생성된 TS 타입(Message·ToolCall·GenerateRequest·StreamEvent 등). 캡슐 간 IPC의 양쪽이 같은 타입을 쓰게 한다.

ⓓ 계약 · 타입 — "제3자 라이브러리가 거의 없다"

특이점: 이 SDK는 외부 crypto·HTTP 라이브러리를 쓰지 않는다. 파일·네트워크·HTTP·난수까지 전부 WIT 호스트 임포트(astrid:*/host@1.0.0)를 거쳐 커널에 부탁한다. 즉 SDK는 디스크나 네트워크를 직접 만지지 않는다 — 보안 샌드박스의 핵심이다. 실제로 임포트하는 호스트 패키지는 11개(fs · net · http · kv · ipc · process · sys · uplink · elicit · approval · identity). 콘텐츠 주소(BLAKE3)·실제 syscall은 전부 Rust 커널 쪽에 있다.

ⓔ 일부러 "안 쓴 것" — 품질은 다르게 지킨다

테스트 러너(vitest/jest)가 없다. npm test는 "나중에 단위테스트가 생기면 채울 자리"라는 빈 껍데기다. 대신 품질을 ① tsc 타입체크(빌드가 곧 전 파일 타입검사), ② CI의 wit-sync --check(아래 §4의 "단일 진실원" 어긋남 감지), ③ 수동 스모크 테스트로 지킨다. docs/ 폴더도 없고, 문서는 각 패키지 README와 notes/phase-*.md 개발 일지가 대신한다.

4아키텍처 심화 분석

먼저 "TS 클래스 한 개가 커널에 설치되기까지" 전체 그림을 본 뒤, 대표 흐름 '도구 호출 한 번'을 끝까지 따라간다.

이 SDK의 일은 두 가지다. (빌드 타임) 내가 쓴 TS 클래스를 wasm32-wasip2 컴포넌트로 굽고, (런타임) 커널이 그 컴포넌트를 부를 때 내 데코레이터 메서드로 정확히 연결해 준다. 부품 이름을 외우기 전에 숲부터 보자.

독수리 시점 — TS 클래스가 커널에 설치되기까지

[개발자가 쓰는 것] [빌드 체인 = @unicity-astrid/build] ┌─────────────────────────┐ │ TestCapsule.ts │ tsc esbuild ComponentizeJS │ @capsule │ ─────▶ ─────────▶ ───────────────▶ test-capsule.wasm │ @tool("increment") │ (타입검사) (한 덩어리 (StarlingMonkey (wasm32-wasip2 │ @interceptor(...) │ ESM 번들) JS엔진 임베드) 컴포넌트, ~11MB) │ + Capsule.toml (권한선언) │ │ └─────────────────────────┘ (Rust astrid-build) │ 패킹 ▼ test-capsule.capsule (Capsule.toml + wasm + wit, gzip tar) │ astrid capsule install ▼ ┌────────────── ASTRID 커널 (Rust · "멍청한 이벤트 라우터") ──────────────────┐ │ │ │ 캡슐 ──(export 4개)──▶ 커널이 호출: │ │ astridHookTrigger · run · astridInstall · astridUpgrade │ │ │ │ 캡슐 ◀──(host import 11개)── 커널이 제공: │ │ fs · net · http · kv · ipc · process · sys · uplink · elicit · … │ │ │ │ [공통 토대] WASM 샌드박스 · capability 권한검사 · 변조불가 감사기록 · 자원예산 │ └──────────────────────────────────────────────────────────────────────────────┘
용어
호스트 ABI — export 4개 · host import 11개
캡슐과 커널이 만나는 "계약면"이다. 캡슐이 커널에 노출하는 함수(export) 4개: astridHookTrigger(도구/인터셉터/커맨드 진입점), run(데몬 실행), astridInstall·astridUpgrade(생애주기). 거꾸로 커널이 캡슐에 제공하는 함수(host import) 11묶음: 파일·네트워크·HTTP·KV·IPC·프로세스 등. 이 면이 Rust SDK와 글자 하나까지 동일해서 언어 호환이 성립한다.

흐름 ① — "도구 호출 한 번"이 처리되는 전 과정

개발자는 이런 캡슐을 쓴다(예제 test-capsule에서 발췌). 데코레이터가 곧 "이 메서드는 도구다/인터셉터다"라는 표식이다.

// 개발자가 작성하는 캡슐 — 익숙한 클래스 + 데코레이터
import { capsule, tool, interceptor, install, ipc, log } from "@unicity-astrid/sdk";

@capsule
export class TestCapsule {
  counter = 0;

  @tool("increment", { mutable: true })   // 상태를 바꾸는 도구
  increment(_args: object): { counter: number } {
    this.counter = (this.counter + 1) >>> 0;
    log.info(`counter = ${this.counter}`);
    return { counter: this.counter };
  }

  @tool("get_counter")                     // 읽기 전용 도구
  getCounter(_args: object): { counter: number } {
    return { counter: this.counter };
  }

  @interceptor("test.v1.event")            // 특정 IPC 토픽을 가로챔
  handleEvent(_payload: unknown): { handled: boolean } {
    return { handled: true };
  }

  @install onInstall(): void { log.info("installed"); }
}

커널이 increment 도구를 부르면, SDK 안의 브릿지(runtime/bridge.ts)가 아래 순서로 일을 처리한다. 이 디스패치 표가 SDK의 심장이고, Rust의 절차 매크로가 컴파일 타임에 만들어 내던 코드를, 여기선 런타임에 한다.

// 커널이 부르는 단일 진입점(WIT export). action 문자열로 분기
astridHookTrigger(action, payload):

  if action == "tool_describe":
      → 등록된 @tool 들의 스키마 목록을 만들어 반환(캐시)
        각 항목 = { name, description, input_schema }

  if action.startsWith("tool_execute_"):
      req = parse(payload)             // { call_id, tool_name, arguments }
      if 도구가 mutable:  this ← KV의 "__state" 로드   // 상태 복원
      result = await 핸들러(req.arguments)
      if mutable:        KV에 "__state" 저장            // 상태 영속
      ipc.publish("tool.v1.execute.<name>.result", result)
      return { action: "continue" }

  else:
      interceptor / command 맵에서 찾아 실행 → 결과를 그대로 반환

  // 어디서든 에러가 나면 →  return { action: "deny", data: 사유 }
용어
mutable 도구 · __state · KV
캡슐은 여러 워커가 공유하는 풀 위에서 도는 무상태(stateless) 인스턴스가 기본이다. 그래서 도구가 인스턴스 필드(this.counter)를 바꾸려면 { mutable: true }를 붙여야 한다. 그러면 브릿지가 호출 전에 KV 저장소의 __state에서 상태를 복원하고, 끝나면 다시 저장한다. "도구 실행 = 상태 로드 → 실행 → 저장"의 트랜잭션인 셈. Rust SDK와 키 이름·방식까지 동일하다.
초보자 함정
핸들러는 "동기적으로 끝나는 async"여야 한다 — 날 setTimeout 금지

WASM의 export 함수는 동기인데 SDK 핸들러는 async다. 브릿지는 이 간극을 메우려고, 핸들러가 돌려준 Promise그 자리에서 마이크로태스크까지 비워 가며 즉시 정착(settle)시킨다. StarlingMonkey가 "export에서 돌아오기 전에 이벤트 루프를 돌려 프로미스를 정리"해 주기에 가능한 일이다. 하지만 await fetch()처럼 SDK 호스트 호출이 아닌 날것의 비동기(예: 순수 setTimeout)를 쓰면 마이크로태스크 드레인 안에 안 끝나고 "Promise가 동기적으로 정착하지 않았다"며 던진다. 규칙: 핸들러 안에서는 Astrid SDK 호출만 써라.

흐름 ② — 데코레이터는 "로드 시점"에 자기를 등록한다

이 모든 디스패치가 가능한 건, 모듈이 로드될 때 데코레이터들이 runtime/registry.ts등록부에 자기를 적어 두기 때문이다. @tool("increment")는 "이름 increment → 이 메서드" 매핑을 등록하고, 중복을 즉시 잡는다: 같은 이름 도구를 두 번 선언하거나, @capsule 클래스가 둘이거나, @install이 여럿이면 로드 시점에 에러를 던진다. (클래스 필드가 클래스보다 먼저 평가되는 순서 문제도 WeakMap 버퍼로 처리한다.) 즉 "런타임 등록부 + 동기 정착 브릿지"가 Rust 매크로의 역할을 대신한다.

이 레포에서 배울 핵심 설계 패턴 셋. 첫째, WIT = 단일 진실원 — 호스트 ABI 원본은 별도 저장소 unicity-astrid/wit에 있고, 그걸 contracts 서브모듈로 받아 Rust·JS 양쪽에서 똑같이 코드 생성한다. CI의 wit-sync --check가 "번들된 WIT 미러가 원본과 어긋났는지"를 막아 손편집을 차단한다. 둘째, capability로 잠근 호스트 모듈 — 모든 fs/net 호출은 권한 검사를 거치고 실패 시 SysError던진다(Rust는 Result 반환). 셋째, 데코레이터로 매크로를 대체 — 컴파일 타임 코드 생성을 런타임 등록으로 옮긴 영리한 절충.

5디렉토리 구조 해부

npm workspaces 모노레포 · 핵심은 packages/astrid-sdk/src의 22개 TS 모듈. 큰 덩어리만 본다.

복잡성의 대부분은 packages/astrid-sdk/src 안에 모여 있다. 그중 꼭 읽을 두 파일runtime/bridge.ts(디스패치 엔진)와 runtime/registry.ts(데코레이터 등록부)다 — §4의 흐름이 전부 여기서 일어난다. 줄 수(LOC)는 "어디가 무겁나"를 가늠하는 힌트다.

sdk-js/ ← npm workspaces 루트(모노레포) ├── packages/ │ ├── astrid-sdk/ → @unicity-astrid/sdk (캡슐 작성 API) │ │ ├── src/ │ │ │ ├── index.ts 공개 배럴: 데코레이터 + 16개 모듈 re-export │ │ │ ├── runtime/ │ │ │ │ ├── bridge.ts (374) ★ 디스패치 엔진: WIT export 4개 구현 │ │ │ │ └── registry.ts (178) ★ 데코레이터 등록부 + 중복 검출 │ │ │ ├── contracts.ts (1218) WIT→TS 자동생성 타입(편집 금지) │ │ │ ├── wit-imports.d.ts(772) 11개 host 패키지 ambient 선언 │ │ │ ├── net.ts(545) process.ts(527) fs.ts(357) ← 호스트 모듈(큰 것) │ │ │ ├── ipc.ts(354) http.ts(345) kv.ts(123) ← 호스트 모듈 │ │ │ ├── capsule.ts tool.ts ← 데코레이터 정의 │ │ │ └── errors.ts(SysError) identity·approval·uplink·elicit·… │ │ ├── wit-contracts/astrid-contracts.wit 번들 WIT 미러(자동생성) │ │ └── scripts/generate-contracts.mjs WIT → contracts.ts 코드젠 │ └── astrid-build/ → @unicity-astrid/build (빌드 오케스트레이터) │ └── src/index.mjs 파이프라인 + wit-codegen.mjs + wit-parser.mjs ├── examples/test-capsule/ 최소 엔드투엔드 예제(+Capsule.toml · wit/events.wit) ├── contracts/ git submodule → unicity-astrid/wit (호스트 ABI 원본) ├── scripts/sync-contracts-wit.sh 서브모듈 WIT → 번들 WIT 재생성 ├── notes/phase-*.md 개발 일지(테스트·문서 대용) └── .github/workflows/ ci.yml(빌드+wit-sync) · release.yml
위치한 줄 역할
src/runtime/bridge.ts디스패치 엔진. 커널의 호출(action 문자열)을 내 데코레이터 메서드로 연결. 상태 로드·저장, 동기 정착, IPC 발행.
src/runtime/registry.ts데코레이터 등록부. 도구·인터셉터·커맨드를 모으고 중복을 로드 시점에 차단.
src/index.ts공개 배럴. 데코레이터 7종 + 16개 호스트 모듈 + 타입을 한곳에서 export.
src/contracts.tsWIT에서 자동 생성된 IPC 타입(1218줄, 편집 금지). 캡슐 간 메시지 타입.
src/fs.ts · http.ts · kv.ts · ipc.ts호스트 모듈. 각각 node:fs/promises·fetch·Map·pub/sub 모양으로 커널 임포트를 감쌈.
src/errors.tsSysError 정의. 호스트가 던진 {tag,val}.code(예: quota·capability-denied) 붙은 에러로 정규화.
packages/astrid-build/src/index.mjs빌드 파이프라인 6단계(§3 ⓑ). tsc→esbuild→ComponentizeJS.
examples/test-capsule/모든 걸 보여 주는 최소 완전 예제. 여기서 시작.
contracts/ (서브모듈)호스트 ABI 원본 WIT. 얕은 클론 땐 비어 있어 --recursive 필요.

6학습 포인트 (기술별)

이 레포 한 채로 배울 수 있는 최신 WASM·TS 기술과, 각각의 실습 아이디어.

sdk-js는 "요즘 가장 뜨거운 WASM·TypeScript 주제"가 한 코드베이스에 모여 있어 공부 소재로 훌륭하다. 기술별로 무엇을 건질지 짚는다.

① WebAssembly 컴포넌트 모델 · wasip2

"JS를 진짜 WASM 부품으로 굽는다"는 게 뭔지

이 저장소의 결과물은 wasm32-wasip2 컴포넌트다. 옛날 WASM 모듈과 달리, 컴포넌트는 타입이 있는 인터페이스(WIT)로 서로 끼워 맞춘다. ComponentizeJS가 JS 엔진(StarlingMonkey)을 품어 평범한 JS를 컴포넌트로 바꾸는 과정을 직접 보면, "WASM = C/Rust 전용"이라는 오해가 깨진다.

실습: npm run build로 예제를 빌드하고 나온 .wasm 크기(~11MB)를 확인. 왜 이렇게 큰지(엔진 임베드) 설명해 보기.

② WIT · 계약 우선(contract-first) 설계

"하나의 IDL"로 두 언어가 같은 타입을 본다

호스트 ABI를 .wit 파일에 한 번 정의하고, Rust·JS가 거기서 코드를 생성해 쓴다(contracts.ts 1218줄은 전부 생성물). 손으로 타입을 두 번 적지 않으니 어긋날 일이 없다. "계약을 단일 진실원으로 두는" API 설계의 모범 사례.

실습: wit-contracts/astrid-contracts.wit를 열어 한 인터페이스를 고른 뒤, 그게 contracts.ts의 어떤 TS 타입으로 변했는지 매칭.

③ TC39 표준 데코레이터(stage-3)

실험 플래그 없는 "진짜 데코레이터" 활용법

@capsule·@tool은 옛 experimentalDecorators가 아니라 TC39 표준 데코레이터다. 메서드 데코레이터가 context.addInitializer로 인스턴스 생성 시점에 자기를 등록부에 넣는 패턴을 보면, "데코레이터가 실제로 무슨 일을 하는지"가 손에 잡힌다.

실습: tool.ts를 읽고, private/static 메서드에 @tool을 달면 왜 에러를 던지는지 추적.

④ Capability 기반 보안 모델

"기본은 아무 권한도 없다" — 최소권한의 실전

캡슐은 Capsule.toml에 적어 허락받은 것만 할 수 있다. 모든 fs/net 호출이 권한 검사를 거치고, 막히면 SysError(코드 capability-denied)를 던진다. "신뢰할 수 없는 코드를 안전하게 돌리는" 샌드박스 설계를 코드로 배운다.

실습: 예제의 Capsule.toml에서 kv 권한을 빼고 빌드/실행해, 어디서 어떤 에러가 나는지 관찰.

⑤ WASM 안의 동기/비동기 브릿지

"동기 export"와 "async 핸들러"를 잇는 묘수

WASM export는 동기인데 JS 핸들러는 async다. bridge.tssyncWait프로미스를 그 자리에서 마이크로태스크까지 비워 정착시키는 코드를 보면, "비동기 런타임을 동기 경계에 가두는" 어려운 문제의 실전 해법을 배운다(StarlingMonkey의 이벤트 루프 보장에 기댄다).

실습: 핸들러 안에 일부러 순수 setTimeout을 넣어 "Promise가 정착 안 됨" 에러를 재현하고, SDK 호출로 바꿔 고치기.

⑥ 모노레포 · ESM · 빌드 오케스트레이션

tsc + esbuild + ComponentizeJS를 코드로 엮기

astrid-build/src/index.mjs여러 빌드 도구를 프로그램으로 순서대로 부르는 오케스트레이터다. npm workspaces 모노레포, 순수 ESM, tsc 프로젝트 레퍼런스, esbuild·ComponentizeJS의 프로그래매틱 API 사용법이 한 파일에 응축돼 있다.

실습: index.mjs의 6단계(읽기→코드젠→tsc→엔트리생성→esbuild→componentize)에 각각 console.log를 넣어 어떤 산출물이 어디 생기는지 추적.

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

GPU 불필요. "Node 20+ 툴체인"이면 빌드는 되지만, 완전히 돌리려면 Rust 커널이 필요하다.

sdk-js는 추론 모델이 아니라 컴파일 도구 + 바인딩 라이브러리라 GPU·고사양이 필요 없다. 다만 주의할 점이 둘 — ① 빌드 산출물 컴포넌트가 ~11MB로 크고, ② 이 저장소만으로는 캡슐을 설치·실행까지 못 한다(Rust 쪽 패킹·커널 필요).

항목요구사항
Node.js20 이상(CI는 22 권장). 빌드 오케스트레이터가 Node에서 돈다.
패키지 매니저npm(workspaces). 첫 셋업은 npm ci.
TypeScript5.6+. 표준 데코레이터 지원이 필요(실험 플래그 아님).
서브모듈contracts/(호스트 ABI 원본 WIT)를 git submodule update --init --recursive로 받아야 함.
빌드 결과wasm32-wasip2 컴포넌트 ~11MB(gzip 시 ~3.5MB). 엔진(StarlingMonkey) 임베드 비용.
완전 실행(중요)최종 .capsule 패킹·설치·호스트 구현은 Rust astrid-build + Astrid 커널(wasmtime)이 담당 — 별도 저장소.
GPU불필요.
npm 설치아직 npm 레지스트리 미발행(릴리스는 GitHub 릴리스만). 소스에서 빌드해 쓴다.

가장 빠른 출발점은 레포를 클론해 npm ci && npm run build예제 캡슐을 컴포넌트로 굽는 것이다. 커널 없이도 "TS→WASM 컴포넌트" 변환까지는 눈으로 확인할 수 있다 — 데코레이터·번들·componentize의 한 사이클을 손에 익히기 좋다.

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

"빌드 한 번"은 10분, "내 도구 추가·언어 이식"은 한나절. 난이도 순으로.

실습 1 난이도 ★☆☆ 입문

클론해서 예제 캡슐을 WASM 컴포넌트로 굽기

레포를 클론하고 서브모듈까지 받은 뒤(git submodule update --init --recursive), npm ci && npm run build를 돌린다. examples/test-capsule.wasm 컴포넌트로 구워지는 과정을 본다.

목표: "TS → wasm32-wasip2 컴포넌트" 한 사이클을 눈으로 확인. 나온 .wasm 크기(~11MB)를 재고 왜 큰지 설명하기.
실습 2 난이도 ★☆☆ 입문

예제 캡슐 해부 — 데코레이터와 export 4개 찾기

examples/test-capsule/src/index.ts를 읽고 @capsule·@tool·@interceptor·@install이 각각 무엇을 하는지 적는다. 그다음 Capsule.toml[capabilities] 블록이 코드의 어떤 호출과 연결되는지 짝짓는다.

목표: "선언(데코레이터·매니페스트) → 동작"의 연결 고리를 이해. 캡슐의 최소 골격을 손에 익히기.
실습 3 난이도 ★★☆ 중급

나만의 mutable 도구 추가하기 (KV 상태)

예제에 @tool("add", { mutable: true })를 새로 단다. 인자로 받은 수를 this.total에 누적하고 반환한다. 여러 번 호출해도 값이 유지되는지(= __state가 KV에 저장·복원되는지) 확인한다.

목표: "도구 실행 = 상태 로드 → 실행 → 저장" 트랜잭션을 체감. mutable을 빼면 왜 값이 리셋되는지 관찰.
실습 4 난이도 ★★☆ 중급

IPC 인터셉터로 캡슐 간 메시지 주고받기

한 도구에서 ipc.publishJson("my.v1.ping", {...})로 토픽을 발행하고, @interceptor("my.v1.ping") 메서드로 그걸 받아 처리한다. Capsule.tomlipc_publish 권한도 맞춰 준다.

목표: "도구 = LLM이 부르는 함수", "인터셉터 = 토픽 구독"의 차이를 이해. 펍섭 기반 캡슐 협업의 기본기.
실습 5 난이도 ★★★ 고급

브릿지의 "동기 정착" 한계를 일부러 깨뜨리기

핸들러 안에 await new Promise(r => setTimeout(r, 10))처럼 SDK가 아닌 비동기를 넣어 "Promise가 동기적으로 정착하지 않았다" 에러를 재현한다. 그다음 bridge.tssyncWait를 읽어 왜 그런지 설명하고, SDK 호출로 바꿔 고친다.

목표: WASM 동기 경계 ↔ JS async의 충돌을 코드로 이해. "핸들러 안엔 SDK 호출만"이라는 규칙의 이유를 체득.
실습 6 난이도 ★★★ 고급

같은 캡슐을 Rust ↔ JS로 비교/이식

sdk-rustexamples/test-capsule와 이 저장소의 같은 예제를 나란히 펴고, 같은 WIT 계약을 두 언어가 어떻게 표현하는지 대조한다(#[astrid::tool]@tool, Result 반환 ↔ throw). 작은 도구 하나를 한쪽 언어로 옮겨 본다.

목표: "호스트 ABI가 같으면 언어는 갈아탈 수 있다"는 핵심 주장을 직접 검증. 두 SDK의 관용구 번역 차이를 정리.

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

sdk-js를 100% 소화하기 위한 6주 코스(주당 5~7시간 가정).

주차주제학습 자료 / 할 일
1주차WASM · 컴포넌트 모델 · wasip2 기초"WASM 모듈 vs 컴포넌트" 차이, wasip2가 뭔지 정리. ComponentizeJS 개념 파악. 실습 1.
2주차WIT · 계약 우선 설계astrid-contracts.witcontracts.ts 매핑. "단일 진실원" 개념. 실습 2.
3주차SDK API 표면(호스트 모듈)fs·kv·ipc·http 모듈 읽기. SysError·capability 모델. 실습 3.
4주차데코레이터 + 브릿지/등록부 내부tool.ts·registry.ts·bridge.ts 정독. 디스패치·동기 정착. 실습 4·5.
5주차빌드 파이프라인 · 매니페스트astrid-build/src/index.mjs 6단계, Capsule.toml·[capabilities] 설계.
6주차Astrid 커널 + Unicity 생태계unicity-astrid/astrid(커널) 훑기, Rust↔JS 캡슐 이식. 실습 6.

10핵심 키워드 사전

이 문서와 레포에 등장한 용어를 한자리에.

용어의미
Astrid OS"AI 에이전트를 위한 운영체제." Rust 마이크로커널이 메시지를 라우팅하고 권한·샌드박스를 관리한다. sdk-js가 확장 모듈을 짜는 대상.
캡슐(capsule)Astrid의 프로세스 단위. Capsule.toml로 기술되는 격리된 WASM 프로그램. 허락받은 권한·자원 안에서만 돈다.
Capsule.toml캡슐의 매니페스트. 이름·버전·필요 권한([capabilities])·인터셉터 등을 선언.
WITWebAssembly Interface Types. 컴포넌트의 함수·타입 계약을 적는 IDL. Astrid의 단일 진실원.
컴포넌트 모델 / wasip2타입 있는 WASM 부품 표준(Component Model)과 그 시스템 인터페이스(WASI Preview 2). 캡슐의 컴파일 타깃.
ComponentizeJS평범한 JS를 WASM 컴포넌트로 굽는 Bytecode Alliance 도구. 핵심에 StarlingMonkey 엔진.
StarlingMonkeyWASM용으로 깎은 SpiderMonkey(JS 엔진). 결과 바이너리에 통째로 임베드 → ~11MB의 원인.
호스트 ABI / host import커널이 캡슐에 제공하는 함수 묶음(11개: fs·net·http·kv·ipc·process·sys·uplink·elicit·approval·identity).
export 4개캡슐이 커널에 노출하는 진입점: astridHookTrigger·run·astridInstall·astridUpgrade.
capability(권한)캡슐이 매니페스트로 허락받은 능력. 없으면 호출 시 SysError(capability-denied).
@capsule / @tool / @interceptorTC39 표준 데코레이터. 클래스를 캡슐로, 메서드를 "도구/토픽 인터셉터"로 표식. Rust 매크로의 대체.
@command / @install / @upgrade / @run이름 기반 RPC 커맨드 / 설치·업그레이드 생애주기 훅 / 데몬 실행 진입점.
브릿지(bridge.ts)커널 호출(action 문자열)을 데코레이터 메서드로 연결하는 디스패치 엔진. 상태 로드·저장·동기 정착 담당.
mutable 도구 · __state인스턴스 상태를 바꾸는 도구. 실행 전후로 KV의 __state에서 상태를 복원·저장.
KV / CAS캡슐별 격리된 키-값 저장소. cas()는 멀티워커 경쟁을 막는 원자적 compare-and-swap.
IPC캡슐 간 pub/sub 메시지 버스. publish/subscribe, 토픽 와일드카드(접미 *).
uplink캡슐이 외부 최종 사용자를 대리해 메시지를 보내거나 신원을 주장하는 권한 통로.
SysErrorSDK의 표준 에러. .code(quota·capability-denied·cas-mismatch 등) 보유. Rust는 Result, JS는 throw.
.capsule 아카이브최종 배포물. Capsule.toml+wasm+wit를 묶은 gzip tar. Rust astrid-build가 패킹.
sdk-rust이 저장소의 Rust 원조. 같은 WIT·같은 .capsule 출력 → 커널은 둘을 구분 못 함.
Unicity"자율 에이전트의 인터넷"을 표방하는 블록체인 플랫폼. Astrid OS가 그 위 런타임 계층.
Astrinaut릴리스 노트가 기여자를 부르는 애칭(🚀). 프로젝트의 우주/공간 테마.

11참고 링크

원문·동반 저장소·관련 기술 더 파기.