Options Paper · trade-off analysis

Wrap witr as an ainb plugin

Date2026-05-19 Author@stevie Branchfeat/witr-plugin Upstreamgithub.com/pranshuparmar/witr · Apache-2.0 Recommendation: Option A
Arecommended

Thin Rust plugin that exec's witr --json

Uses 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.

01

Problem

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 APIpkg/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?

02

Evaluation criteria

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.

Effort to first prod useengineer-days from spike to ship
0.25
Maintenance surfacecode we own forever after merge
0.20
Plugin-model fitrespects the boundary Phase 7 established
0.15
Reuse witr as-isamount of upstream code consumed unmodified
0.15
v0.4 upstream-riskblast radius when witr ships its next minor
0.15
Test pathfits CTS + tripwire harness today
0.10
03

Five options

Two viable (A, B), three explicit rejects (C, D, E) included to make the trade-offs visible rather than silently discarded.

Option A recommend

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.

Effort5
Maint4
Plug-fit5
Reuse5
v0.4 risk4
Test5
weighted4.65 / 5
Option B viable

Go plugin speaks JSON-RPC directly

Hand-write JSON-RPC 2.0 framing in Go, exec witr. Pioneers multi-language story; could seed ainb-plugin-sdk-go. 5–7 days.

Effort3
Maint2
Plug-fit5
Reuse5
v0.4 risk4
Test2
weighted3.45 / 5
Option C reject

Fork witr, expose pkg/api, link in Go plugin

Vendored Go fork — promote internal/ to public, link statically. No subprocess. Maintain fork forever; still must rewrite output to WireBuffer.

Effort1
Maint1
Plug-fit4
Reuse2
v0.4 risk1
Test2
weighted1.70 / 5
Option D reject

Reimplement witr in Rust as a native plugin

Own the /proc readers, libproc cgo replacement, container inspectors, the lot. Pure Rust, zero upstream dep — and months of work.

Effort1
Maint2
Plug-fit5
Reuse1
v0.4 risk5
Test2
weighted2.50 / 5
Option E reject

Host integrates witr (no plugin)

Bake into ainb-core directly. Math says it scores well — but it defeats Phase 7 and couples ainb's release cadence to witr. Anti-pattern.

Effort5
Maint3
Plug-fit1
Reuse5
v0.4 risk4
Test4
weighted3.75 / 5
04

Comparison matrix

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)53115
Maintenance surface (0.20)42123
Plugin-model fit (0.15)55451
Reuse witr as-is (0.15)55215
v0.4 upstream-risk (0.15)44154
Test path (0.10)52224
Weighted total4.653.451.702.503.75
05

Direct answers to the two questions

Cut through the option grid — these are the questions in the brief.

Question 1

Can the plugin setup take witr as-is, or do we have to rework it?

Answer · two-part

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.

Question 2

Can the plugin be in any language?

Answer · yes, with one footnote

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.

06

How Option A wires together at runtime

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).

ainb-core (host) ainb-plugin-witr (Rust subprocess) witr binary (Go · Homebrew) OS · containers /proc · docker · … 1 · spawn subprocess 2a · plugin/init {capabilities} 2b · ok {manifest} 3 · plugin/render({viewport, target}) 4 · exec witr --json <target> (gated by spawn_subprocess capability) 5 · /proc · libproc · docker inspect syscall results 6 · JSON on stdout · exit 0 7 · plugin: serde_json → WireBuffer cells 8 · render-result {wire_buffer} 9 · ainb paints to terminal
JSON-RPC 2.0 over Content-Length framed stdio · capability-gated subprocess at step 4
07

Ranking

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.

1stA · Rust + exec witr4.65
2ndE · Host bake-in (rejected — anti-pattern)3.75
3rdB · Go + exec witr3.45
4thD · Rewrite in Rust2.50
5thC · Fork & link1.70
08

Open questions before kickoff

Is witr's --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 check

Does spawn_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.

@stevie · read runtime crate

Ship witr binary, or require user install?

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 kickoff

Update stale plugin memory entries

Memory 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.

@stevie · separate cleanup task