ainb-core · architecture note

ainb v2 plugin architecture

ainb v2 plugins are native subprocesses that speak JSON-RPC 2.0 over Content-Length-framed stdio. The host — the ainb binary, crate ainb-core — discovers plugins from dist/plugins/<id>/<id> plus a manifest.toml, spawns each one, and exchanges JSON-RPC. The host owns the ratatui terminal; plugins either paint cells the host blits, publish data over an event bus, or get handed the whole terminal. (v1 was wasm/wasmi — retired.)

The system, end to end

ainb v2 plugin architecture native subprocess · JSON-RPC 2.0 over Content-Length stdio binary: ainb ainb-core · host ratatui TUI — owns the terminal ainb-plugin-runtime discover · spawn · route · capability gate (-32001) JSON-RPC 2.0 · Content-Length framed stdio WireBuffer render burndown Analytics screen · ainb usage sessions.usage_data session-reader publisher · no screen caps: spawn_subprocess witr ainb witr CLI + /witr slash witr -i (foreign TTY) host suspends → tmux attach → resume owns plugin/* methods render · handle_key cli_dispatch usage_data via event bus press w → handoff JSON-RPC (render / keys / cli) event-bus snapshot data foreign-TTY handoff (dashed)
diagram generated via /fireworks-tech-graph

The crates

ainb-plugin-protocol

The wire types — every JSON-RPC request, response, and payload that crosses the stdio boundary is defined here.

ainb-plugin-sdk-rust

The Rust SDK that plugins build on. Implement its Plugin trait and you have a conformant plugin.

ainb-plugin-runtime

The host side. Discovers plugins, spawns them, routes JSON-RPC in both directions, and enforces the capability gate.

ainb-plugin-cts-v2

The conformance test suite — 14 axes a plugin (and the runtime) must satisfy to be considered correct.

How a frame is drawn

host (ainb-core) plugin-runtime plugin subprocess spawn dist/plugins/<id>/<id> plugin/init( granted_capabilities ) plugin/render( Viewport ) → WireBuffer · Vec<(Coord, Cell)> host blits cells → terminal host/snapshot/publish (event bus) host/snapshot/subscribe · host/action/invoke solid = host→plugin JSON-RPC dashed = return clay = plugin→host reverse calls
request → render sequence (hand-authored)

The methods that cross the wire

H→P
host → plugin

plugin/init carries the granted_capabilities the manifest declared. plugin/render sends the plugin a Viewport and gets back a WireBuffer — a sparse Vec<(Coord, Cell)> cell grid the host blits onto the terminal. plugin/handle_key forwards a normalized KeyEvent; plugin/handle_event delivers snapshots; plugin/cli_dispatch routes ainb <namespace> <argv> to the plugin and pipes back stdout / stderr / exit; plugin/shutdown ends the session.

P→H
plugin → host (reverse calls)

A plugin calls back into the host with host/snapshot/publish and host/snapshot/subscribe — together these form the event bus — and host/action/invoke to trigger host-side actions.

CAP
capabilities · deny-by-default

Capabilities are declared in manifest.toml and enforced by the runtime. A host-fn call the manifest didn't grant returns JSON-RPC error -32001 (CAPABILITY_DENIED). The set is read_sessions, read_claude_logs, read_codex_logs, write_plugin_data, event_bus, spawn_subprocess, and network. Deny-by-default — if it isn't granted, it's blocked.

Three in-tree reference plugins

burndownscreen-owner

Owns the Analytics screen and the ainb usage CLI. Renders a full ratatui dashboard into a WireBuffer each frame; the host blits it. Fed by snapshots from session-reader over the event bus.

session-readerpure publisher

No screen. Scans ~/.claude/projects and ~/.codex/sessions, then chunk-publishes usage snapshots on topic sessions.usage_data.

witrsubprocess wrapper

Declares spawn_subprocess and execs the external witr CLI. Its ainb witr <target> CLI and /witr slash run witr --json <target> and parse the ancestry JSON. But its interactive all-process browser is not a WireBuffer render — that browser only exists in witr's own bubbletea TUI (there's no JSON for the full process list), so pressing w does a host-level full-screen handoff: tmux new-session -A -d -s ainb-witr "witr -i", suspend ainb's TUI, tmux attach, and resume on quit.

The centerpiece: two render paths a plugin screen can take

Path A

In-process WireBuffer

The host owns the terminal. The plugin paints cells the host blits. Themeable, integrated, and rendered inside the ainb TUI.

Used by burndown. Plugin returns a WireBuffer from plugin/render; the host composites it.
Path B

Host-embedded foreign TTY

The host suspends and hands the whole terminal to an external interactive binary, resuming when it exits. Full fidelity to the foreign app — but not ainb-themed.

Used by witr (witr -i). Same mechanism ainb uses to attach to agent tmux sessions.

Authoring a plugin

Implement the SDK Plugin trait — render, handle_key, handle_event, cli_dispatch, on_init, on_shutdown. Declare your capabilities plus cli_namespaces / screens in manifest.toml. Build the binary and stage it at dist/plugins/<id>/<id>.

Pick the reference closest to what you're building: burndown is the screen-owner reference, session-reader the pure-publisher reference, and witr the subprocess-wrapper reference.