Architecture
Overview
Prismo is built around a simple idea: plugins emit telemetry, the host normalizes it, and the terminal UI renders the latest view of the system.
Internally, the project is split into a few main parts:
- telemetry ingestion and modeling
- plugin protocol and process hosting
- TUI rendering and interaction
- application runtime wiring
- plugin SDKs and implementations
This structure keeps the UI focused on inspection while making it possible to adapt Prismo to different systems through plugins.
Project Layout
crates/core
This crate owns the core telemetry concepts:
ChannelDescriptorChannelValueChannelSampleTelemetryUpdateRuntimeEventPluginHealthPluginRuntimeStateTelemetryStore
It defines the internal telemetry model used after plugin messages are normalized by the host.
crates/plugin-protocol
This crate defines the external plugin contract:
- protobuf message types
- length-prefixed frame helpers
crates/plugin-host
This crate handles:
- plugin manifest parsing and discovery
- subprocess spawn and supervision
stdin/stdout/stderrtransport- handshake validation
- restart policy handling
- conversion from wire messages into
RuntimeEvent
crates/plugin-sdk/rust
This crate is the Rust SDK for writing subprocess plugins.
crates/plugin-sdk/cpp
This crate exposes a small Diplomat-backed C++ surface on top of the Rust SDK core. It lets C++ plugins reuse the same stdio framing and protobuf implementation without reimplementing the protocol natively.
plugins/example-rust
This crate contains the current Rust example plugin implementation. It now runs as a subprocess plugin over the shared wire protocol.
plugins/example-cpp
This target contains the current C++ example plugin implementation. It links against the generated Diplomat C++ bindings and the Rust-backed FFI SDK.
crates/tui
This crate owns:
- pane layout
- key and mouse handling
- focused text cursor behavior
- details and latest-value rendering
- channel tree expansion/collapse state
- scroll offsets and scrollbar rendering
- command/filter prompt rendering
- status bar and help overlay
It renders from StoreSnapshot only. It does not talk directly to plugins.
app
This crate is the executable. It:
- accepts an optional
--pluginsoverride directory - auto-discovers plugins from a sibling
plugins/directory by default - starts plugin subprocesses through
plugin-host - receives
RuntimeEventvalues through a channel - applies normalized telemetry to
TelemetryStore - drives the TUI render loop
- handles clipboard yank via OSC 52
All example plugins go through the same external plugin boundary as real integrations.
Data Flow
At runtime, Prismo looks like this:
plugin subprocess -> stdio + protobuf -> plugin-host -> RuntimeEvent -> app -> TelemetryStore -> StoreSnapshot -> tui
Detailed flow:
- The app resolves the plugin directory from
--pluginsor from a siblingplugins/directory. - The host discovers
prismo-plugin.tomlmanifests in that directory. - The host spawns each discovered plugin subprocess.
- The host sends
Initonstdinand validates the child'sHello. - The host normalizes plugin messages into
RuntimeEventvalues. - The main loop drains pending events with
try_recv. - The store applies descriptors, samples, plugin health, and runtime state.
- The TUI renders from the latest snapshot.
Telemetry Model
The current canonical value types are:
BoolIntegerFloatTextBytes
The store keeps:
- latest sample per channel
- recent numeric history for charting
- per-channel update counts
- the most recent observed inter-sample interval and derived rate
- plugin health snapshots
- total dropped updates caused by missing channel descriptors
Store Behavior
Important store rules:
- channels are keyed by
(plugin_id, path)in aBTreeMap - numeric history retention is capped at 64 samples
- staleness is adaptive: a channel is stale when time since last sample exceeds
3xthe most recent observed interval - channels with only one sample fall back to a
3sinitial stale threshold total_updatescounts applied update batches, not individual samplesdropped_updatesincrements if a sample arrives before its descriptor exists
Plugin Model
Prismo uses a subprocess-first plugin model:
- each plugin runs as a child process
- the host sends
Initonstdin - the plugin writes framed protobuf messages on
stdout - free-form logs go to
stderr
The current Rust and C++ example plugins send:
Helloon startupDeclareChannelsonce- randomized
SampleBatchmessages on each interval tick Healthcontainingemitted_updates
Build System
Bazel is the primary build system for the repository:
MODULE.bazelpins the Bazel module graph. It is also the authoritative repo version; the Cargo workspace package version is intentionally fixed at0.0.0rules_rustprovides the hermetic Rust toolchainrules_pythonprovides the hermetic Python runtimetoolchains_llvmprovides the hermetic C++ toolchain used for the in-repo C++ plugincrate_universereads the Cargo workspace metadata and resolves external Rust crates for Bazelbazel/defs.bzlexposes Prismo-specific packaging rules for downstream Bazel users- each workspace package has its own
BUILD.bazel - plugin manifests are checked into plugin directories. 1st party supported plugins live in the
plugins/directory
The downstream Bazel surface is intentionally small:
prismo_pluginpackages an executable plus its checked-in manifestprismo_bundleassembles the runnable bundle layout and is itself executable viabazel run
The current C++ path is intentionally Rust-backed:
crates/plugin-sdk/cppbuilds a Rust static library- Bazel runs a Rust Diplomat codegen tool and exports the generated C and C++ headers
plugins/example-cpplinks that Rust library into a Bazel-built C++ binary
Cargo manifests remain in the repo for dependency metadata and editor/tooling support, not as the primary execution path.
UI Structure
The UI has three focusable regions:
DetailsLatest ValueChannels
The overall layout is:
- wide terminals: details on the left, channels on the right, footer on the bottom
- narrow terminals: details on top, channels below, footer on the bottom
The channels pane is a tree:
- namespaces are derived from dot-separated channel paths
- namespaces can be collapsed individually with
Enter - the full tree can be toggled with
z - selecting a namespace shows namespace summary details plus descendant channels in the left pane
Renderers:
- numeric values: line chart plus textual summary
- text/integer/bool values: simple text block
- bytes values: hex and ASCII summary
The details pane is intentionally fixed-height and non-scrolling:
- channel details render as a five-line summary block
- namespace details use the same aligned two-column structure
- latest-value content remains the scrollable/expanded pane for long payloads
Input Model
The input model is split by focus:
Channels:j/k, mouse clicks, and mouse wheel choose the active rowDetails: focusable and copyable line-by-line, but intentionally non-scrollingLatest Value: cursor movement and mouse wheel act inside the visible text summary
Copy behavior:
- in
Channels,ycopies the current live channel value - in
DetailsorLatest Value,ycopies the current line under the text cursor
Command/filter behavior:
/opens a persistent filter prompt; a non-empty filter remains visible until clearedEscwhile editing the filter clears it entirely:opens command mode:qis the current quit command
Status and Help
The status bar is intentionally compact:
- left side:
:q quit,: command,? help, current focus, and transient notices - right side: store totals and plugin health
The help overlay is the canonical shortcut reference for the app.
Current Limits
The current implementation does not yet have:
- transport plugins separate from decoders
- multiple instances of the same plugin type
- persistence or replay
- robust clipboard fallback behavior for terminals without OSC 52 support
- responsive/truncated formatting for very large structured payloads beyond the current text renderers