# astrid-sdk: the Rust SDK for building Astrid capsules

> astrid-sdk gives Rust developers typed access to Astrid kernel services from a WASM capsule. It is a small, opinionated surface with a proc macro doing the ABI work, and it is only useful if you are already targeting the Astrid runtime.

**astrid-runtime/sdk-rust** — Rust SDK for building Astrid capsules.

- Repository: https://github.com/astrid-runtime/sdk-rust
- Stars: 4,265 · Forks: 10
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/astrid-runtime-sdk-rust

## What astrid-sdk is for, and who should care

The README frames the project in operating-system terms: "In the OS model, this is the standard library for user-space processes." That is a precise description of the scope. astrid-sdk is not a general Rust library you drop into an application. It is the layer a capsule author uses to reach kernel services, and a capsule is the Astrid equivalent of a user-space process.

The audience is therefore narrow and identifiable: developers who have decided to extend an Astrid host and want to write that extension in Rust. The README states the dependency surface directly, saying capsule authors depend on astrid-sdk and serde and that everything else is handled. That claim is the whole value proposition. If you are not building for Astrid, nothing here applies to you, and the module list (fs, net, process, env, time, log, ipc, kv, http, hooks, cron, uplink, identity, approval, runtime) is not a set of general-purpose utilities you can lift out.

The repository is a Cargo workspace with three members: astrid-sdk, astrid-sdk-macros, and astrid-sys. The last push was on 2026-09-15, and the most recent tagged release listed is v0.7.0 from 2026-05-25. The workspace manifest declares version 0.7.2, so the crate versions in the tree run ahead of the newest tag. That gap matters when you pin a dependency.

## How the crate split and the #[capsule] macro work

Three crates, three jobs. astrid-sdk is the safe surface, and the README notes it mirrors the std module layout before adding Astrid-specific modules. astrid-sys is described as the raw WASM-to-host FFI bindings, "the syscall table," with every parameter crossing as Vec<u8>, and the README says plainly that you should not use it directly. astrid-sdk-macros holds the #[capsule] proc macro.

The macro is where the real mechanism lives. According to the README, #[capsule] generates WASM ABI exports from annotated impl blocks: tool dispatch, command routing, hook handlers, cron handlers, and install/upgrade entry points. It also generates the extern "C" exports, JSON serialization across the boundary, tool schema generation, and dispatch routing. In the quick-start example, a plain Rust method annotated with #[astrid::tool] takes a deserializable argument and returns a Result, and the macro turns that into a callable boundary.

That design has a consequence worth naming. Every call across the boundary is JSON-serialized, and astrid-sys passes raw bytes as Vec<u8>. So the ergonomics of typed Rust methods sit on top of a serialization step you do not see. For small argument structs that is fine. For anything large or hot, the serialization is a cost the abstraction hides rather than removes. The workspace lints make the intent explicit: unsafe_code is denied at the workspace level, and arithmetic_side_effects is denied in Clippy. Capsule authors are expected to stay in safe Rust.

## Installing astrid-sdk and writing a first capsule

The README gives the dependency block for a capsule's Cargo.toml. Note the version in that example, because it does not match the workspace manifest:

```toml
[dependencies]
astrid-sdk = "0.2"
serde = { version = "1.0", features = ["derive"] }
```

With the dependency in place, the README shows a capsule as a struct with a #[capsule] impl block. The example below is copied from the README, including the env and http calls and the format! URL construction:

```rust
use astrid_sdk::prelude::*;

#[derive(Default)]
pub struct MyTools;

#[capsule]
impl MyTools {
    #[astrid::tool]
    fn search_issues(&self, args: SearchArgs) -> Result<SearchResult, SysError> {
        let token = env::var("GITHUB_TOKEN")?;
        let resp = http::get(&format!(
            "https://api.github.com/search/issues?q={}", args.query
        ))?;
        // ...
    }
}
```

What you should see after building is a WASM artifact, not a native binary. Capsules compile to wasm32-wasip2, and the README gives two commands: add the target with rustup target add wasm32-wasip2, then cargo build --target wasm32-wasip2 --release. The README does not document how the resulting artifact is loaded into a host, so treat that step as outside this repository.

For working on the SDK itself rather than a capsule, the README lists cargo build --workspace, cargo test --workspace -- --quiet, and cargo clippy --workspace --all-features -- -D warnings. The examples/test-capsule directory is excluded from the workspace because it targets wasm32-unknown-unknown rather than wasm32-wasip2, and the root Cargo.toml says to build it with cd examples/test-capsule && cargo build --target wasm32-unknown-unknown. That target mismatch between the example and the documented capsule target is a real inconsistency in the tree.

## The chrono feature flag is a build constraint you inherit

One comment in the workspace manifest deserves attention because it explains a failure mode rather than a preference. The workspace pins chrono with default-features = false and only the serde feature enabled. The manifest comment states the reason: the default features would otherwise pull in clock, which drags wasm-bindgen and js-sys into the build on wasm32-unknown-unknown and produces __wbindgen_placeholder__ imports that wit-component refuses to emit.

If you have hit an opaque wit-component error while building a WASM component, this is the kind of cause that costs an afternoon. The manifest also states the workaround for the missing clock: records keep DateTime<Utc> shapes through the serde feature, and capsule code reads wall-clock time via astrid_sdk::time. So the SDK does not remove the need for time, it routes it through its own module.

The practical implication is that a capsule author who enables chrono's default features, or who depends on a crate that does, can reintroduce the exact imports the workspace went out of its way to avoid. Nothing in the README warns about this. It is only visible in the manifest comment, which is a thin place to document a build-breaking interaction.

## Where astrid-sdk is the wrong choice

The strongest limitation is scope. This is an SDK for one runtime. The README's own framing, a standard library for user-space processes, means the crate is meaningless without the kernel it talks to. If your goal is a portable WASM component that runs under wasmtime, Spin, or a bespoke host, astrid-sdk gives you nothing transferable, and its astrid-sys syscall table is specific to the Astrid host.

Versioning is the second problem. The README's dependency example says astrid-sdk = "0.2", the newest tag in the release list is v0.7.0, and the workspace manifest declares 0.7.2. Any of those could be correct for a given host, but they cannot all describe the same thing, and the README does not explain the discrepancy. For a pre-1.0 crate this is expected churn, but it means you should read the tag you intend to use rather than the README's snippet.

The third limitation is the serialization boundary. Because arguments and results cross as JSON over a byte-oriented syscall table, a capsule with high-frequency tool calls pays serialization on every call. The README does not discuss throughput or batching, and no performance figures appear anywhere in the documentation. If your workload is a tight loop of small calls, that is an architectural question to settle before committing.

Finally, the README does not document rollback, host loading, or how install and upgrade entry points are invoked, even though the macro is said to generate them.

## How it differs from hand-written WASM component bindings

The obvious alternative is writing the bindings yourself: define a WIT interface, run a generator such as wit-bindgen, and implement the generated traits directly. The difference in approach is where the boilerplate lives. With wit-bindgen you own the WIT file and the mapping from interface types to Rust types, and you get an interface that any conforming host can implement. With astrid-sdk, the README states that the #[capsule] macro generates the extern "C" exports, the JSON serialization, the tool schema, and the dispatch routing, and the crate list includes wit-parser as a workspace dependency. You trade portability for not writing that layer.

That trade is defensible when the host is fixed. An Astrid capsule that uses http::get, env::var, and the kv module gets typed access without touching FFI, and the workspace denies unsafe code so the generated layer is the only place raw pointers could appear. A hand-rolled binding gives you the same typed surface but you maintain it, and every host-side interface change is your problem.

The second alternative is simply not using WASM. If your extension can run in-process, a plain Rust library with an ordinary trait interface avoids the serialization boundary entirely. astrid-sdk exists because the capsule is isolated and talks to a kernel over a syscall table; if you do not need that isolation, the SDK's central mechanism is overhead rather than a feature.

## Licence, MSRV, and what upgrades cost

The repository ships LICENSE-MIT and LICENSE-APACHE, and both the README and the workspace manifest state the terms as MIT OR Apache-2.0. The README badge text says the same. That is the standard Rust dual licence, and it means downstream users can pick either set of terms. This is a description of what the repository declares, not legal advice; if your organisation has rules about which of the two it accepts, that is a question for whoever handles licensing.

The workspace pins rust-version = "1.94" and edition = "2024", and the README badge repeats the 1.94 MSRV. Edition 2024 and a recent MSRV mean you cannot build this on an older toolchain, which matters if your CI image lags. The workspace also sets resolver = "2" and enables Clippy's pedantic lint group at warn level, so contributing to the SDK itself is held to a stricter standard than consuming it.

Upgrade cost is dominated by the pre-1.0 version line. Three releases are listed between 2026-04-10 and 2026-05-25 (v0.6.0, v0.6.1, v0.7.0), and the manifest has moved past the newest tag to 0.7.2. The CHANGELOG.md at the repository root is the place to look before bumping, and the README does not summarise breaking changes. Because the SDK mirrors std's module layout, code that stays inside fs, net, env, and time is likely to move less than code using the Astrid-specific modules.

## Conclusion

Adopt astrid-sdk if you are writing a capsule for an Astrid host and want typed syscalls plus generated WASM exports instead of hand-written FFI. Do not adopt it if you need a general-purpose WASM component framework or a stable 1.0 ABI: the published dependency example in the README says astrid-sdk = "0.2" while the workspace manifest is at 0.7.2, so verify which version the host you target actually loads before you write code against it.

## FAQ

### What is astrid-sdk?

It is the Rust SDK for building Astrid capsules. The README describes it as the standard library for user-space processes in the Astrid OS model, giving capsule authors typed access to kernel services such as filesystem, IPC, networking, storage, approval, and scheduling.

### What is the difference between astrid-sdk and astrid-sys?

astrid-sdk is the safe surface capsule authors use, with modules like fs, net, ipc, and kv. astrid-sys holds the raw WASM-to-host FFI bindings, described in the README as the syscall table, where every parameter crosses as Vec<u8>, and the README says you should not use it directly.

### How do I install astrid-sdk in a capsule?

Add astrid-sdk and serde to the capsule's Cargo.toml dependencies, then build for the wasm32-wasip2 target. The README's dependency example pins astrid-sdk = "0.2", while the workspace manifest is at 0.7.2, so check which version your host expects.

### What does the #[capsule] macro generate?

According to the README, it generates the WASM ABI exports from annotated impl blocks: extern "C" exports, JSON serialization across the boundary, tool schema generation, and dispatch routing, plus command routing, hook handlers, cron handlers, and install/upgrade entry points.

### Which Rust version does astrid-sdk require?

The workspace manifest sets rust-version = "1.94" and edition = "2024", and the README badge states the same MSRV of 1.94. Older toolchains will not build the workspace.

### What licence is astrid-sdk released under?

The repository contains LICENSE-MIT and LICENSE-APACHE, and both the README and the workspace manifest declare the terms as MIT OR Apache-2.0. The copyright line names Joshua J. Bouw and Unicity Labs.

## Sources

- [astrid-runtime/sdk-rust on GitHub](https://github.com/astrid-runtime/sdk-rust)
- [Issues](https://github.com/astrid-runtime/sdk-rust/issues)
- [License: Apache-2.0](https://github.com/astrid-runtime/sdk-rust/blob/main/LICENSE)
- [README](https://github.com/astrid-runtime/sdk-rust/blob/main/README.md)
- [Releases](https://github.com/astrid-runtime/sdk-rust/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/astrid-runtime-sdk-rust
