Rust plugin exec's witr --json
Uses ainb-plugin-sdk-rust, declares spawn_subprocess, parses witr's stable JSON, renders WireBuffer. 1–2 days. No Go in the build, no fork.
witr as an ainb pluginwitr --jsonUses the existing Rust SDK, declares spawn_subprocess = true, parses witr's stable JSON contract, paints into ainb's WireBuffer. Ships in 1–2 days, reuses the existing tripwire harness, ainb keeps full TUI ownership. No fork, no Go toolchain, no upstream coupling.
We want witr's "why is this process running?" tracing inside ainb. witr is a Go 1.25 / Charmbracelet TUI binary with deep OS-syscall coupling (/proc, libproc via cgo, sysctl, plus shellouts to docker/podman/crictl/launchctl/systemctl) and no importable library API — pkg/model/ exposes data structs only; all logic lives in internal/.
ainb's v2 plugin system (post Phase 7) is native subprocess + JSON-RPC 2.0 over Content-Length framed stdio. Plugins are ordinary OS binaries; capabilities are declared in manifest.toml and enforced by the runtime. The Rust SDK exists (ainb-plugin-sdk-rust); other languages must speak the wire protocol by hand.
The question is the boundary: do we exec the witr binary, fork+vendor its internals, rewrite it in Rust, or skip the plugin layer entirely?
Weights reflect what matters for a small-scope integration: ship fast, don't grow the maintenance surface, stay inside the plugin model that Phase 7 just landed. Each criterion scores 1–5; weighted total / 5.0.
Two viable (A, B), three explicit rejects (C, D, E) included to make the trade-offs visible rather than silently discarded.
Darker green = stronger fit · rust = weak. Weighted totals in the bottom row; the green-filled cell is the recommendation.
| A · Rust + exec | B · Go + exec | C · Fork & link | D · Rewrite Rust | E · Host bake-in | |
|---|---|---|---|---|---|
| Effort (0.25) | 5 | 3 | 1 | 1 | 5 |
| Maintenance surface (0.20) | 4 | 2 | 1 | 2 | 3 |
| Plugin-model fit (0.15) | 5 | 5 | 4 | 5 | 1 |
| Reuse witr as-is (0.15) | 5 | 5 | 2 | 1 | 5 |
| v0.4 upstream-risk (0.15) | 4 | 4 | 1 | 5 | 4 |
| Test path (0.10) | 5 | 2 | 2 | 2 | 4 |
| Weighted total | 4.65 | 3.45 | 1.70 | 2.50 | 3.75 |
Cut through the option grid — these are the questions in the brief.
The witr binary — yes, exactly as-is. ainb's spawn_subprocess capability is the gate for this pattern. The plugin exec's witr --json <target>, captures stdout, parses it, returns a WireBuffer. No witr source code modified.
witr's Go internals — no, can't reuse. witr exposes pkg/model/ as data structs only; all logic lives in internal/ packages that Go's visibility rules prohibit importing. Library reuse would require a fork (Option C, rejected).
The clean boundary is the JSON contract that --json already produces.
Yes — the wire protocol is language-agnostic. Any language that can read/write JSON-RPC 2.0 with Content-Length framing on stdin/stdout qualifies. The crate description for ainb-plugin-protocol literally calls this out: "Wire protocol types for ainb plugins (JSON-RPC 2.0 over stdio)".
Footnote: only Rust has an SDK today. Other languages re-implement the framing, dispatch, and reverse-call client by hand. Worth it for a multi-plugin language; not worth it for witr alone, because we'd still exec the binary either way — Go-as-plugin doesn't unlock witr's internals.
If seeding an ainb-plugin-sdk-go is a separate goal, that's a strategic call worth making on its own merits — not via the witr plugin.
Sequence for a single render frame. Steps 1–2 happen once at startup; 3–8 repeat on every paint (or on every viewport refresh, depending on cadence).
By weighted total. Note that E scores second despite being a reject — the math doesn't model architectural debt. Phase 7 explicitly burned this bridge.
--json schema versioned?If not, pin to a specific witr release (Homebrew formula or vendored prebuilt) to avoid silent breakage on minor bumps. Verify before the plugin lands.
@stevie · gh api checkspawn_subprocess support per-binary allowlisting?Or is it boolean only? If boolean, a malicious plugin could exec anything on $PATH. Confirm in ainb-plugin-runtime/src/; add per-path gating if missing.
Vendoring adds licence + ~8MB to the ainb distribution per platform; requiring install (brew/curl) adds a manual step but keeps ainb lean. Lean wins unless we see pushback.
@stevie · decide before kickoffMemory still describes plugins as wasm32-wasip1 + wasmi (v1, retired). Several reference entries (plugin_sdk_no_host_deps, wasmi_no_wasi_preview1, etc.) need rewrite for v2 subprocess + JSON-RPC.