소스 정보
- 저장소
- unicity-aos/capsule-system
- 최근 소스 활동
- 2026년 5월 29일 14:00
- 감지된 SKILL.md 언어
- 영어
- 스타
- 1
- 포크
- 0
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/unicity-aos/capsule-system --skill capsule-development명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | Capsule Development |
| description | How to build, configure, and install Astrid capsules from scratch |
An Astrid capsule is a WebAssembly (WASM) module compiled from Rust that runs inside the Astrid kernel sandbox. Capsules communicate exclusively via IPC events — they have no direct access to the host system except through kernel-mediated host functions.
my-capsule/
├── Capsule.toml # Manifest (name, version, capabilities, interceptors)
├── Cargo.toml # Rust crate (lib crate, cdylib)
└── src/
└── lib.rs # Capsule logic
[package]
name = "my-capsule"
version = "0.1.0"
description = "Short description of what this capsule does"
authors = ["Your Name"]
astrid-version = ">=0.5.0"
[[component]]
id = "my-capsule"
file = "my_capsule.wasm" # underscores, not hyphens
type = "executable"
[capabilities]
# Filesystem access (read and/or write)
fs_read = ["home://data/"]
fs_write = ["home://data/"]
# IPC topics this capsule publishes to
ipc_publish = ["my.namespace.events.*"]
# IPC topics this capsule subscribes to (must match interceptor events)
ipc_subscribe = ["my.namespace.request.*"]
# Spawn host processes (list allowed binary names)
host_process = ["git", "cargo"]
# Allow outbound HTTP requests
allow_http = true
# Allow outbound network connections
allow_network = true
[imports]
# Interfaces this capsule requires from others
astrid = { session = "^1.0" }
[exports]
# Interfaces this capsule provides to others
my-namespace = { "my-interface" = "1.0.0" }
[[interceptor]]
event = "my.namespace.request.do-thing"
action = "tool_execute_do_thing" # must match generated arm name
[[interceptor]]
event = "system.v1.lifecycle.capsule_loaded"
action = "on_capsule_loaded"
priority = 50 # lower fires first; default 100
[package]
name = "my-capsule"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
astrid-sdk = "0.5.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["v4"] }
#![deny(unsafe_code)]
#![deny(clippy::all)]
use astrid_sdk::prelude::*;
use astrid_sdk::schemars;
use serde::{Deserialize, Serialize};
#[derive(Default)]
pub struct MyCapsule;
#[capsule]
impl MyCapsule {
/// Run on first install. Set up VFS directories, write config, etc.
#[astrid::install]
pub fn on_install(&self) -> Result<(), SysError> {
astrid_sdk::fs::create_dir("home://data/my-capsule")?;
Ok(())
}
/// Run when capsule is upgraded to a new version.
#[astrid::upgrade]
pub fn on_upgrade(&self) -> Result<(), SysError> {
Ok(())
}
/// Handle an IPC event. The action name must match the `action` field
/// in Capsule.toml. InterceptResult controls the middleware chain:
/// Continue(payload) — next interceptor sees the (optionally mutated) payload
/// Final(payload) — chain stops, payload is the final result
/// Deny { reason } — chain stops, event is rejected
#[astrid::interceptor("on_some_event")]
pub fn on_some_event(&self, payload: <>) <InterceptResult, SysError> {
: serde_json::Value = serde_json::(&payload)?;
(InterceptResult::(payload))
}
}
#[astrid::tool("tool_name")]Sugar for #[astrid::interceptor] with tool IPC conventions baked in. Generates:
tool_execute_<tool_name> — handles tool.v1.execute.<tool_name>, publishes result to tool.v1.execute.<tool_name>.resulttool_describe — handles tool.v1.request.describe, returns all tool JSON schemasThe function receives a strongly-typed args struct and returns Result<String, SysError>:
#[derive(Debug, Default, Deserialize, schemars::JsonSchema)]
pub struct MyArgs {
/// The thing to process (shown to the LLM as the parameter description)
pub input: String,
}
#[astrid::tool("my_tool")]
pub fn my_tool(&self, args: MyArgs) -> Result<String, SysError> {
Ok(format!("processed: {}", args.input))
}
Add the Capsule.toml interceptors:
[[interceptor]]
event = "tool.v1.execute.my_tool"
action = "tool_execute_my_tool"
[[interceptor]]
event = "tool.v1.request.describe"
action = "tool_describe"
And capabilities:
ipc_publish = ["tool.v1.execute.*.result", "tool.v1.response.describe.*"]
ipc_subscribe = ["tool.v1.execute.my_tool", "tool.v1.request.describe"]
#[astrid::interceptor("action_name")]Raw event handler. Action name matches the action field in [[interceptor]]. Receives raw bytes, returns InterceptResult:
#[astrid::interceptor("my_handler")]
pub fn my_handler(&self, payload: Vec<u8>) -> Result<InterceptResult, SysError> {
Ok(InterceptResult::Final(b"done".to_vec()))
}
#[astrid::install] / #[astrid::upgrade]Lifecycle hooks. install runs once on astrid capsule install. upgrade runs on version update. Both have signature fn(&self) -> Result<(), SysError>.
use astrid_sdk::ipc;
ipc::publish("my.namespace.event", b"{\"key\":\"value\"}")?;
use astrid_sdk::ipc;
use uuid::Uuid;
let correlation_id = Uuid::new_v4().to_string();
let response_topic = format!("my.namespace.response.{correlation_id}");
ipc::subscribe(&response_topic)?;
ipc::publish("my.namespace.request", serde_json::to_vec(&serde_json::json!({
"response_topic": response_topic,
"data": "..."
}))?)?;
// Poll for response (500ms timeout)
let response = ipc::recv_bytes(&response_topic, 500)?;
ipc::unsubscribe(&response_topic)?;
Used to broadcast to all interceptors on a topic and collect their payloads:
use astrid_sdk::hooks;
let results: Vec<Vec<u8>> = hooks::trigger("tool.v1.request.describe", b"")?;
All file paths are UTF-8 strings. Scheme home:// maps to the calling principal's home directory.
use astrid_sdk::fs;
// Read
let content = fs::read_to_string("home://data/config.json")?;
// Write (requires fs_write capability)
fs::write("home://data/output.txt", b"hello")?;
// Directory
fs::create_dir("home://data/my-dir")?;
let entries = fs::read_dir("home://data/")?;
for entry in entries {
println!("{}", entry.file_name());
}
// Check existence
if fs::exists("home://data/file.txt")? { ... }
Per-capsule, per-principal key-value store. Scoped automatically by the kernel.
use astrid_sdk::kv;
kv::set("my-key", b"value")?;
let val = kv::get("my-key")?; // Option<Vec<u8>>
kv::delete("my-key")?;
let keys = kv::list_keys("prefix:")?;
use astrid_sdk::log;
log::info("capsule started")?;
log::warn("something unusual")?;
log::error("something failed")?;
// Or with format:
log::info(format!("processed {} items", count))?;
WIT files are installed to home://wit/ during astrid init. Use them to understand message schemas:
let session_wit = astrid_sdk::fs::read_to_string("home://wit/session.wit")?;
Available interfaces: session.wit, tool.wit, llm.wit, prompt.wit, context.wit, hook.wit, registry.wit, spark.wit, types.wit.
cargo build --target wasm32-unknown-unknown --release
# Output: target/wasm32-unknown-unknown/release/my_capsule.wasm
Requires the target:
rustup target add wasm32-unknown-unknown
Use the install_capsule system tool, or from the CLI:
astrid capsule install ./path/to/capsule
astrid capsule install @github-org/capsule-repo
| Error | Fix |
|---|---|
capability denied: fs_write | Add fs_write = ["home://..."] to [capabilities] |
ipc: topic not subscribed | Add topic to ipc_subscribe in [capabilities] |
interceptor action not found | Check action in [[interceptor]] matches generated arm name |
wasm trap: unreachable | Panic in guest — check for unwrap/expect on error paths |
boot validation: unsatisfied import | Install the capsule that exports the required interface |