Skip to content

Releases: astrid-runtime/astrid

v0.10.4

Choose a tag to compare

@github-actions github-actions released this 20 Jul 12:18
Immutable release. Only release title and notes can be modified.
v0.10.4
b6bf5d1

Removed

  • Capsule manifests no longer embed the agent-specific [[skill]]
    protocol.
    Skills remain a user-space concern that agent runtimes can
    discover over IPC or advertise through product registries without teaching
    the Astrid kernel or generic capsule archive about an AI workflow format.
    Builders still preserve files under assets/ and legacy skills/ as opaque,
    symlink-free capsule data without generating manifest metadata.

Fixed

  • astrid capsule check no longer treats embedded Rust examples as live
    tools.
    The scanner now parses Rust attributes instead of matching source
    text, so scaffold templates and documentation strings cannot create phantom
    unrouted-tool failures.
  • Interrupted immutable-release drafts recover without replacing uploaded
    assets.
    Release uploads are sequential, and reruns authenticate every
    existing payload and signature before adding only the missing files. Closes
    #1285.

Install

From source (requires Rust 1.95+):

cargo install astrid

Pre-built binaries:
Download the archive for your platform, extract, and add to PATH:

tar xzf astrid-*-$(uname -m)-*.tar.gz
sudo mv astrid-*/astrid astrid-*/astrid-daemon astrid-*/astrid-build astrid-*/astrid-emit /usr/local/bin/

Astrid Runtime does not bundle a product distro. To compose it with a
distro you trust, run:

astrid init --distro <name, @org/repo, path, or .shuttle>

With many thanks from the following Astrinauts 🚀

  • Joshua J. Bouw

v0.10.2

Choose a tag to compare

@github-actions github-actions released this 19 Jul 17:21
Immutable release. Only release title and notes can be modified.
v0.10.2
fec3ff9

Fixed

  • astrid mcp serve now waits for the authenticated principal's broker to
    answer before exposing MCP stdio.
    Non-default capsule views intentionally
    warm after global daemon readiness; an immediate client tools/list could
    previously publish before the broker subscribed, lose the non-durable bus
    request, and hang for the full 55-second reply deadline. Startup now proves
    the generic broker path with an idempotent tools/list probe and reissues it
    when the principal's capsules_loaded signal arrives, without hardcoding a
    product capsule name.

Install

From source (requires Rust 1.95+):

cargo install astrid

Pre-built binaries:
Download the archive for your platform, extract, and add to PATH:

tar xzf astrid-*-$(uname -m)-*.tar.gz
sudo mv astrid-*/astrid astrid-*/astrid-daemon astrid-*/astrid-build astrid-*/astrid-emit /usr/local/bin/

Astrid Runtime does not bundle a product distro. To compose it with a
distro you trust, run:

astrid init --distro <name, @org/repo, path, or .shuttle>

With many thanks from the following Astrinauts 🚀

  • Joshua J. Bouw

v0.10.1

Choose a tag to compare

@github-actions github-actions released this 17 Jul 15:51
Immutable release. Only release title and notes can be modified.
v0.10.1
4771bab

Fixed

  • Runtime service discovery is product-neutral. CLI and unique compatible
    model/session interfaces authenticate live source IDs. Closes #1267.

Install

From source (requires Rust 1.95+):

cargo install astrid

Pre-built binaries:
Download the archive for your platform, extract, and add to PATH:

tar xzf astrid-*-$(uname -m)-*.tar.gz
sudo mv astrid-*/astrid astrid-*/astrid-daemon astrid-*/astrid-build astrid-*/astrid-emit /usr/local/bin/

Astrid Runtime does not bundle a product distro. To compose it with a
distro you trust, run:

astrid init --distro <name, @org/repo, path, or .shuttle>

With many thanks from the following Astrinauts 🚀

  • Joshua J. Bouw

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 17 Jul 07:19
Immutable release. Only release title and notes can be modified.
v0.10.0
7d3f0c2

Added

  • Native process requests now honor their declared environment and working
    directory.
    Every spawn tier starts from a small host environment allowlist,
    applies validated guest variables, rejects session-token injection, confines
    relative working directories to the capsule workspace, and can resolve
    HOME=home://... or a home://... working directory after host-side policy
    checks. The native child receives the resolved path, but the process host API
    does not return it directly. Principal-home reads require an
    explicit read capability; the OS sandbox makes writable only the capsule's
    declared home:// write roots for that principal. Recv-driven capsules now install the same
    per-principal home/tmp overlays as interceptor invocations.

  • Capsule installation has an explicit non-interactive configuration path.
    astrid capsule install --yes resolves lifecycle fields from repeatable
    --var KEY=VALUE inputs, ASTRID_VAR_<KEY> environment variables, or
    manifest defaults, and fails instead of silently choosing an enum value or
    empty secret when a required value is absent. Secret automation can use the
    environment form so credentials do not appear in process arguments.

  • Signed runtime release channels and immutable manifests. Every release records the exact
    runtime and WIT source commits, release-workflow identity, and all four platform archives with
    their sizes, BLAKE3 digests, SHA-256 compatibility digests, and Sigstore bundle
    names. Protected manual promotion advances signed, expiring stable, dev,
    or nightly pointers only to those immutable releases. astrid update
    authenticates the pointer, manifest, and archive; rejects generation rollback,
    same-generation equivocation, expiry, digest drift, and workflow-identity
    drift; and follows deliberate higher-generation rollbacks for self-managed
    installations. An opt-in daily nightly train reuses the same tag-bound signer,
    remains disabled by default, and still requires protected release and channel
    approvals. A merge to main never publishes a release or moves a pointer.

  • Operators can enforce a distro for astrid init.
    ASTRID_ENFORCED_DISTRO supplies the distro source and rejects CLI attempts
    to override it. Standalone astrid init requires an explicit --distro;
    Astrid Runtime never chooses a product distro. Closes #1253.

  • Passive content-addressed capability-registry primitives. Astrid now has
    exact capability IDs, typed content-bound references, immutable registered
    definitions, deterministic BLAKE3 semantic digests and canonical registry
    manifests. Existing profile persistence, wildcard evaluation, bootstrap,
    socket and wire behavior remain unchanged. Closes #1233. Refs #1228.

  • Distro init can grant exactly installed capsules to an explicit target.
    astrid --principal <operator> init --target-principal <target> --grant-capsules ensures the runtime daemon, verifies the operator has
    agent:modify authority over an existing target before provisioning, and
    applies the installed set through the shared admin.agent.modify path.
    Distro capsules require a concrete released version or tag; identity and
    declared version are checked before install mutation, and locks record the
    version and WASM hash that actually landed. Fresh-lock reuse rehashes the
    installed content blob before names become grants. Concurrent provisioning
    of one target is rejected, and recovery commands preserve the operator
    identity. The target defaults to the process principal when omitted; no
    principal name receives special treatment.
    Signed .shuttle grant composition remains deferred and fails explicitly.
    Closes #1195.

  • Capability-registry revision 1 now has fixed semantics and BLAKE3 digest
    vectors.
    All 51 kernel entries bind scope, target kinds, delegability,
    privileged status and provenance. Kernel/admin request mappings and the
    current capsule-side secondary enforcement constants resolve through the
    registry in tests, while the complete role partition is frozen independently.
    Authorization and persisted state remain unchanged. Closes #1235. Refs #1228
    and #1233.

Changed

  • Device key IDs now use BLAKE3. The short per-device handle is derived from
    the first eight bytes of BLAKE3(pubkey_hex_bytes). Profile loading already
    treats the stored key_id as informational and re-derives it from the public
    key, so existing local profiles self-heal; device-scoped bearer sessions must
    authenticate again after upgrading.

  • Self-managed updates now authenticate the release publisher before any
    archive is extracted or installed.
    The updater requires a Sigstore bundle
    for the exact archive bytes, verifies it with fresh public-good trust
    material, and pins the certificate to Astrid's release workflow, repository,
    tag, and GitHub Actions issuer. Only an authenticated archive can enter the
    independent BLAKE3 integrity stage; missing, duplicated, malformed, or
    mismatched evidence fails closed with a distinct publisher-authentication or
    integrity error. Homebrew and Cargo installs remain delegated to their
    package managers, and the signed SHA-256 compatibility manifest remains
    available to downstream tooling. Before publishing, release automation now
    requires both Cosign and the updater's native production verifier to accept
    every generated archive and bundle pair. Existing v0.9.x self-updaters cannot
    enforce the new publisher policy retroactively; it applies from the first
    release containing this updater onward. Closes #1250.

  • Astrid-owned identifiers now use domain-separated BLAKE3. Invite and
    pair-device token stores carry an explicit schema and invalidate
    legacy SHA-256 records that cannot be rehashed without their raw secrets;
    newly issued bearer tokens use type-specific astrid_inv_ and
    astrid_pair_ prefixes, while fingerprints use an explicit blake3: label.
    CLI key metadata self-heals from the retained public key. Public-key
    fingerprints share a typed derivation primitive, MCP binary pins now carry
    an honest blake3: label, and gateway env-write logs no longer expose
    dictionary-testable fingerprints of low-entropy values. External SHA-based
    protocols such as SRI, Git, and registry checksums remain unchanged. Closes
    #1247.

  • Astrid release archives now use BLAKE3 as their primary integrity
    manifest.
    Release automation publishes, signs, and attests
    BLAKE3SUMS.txt, while retaining a signed SHA256SUMS.txt compatibility
    manifest for Homebrew and existing downstream tooling. Self-managed updates
    require a strict lowercase BLAKE3 entry and reject absent, malformed,
    duplicate, or SHA-only manifests. Existing v0.9.x installations can still
    cross the boundary through the compatibility manifest. External protocol
    requirements remain unchanged. Closes #1249.

  • Project runtime state now uses one validated workspace layout. The CLI
    and daemon default to .astrid, while distributions can select another safe
    relative directory name through --workspace-state-dir or
    ASTRID_WORKSPACE_STATE_DIR. Config, capsule installation and discovery,
    kernel boot, gateway source checks, hooks, and WIT garbage collection share
    the selected layout and reject persistent symlink/reparse redirection anywhere
    in workspace capsule and hook trees before reading them. They never scan both
    project roots. CLI uplinks, including long-lived MCP reconnects, and
    project-sensitive management reads reject a
    daemon booted for a different project or layout; daemon stop remains
    available as a recovery operation.

  • Runtime E2E now stages the pinned Unicity AOS monorepo. The workflow
    preserves the AOS Cargo workspace outside the core checkout and supplies
    compatibility directory aliases for the existing runtime harness, replacing
    seven historical per-capsule repository checkouts. Closes #1220.

  • Astrid Runtime no longer silently selects a product distro. Standalone
    astrid init and astrid distro apply now require an explicit distro, first-run
    bootstrap only creates runtime state, self-update refreshes only an already locked
    distro, and agent creation no longer carries a product-distro default. Closes #1214.

  • Removed residual product distro policy from Astrid Runtime. Standalone
    distro installation now requires an explicit @owner/repo, URL, local
    manifest, or signed .shuttle input; it no longer manufactures a source URL
    from a bare distro name. Background update does not reconstruct a source from
    a lockfile identifier, and runtime guidance, examples, and mutable repository
    links use neutral/current values while published WIT and package identities
    remain unchanged. Closes #1216.

  • Daemon status and shutdown now use one typed runtime-control path.
    astrid status propagates daemon connection and response failures instead of
    reporting success, and astrid stop sends its shutdown request through the
    authenticated, correlated uplink client. Closes #1210.

  • CLI runtime-control requests now use the shared typed uplink client.
    astrid status, ps, who, and doctor no longer rebuild socket frames or
    parse response envelopes themselves; they use astrid-uplink for the same
    authenticated, principal-bound astrid.v1 control contract that external
    uplinks will consume. Closes #1208.

  • Removed the product-branded README image. Astrid's repository front door
    now presents the runtime without Unicity artwork. Closes #1204.

  • Astrid's mutable repository identity now points to astrid-runtime. Current
    documentation links, release automation, the WIT submodule origin, and the
    self-update default use the Astrid Runtime organization, while published crate,
    WIT, tag, artifact, and binary identifiers remain compatible. Closes #1202.

  • Astrid's MCP bridge now uses RMCP 2.2. The client and ser...

Read more

Astrid stable channel

Astrid stable channel Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Jul 06:22
39b9a23

Signed mutable channel pointer; immutable generations are retained as assets.

Astrid nightly channel

Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Jul 06:22
39b9a23

Signed mutable channel pointer; immutable generations are retained as assets.

Astrid dev channel

Astrid dev channel Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Jul 06:22
39b9a23

Signed mutable channel pointer; immutable generations are retained as assets.

v0.9.4

Choose a tag to compare

@github-actions github-actions released this 09 Jul 20:24
v0.9.4
dcde6f6

Added

  • WIT pin retention and contracts-skew visibility. Capsules record BLAKE3 pins of their vendored WIT files in meta.json (wit_files), but the bytes behind an astrid-contracts.wit pin were only incidentally retained and no astrid capsule surface reported when a capsule's contracts snapshot drifted from the daemon's — a real gap found on a live deployment where one side-loaded dev capsule pinned newer contracts than 13 registry capsules, invisible until someone b3sum-ed files by hand. Because the contracts are pure data-shape records over the bus (zero WIT funcs), such skew never fails at link time; it fails silently at runtime when a record shape moves. Install now content-addresses every vendored WIT file into a dedicated blob store at ~/.astrid/wit/store/<hash>.wit (moved out of the top of wit/, which now holds only the daemon's canonical named copies, so astrid capsule wit gc sweeps the store without endangering them), and seeds the daemon canonical astrid-contracts.wit first-writer-wins on the first install that vendors it. Three read paths surface skew, warn-only: astrid capsule show <name> prints the capsule's contracts pin with a match / MISMATCH marker; astrid capsule list prints one summary line naming any mismatching capsule(s) (per-capsule pins in --verbose); and a successful install whose contracts pin differs from the canonical prints a notice. Side-loading an ahead-of-daemon dev build is legitimate, so skew is never a failure — install and every read path succeed regardless, and an absent canonical (fresh home, daemon never booted) degrades silently. Closes #1163.

Fixed

  • astrid mcp serve now keeps an ephemeral daemon alive for the MCP session lifetime. The MCP stdio shim could be alive while the daemon still aged out underneath it, because the daemon's idle monitor only counts authenticated uplink connections and mcp serve had no dedicated session-lifetime guard. The shim now quietly ensures the daemon before starting, then opens a separate authenticated guard uplink for the same principal and holds it until the MCP process exits; if the daemon drops the connection, the guard reconnects. This makes Codex/Claude plugin sessions behave as real daemon clients without writing anything to the MCP stdout transport.

  • The daemon is now always killable by SIGTERM, even when every worker is pinned by guest compute. A live daemon wedged: alive but ignoring SIGTERM (only SIGKILL freed it), holding the audit-DB LOCK, with astrid status failing. Root cause: signals were handled by a tokio::select!, which can only fire when a tokio worker is free to poll the reactor — and enough concurrent exempt guest compute (an uplink broker's run-loop, pinned at epoch_deadline = u64::MAX with no yield callback) pinned every worker, so the SIGTERM arm never ran. SIGTERM/SIGINT/SIGHUP are now owned by a dedicated OS-thread watchdog (astrid-daemon/src/signal.rs) scheduled by the OS, independent of the tokio runtime: the first signal requests a graceful shutdown through the kernel's shutdown_tx watch channel (so the normal kernel.shutdown().await still runs when the reactor is healthy) and arms an 8s hard deadline on a separate thread that force-exits if the graceful path itself wedges; a second signal before the deadline force-exits immediately. The tokio signal arms were removed so the watchdog is the single owner (no double-handling). Killable-but-abrupt beats wedged, and the OS releases every lock on the forced exit.

  • Exempt capsules can no longer starve the daemon. An exempt capsule (owner holds CAP_RESOURCES_UNBOUNDED / CAP_NET_BIND / CAP_UPLINK — e.g. an sage-mcp/sibyl-mcp uplink broker) ran its run-loop guest with the wasmtime epoch deadline pinned at u64::MAX and no callback, so the guest fiber never reached a cooperative yield point; enough concurrent exempt compute pinned every tokio worker and starved the reactor (the direct cause of the SIGTERM-deafness above). Exempt run-loops now use the same finite epoch window as bound run-loops but with a yield-always policy (exempt_epoch_action): they remain unbounded CPU — never Interrupt-trapped, never fuel-out — yet cooperatively Yield the tokio worker every window and re-arm, so they can no longer starve the daemon. An OS cgroup remains the backstop for raw CPU burn. (The exempt interceptor-pool epoch path is deliberately unchanged this round — see the STOP-AND-REPORT note in review.)

  • The daemon acquires its singleton lock before opening any shared store, so a boot-race loser reports "already running" instead of a raw store-lock error. Kernel::new opened the KV and audit stores before acquiring the singleton advisory flock, so a second daemon losing the boot race died on a raw surrealkv Database ... LOCK is already locked after having already opened (and touched) the shared stores it should never open, instead of the actionable "already running (singleton lock held)" message. The flock is now the FIRST fallible boot step (socket::acquire_boot_singleton_lock), before the KV/audit opens; the listener bind (socket::bind_listener) runs later and no longer re-acquires it. The loser now fails at the flock and never opens the shared stores.

  • The audit log is now closed on graceful shutdown, releasing its LOCK on exit rather than only on process death — and the store locks are released before the capsule drain. Kernel::shutdown closed the KV store but never closed the audit log, so its persistent surrealkv LOCK was held until the process died — which is why a wedged daemon needed SIGKILL to free the audit lock, and why the next boot could then race the still-held lock. AuditLog::close (which flushes and closes through the backend's shared Arc<dyn KvStore>, no exclusive ownership required) is now called during shutdown. Shutdown also reorders so the KV and audit stores close first, before the best-effort capsule drain: the drain is bounded per capsule but a large fleet could otherwise exceed the watchdog's force-exit grace and be killed with the lock still held. The drain itself is now concurrent (drain() yields one Arc per distinct runtime, so unload tasks never contend on the same Arc::get_mut), keeping the whole graceful path well under the grace so MCP subprocesses are actually disconnected rather than force-exited mid-drain.

  • astrid mcp serve no longer orphans forever when its launching session dies without closing stdin. The MCP stdio shim blocked on stdin EOF with no parent-death handling, so a client (Claude Desktop, an IDE, another agent runtime) that died without closing stdin left the shim alive indefinitely — an orphan pinning its daemon uplinks open (observed in the field: a 4-day orphan, each pinning ≥2 uplinks). serve() now races the stdin-EOF quit against a portable parent-death future (mcp/parent_death.rs, a low-frequency getppid()-change poll that works on macOS and Linux without platform syscalls); when the launching session dies the shim drops its transport and exits, freeing the uplinks.

  • A crash-looping capsule no longer thrashes-and-leaks a fresh instance every ~10s, and a busy capsule that hiccups is no longer permanently disabled. restart_capsule tore the old instance down via Arc::get_mut + unload, which is skipped when a live dispatcher consumer still holds an Arc clone (strong_count > 1) — yet it returned Ok, so the health monitor cleared the capsule's RestartTracker, reset its attempt budget every tick, and reloaded the capsule forever, leaking the old instance's run-loop/epoch-thread/subprocess each round. The old instance is now cooperatively stopped without exclusive ownership: request_cancel cancels the instance token, and the run-loop task races that token against its guest call (select!), so even a compute-bound run-loop stops (it now reaches a yield point every epoch window). The retry cap is now driven by consecutive health failures, pruned the moment the capsule recovers — not by the restart-call outcome. A restart that leaves the old instance lingering behind a still-held Arc (normal for a busy capsule whose dispatcher consumer holds a clone for up to its idle grace) is not counted as a failure, so a transient hiccup on a loaded capsule can no longer exhaust the 5-attempt cap and permanently disable it; only a capsule that keeps failing health across ticks is capped.

  • Raised the per-principal routed sub-queue message-count cap from 256 to 16384 so a legitimate high-rate LLM token stream is no longer silently truncated or hung. The per-principal routed queue (PENDING_PER_PRINCIPAL_FALLBACK in astrid-events) drops the oldest events once a principal's bucket exceeds the cap — a count-based flood guard secondary to the primary 1 MiB byte budget. An LLM token stream publishes one small event per token, so a fast provider (~2000 tok/s) emits thousands of tiny messages in a 1-2 s burst — far past 256, while the byte budget sits at ~80 KB for a typical response. The queue then dropped oldest, which either corrupted the accumulated response (missing interior tokens) or, when the terminal Done was the dropped event, hung the consuming turn indefinitely; short responses stayed under the cap and always completed, which is why only long generations were affected. For real (non-empty) deltas the 1 MiB byte budget binds first — it trips long before a bucket reaches the count cap — so the raised cap only governs a flood of near-0-byte messages the byte budget cannot see. Each queued slot is an ~8-byte Arc handle, but the Arc retains the whole event allocation, so that pathological flood is bounded per bucket at roughly the cap × per-event struct overhead (a few MiB), not a few KiB; proper memory-based budgeting for it is tracked in #1159. This admits a legitimate high-rate stream without weakening the anti-flood memory guard. Closes #...

Read more

v0.9.3

Choose a tag to compare

@github-actions github-actions released this 06 Jul 04:46
v0.9.3
3c5b964

Added

  • astrid capsule check — a static, CI-friendly linter for a capsule's tool wiring. Several capsule-authoring mistakes fail silently at runtime: a #[astrid::tool] with no [subscribe] "tool.v1.execute.<name>" route advertises in tools/list but never executes (#1127); a tool capsule without the mandatory [publish] boilerplate can't return results or answer the describe fan-out; a [subscribe] handler that isn't tool_execute_<name> routes then gets denied. astrid capsule check [PATH] cross-checks the #[astrid::tool("…")] annotations in src/ against the Capsule.toml [subscribe]/[publish] tables and reports each problem with the exact line to add, exiting non-zero on any finding so it drops straight into a CI job or pre-commit hook. It is deliberately static — it derives the advertised-tool set from source, not by instantiating the WASM component — so it needs no build, no daemon, no WASM runtime, and no ASTRID_HOME, making it fast and deterministic (cargo check for capsules). Check #1 reuses the same tools_missing_execute_route predicate as the kernel's load-time warning, so the CI gate and the runtime can never disagree; a --deep mode (ephemeral-load + the real tool_describe) is a documented later addition. Closes #1129.

Changed

  • astrid-kernel now compiles for wasm32-unknown-unknown, completing the browser-host portability seam. The portable composition root (Kernel::with_resources) built cleanly, but the kernel crate still unconditionally compiled its native-only machinery: the Wasmtime capsule loader and MCP host client, the cap-std VFS and per-principal overlay registry, the disk-backed capsule install/discovery/reload/health paths, the Unix socket manager (UnixListener bind + singleton flock), the management-API router, and the idle-shutdown monitor (process::exit). Those are now target-gated to native (cfg(not(all(target_arch = "wasm32", target_os = "unknown"))), with the socket manager and native composition root Kernel::new under cfg(unix)), and their native-only dependencies (astrid-mcp, astrid-vfs, astrid-capsule-install, and tokio's full/signal features) move into a matching target-specific dependency table; the base tokio set is trimmed to the wasm-clean subset. The Kernel.kv and KernelResources.kv fields change from the concrete Arc<SurrealKvStore> to Arc<dyn KvStore> so a portable host can inject its own backend — a new default KvStore::close trait method (a no-op for backends with nothing to flush, delegating to the inherent flush on SurrealKvStore) carries the shutdown flush that previously required the concrete type, superseding the #1136 note. The pairing token/invite stores keep their types and validation but gate disk persistence to native (in-memory, no-op on the browser arm), and astrid-runtime gains a spawn_blocking facade (native tokio re-export; inline execution on the single-threaded wasm arm). astrid-kernel is added to the wasm32-unknown-unknown portability CI guard. This is a deliberate 0.x break: Kernel.kv/KernelResources.kv are now trait objects, and Kernel::new/the socket module now require cfg(unix). Native build/test/clippy/fmt are unaffected. Closes #1148.

  • astrid-capsule's engine-agnostic machinery now compiles for wasm32-unknown-unknown, groundwork for an alternate capsule host. The dispatcher, registry, access resolver, context, trait layer, and manifest analysis (readiness/topic/toposort) are pure logic, but the crate unconditionally compiled the Wasmtime engine and its native-only dependencies (wasmtime/wasmtime-wasi/wasmparser, reqwest, socket2, tokio-util, notify, nix, astrid-mcp, astrid-workspace, and tokio's net/rt-multi-thread features), so no alternate engine host could reuse the shared machinery. The Wasmtime engine, the process-backed MCP engine, manifest discovery, the file-watch hot-reload path, and the per-Store StoreMemoryMeter are now target-gated to native (cfg(not(all(target_arch = "wasm32", target_os = "unknown")))), and their native-only dependencies move into a matching target-specific dependency table; the portable modules route task spawning and timers through the astrid-runtime facade. The host audit-sink contract (HostAuditSink, HostAuditEvent, HostAuditOutcome) — pure Rust with no Wasmtime dependency — moves out of the engine subtree to astrid_capsule::audit_sink; the crate-root re-exports (astrid_capsule::{HostAuditSink, HostAuditEvent, HostAuditOutcome, CapsuleRuntimeLimits, HttpLimits}) stay reachable and unchanged. An alternate host can now supply its own ExecutionEngine behind the same dyn Capsule trait. astrid-capsule is added to the wasm32-unknown-unknown portability CI guard; native build/test/clippy/fmt are unaffected. Closes #1142.

  • The kernel's task-spawning and time surface is now selected per target through a new astrid-runtime facade crate, groundwork for alternate host profiles. The kernel spawned tasks and read the clock via tokio::spawn, tokio::time::{sleep, interval, timeout, Instant, MissedTickBehavior}, tokio::time::Instant/std::time::Instant, and std::time::SystemTime directly at ~50 call sites — none of which exist on wasm32-unknown-unknown (no tokio runtime, no OS threads, no std::time clock), so a portable host could not reuse the kernel's scheduling and timekeeping. Those calls now route through astrid-runtime, a single seam that selects the surface at compile time: the native arm (cfg(not(target_family = "wasm"))) is pure tokio/std re-exports — every item resolves to the exact same type it replaced, so it is zero-cost with no behaviour change on native — while the wasm arm (cfg(target_family = "wasm")) maps the task surface to the JS microtask queue (wasm-bindgen-futures), the timer surface to JS timers (wasmtimer), and the wall clock to the browser time bridge (web-time). The facade is deliberately a cfg-gated re-export rather than a trait: the implementation never varies within one platform build, so there is no runtime polymorphism to model. astrid-runtime is added to the wasm32-unknown-unknown portability CI guard; native build/test/clippy/fmt are unaffected. See #1140.

  • The kernel's pure-semantics crates now compile for wasm32-unknown-unknown, groundwork for a browser host profile. astrid-types, astrid-core, astrid-crypto, astrid-capabilities, astrid-audit, astrid-events, astrid-config, and astrid-approval build for the wasm target with no behaviour change on native. Two structural blockers were cleared. First, the workspace-level tokio dependency pinned an additive base feature set (net, signal, rt-multi-thread) onto every workspace = true consumer — including leaf crates that only asked for sync/time — dragging in mio (which compile_error!s on wasm) and the multi-threaded runtime (which needs OS threads). The base is trimmed to the portable subset (sync, macros, time, rt, io-util); the native-only features are now declared locally by the crates that actually use them (uplink/cli/kernel/gateway/capsule for net, daemon/kernel for signal, daemon/emit/cli/capsule-install/capsule/hooks/kernel/gateway for rt-multi-thread). Second, getrandom (reached via uuid/rand at major 0.4 and surrealkv at major 0.3) refuses to build for wasm without a backend selection: the wasm_js feature is now enabled through [target.'cfg(target_family = "wasm")'.dependencies] entries on the crates owning those edges, paired with the build-time --cfg getrandom_backend="wasm_js" that scripts/check-wasm-portability.sh (a new cargo check-only regression guard, wired into CI) sets. The audit store's block_in_place fast path (native-only) is cfg-gated to its scoped-thread fallback on wasm. No public API changes; native build/test/clippy/fmt are unaffected.

  • Engine-agnostic capsule types extracted into a new astrid-capsule-types crate. The pure data the kernel routes and gates on — the capsule manifest (CapsuleManifest and its validation/error types), the capsule id (CapsuleId), the per-principal fuel and memory peak ledgers (FuelLedger/FuelRateLimiter, MemoryLedger), and the resource/HTTP limit types (CapsuleRuntimeLimits, HttpLimits) — lived inside astrid-capsule, the crate that owns the Wasmtime execution engine. Because those types were trapped in the engine crate, every component that needs only the data — the kernel included — transitively depended on Wasmtime, and a second capsule engine profile could not share the vocabulary without dragging in the engine it replaces. They now live in a wasmtime-free, tokio-free astrid-capsule-types crate that every engine can depend on; astrid-capsule re-exports each moved item at its exact original path, so the kernel and all other consumers compile unchanged. Behaviour that genuinely touches the engine stays behind: the tokio-Semaphore constructors on CapsuleRuntimeLimits become the CapsuleRuntimeLimitsExt extension trait, and the per-Store StoreMemoryMeter (wasmtime::ResourceLimiter) keeps feeding the shared MemoryLedger. A pure move for every consumer that uses the re-exported paths — no behaviour changes, and the manifest's untrusted-input skip_deserializing protections and parser-isolation tests move intact. One API-shape caveat for external consumers of astrid-capsule: the semaphore constructors are now trait methods, so CapsuleRuntimeLimitsExt must be in scope to call blocking_semaphore()/io_semaphore(). Closes #1133.

  • The kernel's boot is now split into a portable composition root and a native one, groundwork for alternate host profiles. Kernel::new performed every platform-specific side-effect inline — resolving the Astrid home, opening the SurrealKV store and the audit log, loading the runtime signing ...

Read more

v0.9.2

Choose a tag to compare

@github-actions github-actions released this 03 Jul 04:22
v0.9.2
15d2d76

Fixed

  • First-run consent no longer storms a GrantRequired for every ungranted capsule in the caller's view. The dispatch access gate ran the grant-on-use check — and emitted a GrantRequired signal — for every ungranted capsule in the caller's view before checking whether the capsule's subscription matched the dispatched topic. A single tools/call on a fresh principal therefore fired a grant prompt for every ungranted view capsule, including all the capsules the call never touched, making first-run consent effectively unconvergeable. The gate now evaluates the cheap, manifest-local interceptor topic-match first (using the same crate::topic::topic_matches the delivery push uses) and engages the per-principal access gate only for a capsule that actually provides an interceptor for the requested topic; a non-matching capsule never reaches the gate. A matching ungranted capsule still fail-closed drops and signals exactly once, and behaviour for granted capsules, interceptor ordering, and non-tool topics is unchanged. Closes #1113.
  • The MCP shim now resolves every grant a single tools/call needs, instead of only the first. The shim resolved exactly one grant_required per call — it elicited consent, re-sent the call once, and passed the re-send's reply straight to the result reshaper — so when the re-sent call tripped the access gate on a different ungranted capsule, that second grant_required reply was returned to the client as the tool result (empty content, isError: false): a phantom empty success, while the broker kept a pending marker for a prompt that never surfaced. The shim now loops resolve→re-send→re-classify until the reply is grant-free or a bound (MAX_GRANT_RESOLUTIONS = 8, a distro's worth) trips; a present grant signal is never classified as terminal, so the only exits are a grant-free reply or an honest isError (malformed / denied / bound exceeded) — never a fabricated empty success. Ingress, approval-elicitation, grant dedup/marker, and fail-secure deny paths are preserved. Closes #1117.
  • astrid mcp serve now actually pushes notifications/tools/list_changed when a capsule is installed/loaded/unloaded during a live session, so newly hot-loaded tools light up without a session restart. The hot-reload watcher advertised tools.listChanged but never delivered a notification: (1) it seeded its baseline on a separate, short-lived uplink and then only ever read, so its own watch uplink was never bound to a principal — and the cli-proxy delivers a principal-stamped astrid.v1.capsules_loaded broadcast only to bound uplinks, so the watcher was silently starved of every reload signal; and (2) on a reload it re-enumerated the tool set over a fresh tools/list round trip that timed out precisely when a reload was in flight (the broker busy with its own describe fan-out), swallowing the push. The watcher now seeds its baseline on the watch uplink itself (that first request binds the connection) and reads the new tool surface directly from the capsules_loaded payload (capsules[].meta.tools[].name, principal-filtered) — the signal's documented intended contract — so the notification fires ~2 s after an install with no dependency on a follow-up request. Refs #1118.

Install

From source (requires Rust 1.95+):

cargo install astrid

Pre-built binaries:
Download the archive for your platform, extract, and add to PATH:

tar xzf astrid-*-$(uname -m)-*.tar.gz
sudo mv astrid-*/astrid astrid-*/astrid-daemon astrid-*/astrid-build astrid-*/astrid-emit /usr/local/bin/

Then run astrid init to set up capsules.


With many thanks from the following Astrinauts 🚀

  • Joshua J. Bouw