Skip to content

feat(process): persistent-process tier SDK surface#53

Merged
joshuajbouw merged 4 commits into
mainfrom
feat/process-persistent-sdk
Jun 7, 2026
Merged

feat(process): persistent-process tier SDK surface#53
joshuajbouw merged 4 commits into
mainfrom
feat/process-persistent-sdk

Conversation

@joshuajbouw

Copy link
Copy Markdown
Contributor

Summary

Adds the ergonomic SDK surface for the persistent-process tier of astrid:process@1.0.0 (host side: astrid-runtime/astrid#867; contract: astrid-runtime/wit#12), so capsule authors can spawn a background child that outlives the pooled, stateless instance that started it — unlike process::Process, whose kernel resource is reaped on instance reset.

Changes

  • contracts submodule → the merged astrid:process@1.0.0 persistent tier (wit commit 1a06cf4, the feat(net): mpsc-style channel API — recv/send/try_recv/try_accept #12 squash). Scoped deliberately to the process package: 1a06cf4 predates feat(net): mpsc-style channel API — recv/send/try_recv/try_accept #11, so the interfaces/ bundle (astrid-sdk/wit/astrid-contracts.wit) is untouched and the open hook-event-request PR feat(contracts): regenerate bundle with hook-event-request #50 is unaffected (sync-contracts-wit.sh --check stays green). astrid-sys/build.rs restages wit-staging/deps/astrid-process/; the raw wit_process::* bindings regenerate.
  • astrid-sdk/src/process.rs ergonomic layer:
    • Command gains the persistent-only builder knobs (label, keep_stdin_open, overflow, log_ring_bytes, max_lifetime, idle_timeout, exit_retention, limits) and a spawn_persistent() terminal → PersistentProcess.
    • PersistentProcess (keyed by an opaque ProcessId): status, read_logs (drain), read_since (non-draining cursor, byte-faithful LogChunk), write_stdin, close_stdin, signal, wait (bounded), stop (consumes), release (consumes).
    • Module fns: attach(id) — an id-wrapper so reattach from a later invocation works without the host's deferred attach resource fn (the first id-keyed call validates ownership) — plus list and status_many.
    • New types: ProcessId, ProcessInfo, ProcessPhase, LogStream, LogCursor, LogChunk, OverflowPolicy, ResourceLimits. Signal gains Stop / Cont.
  • Command::into_wit updated for the new SpawnRequest fields (a required fix — the WIT bump added 8 fields).

Not exposed (host-deferred)

watch / unwatch (open publish-authority RFC question — poll via status + bounded wait) and resource-limit enforcement (limits is plumbed through but the host does not enforce it yet).

Test Plan

  • cargo build --workspace + cargo test --workspace green
  • No new clippy warnings — my diff is astrid-sdk (clean); the 2 astrid-sdk-macros backtick warnings are pre-existing and untouched
  • cargo fmt --all --check clean
  • scripts/sync-contracts-wit.sh --check passes (interfaces bundle untouched — feat(contracts): regenerate bundle with hook-event-request #50 unaffected)

Bump contracts to the merged astrid:process@1.0.0 persistent tier (#12 commit 1a06cf4 — process package only, leaves the interfaces bundle / hook-event-request untouched) and add the ergonomic wrapper in astrid-sdk/src/process.rs.

Command gains spawn_persistent() + the persistent knobs (label/keep_stdin_open/overflow/log_ring_bytes/max_lifetime/idle_timeout/exit_retention/limits). New PersistentProcess handle (status/read_logs/read_since/write_stdin/close_stdin/signal/wait/stop/release), process::{attach,list,status_many}, ProcessId/ProcessInfo/ProcessPhase/LogStream/LogCursor/LogChunk/OverflowPolicy/ResourceLimits types, Signal::{Stop,Cont}. attach is an id-wrapper (reattach works without the host's deferred attach fn).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the persistent-process tier to the process module, mirroring the host astrid:process@1.0.0 specification. It adds Command::spawn_persistent, the PersistentProcess handle, and associated management functions such as attach, list, and status_many, along with supporting types like ProcessId, LogCursor, and ProcessInfo. Feedback on the changes highlights a compilation error in status_many due to incorrect WIT-binding types, and recommends deriving serde::Serialize and serde::Deserialize on ProcessId and LogCursor to facilitate state persistence.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread astrid-sdk/src/process.rs Outdated
Comment thread astrid-sdk/src/process.rs
Comment thread astrid-sdk/src/process.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the capsule-facing Rust SDK surface for the astrid:process@1.0.0 persistent-process tier, enabling background child processes that can outlive the pooled/stateless instance that spawned them and be reattached via an opaque ProcessId.

Changes:

  • Updates the staged astrid:process@1.0.0 WIT contract to include the persistent tier types/functions and expanded spawn request fields.
  • Extends astrid_sdk::process::Command with persistent-only builder knobs and a new spawn_persistent() terminal.
  • Introduces PersistentProcess plus supporting types (ProcessId, ProcessInfo, ProcessPhase, log cursor/chunk types, overflow/limits types) and module functions (attach, list, status_many).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
CHANGELOG.md Documents the new persistent-process API surface and related types.
astrid-sys/wit-staging/deps/astrid-process/process@1.0.0.wit Bumps/restages the process WIT contract to include persistent tier capabilities and new request fields.
astrid-sdk/src/process.rs Implements the ergonomic persistent-process API and wires new spawn request fields into Command::into_wit.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread astrid-sdk/src/process.rs
Comment thread astrid-sdk/src/process.rs Outdated
…#55)

## What

Adds `capabilities::enumerate()` to `astrid-sdk` — the ergonomic wrapper
for `astrid:sys/host.enumerate-capabilities` (astrid-runtime/wit#13,
merged), implemented kernel-side in unicity-astrid/astrid (`feat(sys):
implement enumerate-capabilities + complete check-capsule-capability`).

It is the infallible list dual of `capabilities::check`: returns the
calling capsule's own held capability names — the categories declared in
its `[capabilities]` manifest block (`host_process`, `net_connect`,
`fs_read`, …), not the scoped arguments within them. Argument-free,
returns `Vec<String>` with no error path (an empty list is the valid "no
capabilities" answer), so a reusable capsule can ground its behaviour in
what it can actually do instead of hard-coding it.

The `contracts` submodule is bumped to the merged `9742f80` so
`astrid-sys` regenerates the binding; `astrid-sys/wit-staging` restaged.

## Stacking

Stacked on `feat/process-persistent-sdk` (#53) — base is that branch, so
the diff shows only the enumerate delta. Rebase onto `main` and retarget
once #53 lands.

## Verify

`cargo build --workspace` and `cargo clippy --workspace --all-targets`
green.
… persist-unsupported

Derives Serialize/Deserialize on ProcessId and LogCursor so a capsule can
persist the reattach id and a resumable log cursor (e.g. as one KV state
struct) and resume read_since from the same position in a later invocation —
LogCursor's token was private with no accessor, so it could not be persisted
at all before. ProcessId stays an opaque handle, not a credential: a leaked id
is inert across the principal/capsule boundary (the host re-checks ownership
on every id-keyed call), so exposing it for serialization is safe.

Also documents that spawn_persistent can fail with persist-unsupported even
when host_process is granted — when the invocation has no authenticated
principal in scope (owner-fallback), since a persistent id must be scoped to a
real principal.
The astrid-contracts.wit events mirror was stale relative to the contracts
submodule (at 9742f80, unchanged): it was missing astrid:hook's
hook-event-request record, which landed in contracts/interfaces when the pin
moved to include it. Regenerated via scripts/sync-contracts-wit.sh so
sync-contracts-wit.sh --check passes. Only the mirror is touched — the
submodule pin is untouched.
@joshuajbouw joshuajbouw merged commit 0c52808 into main Jun 7, 2026
8 checks passed
@joshuajbouw joshuajbouw deleted the feat/process-persistent-sdk branch June 7, 2026 04:13
joshuajbouw added a commit to astrid-runtime/sdk-js that referenced this pull request Jun 7, 2026
## Summary

Mirrors the Rust SDK and the host `astrid:process@1.0.0`
**persistent-process tier** (host: astrid-runtime/astrid#867; Rust SDK:
astrid-runtime/sdk-rust#53; contract: astrid-runtime/wit#12), so JS/TS
capsule authors can spawn a background child that **outlives the pooled,
stateless instance** that started it — unlike
`process.BackgroundProcessHandle`, whose kernel resource is reaped on
instance reset.

## Changes

- **`contracts` submodule → the merged `astrid:process@1.0.0` persistent
tier** (wit commit `1a06cf4`, the #12 squash). Deliberately the
**#12-only** commit (predates #11), so the `astrid:contracts` events
bundle (`astrid-contracts.wit` / generated `contracts.ts`) is untouched
(`sync-contracts-wit.sh --check` stays green) and unrelated open SDK PRs
are unaffected. On `main`, `astrid-build` reads the host WIT **live from
`contracts/host/`** (`CANONICAL_WIT_DIR`), so the bump is all
ComponentizeJS needs.
- **`wit-imports.d.ts`** (hand-written host ABI types): persistent types
+ free-function declarations on `astrid:process/host@1.0.0` —
`spawnPersistent`, `attach`, `listProcesses`, `status`, `statusMany`,
`readLogs`, `readSince`, `writeStdin`, `closeStdin`, `signal`, `wait`,
`stop`, `releaseProcess`, `watch`, `unwatch`; `ProcessInfo`,
`ProcessPhase`, `LogStream`, `LogCursor`, `LogChunk`, `OverflowPolicy`,
`ResourceLimits`; `ErrorCode` gains `no-such-process` / `registry-full`
/ `persist-unsupported`; `ProcessSignal` gains `stop` / `cont`;
`SpawnRequest` gains the 8 persistent fields.
- **`process.ts`** ergonomic layer: `spawnPersistent(cmd, args,
options)` + the persistent `SpawnPersistentOptions` knobs;
`PersistentProcess` class (`status` / `readLogs` (drain) / `readSince`
(non-draining cursor → byte-faithful `LogChunkResult`;
`logCursorStart()`) / `writeStdin` / `closeStdin` / `signal` / `wait`
(bounded) / `stop` (consumes — SIGTERM→grace→SIGKILL) / `release`);
module fns `attach(id)` (id-wrapper reattach — works without the host's
deferred `attach` resource fn), `listProcesses`, `statusMany`; ergonomic
types. `buildSpawnRequest` updated for the new `SpawnRequest` fields
(required).
- **`index.ts`**: top-level re-exports for the new public types.

## Not exposed (host-deferred)

`watch` / `unwatch` (open publish-authority RFC question — poll via
`status` + bounded `wait`) and resource-limit enforcement (`limits` is
plumbed through but the host doesn't enforce it yet).

## Test Plan

- [x] `npm run build` (tsc) green across the workspace
- [x] **End-to-end:** the `examples/test-capsule` **componentizes**
against the persistent WIT — ComponentizeJS produced a 12.96 MB WASM
with **169 host imports**, proving the new host functions wire through
the JS build path
- [x] `scripts/sync-contracts-wit.sh --check` passes (events bundle
untouched)
- [x] Git diff scoped to 5 files (contracts pointer + 3 SDK sources +
CHANGELOG); no `contracts.ts` drift
joshuajbouw added a commit that referenced this pull request Jun 10, 2026
Rebuilt on main post-#53/#56 squash-merge: the branch previously carried its own pre-squash copy of the feature diff; now it is the bump + CHANGELOG only.

- Workspace 0.7.0 -> 0.7.1 (+ the three internal dependency pins).
- CHANGELOG: [Unreleased] -> [0.7.1] - 2026-06-10, with a release synopsis (extracted into the GitHub release body by release.yml), the previously missing Fixed entry for the #56 tool_describe fan-out fix, and PR attribution on the #53 entries.
joshuajbouw added a commit that referenced this pull request Jun 11, 2026
## Summary

`chore: release` — bumps the workspace to **0.7.1**, the patch release
that restores capsule tool discovery (#56) and ships the post-0.7
additive surfaces (#53).

Per the repo rule, a version bump is its own PR. The branch was
originally stacked on #53; after #53 and #56 squash-merged it has been
**rebuilt on `main`** so the diff is exactly the bump + CHANGELOG.

No breaking changes — `astrid-sdk = "0.7"` consumers resolve 0.7.1
automatically once published, so capsules pick up the tool_describe fix
with a rebuild and **no** dep bump.

## Changes

- `[workspace.package].version` `0.7.0` → `0.7.1` (+ the three internal
`[workspace.dependencies]` pins: `astrid-sdk`, `astrid-sdk-macros`,
`astrid-sys`).
- CHANGELOG: `[Unreleased]` → `[0.7.1] - 2026-06-10` with:
- a release **synopsis** paragraph (release.yml extracts the whole `##
[0.7.1]` block into the GitHub release body on tag),
- the previously **missing `Fixed` entry for #56** (the tool_describe
describe-fan-out fix — the fleet-critical one),
- PR attribution on the #53 `Added` entries (`capabilities::enumerate`,
persistent-process tier).

## Release steps (maintainer)

1. Merge this PR.
2. `cargo publish` in dependency order: `astrid-sys`,
`astrid-sdk-macros`, `astrid-sdk`.
3. Tag `v0.7.1` on the merge commit → release.yml creates the GitHub
release from the CHANGELOG block.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants