ryu-storage
The plugin-owned key/value storage primitive: an isolated, namespaced SQLite KV
store that backs the plugin-host storage:kv capability.
Role in the decomposition
An extracted Core capability crate (L0), compiled into Core by default and
consumed as a NON-optional path dependency — the plugin turn-hook runtime reaches
it unconditionally. ZERO dependency on apps/core.
This is where a plugin keeps durable state instead of Core growing bespoke columns
for it — e.g. the goal plugin stores its per-conversation completion condition +
turn count here (key = conversation id), not on the conversations table.
Key API
PluginStorage (cheap to clone; wraps an Arc<Mutex<Connection>>):
open(path)/in_memory()— open + migrate the store.get / set / deleteandkeys— all keyed by(plugin_id, namespace, key).setis an upsert;keyslists a plugin's keys within a namespace, newest first.
Rows are namespaced by (plugin_id, namespace, key) so one plugin can never read
another's state — the isolation guarantee the storage:kv grant relies on.
How it is consumed
PluginStorage::open takes an explicit db path, so the crate is pure. The single
kernel coupling — choosing the default ~/.ryu/plugin-storage.db path — and the
process-global handle stay Core-side as wiring (apps/core/src/plugin_storage).
Placement
It stores what a plugin is tracking (decides what runs, not what is allowed) → Core-tier.