The rib model
A rib is a capability in its own package, on its own version, bolted onto a harness that never changes. It is a deliberate alternative to the usual ways new capability gets added: forking the harness and maintaining the fork, upstreaming a plugin into a monolith and waiting on review, or running a sidecar and inventing a protocol to reach it. A rib is a dependency you add and remove, nothing more.
The contract that makes that work, and the boundary it rests on, are the rest of this page.
Packages, not forks
Section titled “Packages, not forks”A rib is a Bun package named @keelson/rib-<id>, usually in a repository
named keelson-rib-<id>. Installation is dependency management, not
integration work: keelson rib add <source> hands the source to bun add,
and at the next boot the server discovers whatever rib-* packages sit in
the home’s @keelson scope. There is no central registry of ribs, no
manifest to edit, and no plugin API key. Anyone can publish a rib; installing
one is a one-line decision you can reverse with keelson rib remove.
Discovery also separates two states that plugin systems usually conflate.
Installed means the package is present in the home. Active means the
server validated and registered it at boot. KEELSON_RIBS filters between
them, so an operator can keep five ribs installed and activate one, per
process, without uninstalling anything.
What a rib can contribute
Section titled “What a rib can contribute”The contract is one interface, and every hook on it is optional. A rib implements the subset it needs, and each hook lands its contribution in a specific place in the harness. The figure shows the main destinations:
Figure 1. One rib, several destinations. Each optional hook lands its contribution in a specific harness registry; the figure traces the four core ones, and policies, docs, agents, and commands land in registries of their own. Everything the rib touches stays under its own namespace.
| Hook | What it contributes |
|---|---|
registerTools | Tools for the shared registry. They reach the chat agent with no further wiring; a workflow prompt node opts each one in by name. |
composeBundle | A snapshot composer under the rib’s key namespace. The browser renders the published data as live boards. |
views, surfaces | Static declarations of how those snapshots appear, up to a full top-level tab in the browser. |
contributeWorkflows | Workflow definitions built in code and merged into the catalog at activation, optionally bound so a run’s output republishes a snapshot key. A rib can also ship static definitions as plain YAML files in a workflows/ folder at its package root; they merge with the same rib provenance, no hook required. |
contributePolicies | Governance policies merged into the harness policy engine at activation, evaluated at each turn’s hook points. |
contributeDocs | Documentation sources the rib folds into the harness docs catalog, listed and sliced on demand by the keelson_docs tool, so an installed rib extends what the agent can read about itself. |
onAction | A handler for inbound actions over the loopback API, so a board’s buttons can reach the rib. |
listAgents, resolveAgent, listCommands, invokeCommand, completeCommand | Named chat agents and slash commands the rib adds to the chat composer. |
authStatus | A credential probe surfaced in the rib inventory, so the harness can report whether the rib’s external system is reachable. |
dispose | Teardown. The harness awaits it at shutdown, before the database closes. |
The chat composer is a further destination the figure leaves out: the agents and commands hooks feed it. The pattern holds across all of them, though: a rib contributes data and declarations, and the harness owns the machinery. A rib never renders UI, never opens its own port, and never touches the database.
The boundary is enforced, not requested
Section titled “The boundary is enforced, not requested”The architecture page states the rule from the harness side: the core never imports a rib. The rib model adds the other half: a rib cannot reach outside its own namespace, and the harness checks rather than trusts.
- Snapshot keys are namespaced. Everything a rib publishes lives under
rib:<id>orrib:<id>:*. The snapshot manager a rib receives is scoped: registering a key outside the namespace throws at activation, and reading another rib’s keys returns nothing. - Credentials are namespaced. A rib’s
getCredential("token")resolves to a keychain entry underrib_<id>_token. The accessor is read-only and the account format is validated, so one rib cannot enumerate or read another rib’s secrets through the harness. - Tool names are global, collisions lose. A tool name already claimed is
skipped with a warning, which is why rib tools carry a family prefix, like
weather_now.
The payoff: zero wiring
Section titled “The payoff: zero wiring”The model earns its keep when a contribution travels. Follow one tool call end to end:
- A chat turn starts. The server assembles the turn’s tools from the shared
registry, the same list
GET /api/toolsreports, with rib tools folded in beside the harness’s own. - The provider adapter projects each tool into its SDK’s format. The agent sees a typed tool; it does not see ribs at all.
- The agent calls the tool. The provider validates the input against the
tool’s schema, then runs the rib’s
executewith the rib’s own context: its process-exec helper, its scoped credentials, its snapshot manager. - The result streams back as a
tool_resultchunk, through the same pipeline every other chunk uses, into the conversation record.
Nothing in that path is rib-specific. The same registry serves workflow
prompt nodes, so a tool written once is callable from chat, from a
deterministic workflow, and from any surface that can start either. That is
the rib model’s bet: if the contract is narrow and the destinations are
shared, capability scales by adding packages, not by modifying the harness.
Where to go next
Section titled “Where to go next”- Snapshots and surfaces covers the publishing
substrate behind
composeBundle, views, and surfaces. - The Rib contract is the precise interface, the context the harness injects, and the validation rules it enforces.