Skip to content

ainb v2 plugins — overview

What a v2 subprocess plugin is, conceptually. New here? Read README.md first — it disambiguates from Claude Code plugins.

For the user CLI flow, jump to user-guide.md. To write one, authoring.md. For the wire contract, spec-v2.md.

What is a plugin?

A plugin is a self-contained capsule that adds a screen, CLI subcommand, sidebar entry, or statusline segment to ainb without recompiling the host. A v2 plugin is a native executable the host spawns as a child process; the two talk JSON-RPC 2.0 over framed stdio.

Plugins only see the host capabilities they declare in their manifest, and they cannot reach the network, filesystem, or subprocess launcher unless you grant those capabilities.

Architecture at a glance

ainb v2 plugin architecture — host, JSON-RPC stdio, plugin subprocesses, capability gate, event bus

The host (ainb-core) spawns each plugin as a child process and drives it over JSON-RPC 2.0 / Content-Length-framed stdio. Host→plugin methods: plugin/init (with the granted capabilities), plugin/render (host sends a Viewport, plugin returns a WireBuffer), plugin/handle_key, plugin/handle_event, plugin/cli_dispatch, plugin/shutdown. Plugin→host (reverse) calls: host/snapshot/publish + host/snapshot/subscribe (the event bus) and host/action/invoke. The ainb-plugin-runtime enforces capabilities — an ungranted host-fn call comes back as JSON-RPC -32001 (CAPABILITY_DENIED).

Two ways a plugin screen renders

  • In-process WireBuffer — the host owns the terminal; the plugin paints a sparse cell grid the host blits each frame. Integrated and themeable. This is how burndown draws the Analytics dashboard.
  • Host-embedded foreign TTY — for an interactive program that has no machine-readable render (only its own TUI), the host suspends and hands the whole terminal to the external binary, resuming when it exits — the same mechanism ainb uses to attach to agent sessions. This is how witr opens its all-process browser (witr -i): there’s no JSON for witr’s live process list, so pressing w runs tmux new-session -A -d -s ainb-witr "witr -i" and attaches full-screen.

What a plugin can own

  • A TUI screen — implement Plugin::render and paint a WireBuffer per frame; the host blits it onto the terminal.
  • A CLI subcommand tree — claim a cli_namespaces entry in your manifest; the host dispatches ainb <ns> ... invocations through Plugin::cli_dispatch.
  • Snapshot topics (publish/subscribe) — push data on a topic via HostClient::snapshot_publish; subscribers (other plugins or the host) get plugin/handle_event deliveries.
  • A statusline segment — own a slice of the persistent status bar.
  • Its own state — write under ~/.agents-in-a-box/plugins/<name>/ (gated by write_plugin_data).
  • Host actions — invoke host-owned operations via host/action/invoke (gated by capability).

Reference plugins

Four plugins ship in-tree as the canonical examples:

  • burndown — screen-owner reference. Owns the Analytics screen (renders a ratatui dashboard into a WireBuffer each frame) and the ainb usage CLI tree; subscribes to sessions.usage_data from session-reader.
  • session-reader — pure-publisher reference. No screen. Scans ~/.claude/projects/**, ~/.codex/sessions/** (and more) and chunk-publishes usage snapshots on sessions.usage_data for burndown to render.
  • witr — subprocess-wrapper reference. The ainb witr <target> CLI + /witr slash run witr --json <target> and parse the ancestry JSON; its screen is a host-embedded foreign TTY (w hands the terminal to witr -i — see the two render paths). Declares spawn_subprocess + event_bus.
  • learnings — read-only-browser reference. A path-scoped fs reader + qmd subprocess: it browses, searches and graphs the reflect knowledge base under ~/.learnings (Markdown notes + .entities.yaml sidecars + the nano-graphrag cache + the qmd index). Opens with m / /recall / /memory; its Graph tab renders an entity neighbourhood, community clusters, and a deterministic radial ego local-graph. Declares read_paths + spawn_subprocess + event_bus.
  • abtop — subprocess-wrapper plugin for abtop (top-for-agents). The ainb abtop CLI execs abtop --once; pressing t attaches the terminal full-screen to abtop --exit-on-jump (same foreign-TTY hand-off pattern as witr). First launch shows a one-time rate-limit --setup consent dialog. Declares spawn_subprocess only.

Each links to its own page with a /fireworks-tech-graph diagram of how it works.

Not a plugin: the Inbox screen + ainb-notifyd daemon are sometimes mistaken for an in-tree plugin (the crate is named ainb-plugin-notifyd). They are host code compiled into ainb-core — no manifest, no JSON-RPC, no capability gate. They’re documented under TUI → Inbox & notifications, not here.

Burndown plugin — full analytics dashboard

The burndown plugin rendering the full analytics dashboard against real ~/.claude/projects data.

Where plugins live on disk

The host discovers plugins from a flat staging directory:

dist/plugins/
├── abtop/
│ ├── abtop (native executable, ad-hoc signed on macOS)
│ └── manifest.toml
├── burndown/
│ ├── burndown (native executable, ad-hoc signed on macOS)
│ └── manifest.toml
├── session-reader/
│ ├── session-reader
│ └── manifest.toml
└── witr/
├── witr
└── manifest.toml

(notifyd is not here — it’s a daemon compiled into ainb-core, not a staged subprocess plugin; see TUI → Inbox & notifications.)

That layout is what just stage-plugins produces from in-tree crates, and what the host walks on startup. The AINB_PLUGIN_ROOT env var overrides it (defaults to <workspace-root>/dist/plugins).

Plugin-writable state lives under ~/.agents-in-a-box/plugins/<name>/ (override with AINB_HOME), gated by the write_plugin_data capability.

Capability model (summary)

Every plugin declares the host capabilities it needs in its manifest.toml:

[capabilities]
read_sessions = true # ~/.agents-in-a-box/sessions/**
read_claude_logs = true # ~/.claude/projects/**/*.jsonl
read_codex_logs = false # ~/.codex/sessions/**/*.jsonl
write_plugin_data = true # ~/.agents-in-a-box/plugins/<name>/ writable
event_bus = true # publish/subscribe across plugins
spawn_subprocess = false # exec child processes
network = [] # bool or hostname allow-list

Default for every flag is deny (false / []). The runtime rejects host-fn calls against a capability the manifest doesn’t grant with JSON-RPC error code -32001 (CAPABILITY_DENIED).

Full semantics: spec-v2.md §1 and spec-v2.md §9.

Versus the deprecated v1 wasm contract

v1 used wasm32-wasip1 cdylibs in a wasmi host runtime with linker-omitted host-fn imports for capability gating. v2 dropped wasm entirely — a normal cargo build binary, native code, OS-process boundary. The wasm sandbox added implementation cost without buying any safety property the OS process boundary doesn’t already provide for ainb’s threat model.

Next steps

  • End user? user-guide.md — every ainb plugin command.
  • Building a plugin? authoring.md — Rust SDK, scaffolding, debugging.
  • Implementing a host? spec-v2.md — the wire contract.