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)한 문장으로 이 프로젝트가 뭘 하는지부터.
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는 똑같고, 관용구만 번역했다."
Astrid 커널은 표준 USB-C 포트를 하나 정의해 뒀다. 이 포트의 모양(전압·핀 배치)이 바로 WIT 계약(host ABI)이다. 캡슐은 그 포트에 꽂는 기기(USB-C 플러그가 달린 장치)다. 기기 내부를 Rust로 만들었든 JS로 만들었든, 플러그 규격(wasip2 컴포넌트 + WIT 계약)만 맞으면 커널은 차이를 못 느끼고 똑같이 받아들인다.
sdk-js는 "USB-C 기기를 JS로 만들 수 있게 해 주는 공구 세트"다. 지금까지 USB-C 기기는 Rust 공장에서만 찍어냈는데, 이제 JS 개발자도 같은 규격의 기기를 만들 수 있게 됐다. 포트(커널)는 손댈 필요가 없다.
Capsule.toml 매니페스트로 기술되는 격리된 WASM 프로그램이다. 각 캡슐은 ① WebAssembly 샌드박스 안에 갇히고, ② 매니페스트에 적어 명시적으로 허락받은 권한(capability)만 쓰며(파일 읽기·네트워크 등), ③ 정해진 자원 예산 안에서 돌고, ④ 모든 행동이 변조 불가능한 감사 기록(audit chain)에 남는다. "에이전트에게 새 능력(도구)을 붙여 주는 플러그인"이라고 보면 쉽다.wasm32-wasip2 타깃의 컴포넌트 하나로 컴파일된다..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에서 냉정하게 본다.
TrendShift 월간 16위 — "에이전트 OS 확장을 Rust 독점에서 풀어 준다"는 한 방.
2026년 들어 GitHub 트렌딩은 온통 "에이전트 스킬 · 에이전트 OS" 물결이다(skills 저장소들, hermes-agent, omnigent…). 그 흐름에서 Astrid는 "에이전트를 위한 진짜 운영체제"를 표방하는데, 그 OS를 확장하는 길이 지금까지 Rust 하나뿐이었다. sdk-js는 그 문을 JS/TS 쪽으로 연다. 주목 포인트를 다섯 가지로 본다.
캡슐을 짜려면 Rust + 매크로 + WIT를 알아야 했다. 이건 진입장벽이 높다. sdk-js는 똑같은 일을 전 세계에서 가장 인구가 많은 JS/TS 개발자가 할 수 있게 만든다. "에이전트 OS의 플러그인 생태계"를 키우려면 결국 사람 수가 관건인데, 이 SDK가 그 수문을 연다.
흔한 접근은 Rust API를 그대로 JS로 노출하는 얇은 바인딩(FFI)이다 — 어색하고 Rust 냄새가 난다. sdk-js는 반대로 갔다. 파일은 node:fs/promises 모양으로, HTTP는 fetch 폴리필까지, 에러는 Rust의 Result가 아니라 JS답게 throw로. README의 표현대로 "JS와 싸워 가며 Rust 모양 에러를 붙이는 건 '네이티브 느낌'의 정반대"라서다.
핵심 설계 결정 하나. Rust SDK와 JS SDK는 완전히 동일한 WIT 호스트 ABI를 쓴다. 그래서 자주 호출돼 성능이 중요한 캡슐은 Rust로, 빨리 만들고 싶은 캡슐은 JS로 — 커널을 전혀 바꾸지 않고 캡슐만 언어 이식할 수 있다. "프로토타입은 JS로, 검증되면 Rust로 다시"라는 현실적 전략이 가능해진다.
이 저장소는 그 자체로 "JS를 진짜 WASM 컴포넌트로 만드는 법"의 교본이다. Bytecode Alliance의 @bytecodealliance/componentize-js(SpiderMonkey 임베드 StarlingMonkey)로 JS를 wasm32-wasip2 컴포넌트로 굽고, TC39 표준 데코레이터(실험 플래그 없는 stage-3)로 Rust의 절차 매크로를 대체한다. 요즘 WASM·TS 생태계의 최첨단이 한 곳에 모여 있다.
배경 서사도 한몫한다. 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가 그걸 자동화. |
데코레이터 몇 개와 fs/kv/ipc 같은 익숙한 모듈만으로 캡슐을 짜면, ComponentizeJS가 알아서 WASM 컴포넌트로 굽는다. 호스트 ABI가 Rust와 동일해 "잘 나가는 캡슐만 골라 Rust로 재작성"하는 길도 열려 있다. 웹 개발자에게는 새 생태계의 가장 낮은 입구다.
① 상태가 Alpha다 — 핵심 경로(TS→컴포넌트→.capsule→설치)는 "엔드투엔드로 증명"됐지만 여전히 초기다. ② 바이너리가 ~11MB(gzip 3.5MB)로 Rust(~200KB)의 50배다. StarlingMonkey(JS 엔진) 자체를 안에 품기 때문 — 크기 민감한 곳엔 부적합하고, 한 번 띄워 오래 쓰는 데몬형에 어울린다. ③ 이 저장소 혼자로는 캡슐을 완성 못 한다 — 최종 .capsule 패킹과 호스트 구현은 Rust 쪽 astrid-build와 커널이 한다(contracts 서브모듈도 따로 받아야 함). ④ README가 거는 LICENSE 파일들이 정작 저장소에 없고, 테스트 프레임워크도 없으며(npm test는 빈 껍데기), 릴리스는 GitHub 릴리스만, npm publish는 아직 없다.
루트 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. |
| 도구 | 버전 | 역할 |
|---|---|---|
| tsc | TS 5.6 | tsc -b(프로젝트 레퍼런스/composite)로 src/*.ts → dist/*.js 컴파일. |
| esbuild | ^0.24 | 컴파일된 엔트리 + SDK를 자기완결 ESM 한 덩어리로 번들(astrid:* WIT 지정자는 external 처리). |
| @bytecodealliance/ | ^0.18 | ★ 번들된 JS + WIT를 받아 wasm32-wasip2 컴포넌트로 굽는다. 내부에 StarlingMonkey(SpiderMonkey) JS 엔진 임베드. |
| (Rust) astrid-build | 별도 저장소 | 위 결과 .wasm을 Capsule.toml+wit과 함께 .capsule 아카이브(gzip tar)로 패킹. JS 저장소 밖. |
| 패키지 | 한 줄 역할 |
|---|---|
| @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/contracts | WIT에서 자동 생성된 TS 타입(Message·ToolCall·GenerateRequest·StreamEvent 등). 캡슐 간 IPC의 양쪽이 같은 타입을 쓰게 한다. |
특이점: 이 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 개발 일지가 대신한다.
먼저 "TS 클래스 한 개가 커널에 설치되기까지" 전체 그림을 본 뒤, 대표 흐름 '도구 호출 한 번'을 끝까지 따라간다.
이 SDK의 일은 두 가지다. (빌드 타임) 내가 쓴 TS 클래스를 wasm32-wasip2 컴포넌트로 굽고, (런타임) 커널이 그 컴포넌트를 부를 때 내 데코레이터 메서드로 정확히 연결해 준다. 부품 이름을 외우기 전에 숲부터 보자.
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: 사유 }
__state · KVthis.counter)를 바꾸려면 { mutable: true }를 붙여야 한다. 그러면 브릿지가 호출 전에 KV 저장소의 __state 키에서 상태를 복원하고, 끝나면 다시 저장한다. "도구 실행 = 상태 로드 → 실행 → 저장"의 트랜잭션인 셈. Rust SDK와 키 이름·방식까지 동일하다.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 반환). 셋째, 데코레이터로 매크로를 대체 — 컴파일 타임 코드 생성을 런타임 등록으로 옮긴 영리한 절충.
npm workspaces 모노레포 · 핵심은 packages/astrid-sdk/src의 22개 TS 모듈. 큰 덩어리만 본다.
복잡성의 대부분은 packages/astrid-sdk/src 안에 모여 있다. 그중 꼭 읽을 두 파일은 runtime/bridge.ts(디스패치 엔진)와 runtime/registry.ts(데코레이터 등록부)다 — §4의 흐름이 전부 여기서 일어난다. 줄 수(LOC)는 "어디가 무겁나"를 가늠하는 힌트다.
| 위치 | 한 줄 역할 |
|---|---|
| src/runtime/bridge.ts | ★ 디스패치 엔진. 커널의 호출(action 문자열)을 내 데코레이터 메서드로 연결. 상태 로드·저장, 동기 정착, IPC 발행. |
| src/runtime/registry.ts | ★ 데코레이터 등록부. 도구·인터셉터·커맨드를 모으고 중복을 로드 시점에 차단. |
| src/index.ts | 공개 배럴. 데코레이터 7종 + 16개 호스트 모듈 + 타입을 한곳에서 export. |
| src/contracts.ts | WIT에서 자동 생성된 IPC 타입(1218줄, 편집 금지). 캡슐 간 메시지 타입. |
| src/fs.ts · http.ts · kv.ts · ipc.ts | 호스트 모듈. 각각 node:fs/promises·fetch·Map·pub/sub 모양으로 커널 임포트를 감쌈. |
| src/errors.ts | SysError 정의. 호스트가 던진 {tag,val}을 .code(예: quota·capability-denied) 붙은 에러로 정규화. |
| packages/astrid-build/src/index.mjs | 빌드 파이프라인 6단계(§3 ⓑ). tsc→esbuild→ComponentizeJS. |
| examples/test-capsule/ | 모든 걸 보여 주는 최소 완전 예제. 여기서 시작. |
| contracts/ (서브모듈) | 호스트 ABI 원본 WIT. 얕은 클론 땐 비어 있어 --recursive 필요. |
이 레포 한 채로 배울 수 있는 최신 WASM·TS 기술과, 각각의 실습 아이디어.
sdk-js는 "요즘 가장 뜨거운 WASM·TypeScript 주제"가 한 코드베이스에 모여 있어 공부 소재로 훌륭하다. 기술별로 무엇을 건질지 짚는다.
이 저장소의 결과물은 wasm32-wasip2 컴포넌트다. 옛날 WASM 모듈과 달리, 컴포넌트는 타입이 있는 인터페이스(WIT)로 서로 끼워 맞춘다. ComponentizeJS가 JS 엔진(StarlingMonkey)을 품어 평범한 JS를 컴포넌트로 바꾸는 과정을 직접 보면, "WASM = C/Rust 전용"이라는 오해가 깨진다.
실습: npm run build로 예제를 빌드하고 나온 .wasm 크기(~11MB)를 확인. 왜 이렇게 큰지(엔진 임베드) 설명해 보기.
호스트 ABI를 .wit 파일에 한 번 정의하고, Rust·JS가 거기서 코드를 생성해 쓴다(contracts.ts 1218줄은 전부 생성물). 손으로 타입을 두 번 적지 않으니 어긋날 일이 없다. "계약을 단일 진실원으로 두는" API 설계의 모범 사례.
실습: wit-contracts/astrid-contracts.wit를 열어 한 인터페이스를 고른 뒤, 그게 contracts.ts의 어떤 TS 타입으로 변했는지 매칭.
@capsule·@tool은 옛 experimentalDecorators가 아니라 TC39 표준 데코레이터다. 메서드 데코레이터가 context.addInitializer로 인스턴스 생성 시점에 자기를 등록부에 넣는 패턴을 보면, "데코레이터가 실제로 무슨 일을 하는지"가 손에 잡힌다.
실습: tool.ts를 읽고, private/static 메서드에 @tool을 달면 왜 에러를 던지는지 추적.
캡슐은 Capsule.toml에 적어 허락받은 것만 할 수 있다. 모든 fs/net 호출이 권한 검사를 거치고, 막히면 SysError(코드 capability-denied)를 던진다. "신뢰할 수 없는 코드를 안전하게 돌리는" 샌드박스 설계를 코드로 배운다.
실습: 예제의 Capsule.toml에서 kv 권한을 빼고 빌드/실행해, 어디서 어떤 에러가 나는지 관찰.
WASM export는 동기인데 JS 핸들러는 async다. bridge.ts의 syncWait가 프로미스를 그 자리에서 마이크로태스크까지 비워 정착시키는 코드를 보면, "비동기 런타임을 동기 경계에 가두는" 어려운 문제의 실전 해법을 배운다(StarlingMonkey의 이벤트 루프 보장에 기댄다).
실습: 핸들러 안에 일부러 순수 setTimeout을 넣어 "Promise가 정착 안 됨" 에러를 재현하고, SDK 호출로 바꿔 고치기.
astrid-build/src/index.mjs는 여러 빌드 도구를 프로그램으로 순서대로 부르는 오케스트레이터다. npm workspaces 모노레포, 순수 ESM, tsc 프로젝트 레퍼런스, esbuild·ComponentizeJS의 프로그래매틱 API 사용법이 한 파일에 응축돼 있다.
실습: index.mjs의 6단계(읽기→코드젠→tsc→엔트리생성→esbuild→componentize)에 각각 console.log를 넣어 어떤 산출물이 어디 생기는지 추적.
GPU 불필요. "Node 20+ 툴체인"이면 빌드는 되지만, 완전히 돌리려면 Rust 커널이 필요하다.
sdk-js는 추론 모델이 아니라 컴파일 도구 + 바인딩 라이브러리라 GPU·고사양이 필요 없다. 다만 주의할 점이 둘 — ① 빌드 산출물 컴포넌트가 ~11MB로 크고, ② 이 저장소만으로는 캡슐을 설치·실행까지 못 한다(Rust 쪽 패킹·커널 필요).
| 항목 | 요구사항 |
|---|---|
| Node.js | 20 이상(CI는 22 권장). 빌드 오케스트레이터가 Node에서 돈다. |
| 패키지 매니저 | npm(workspaces). 첫 셋업은 npm ci. |
| TypeScript | 5.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의 한 사이클을 손에 익히기 좋다.
"빌드 한 번"은 10분, "내 도구 추가·언어 이식"은 한나절. 난이도 순으로.
레포를 클론하고 서브모듈까지 받은 뒤(git submodule update --init --recursive), npm ci && npm run build를 돌린다. examples/test-capsule가 .wasm 컴포넌트로 구워지는 과정을 본다.
.wasm 크기(~11MB)를 재고 왜 큰지 설명하기.examples/test-capsule/src/index.ts를 읽고 @capsule·@tool·@interceptor·@install이 각각 무엇을 하는지 적는다. 그다음 Capsule.toml의 [capabilities] 블록이 코드의 어떤 호출과 연결되는지 짝짓는다.
mutable 도구 추가하기 (KV 상태)예제에 @tool("add", { mutable: true })를 새로 단다. 인자로 받은 수를 this.total에 누적하고 반환한다. 여러 번 호출해도 값이 유지되는지(= __state가 KV에 저장·복원되는지) 확인한다.
mutable을 빼면 왜 값이 리셋되는지 관찰.한 도구에서 ipc.publishJson("my.v1.ping", {...})로 토픽을 발행하고, @interceptor("my.v1.ping") 메서드로 그걸 받아 처리한다. Capsule.toml의 ipc_publish 권한도 맞춰 준다.
핸들러 안에 await new Promise(r => setTimeout(r, 10))처럼 SDK가 아닌 비동기를 넣어 "Promise가 동기적으로 정착하지 않았다" 에러를 재현한다. 그다음 bridge.ts의 syncWait를 읽어 왜 그런지 설명하고, SDK 호출로 바꿔 고친다.
sdk-rust의 examples/test-capsule와 이 저장소의 같은 예제를 나란히 펴고, 같은 WIT 계약을 두 언어가 어떻게 표현하는지 대조한다(#[astrid::tool] ↔ @tool, Result 반환 ↔ throw). 작은 도구 하나를 한쪽 언어로 옮겨 본다.
sdk-js를 100% 소화하기 위한 6주 코스(주당 5~7시간 가정).
| 주차 | 주제 | 학습 자료 / 할 일 |
|---|---|---|
| 1주차 | WASM · 컴포넌트 모델 · wasip2 기초 | "WASM 모듈 vs 컴포넌트" 차이, wasip2가 뭔지 정리. ComponentizeJS 개념 파악. 실습 1. |
| 2주차 | WIT · 계약 우선 설계 | astrid-contracts.wit ↔ contracts.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. |
이 문서와 레포에 등장한 용어를 한자리에.
| 용어 | 의미 |
|---|---|
| Astrid OS | "AI 에이전트를 위한 운영체제." Rust 마이크로커널이 메시지를 라우팅하고 권한·샌드박스를 관리한다. sdk-js가 확장 모듈을 짜는 대상. |
| 캡슐(capsule) | Astrid의 프로세스 단위. Capsule.toml로 기술되는 격리된 WASM 프로그램. 허락받은 권한·자원 안에서만 돈다. |
| Capsule.toml | 캡슐의 매니페스트. 이름·버전·필요 권한([capabilities])·인터셉터 등을 선언. |
| WIT | WebAssembly Interface Types. 컴포넌트의 함수·타입 계약을 적는 IDL. Astrid의 단일 진실원. |
| 컴포넌트 모델 / wasip2 | 타입 있는 WASM 부품 표준(Component Model)과 그 시스템 인터페이스(WASI Preview 2). 캡슐의 컴파일 타깃. |
| ComponentizeJS | 평범한 JS를 WASM 컴포넌트로 굽는 Bytecode Alliance 도구. 핵심에 StarlingMonkey 엔진. |
| StarlingMonkey | WASM용으로 깎은 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 / @interceptor | TC39 표준 데코레이터. 클래스를 캡슐로, 메서드를 "도구/토픽 인터셉터"로 표식. 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 | 캡슐이 외부 최종 사용자를 대리해 메시지를 보내거나 신원을 주장하는 권한 통로. |
| SysError | SDK의 표준 에러. .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 | 릴리스 노트가 기여자를 부르는 애칭(🚀). 프로젝트의 우주/공간 테마. |
원문·동반 저장소·관련 기술 더 파기.