Skip to content

RFC: Wasmi extensions and native channels - #167

Merged
pcarrier merged 23 commits into
mainfrom
codex/wasmi-plugin-rfc
Aug 13, 2026
Merged

RFC: Wasmi extensions and native channels#167
pcarrier merged 23 commits into
mainfrom
codex/wasmi-plugin-rfc

Conversation

@pcarrier

@pcarrier pcarrier commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • define Wasmi-hosted Rust extensions as in-process logical Blit clients using the ordinary packet dispatcher
  • expose a four-call host ABI: packet send, blocking packet recv, direct realtime/monotonic clock, and OS-backed random
  • use BLAKE3 content-addressed upload so blit run --on ... FILE ARGS... sends module bytes only on a cache miss
  • supervise attached, detached, autorestarting, and restart-persistent extensions, with one named OS thread per running attempt
  • add reliable bidirectional named channels as an ordinary packet family
  • let live named persistent extensions advertise discoverable @name CLI command trees, with invocation I/O carried over channels

Lifecycle and resource model

  • a module hash identifies exact immutable Wasm bytes; an extension_id identifies one supervised installation; (extension_id, attempt) identifies one Wasmi instance
  • the same hash can back multiple isolated extensions, and separate durable names permit concurrent versions such as builder and builder-canary
  • failures restart with backoff under on-failure; update, cancellation, shutdown, and persistence have explicit supervisor transitions
  • the raw Wasm CAS uses automatic unpinned LRU under a 2 GiB default budget; persistent definitions pin their objects
  • server-wide defaults bound running threads, supervisors, storage, validation, guest memory/tables/stacks, and transport queues

CLI and command extensibility

  • blit ext run --on ... FILE ARGS... installs/runs an extension; blit run is an alias and positional tokens after FILE are guest arguments
  • blit ext update NAME FILE ARGS... atomically switches a persistent definition after ID/revision checks
  • a live named persistent extension registers a bounded blit.cli.v1 descriptor and channel listener through EXT_COMMAND
  • blit ext commands, @name --help, and completion use immutable directory snapshots; invocation is never retried across attempts

Key decisions

  • Wasmi only; no QuickJS, WASI, manifest, capability/grant model, per-run limits, state, or topic family
  • one guest target, wasm32-unknown-unknown, and one host import module, blit_v1
  • direct host services are packet I/O, clocks, and entropy; every Blit operation remains a packet
  • pipe-oriented non-PTY process execution is independent and moved to draft RFC: Native non-PTY process protocol #173

Protocol allocation

  • feature bit 11: EXTENSION, direction-local 0x90 through 0x94
  • feature bit 12: CHANNEL, bidirectional 0x95; 0x96 remains free
  • feature bit 13 remains unallocated in this PR
  • feature bit 14: proposed correlated CREATE2(WANT_STATUS) failures

Split follow-ups

Both are stacked on this branch so #167 remains focused and reviewable. They can merge separately after the base.

Scope and verification

This PR is a design RFC plus protocol documentation corrections. It does not implement the runtime, SDK, CLI, packet families, or non-PTY process protocol.

  • git diff --check passes
  • the focused net diff is docs/design/extensions.md, selected docs/protocol.md changes, and the stale Git opcode comment correction

@indent

indent Bot commented Aug 5, 2026

Copy link
Copy Markdown
PR Summary

This PR adds an RFC design document for Wasmi-hosted Rust extensions (in-process logical blit clients) and reliable bidirectional named channels, and carries a small code delta that aligns the remaining existing families with the common status registry. Since it was opened, much of the RFC's protocol groundwork (the common status registry, CREATE_STATUS/CREATE2(WANT_STATUS)/CREATE_FAILED, 64 MiB bounded reassembly, family-local 128+ allocation) has already landed in main via other PRs, so this PR now sits on top of that and mostly contributes the extension/channel design plus the residual alignment.

  • docs/design/extensions.md (new): the extension family (feature bit 11, opcodes 0x90–0x94), native channel family (bit 12, 0x95), a four-call host ABI (send/recv/clock/random), BLAKE3 content-addressed module upload, supervisor lifecycle, EXT_COMMAND @name CLI directory, server-wide resource caps, 2 GiB LRU object eviction, and a per-family-gated security posture. The process/subprocess family is explicitly deferred to a future RFC.
  • docs/protocol.md: fills in the feature-bit rows main reserved — 11 EXTENSION, 12 CHANNEL — marks bit 13 RESERVED (process split out), and adds the extension-specific fragmentation rules. The common status registry and CREATE_STATUS prose it depends on are already in main.
  • Status alignment (crates/git, crates/lsp, crates/remote/{fs,git,kv,lsp,net}, js/core renderers): reclassifies malformed Git/LSP inputs as INVALID (7) instead of OTHER (9) — matching main's registry rule that "malformed fields return INVALID" — and extends the "backend error" vs "unknown status N" renderer distinction to the families main hasn't already covered. js/core/src/git.ts is now effectively a no-op comment tweak since main already applied that renderer change.
  • Sibling design docs (git.md, lsp.md, kv.md, net.md, fs-*.md) updated to point at the canonical common status registry.

Issues

All clear! No issues remaining. 🎉

5 issues already resolved
  • Channel C2S ACK is documented as "cumulative received payload bytes", but the S2C ACK row and the flow-control prose define ACK as cumulative consumed bytes, acked only after delivery/discard; the two directions should match. (fixed by commit cea9dea)
  • The suggested default plugin mailbox window is 4 MiB, but a legal single blit packet can be up to the 16 MiB logical-message cap, so a max-size packet can't fit the default window; specify that the mailbox always admits at least one whole packet or raise the default to the message cap. (fixed by commit cea9dea)
  • Extension status code collision: EXT_PUT_STATUS (docs/design/extensions.md) defines ALREADY_HAVE = 12, but the unified status table this RFC says it reuses already assigns 12 to NO_MERGE_BASE (docs/design/git.md); use the next free code (13), since git.md requires one shared status table across families. (fixed by commit 698745e)
  • The force-push bundled an unrelated functional change (commit 3a73eff, crates/server/src/lib.rs +121: adaptive-quantizer app-limited handling and a Vulkan H.264/AV1 QP-scale fix) into this docs-only extensions RFC, contradicting the PR's stated scope; the code itself is correct, but it likely belongs in its own PR. (fixed by commit 1ee6559)
  • Commit 1ee6559 (message prefixed docs:) bundles substantial non-docs work into this RFC PR: a cross-family code refactor (crates/git, lsp, remote/*, js/core) with wire-visible git/lsp status changes (malformed requests now return INVALID instead of OTHER), plus new protocol features (CREATE_STATUS feature bit 14 / CREATE2 WANT_STATUS / CREATE_FAILED 0x10, and a 64 MiB bounded-reassembly ceiling). The changes look correct and self-consistent, but landing wire-behavior and protocol changes under a docs: commit in a PR described as docs-only is easy to miss and hard to bisect; split them into their own PR(s) and relabel. (fixed by commit 3665e94)

View session

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Coverage

Crate Lines Functions Regions
alacritty-driver 73.5% (837/1139) 76.4% (68/89) 77.0% (1360/1767)
browser 0.0% (0/822) 0.0% (0/68) 0.0% (0/1401)
cli 24.6% (2436/9889) 36.0% (300/833) 27.2% (4128/15172)
compositor 33.4% (5534/16578) 47.2% (452/958) 33.2% (7592/22872)
fonts 81.4% (721/886) 88.6% (70/79) 83.0% (1427/1719)
fssync 92.6% (5521/5961) 94.4% (501/531) 92.8% (10189/10981)
gateway 30.7% (492/1603) 34.5% (50/145) 26.1% (692/2655)
git 87.6% (4432/5060) 89.3% (359/402) 87.3% (7059/8082)
lsp 77.4% (2688/3471) 79.4% (262/330) 75.2% (4210/5595)
proxy 19.2% (172/898) 20.5% (26/127) 21.0% (293/1392)
remote 91.0% (10177/11183) 93.6% (743/794) 89.0% (17081/19182)
sd-notify 72.8% (67/92) 100.0% (6/6) 82.4% (108/131)
server 47.6% (11823/24829) 61.2% (1132/1850) 49.6% (19359/39001)
ssh 32.2% (165/512) 48.2% (27/56) 31.4% (261/830)
upsidedown 31.4% (391/1247) 27.8% (55/198) 34.8% (797/2287)
webrtc-forwarder 6.9% (189/2756) 10.3% (21/204) 5.3% (243/4549)
webserver 62.1% (1133/1825) 65.9% (164/249) 64.5% (1912/2964)
Total 52.7% (46778/88751) 61.2% (4236/6919) 54.6% (76711/140580)

Comment thread docs/design/plugins.md Outdated
Comment thread docs/design/plugins.md Outdated
Comment thread docs/design/plugins.md Outdated
@pcarrier pcarrier changed the title RFC: Wasmi plugins and native communication RFC: Wasmi extensions and native communication Aug 5, 2026
@pcarrier
pcarrier force-pushed the codex/wasmi-plugin-rfc branch from f2553b1 to 3a73eff Compare August 5, 2026 04:39
@pcarrier pcarrier changed the title RFC: Wasmi extensions and native communication RFC: Wasmi extensions, native channels, and processes Aug 5, 2026
@pcarrier
pcarrier force-pushed the codex/wasmi-plugin-rfc branch from f611f4b to 1ee6559 Compare August 5, 2026 16:19
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

🔗 Preview: https://blit-dej8w2qha-indent.vercel.app

@pcarrier pcarrier changed the title RFC: Wasmi extensions, native channels, and processes RFC: Wasmi extensions and native channels Aug 5, 2026
@indent

indent Bot commented Aug 5, 2026

Copy link
Copy Markdown

Reviewing this against #94 (units — declarative process supervision), which I'd been designing as server-side code. Having read this, most of that unit layer should be an extension instead, and I'm yielding the allocations I'd claimed: feature bit 11, the 0x90-0x94 block, and S2C 0x10 are yours. My S2C_CREATE_FAILED also goes away in favour of CREATE2(WANT_STATUS), which is the better design — opt-in behind a negotiated flag means a legacy client can't mistake a refusal for PTY zero, and BUDGET covers PTY-cap exhaustion exactly. Consider this a second consumer confirming that shape.

I need one thing added, and it's a sentence of specification rather than a design change.

Please state that an extension endpoint receives unsolicited S2C messages

Packet parity is stated in one direction only. "An extension may form any valid C2S packet… If an operation is available to an ordinary client, it is available to an extension" tells me what a guest can send. What a guest receives unprompted is only ever implied — by the initial-burst inheritance, and by the snapshot-to-live reconciliation machinery, which would be pointless if global notifications didn't reach the extension endpoint.

Anything supervisory built on this depends on the receive direction, and specifically on S2C_EXITED. A supervisor that has to poll to notice its child died is not a supervisor; restart-on-exit, health gating, and dependency ordering all key off that push. The same goes for S2C_TITLE, S2C_USED_ROWS, and S2C_TERM_CWD_EVENT, which the protocol describes as fanning out "to every connected client" — I'd like it written down that an extension endpoint is one of those clients.

Concretely: a line in Packet parity saying the guest receives every unsolicited S2C message an ordinary client would, subject to the same subscription gating, with S2C_EXITED named. If the intent is narrower — say, broadcasts reach it but per-PTY pushes need an explicit subscribe — that's fine too, but it needs saying, because it's the difference between "a supervisor can be an extension" and "it can't."

Two observations, no action needed

BLIT_EXT_MAX_RUNNING is min(4, cpus-1), validated 1..4. For short-lived or interactive extensions that's clearly right. For a long-lived daemon-shaped one — a supervisor, a watcher — it means competing for one of at most four slots and potentially sitting in QUEUED indefinitely while something else runs. Not asking you to change it here; flagging that --persist --restart always invites exactly the workload the cap is least suited to, and the two may want different pools eventually.

The at-least-once restart contract is the right call and worth keeping loud. "Blit does not checkpoint Wasm memory" plus "must make side effects idempotent or store their own progress transactionally" is precisely the constraint a supervisor has to design around, and it pushed me toward keeping unit state in KV rather than in guest memory.

Happy to review the receive-direction wording once you've picked it.

pcarrier added a commit that referenced this pull request Aug 5, 2026
## Summary

Move the existing FS/Git/KV/LSP/NET implementations and client
diagnostics onto the common status registry proposed by #167.

- classify malformed Git and LSP inputs as `INVALID` instead of `OTHER`
- make Rust and TypeScript status renderers distinguish declared backend
`OTHER` from unknown numeric status values
- update existing-family documentation and comments to point at the
canonical registry
- add Rust and TypeScript regression tests

This deliberately excludes the native process RFC, extension runtime
work, Git fetch NUL hardening, and Git diff fixture stabilization.

## Stack

This is based on #167 because that PR introduces
`docs/protocol.md#common-status-registry`. The code cleanup can be
reviewed separately and merged after the base lands.

## Verification

- `cargo fmt --check`
- changed Git status cases: `open_reports_repo`, `tree_blob_and_base`,
and `log_follow_directory_and_unknown_flags`
- LSP malformed-path regression
- four remote status-text regressions
- TypeScript status-text test
- `tsc --noEmit`
- `git diff --check`
@pcarrier

pcarrier commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in c923d46. Packet parity now explicitly says an extension endpoint receives the same initial state, correlated replies, and unsolicited S2C messages as an ordinary client under the same subscription, ownership, fan-out, and ordering rules. It names S2C_EXITED plus S2C_TITLE, S2C_USED_ROWS, and S2C_TERM_CWD_EVENT.

@pcarrier
pcarrier force-pushed the codex/wasmi-plugin-rfc branch from c923d46 to 6dd71f5 Compare August 6, 2026 01:21
pcarrier added a commit that referenced this pull request Aug 6, 2026
…dlines, retention (#204)

Closes #181.

Implements all three gaps in #181. They share one cause — nothing in the server owned a PTY's lifetime — and one implementation: a supervisor loop that runs when the delivery tick does not.

Five commits, each building standalone and separately verified:

| Commit | #181 item | What it fixes |
| --- | --- | --- |
| `answer a refused create instead of dropping it` | 3 (half) | all four create arms refuse with a bare `continue`, so a nonce-bearing client waits forever |
| `kill a terminal's process group, not just its leader` | 2 | `kill(pid)` / `kill(pid, SIGHUP)` reached the session leader alone — kill a shell, keep its children |
| `detect a terminal's exit from the child, not from EOF` | 1, 2 | exit detection was EOF-on-master, which means "the slave closed", not "the child exited" |
| `enforce opt-in terminal deadlines, and say when one fired` | 1 | every timeout was client-side, so none survived the client that set it |
| `bound retained terminals, and count the cap against live ones` | 3 (rest) | nothing but an explicit `CLOSE` ever removed an exited entry |

## Relationship to #188

#188 made `BLIT_MAX_PTYS` reachable and argued — correctly — that unlimited is the right default. It also documented the gap this PR closes, in `allocate_pty_id`: *"the protocol has no 'create refused' message"*, settling for an `eprintln` so the cap at least leaves a trace in the server log.

So the cap could be set but not safely used: turning it on traded an unbounded terminal count for a client that hangs. This adds the missing message and **leaves the default at 0**. `--max-ptys` is added alongside the env var for symmetry with the other server knobs; the `eprintln` stays, because the older create opcodes still drop the request silently by design.

The last commit does change the cap's *counting* to live terminals only, so a client running short commands under `--max-ptys N` is not refused after N of them with nothing running. Exited terminals get their own bound instead.

## Verification

Each commit message records its own check. The load-bearing ones, all re-run after the rebase onto main:

- **Refusal**: server with `--max-ptys 1` refuses the second create in milliseconds with `budget exhausted (terminal cap reached (1); raise --max-ptys or close a terminal)` and exit 1. Previously: a 10s hang, then a generic socket timeout.
- **Exit detection**: A/B'd against a pre-change server with the same command, `bash -c '(trap "" HUP; sleep N) & exit 7'` — a grandchild that ignores the hangup and keeps the slave open. Before: the terminal sits at `running` indefinitely. After: `exited(7)`.
- **Deadlines**: a terminal created with `--deadline 5` and abandoned dies at ~5s with no client attached. `blit terminal wait` prints `signal(15) — killed by deadline` where a hand-rolled `kill 9` prints a bare `signal(9)`. Refreshed every 2s against a 4s deadline it survived 12s, then died 8s after the refreshes stopped.
- **Retention**: with `--max-ptys 2 BLIT_MAX_EXITED=3`, six consecutive short commands all succeed and the list settles at the newest three.
- **Group kill**: two tests pin both halves — one asserts a child survives a leader-only kill, the other that a group kill reaches it. Mutation-checked by flipping the second to leader-only and confirming it fails.

Workspace clippy clean, `cargo fmt` clean, 556 Rust tests and 812 JS tests passing, JS typecheck clean.

## Review notes

**Two things not verified here.** The Windows job-object half has no toolchain in this checkout (Nix, no rustup) and rests on CI's windows build. And the third commit removes `reap_zombies`' global `waitpid(-1)` drain — a strict improvement, since it was reaping other subsystems' children and discarding their statuses out from under the audio pipeline's own `try_wait`, but it is a change outside the PTY family.

**Group kill's limit, stated rather than papered over.** It reaches the leader's process group and, via `TIOCGPGRP`, the terminal's foreground group. A job backgrounded by an interactive shell is in neither and survives. Bounding that needs a cgroup, not a signal.

**Feature bits 11–13 are left unallocated** for the extension, channel, and process families under review in #167 and #173. This takes 14 (`CREATE_STATUS`), 15 (`KILL_MODE`), and 16 (`PTY_DEADLINE`), matching the allocation #167's `protocol.md` already proposes for 14. The common status registry this introduces is #167's design; landing it here means #167 can drop that section rather than restate it.

**Five pre-existing test failures in `crates/git`** are unrelated — identical 55-passed/5-failed on a tree with none of these changes (a local git config makes `git tag v1` demand a message).

## Follow-ups, deliberately not in here

- `docs/design/units.md` (#94) needs reconciling before it merges: it allocates `S2C_LEASE = 0x10`, which this PR now uses for `CREATE_FAILED`; it gives `CREATE_FAILED` a different opcode *and* payload; and its "the `C2S_KILL` flags arm is `data.len() >= 7`" is off by one, since 7 is the existing message length.
- The timed `C2S_CLOSE` escalation from units.md needs `CLOSE` to hold the entry in a "closing" state, which tangles with the retention path, and is not part of what #181 asks for.
- Bounding the *aggregate* `S2C_LIST` size needs a logical-message ceiling that does not exist yet. The per-field `TOO_LARGE` check is in.
pcarrier added a commit that referenced this pull request Aug 6, 2026
The primitives section proposed wire that has since landed, and in two
places landed differently. Left as written it now contradicts the
protocol.

  - `S2C_LEASE` moves to `0x11`. `0x10` was free when this was written;
    #204 shipped `S2C_CREATE_FAILED` there.

  - `S2C_CREATE_FAILED` is `[0x10][nonce:2][status:1][detail:N]`, not
    `[0x11][nonce:2][reason:1]` — the common status registry rather than
    a message-local reason byte, matching what #167's protocol.md had
    already allocated. It is also opt-in per request via
    `CREATE2_WANT_STATUS`, so a legacy client cannot read a refusal as
    PTY zero.

  - `max_ptys` kept its `0` default rather than gaining a real one.
    #188 landed the env var in the meantime and argued unlimited is
    right, and that argument holds: a client that can open a terminal
    can already spend the machine from inside it.

  - `FEATURE_UNITS` moves to bit 17. 11-13 are reserved for the
    extension, channel, and process families; 14-16 shipped with #204.

  - The `C2S_KILL` flags arm is `data.len() >= 8`, not `>= 7`. Seven is
    the existing message length, so arming there reads a byte that is
    not there.

Delivery marks 1-3 shipped and narrows 2 to what is actually left: the
lease family, and the timed `C2S_CLOSE` escalation, which needs `CLOSE`
to hold a "closing" state and tangles with the retention path.
pcarrier added a commit that referenced this pull request Aug 6, 2026
#204 landed the RFC's primitives (delivery items 1-3, tracked as #181). Two of them landed differently from what the RFC proposed, and the RFC now contradicts the shipped protocol in ways that would mislead whoever implements the unit layer on top.

| RFC said | Shipped | Why |
| --- | --- | --- |
| `S2C_LEASE [0x10]` | must move to `0x11` | `0x10` was free when written; #204 put `S2C_CREATE_FAILED` there |
| `S2C_CREATE_FAILED [0x11][nonce:2][reason:1]` | `[0x10][nonce:2][status:1][detail:N]` | common status registry rather than a message-local byte, matching #167's `protocol.md` |
| `max_ptys` gets a real default | kept `0` | #188 landed the env var meanwhile and argued unlimited is right |
| `FEATURE_UNITS` bit 11 | bit 17 | 11-13 reserved for extension/channel/process, 14-16 shipped with #204 |
| `C2S_KILL` flags arm at `len >= 7` | `>= 8` | 7 is the existing message length — arming there reads a byte that isn't present |

Also worth knowing for the layer above: the refusal is **opt-in per request**. A client sets `CREATE2_WANT_STATUS` (bit 3) after seeing `FEATURE_CREATE_STATUS` (HELLO bit 14), so `CREATE`, `CREATE_AT`, `CREATE_N` and unflagged `CREATE2` keep their success-only contract and a legacy client can't read a refusal as PTY zero.

Delivery now marks 1-3 shipped and narrows item 2 to what's actually left: the lease family, and the timed `C2S_CLOSE` escalation. That second one needs `CLOSE` to hold the entry in a "closing" state, which tangles with the retention path #204 added — it was deliberately out of scope for #181 and is still open.

No changes to the unit layer itself; how the primitives landed doesn't affect it.
Move the existing FS/Git/KV/LSP/NET implementations and client
diagnostics onto the common status registry proposed by #167.

- classify malformed Git and LSP inputs as `INVALID` instead of `OTHER`
- make Rust and TypeScript status renderers distinguish declared backend
`OTHER` from unknown numeric status values
- update existing-family documentation and comments to point at the
canonical registry
- add Rust and TypeScript regression tests

This deliberately excludes the native process RFC, extension runtime
work, Git fetch NUL hardening, and Git diff fixture stabilization.

This is based on #167 because that PR introduces
`docs/protocol.md#common-status-registry`. The code cleanup can be
reviewed separately and merged after the base lands.

- `cargo fmt --check`
- changed Git status cases: `open_reports_repo`, `tree_blob_and_base`,
and `log_follow_directory_and_unknown_flags`
- LSP malformed-path regression
- four remote status-text regressions
- TypeScript status-text test
- `tsc --noEmit`
- `git diff --check`
@pcarrier
pcarrier force-pushed the codex/wasmi-plugin-rfc branch from 6dd71f5 to d6417e0 Compare August 13, 2026 02:09
pcarrier added a commit that referenced this pull request Aug 13, 2026
| RFC said | Shipped | Why |
| --- | --- | --- |
| `S2C_LEASE [0x10]` | must move to `0x11` | `0x10` was free when written; #204 put `S2C_CREATE_FAILED` there |
| `S2C_CREATE_FAILED [0x11][nonce:2][reason:1]` | `[0x10][nonce:2][status:1][detail:N]` | common status registry rather than a message-local byte, matching #167's `protocol.md` |
| `max_ptys` gets a real default | kept `0` | #188 landed the env var meanwhile and argued unlimited is right |
| `FEATURE_UNITS` bit 11 | bit 17 | 11-13 reserved for extension/channel/process, 14-16 shipped with #204 |
| `C2S_KILL` flags arm at `len >= 7` | `>= 8` | 7 is the existing message length — arming there reads a byte that isn't present |

Also worth knowing for the layer above: the refusal is **opt-in per request**. A client sets `CREATE2_WANT_STATUS` (bit 3) after seeing `FEATURE_CREATE_STATUS` (HELLO bit 14), so `CREATE`, `CREATE_AT`, `CREATE_N` and unflagged `CREATE2` keep their success-only contract and a legacy client can't read a refusal as PTY zero.

Delivery now marks 1-3 shipped and narrows item 2 to what's actually left: the lease family, and the timed `C2S_CLOSE` escalation. That second one needs `CLOSE` to hold the entry in a "closing" state, which tangles with the retention path #204 added — it was deliberately out of scope for #181 and is still open.

No changes to the unit layer itself; how the primitives landed doesn't affect it.
@blacksmith-sh

blacksmith-sh Bot commented Aug 13, 2026

Copy link
Copy Markdown

Found 5 test failures on Blacksmith runners:

Failures

Test View Logs
[chromium] › tests/
font-size-sync.spec.ts:11:5 › font size stays local while previewing and reaches peers
on Apply
View Logs
[chromium] › tests/
pane-tools.spec.ts:193:7 › Parked terminal does not resurrect › closing the session tha
t displaced a parked one shows it, not an empty pane
View Logs
[chromium] › tests/
touch-draggables.spec.ts:205:7 › Touch drag on list rows › a swipe across a dock card s
till dismisses it
View Logs
tests/font-size-sync.spec.ts/
font size stays local while previewing and reaches peers on Apply
View Logs
Touch drag on list rows/a swipe across a dock card still dismisses it View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@pcarrier
pcarrier merged commit e12931b into main Aug 13, 2026
9 of 11 checks passed
@pcarrier
pcarrier deleted the codex/wasmi-plugin-rfc branch August 13, 2026 02:27
pcarrier added a commit that referenced this pull request Aug 13, 2026
## Summary

Extract the non-PTY process design from #167 into a standalone,
all-client protocol RFC.

- feature bit 13 and direction-local `0xC0` through `0xC6`
- direct argv execution with bounded arguments and environment
- flow-controlled binary stdin, stdout, and stderr with lifetime offsets
- ordinary endpoint-scoped children plus opt-in detachable children
- client-generated 128-bit adoption tokens and atomic snapshot-to-live
reattachment
- bounded output gaps and repeatable retained exit snapshots across
reconnects
- correlated spawn/control outcomes, stdin state, explicit exit reasons,
and kill causes
- Unix process-group and Windows job cleanup semantics
- count, stream-window, pending-request, spawn-concurrency, and
retained-result limits
- feature gate and security posture independent of Wasmi

Network clients use their existing transport. In-process extensions use
the same packets through the ordinary host ABI, but neither
implementation depends on the other.

## Review follow-up

The revised RFC makes detach/adopt ownership, outbox cutoffs, inherited
stdin, output-credit reset, terminal expiry, and concurrent adoption
linearization explicit. It also serializes Unix spawn registration
against Blit's existing `waitpid(-1)` backstop, documents expected
supervisor `BUDGET` handling, and gives supervisors an explicit Unix
per-process grace-period recipe.

Detached state is intentionally server-memory-only: it does not survive
a Blit restart, and output produced while unbound is drained and
represented as an offset gap rather than replayed.

## Stack

This is intentionally based on #167 so the extension RFC can link to it
while keeping the process family out of the parent PR. It should be
reviewed and merged separately after the base lands.

## Verification

- `./bin/lint` passes
- `cargo fmt --check`, repository-wide Prettier, and `git diff --check`
pass
- independent consistency and flow-control reviews found no remaining
merge blocker
indent Bot pushed a commit that referenced this pull request Aug 13, 2026
| RFC said | Shipped | Why |
| --- | --- | --- |
| `S2C_LEASE [0x10]` | must move to `0x11` | `0x10` was free when written; #204 put `S2C_CREATE_FAILED` there |
| `S2C_CREATE_FAILED [0x11][nonce:2][reason:1]` | `[0x10][nonce:2][status:1][detail:N]` | common status registry rather than a message-local byte, matching #167's `protocol.md` |
| `max_ptys` gets a real default | kept `0` | #188 landed the env var meanwhile and argued unlimited is right |
| `FEATURE_UNITS` bit 11 | bit 17 | 11-13 reserved for extension/channel/process, 14-16 shipped with #204 |
| `C2S_KILL` flags arm at `len >= 7` | `>= 8` | 7 is the existing message length — arming there reads a byte that isn't present |

Also worth knowing for the layer above: the refusal is **opt-in per request**. A client sets `CREATE2_WANT_STATUS` (bit 3) after seeing `FEATURE_CREATE_STATUS` (HELLO bit 14), so `CREATE`, `CREATE_AT`, `CREATE_N` and unflagged `CREATE2` keep their success-only contract and a legacy client can't read a refusal as PTY zero.

Delivery now marks 1-3 shipped and narrows item 2 to what's actually left: the lease family, and the timed `C2S_CLOSE` escalation. That second one needs `CLOSE` to hold the entry in a "closing" state, which tangles with the retention path #204 added — it was deliberately out of scope for #181 and is still open.

No changes to the unit layer itself; how the primitives landed doesn't affect it.
indent Bot pushed a commit that referenced this pull request Aug 13, 2026
Every file:line citation had drifted; #204 moved most of them and
twelve more commits moved the rest. Refreshed all of them against
1919717 and rewrote 'What exists today' to say what is true after
#204 rather than before it, marking which rows it changed.

Three wire claims collided with merged reality: #167 took feature bit
11 and C2S/S2C 0x90-0x95 for extensions and channels, #173 took bit 13
and 0xC0-0xC6 for processes, and #260 took C2S 0x1E / S2C 0x11 for
scroll-by. The unit family moves to 0xD0 on feature bit 20 and the
lease pair to C2S 0x1F / S2C 0x12.

Two corrections to what #212 recorded: the timed half of the C2S_CLOSE
escalation did ship, on the abandoned pid rather than a 'closing' slot;
and the create refusal reaches only C2S_CREATE2, so the silent drop
survives on the three legacy create opcodes.
pcarrier added a commit that referenced this pull request Aug 13, 2026
## Summary

- define Wasmi-hosted Rust extensions as in-process logical Blit clients
using the ordinary packet dispatcher
- expose a four-call host ABI: packet `send`, blocking packet `recv`,
direct realtime/monotonic `clock`, and OS-backed `random`
- use BLAKE3 content-addressed upload so `blit run --on ... FILE
ARGS...` sends module bytes only on a cache miss
- supervise attached, detached, autorestarting, and restart-persistent
extensions, with one named OS thread per running attempt
- add reliable bidirectional named channels as an ordinary packet family
- let live named persistent extensions advertise discoverable `@name`
CLI command trees, with invocation I/O carried over channels

## Lifecycle and resource model

- a module hash identifies exact immutable Wasm bytes; an `extension_id`
identifies one supervised installation; `(extension_id, attempt)`
identifies one Wasmi instance
- the same hash can back multiple isolated extensions, and separate
durable names permit concurrent versions such as `builder` and
`builder-canary`
- failures restart with backoff under `on-failure`; update,
cancellation, shutdown, and persistence have explicit supervisor
transitions
- the raw Wasm CAS uses automatic unpinned LRU under a 2 GiB default
budget; persistent definitions pin their objects
- server-wide defaults bound running threads, supervisors, storage,
validation, guest memory/tables/stacks, and transport queues

## CLI and command extensibility

- `blit ext run --on ... FILE ARGS...` installs/runs an extension; `blit
run` is an alias and positional tokens after `FILE` are guest arguments
- `blit ext update NAME FILE ARGS...` atomically switches a persistent
definition after ID/revision checks
- a live named persistent extension registers a bounded `blit.cli.v1`
descriptor and channel listener through `EXT_COMMAND`
- `blit ext commands`, `@name --help`, and completion use immutable
directory snapshots; invocation is never retried across attempts

## Key decisions

- Wasmi only; no QuickJS, WASI, manifest, capability/grant model,
per-run limits, state, or topic family
- one guest target, `wasm32-unknown-unknown`, and one host import
module, `blit_v1`
- direct host services are packet I/O, clocks, and entropy; every Blit
operation remains a packet
- pipe-oriented non-PTY process execution is independent and moved to
draft #173

## Protocol allocation

- feature bit 11: `EXTENSION`, direction-local `0x90` through `0x94`
- feature bit 12: `CHANNEL`, bidirectional `0x95`; `0x96` remains free
- feature bit 13 remains unallocated in this PR
- feature bit 14: proposed correlated `CREATE2(WANT_STATUS)` failures

## Split follow-ups

- #173: standalone all-client non-PTY process RFC
- #174: existing-family common-status implementation and diagnostics
cleanup

Both are stacked on this branch so #167 remains focused and reviewable.
They can merge separately after the base.

## Scope and verification

This PR is a design RFC plus protocol documentation corrections. It does
not implement the runtime, SDK, CLI, packet families, or non-PTY process
protocol.

- `git diff --check` passes
- the focused net diff is `docs/design/extensions.md`, selected
`docs/protocol.md` changes, and the stale Git opcode comment correction
pcarrier added a commit that referenced this pull request Aug 13, 2026
## Summary

Extract the non-PTY process design from #167 into a standalone,
all-client protocol RFC.

- feature bit 13 and direction-local `0xC0` through `0xC6`
- direct argv execution with bounded arguments and environment
- flow-controlled binary stdin, stdout, and stderr with lifetime offsets
- ordinary endpoint-scoped children plus opt-in detachable children
- client-generated 128-bit adoption tokens and atomic snapshot-to-live
reattachment
- bounded output gaps and repeatable retained exit snapshots across
reconnects
- correlated spawn/control outcomes, stdin state, explicit exit reasons,
and kill causes
- Unix process-group and Windows job cleanup semantics
- count, stream-window, pending-request, spawn-concurrency, and
retained-result limits
- feature gate and security posture independent of Wasmi

Network clients use their existing transport. In-process extensions use
the same packets through the ordinary host ABI, but neither
implementation depends on the other.

## Review follow-up

The revised RFC makes detach/adopt ownership, outbox cutoffs, inherited
stdin, output-credit reset, terminal expiry, and concurrent adoption
linearization explicit. It also serializes Unix spawn registration
against Blit's existing `waitpid(-1)` backstop, documents expected
supervisor `BUDGET` handling, and gives supervisors an explicit Unix
per-process grace-period recipe.

Detached state is intentionally server-memory-only: it does not survive
a Blit restart, and output produced while unbound is drained and
represented as an offset gap rather than replayed.

## Stack

This is intentionally based on #167 so the extension RFC can link to it
while keeping the process family out of the parent PR. It should be
reviewed and merged separately after the base lands.

## Verification

- `./bin/lint` passes
- `cargo fmt --check`, repository-wide Prettier, and `git diff --check`
pass
- independent consistency and flow-control reviews found no remaining
merge blocker
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