Releases: astrid-runtime/sdk-rust
Release list
v0.7.0
Breaking
- Per-domain WIT host ABI. The single
astrid:capsule@0.1.0world has been split into per-domain frozen packages at@1.0.0(astrid:fs,astrid:ipc,astrid:kv,astrid:net,astrid:http,astrid:sys,astrid:process,astrid:uplink,astrid:elicit,astrid:approval,astrid:identity) plus Astrid-owned foundation I/O (astrid:io/{error, poll, streams}). Capsule authors must recompile against this SDK; the contract surface is wire-level incompatible with pre-0.7 capsules. Guest exports moved to per-export worlds inastrid:guest@1.0.0(interceptor,background,installable,upgradable). - Resource-backed handles.
ipc::Subscription,fs::File,process::Process,net::TcpStream,net::TcpListener,net::UnixListener,net::UdpSocket, andhttp::HttpStreamare now component-model resources with RAIIDropthat release the underlying kernel handle. Pre-migration code that carried opaqueu64handles (SubscriptionHandle,StreamHandle,ListenerHandle,BackgroundProcessHandle,HttpStreamHandle) and called free fns likeipc::unsubscribe,net::close,http::stream_close,process::kill(id)is gone. The new pattern: hold the typed handle in scope, call methods on it, let scope-end clean up. Explicitclosemethods remain where the spec defines them (e.g.HttpStream::close). - Typed
ErrorCodeenums per domain. Every host fn previously returningResult<T, String>now returns a domain-specific typedErrorCode(astrid:fs/host.error-code,astrid:net/host.error-code, …). The SDK preservesSysError::HostError(String)as the unified public error type — typed kernel errors are converted viaDebugformatting at the boundary (see the newhost_errhelper) so capsule code that branches onSysError::HostError(msg).contains("Timeout")continues to work without change. Pattern-matching on a typed variant requires calling the host fn directly throughastrid_sys::astrid::<domain>::host::*. identity::resolvereturnsOk(None)for unlinked. The new contract surfaces "no link" as a typedErrorCode::LinkNotFoundrather than the pre-migrationfound: boolflag. The SDK wrapper translates this toOk(None)so callers use idiomaticOptionsemantics.identity::unlinksimilarly returnsOk(false)when no link existed.fs::Metadatareshaped.Metadata.mtime(raw u64 seconds) is replaced by typedmodified() -> Result<SystemTime>/created()/accessed()reading optionalDatetimerecords.is_diris replaced by aFileTypeenum withRegular,Directory,Symlink,Othervariants. New:symlink_metadata,Metadata::mode.fs::FileHandle(nowfs::File). Returned byfs::File::open/fs::File::create/fs::File::open_mode. Wraps the resource; providesread_at/write_at/sync_data/sync_all/set_len/metadatamethods. Replaces ad-hocu64file handles.process::ProcessResultrenamed toprocess::Output;process::ExitInfoadded. The new contract carries structured(exit_code, signal)so capsules can distinguish SIGTERM-shutdown from SIGKILL-oom from normal-exit.Output::exit_code() -> i32is kept as a backward-compat accessor (returns-1for signal-killed).BackgroundProcessHandleis replaced byProcess(Drop-reaped) with methodsread_logs,write_stdin,close_stdin,signal,kill,wait,wait_with_output,os_pid. Newprocess::Commandbuilder surfaces thecwd,env, andstdinfields the new ABI adds.approval::request_decisionreturns typedDecision.approval::requeststill returnsbool(any approval variant =true). The newrequest_decisionexposes the fullDenied / Approved / ApprovedSession / ApprovedAlways / Allowanceladder.uplink::registertakes a string profile oruplink::Profile. NewProfileenum mirrors the WIT enum;registervalidates the string against canonical names (chat/interactive/notify/bridge) andregister_profiletakes the typed enum directly.net::TcpStream::connectaccepts both Unix-domain accepted streams and outbound TCP. The pre-migration distinction betweenStreamHandle(accepted) andTcpStream(outbound) collapses — the new contract uses a singleastrid:net.tcp-streamresource for both, and TCP-only options return aNotTcphost error when called on a Unix-domain stream.net::recv/net::send/net::try_recv/net::accept/net::close/net::bind_unix(free fns) are now methods onTcpStream/UnixListener. Newnet::bind_tcpexposes inbound-TCP listeners; newnet::udp_bindexposesUdpSocket.http::HttpStreamreshaped.stream_startreturns a singleHttpStream(resource-backed) carrying status, headers, andread_chunk/closemethods. The pre-migration(handle, status, headers)triple is gone.HttpStream::read_chunk()returnsOk(None)at EOF.ipc::Messagecarries typedPrincipalAttribution. Subscribers see the principal asVerified(...),Claimed(...), orSystemso cross-uplink trust decisions branch on the variant rather than parsing a string.Subscription::poll/Subscription::recvare methods; the free-fnipc::poll/ipc::recvare gone.hooks::triggerremoved. The pre-migrationwit_sys::trigger_hookhost fn is no longer part of the host ABI surface. User-middleware triggering is now an internal capsule-to-capsule concern.interceptors::pollremoved. Interceptor events are delivered throughastrid-hook-trigger(the existing guest export), not run-loop subscriptions.interceptors::bindings()remains for enumeration / debugging.env::varreturnsOk("")for missing keys;env::var_optis the new disambiguator. The host fn now returnsResult<Option<String>>; the SDK keepsvarreturningStringfor source compatibility (empty for missing) and addsvar_optfor the option semantics.
Added
host_errhelper. Single point of conversion from any per-domainErrorCode(or otherDebughost failure) intoSysError::HostError(String).kv::list_keys_page+kv::cas. Wrappers for the new paginated key listing and atomic compare-and-swap host fns.kv::get_bytes_opt/kv::get_json_opt. Disambiguate "missing key" from "empty value"; the existingget_bytes/get_jsoncontinue to collapse both into empty.time::sleep/time::monotonic/runtime::random_bytes. Wrap the newastrid:sysprimitives (sleep-ns, clock-monotonic-ns, random-bytes).process::Commandbuilder. Mirrorsstd::process::Commandfor the newenv/cwd/stdinfields onspawn-request.net::lookup_host. Wraps the new DNS-with-airlock host fn.net::TcpListener+net::bind_tcp. Inbound TCP server posture for self-hosted webhook receivers, gRPC endpoints, etc.net::UdpSocket+net::udp_bind. Unconnected and connected-mode UDP.net::TcpStream::peek/set_keepalive/set_linger/set_reuseaddr/set_hop_limit. Full surface of the newtcp-streamresource.net::TcpStream::shutdown(Shutdown::Read | Write | Both). Mirrorsstd::net::Shutdown.fs::append/fs::create_dir_all/fs::copy/fs::rename/fs::canonicalize/fs::read_link/fs::hard_link/fs::symlink_metadata/fs::remove_dir_all. Bring the SDK to feature parity withstd::fsfor the operations the new VFS surfaces.
Notes
- The
request_responsehelper is unchanged in semantics; the parallel TS SDK is migrating to match.
With many thanks from the following Astrinauts 🚀
- Joshua J. Bouw
v0.6.1
Added
ipc::publish_as(topic, payload, principal)andipc::publish_json_as. SDK helpers for uplink principal propagation. Without these, an uplink that fans inbound socket messages onto the bus stamps every published event with its own (capsule-owner) principal — the kernel's Layer 5/6 enforcement preamble would then seecaller = defaultregardless of which agent the operator was impersonating via the local CLI, andastrid agent switch alice; astrid agent disable bobwould silently succeed as default. Uplink capsules (CLI proxy, future Telegram/Discord bridges) can now claim a principal other than their own at the IPC boundary. Gated host-side by anuplink: boolcapability — non-uplink capsules calling these get a clear error. (#37)
Changed
-
astrid-sdk/wit/astrid-contracts.witis now sourced fromunicity-astrid/wit. The contract types (astrid_sdk::contracts::*) were generated from a hand-maintained bundled WIT that drifted from the canonicalunicity-astrid/witrepo — 9 of 17 interfaces present, 33 of 71 records. With sdk-js coming online, having two SDKs generate types from independent WIT copies would have meant cross-SDK contract drift the moment the canonical repo added or changed a record. Now:astrid-sdk/wit/astrid-contracts.witbecomes a sync artifact, written byscripts/sync-contracts-wit.shfromcontracts/interfaces/*.wit. Samecargo packagerationale asastrid-capsule.wit: file has to physically live in the crate dir, but the submodule is authoritative.- The sync transforms the per-package canonical layout (
package astrid:context@1.0.0;etc.) into the single-package bundled layout thewit_events!macro consumes (package astrid:contracts@1.0.0;). Cross-packageuse astrid:types/types.{…};references become same-packageuse types.{…};. - New CI job step runs
scripts/sync-contracts-wit.sh --check. Drift fails CI. wit_events!now emits onepub mod <interface> { … }per WIT interface instead of flat top-level types. Required because the canonical interface set has same-named records across packages (e.g.agent::response,approval::response,elicit::response); without per-interface namespacing they collided. Type references across interfaces are emitted as fully-qualifiedsuper::<iface>::<Name>paths.- Breaking (technical): if anything was using flat
astrid_sdk::contracts::CompactRequest, it's nowastrid_sdk::contracts::context::CompactRequest. No known consumer today — capsule code hand-rolls equivalent structs and is being migrated separately. (#39)
-
astrid-sys/wit/astrid-capsule.witis now sourced fromunicity-astrid/wit. The host ABI lived as an unsynced copy in three repos (kernel, sdk-rust, sdk-js); PRunicity-astrid/wit#3madeunicity-astrid/witthe canonical home (newhost/astrid-capsule.witpath).- This repo's
contracts/submodule pointer is bumped to that commit. astrid-sys/wit/astrid-capsule.witbecomes a sync artifact maintained byscripts/sync-host-wit.sh. Can't go away entirely becauseastrid-syspublishes to crates.io andcargo packageonly bundles files inside the crate dir — but the submodule is authoritative.- New CI job
wit-syncrunsscripts/sync-host-wit.sh --checkon every push/PR. Drift betweencontracts/host/andastrid-sys/wit/fails CI. (#38)
- This repo's
Fixed
cargo publish -p astrid-sysnow works. The WIT file lived outside the crate directory (../wit/astrid-capsule.wit), whichcargo packagestrips andcargo publishrejects. WIT files moved into their respective crate dirs (astrid-sys/wit/,astrid-sdk/wit/), rootwit/directory removed,includedirective added toastrid-sys/Cargo.toml. (#36)
With many thanks from the following Astrinauts 🚀
- Joshua J. Bouw
v0.6.0
Breaking
- WASM engine migrated from Extism to wasmtime Component Model. Capsules must be rebuilt targeting
wasm32-wasip2. The#[capsule]macro now generatesimpl Guest+export!()instead ofextern "C"exports. The ABI is completely different — existing.wasmbinaries will not load. (#27) - All
&[u8]parameters changed to&str. IPC topics, KV keys, filesystem paths, and uplink params now require UTF-8 at compile time (was runtime validation). (#31) - Approval API simplified to OS permission model.
approval::request(action, resource) -> Result<bool>replaces the 3-param version withRiskLevel. RemovedRiskLevel,ApprovalDecision,ApprovalResult. Capsules declare what they want; the kernel classifies risk. (#31, astrid-runtime/astrid#641) - Interceptor errors halt the chain. The
#[capsule]macro returns"deny"on error (was"error"which the kernel silently treated as"continue"). (#27) CallerContextfields corrected.session_id→source_id(capsule UUID), addedtimestamp. (#27)identity::linkreturnsResult<()>. WasResult<Link>with emptylinked_atfield. (#27)
Removed
ipc::publish_bytes,ipc::publish_msgpack,ipc::poll_bytes,ipc::recv_bytes— use typedpublish/publish_json/poll/recv. (#31)uplink::send_bytes— useuplink::send. (#31)net::bind_unix(path)— usenet::bind_unix()(no path arg). (#31)hooks::trigger(&[u8])— nowtrigger(&str) -> String. (#31)process::ProcessRequest,SubscriptionHandle::as_bytes(),host_result.rs,cronmodule. (#27, #31)extism-pdkandrmp-serdedependencies. (#27, #31)
Added
wit_events!proc macro. Reads a.witfile and generates Rustpub struct/pub enumdefinitions for every named WIT record and enum, withSerialize + Deserialize + PartialEq + Clone + Debugderives and///doc comments preserved. Capsule authors write WIT once — the same file feedswit_events!()for Rust types and the core'swit-parserfor JSON Schema extraction. Zero type duplication. (#32)- Serde derives on all WIT-generated types.
astrid-sysnow passesgenerate_unused_types: trueandadditional_derives: [serde::Serialize, serde::Deserialize, PartialEq]towit_bindgen::generate!. (#32) - Typed IPC poll/recv.
ipc::poll()/ipc::recv()returnPollResult { messages: Vec<Message>, dropped, lagged }. (#31) - HTTP Response exposes status + headers.
Response::status(),Response::headers(),Response::is_success(). (#31) log::trace(). All log functions now return()(wasResult). (#31)- Component Model test capsule.
examples/test-capsule/validates the full SDK→macro→WIT pipeline, includingwit_events!generated types used inipc::publish_json(). (#27, #32) - Mandatory WIT exports.
#[capsule]macro generates all 4 guest exports with no-op stubs for unused ones. Solves astrid-runtime/astrid#638. (#27)
Changed
astrid-sysuseswit_bindgen::generate!instead ofextism_pdkFFI. (#27)SysError::HostErrorwrapsString(wasextism_pdk::Error). (#27)- Handle types internally wrap
u64with consistentid()accessor. (#31) http::Responsefields private with accessor methods. (#31)interceptors::pollhandler receives typed&ipc::PollResult. (#31)- State not persisted on tool error. Install/upgrade/run errors logged. (#27)
#![forbid(unsafe_code)]onastrid-sdk. (#27)
With many thanks from the following Astrinauts 🚀
- Joshua J. Bouw
v0.5.3
Added
host_resultmodule — canonical decoder for theHostResultwire format (0x00Ok + payload /0x01Err + message). All host functions that return data use this encoding instead of WASM traps for recoverable errors. (#25)
Changed
fs::read,fs::metadata,fs::exists,fs::read_dir— decodeHostResultfrom kernel. File-not-found, permission denied, and VFS errors returned asErr(SysError)instead of crashing the capsule. (#25)kv::get_bytes,kv::list_keys,kv::clear_prefix— decodeHostResultfrom kernel. (#25)
Fixed
- Interceptor chain payload corruption — interceptors returning
Result<(), SysError>serialized()asb"null"(4 bytes), overwriting the chain payload for subsequent interceptors. Now returns empty bytes, preserving the original payload. (#25)
With many thanks from the following Astrinauts 🚀
- Joshua J. Bouw
v0.5.2
v0.5.1
v0.5.0
Changed
- Tools are now IPC convention.
#[astrid::tool]macro rewired to generate interceptor arms inastrid_hook_triggerinstead of a separateastrid_tool_callWASM export. Each tool generates atool_execute_<name>action (deserializesToolExecuteRequest, calls handler, publishes result totool.v1.execute.<name>.resultvia IPC) and a sharedtool_describeaction (returns all tool schemas as JSON). Capsule code using#[astrid::tool]compiles unchanged — only the generated glue changes.
Added
- WIT interface definitions for all standard contracts: llm, session, spark, context, prompt, tool, hook, registry, types (
wit/directory)
Removed
astrid_tool_callWASM export — replaced bytool_execute_<name>interceptor arms inastrid_hook_triggerastrid_export_schemasWASM export — replaced bytool_describeinterceptor armastrid_cron_triggerWASM export — dead code, cron was never implemented
With many thanks from the following Astrinauts 🚀
- Joshua J. Bouw
v0.4.0
Added
fsmodule:Metadata,DirEntry,ReadDir,FileTypetypes mirroringstd::fs.read_dir()returns an iterator,metadata()returns a typed struct with.len(),.is_dir(),.is_file(),.modified(). (astrid-sdk)httpmodule: typedRequestbuilder (get/post/put/delete/header/body/json) andResponsewith.bytes()/.text()/.json().send()andstream_start()take&Request. (astrid-sdk)netmodule:recv/try_recv/send/try_acceptwithRecvError/TryRecvError/SendErrormirroringstd::sync::mpsc.NetReadStatuswire format with status-byte prefix replaces sentinel hack. (astrid-sdk)impl std::error::ErrorforRecvError,TryRecvError,SendError. (astrid-sdk)#[capsule(state)]attribute for explicit stateful opt-in alongside&mut selfauto-detection. (astrid-sdk-macros)
Changed
time::now_ms() -> Result<u64>replaced bytime::now() -> Result<SystemTime>usingstd::time::SystemTimedirectly. (astrid-sdk)logfunctions takeimpl Displayinstead ofimpl AsRef<[u8]>for messages,&strfor level. (astrid-sdk)fsmodule extracted to its own file (fs.rs). (astrid-sdk)- Handle types (
ListenerHandle,StreamHandle,BackgroundProcessHandle) inner fields are now private. (astrid-sdk)
Removed
read(),write(),poll_accept()fromnetmodule — replaced byrecv/send/try_accept. (astrid-sdk)request_bytes()fromhttpmodule — replaced bysend(&Request). (astrid-sdk)now_ms()fromtimemodule — replaced bynow(). (astrid-sdk)
Fixed
SysErrorconversion in macro-generated dispatch code —?on method calls now mapsSysErrorexplicitly instead of relying on unimplementedFrom<SysError> for WithReturnCode<Error>. (astrid-sdk-macros)net::readno longer traps on peer disconnect — usesNetReadStatuswire format instead of WASM trap. (astrid-sdk)
With many thanks from the following Astrinauts 🚀
- Joshua J. Bouw
v0.3.0
Added
- Doc comments as tool/capsule descriptions —
///on#[astrid::tool]methods becomemetadata.descriptionin the generated JSON schema. Doc comments on the#[capsule]impl block become the capsule-level description. Full doc text (all paragraphs) preserved for LLM context. (astrid-sdk-macros) - Inline mutable flag —
#[astrid::tool("name", mutable)]or#[astrid::tool(mutable)](name inferred from method). Standalone#[astrid::mutable]still works for backward compatibility. (astrid-sdk-macros)
Changed
- Schema export format now returns
{ "tools": {...}, "description": "capsule doc" }with backward compatibility when no capsule-level doc comment is present. (astrid-sdk-macros)
With many thanks from the following Astrinauts 🚀
- Joshua J. Bouw
v0.2.2
What's Changed
- feat(sdk): add streaming HTTP API by @joshuajbouw in #4
Full Changelog: v0.2.1...v0.2.2