Skip to main content
A capability is any skill, tool, or connector an agent can use. Clawboo runs five runtimes, and each one tracks its own capabilities differently: a brokered MCP tool in a clawboo-owned table, a Hermes SKILL.md on disk, an OpenClaw Gateway extension behind config.patch, a built-in nobody can touch. The capability inventory normalizes all of that into one shape, the CapabilityRecord, fanned in from per-runtime adapters, merged by a multiplexer, and persisted to a durable table. One stream feeds two surfaces: the per-agent skill and connector nodes on the Ghost Graph, and the Capabilities dashboard. Every record carries a manageability tier, and that tier alone decides whether Clawboo can install, enable, or disable the capability, or merely observe it. The UI affordances and the write path are a pure function of the tier. This page explains the record shape, the five sources, the manageability tiers and how they route writes, the multiplexer’s never-fail fan-in, the durable projection that survives a disconnected source, and the boundaries Clawboo deliberately does not cross.

What it is, and what it isn’t

The inventory is a read-mostly observation layer with a tier-gated write path. Clawboo’s stance is: observe every capability across all runtimes; manage only what the owning runtime cedes. That stance is encoded in the data, not in branching logic. A record that says observe-only is a record Clawboo will never write; the panel renders no Enable/Disable button for it, and the REST layer refuses any write against it. It is not a marketplace and not a skill installer in itself. The marketplace is where you browse the 304 agents and 82 teams; installing a curated skill is one specific write the inventory routes (to the native source), but the inventory’s primary job is to surface what exists across heterogeneous runtimes in one consistent shape. It is also not a second copy of any runtime’s own state. A capability source read() reflects the runtime’s authoritative store: the Gateway config, the Hermes home on disk, the tool_registry table, into records each time. The persisted capabilities table is a cache of the last good read per source, not a parallel source of truth. The inventory sits at the boundary between Clawboo’s shared plane and each runtime’s private plane: the manageability tier is exactly the line between “Clawboo can change this” and “this belongs to the runtime, Clawboo only watches.”

The model

Five CapabilitySource adapters each project their runtime’s capabilities into CapabilityRecords. A CapabilityMultiplexer fans their read() calls into one merged list. A service layer persists each healthy source’s records and serves cached rows for any source that degraded. Both the Ghost Graph and the dashboard consume the merged result.

The CapabilityRecord

Every source projects into one normalized row. A brokered MCP tool, a Hermes SKILL.md, an OpenClaw plugin, and a runtime built-in are all the same shape; that uniformity is what lets one renderer and one write path serve all five runtimes. The load-bearing fields:
FieldMeaning
idSource-namespaced composite ${sourceId}:${rawKey}. Opaque to the UI; the prefix is how a write routes back to the owning source.
sourceKeyThe natural identifier inside the owning store: a tool name, a skill slug, a connector id.
kindskill, tool, or connector.
runtimeThe runtime that owns the capability (open set, includes a dormant human seam).
scopeteam, agent, or global. agentId is set for agent scope, null otherwise.
sourceThe origin it was read from (brokered-mcp, curated-skill, filesystem-skill-md, mcp-connector, runtime-builtin, openclaw-extension, external-vendor-cli). This drives both the tier and the write route.
manageabilityThe tier that gates the write path (see below).
available + diagnosticsServer-evaluated availability. available: false greys the row in both renderers; diagnostics says why.
statusready, disabled, manageable-but-pending-auth, or unavailable.
writablefalse when the owning source emits a row it cannot actually write yet; so the dashboard renders no dead button. Defaults to true.
hintA source-supplied affordance string (e.g. the auth command for a pending-auth connector) so the panel never hardcodes a per-runtime string.
tenantIdA dormant multi-tenant seam, always null today.
The CapabilityRecord is a superset of the tool broker’s ToolDescriptor; it inherits availability, provenance, and risk, and adds kind, runtime, scope, and manageability so a tool, a skill, and a connector unify. The type lives in a browser-safe, zero-dependency package so the SPA can import it to type the REST response. The id is deterministic: it encodes sourceId + runtime + scope + agentId + kind + sourceKey, so the same capability re-reads to the same row, and the persistence layer can upsert by id rather than diffing.

The five sources

Each source is a CapabilitySource adapter, a read() that projects records plus a write() that the multiplexer routes by id prefix. The adapters live server-side; the package holds only the neutral types, the trait, and the multiplexer.
SourceOwnsManageability of its recordsWhat it reads
nativeThe clawboo-managed planemanagedThe tool_registry brokered tools (global), each native agent’s MCP toggles (AgentConfig.tools), and the per-agent skills table (curated installs).
hermesHermes’s private self-modelobserve-onlySKILL.md files and mcp.json connectors under each Hermes per-identity home, plus a built-ins roll-up.
claude-codeClaude Code’s own ~/.claudeobserve-onlyThe clawboo-attached MCP servers and Claude’s built-ins. Clawboo has no persistent Claude store to manage.
codexAn ephemeral per-run homeexternal-write (auth-blocked)The clawboo-attached MCP servers, surfaced manageable-but-pending-auth (real, but blocked behind codex login), plus built-ins.
openclawThe Gateway config domainruntime-of-recordtools.allow/tools.deny, mcp.servers, and plugins read over the shared operator connection, plus built-ins.
Two details about the native source are worth calling out, because they’re where “manage what’s ceded” gets concrete:
  • A curated skill installed onto any agent is a clawboo-managed annotation, the same thing a TOOLS.md bullet always was. Its record carries the agent’s runtime (so it groups under that runtime in the dashboard) but manageability: 'managed', because Clawboo owns the skills table row. A curated skill on an OpenClaw agent is still managed by Clawboo.
  • A native install reuses the existing tool-broker pipeline: scanForInjection on the whole supply-chain payload, then appendAudit, rather than forking a second install path. The injection scan covers a connector’s command, args, and env, not just the name, so a malicious MCP-connector command can’t slip the scan before a future caller wires it to a spawn.
The openclaw source reuses the shared operator connection that the OpenClaw AgentSource already holds; it never opens a second Gateway connection. When the Gateway is down, its read() returns no records with a degraded status, and the service layer falls back to the last good rows (see The durable projection).

Manageability tiers

The four tiers are the heart of the model. Each tier answers one question: what may Clawboo do to this capability?, and the answer is mechanical at every layer: the panel derives its button set from the tier, and the REST handler refuses any write the tier forbids.
TierWhat Clawboo may doWhere it applies
managedClawboo fully owns the durable row, install, enable, disable.Brokered tools, native MCP toggles, curated skills (the native source).
external-writeThe runtime owns the store; Clawboo writes through it (e.g. dialecting an MCP config).Codex’s attached MCP servers (today auth-blocked).
runtime-of-recordThe runtime owns it; Clawboo drives changes through the runtime’s own API.OpenClaw tools.allow/tools.deny, driven via config.patch.
observe-onlyClawboo can read but never write.Built-ins, Hermes skills, attached spines Clawboo doesn’t surface as a durable user store.
A deliberate deviation from Clawboo’s sibling scheduler seam: manageability is per-record, not per-source. One adapter can emit mixed tiers: Hermes emits observe-only SKILL.md rows and an observe-only built-ins roll-up, while the native adapter emits only managed rows. Because the tier rides each record, the write gate lives at the REST layer (resolve the target record, reject if observe-only) and is defended again inside each source’s write(), rather than being a single flag on the source. The action set the dashboard renders is a literal function of the record:
  • observe-only → no action; the row reads “built-in, managed by <runtime>.”
  • available: false → no action; a row the user can see is unusable shouldn’t offer a live button.
  • writable: false → no action; this is the OpenClaw connector/plugin case where a config.patch toggle is a documented follow-up, so there’s no dead button.
  • manageable-but-pending-auth → a disabled Enable button carrying the source’s hint (e.g. “pending auth, run codex login”).
  • otherwise → Enable when disabled, Disable when ready.
The REST handler enforces the same gate it shows: before dispatching an enable/disable, it resolves the record and returns 422 if the capability is observe-only or non-writable, so the writability check is never delegated to an adapter’s write() throw. An install against an unknown agent is rejected 404 up front (an unknown agent would otherwise produce an invisible orphan annotation plus a false { ok: true }).
The install request carries a runtime, but the REST handler ignores it and resolves the owning runtime authoritatively from the agent row. The browser’s installSkill hardcodes openclaw as a placeholder; trusting that would mislabel a skill installed on a non-OpenClaw agent. The agent row is the source of truth for the owning runtime.

The multiplexer and never-failing reads

The CapabilityMultiplexer registers all five sources and fans their read() calls into one merged { records, sources } result. The contract that makes the inventory robust is: a source read() never rejects: degradation is data. The multiplexer wraps each source in a try/catch, so even a source that violates its own contract becomes a degraded status entry rather than taking the whole inventory down. One dead Gateway can’t blank the dashboard. Writes route by ownership: an install routes by the spec’s via field; an enable/disable routes by the capability id’s source prefix (parseCapabilityId splits on the first :, so a rawKey containing a colon survives). An unknown source throws a typed UnknownCapabilityError that the REST layer maps to 404; an observe-only write raises UnsupportedCapabilityWriteError422.

The durable projection

The service layer (loadCapabilities) is the single read path both renderers go through. It does three things on every fetch:
  1. Fan the multiplexer to get the live merged records plus each source’s status.
  2. Persist each OK source’s records to the capabilities table with a source-scoped reconcile; upsertCapabilities deletes only the rows that source no longer reports and upserts the rest, in one BEGIN IMMEDIATE transaction. A re-read from one adapter never touches another adapter’s rows.
  3. Serve cached rows for degraded sources; if a source came back ok: false, the service reads its last good rows from the table and merges them in, so a blipped Gateway never blanks its slice of the inventory.
The merge dedupes by id with fresh records winning over cached ones, then applies the query filter (runtime, kind, scope, agentId). The source-scoped reconcile is the disconnect-tolerance mechanism: the table is a per-source cache, and a fresh read() repopulates it. There is no migration or back-fill; a hard reset of the table is acceptable because the next read rebuilds it.
The writable: false derivation for a degraded OpenClaw connector is recomputed when serving cached rows. The column doesn’t persist writable, so rowToRecord re-derives it from the row’s origin and tier; without that, the cached (disconnected-Gateway) path would drop writable and the dead Enable/Disable button would resurface.

One stream, two surfaces

The “one inventory” claim is structural, not aspirational: the Ghost Graph and the Capabilities dashboard both call the same browser client (fetchCapabilitiesGET /api/capabilities) and never issue a divergent query. The Ghost Graph groups the agent-scoped records by agentId into its per-agent skill and connector nodes, and uses each record’s available flag to grey unavailable nodes, the same greying the dashboard applies. This replaced an earlier per-agent TOOLS.md parse: the graph and the dashboard would have drifted if they read capabilities two different ways, so they were collapsed onto one stream.

Design rationale and trade-offs

The inventory exists because “what can this team do?” had no single answer across five runtimes. Each runtime’s capability model is genuinely different: a config domain, a filesystem of SKILL.mds, a tool_registry table, an inline MCP attach, and Clawboo’s honest position is that it cannot own all of them. Encoding “who owns this” as a per-record manageability tier, rather than as branching code in the UI, buys two things: the panel and the write path stay a pure function of the record (no per-runtime special cases to drift), and a new source can declare a mix of tiers without changing any consumer. The cost is a second persistence layer beside each runtime’s own store, kept honest by the source-scoped reconcile and the fresh-wins merge. The trait and multiplexer deliberately mirror the scheduler seam and the AgentSource registry, so the three “fan many sources into one view, degrade gracefully” surfaces share a shape a contributor learns once.

Boundaries and non-goals

  • Clawboo never writes a runtime’s private store it has declared off-limits. Hermes SKILL.md skills are observe-only precisely because the invariant is that Clawboo never writes a Hermes skills directory; the Hermes mcp.json is regenerated every run, so it’s surfaced read-only too. Observing the private plane is the line; clobbering it is not.
  • Not every tier has a live runtime surface yet. The external-write write path (the MCP-config transcoder that dialects a canonical spec per runtime) is built and unit-tested, but no runtime exposes a clawboo-managed persistent connector store this session; Codex’s home is ephemeral and auth-blocked, Hermes’s mcp.json is regenerated. So the genuine live writes today are managed (native) and runtime-of-record (OpenClaw tools.allow/tools.deny); the other tiers are represented and gated but await a persistent store to plug into.
  • OpenClaw connector and plugin toggles are not writable yet. The openclaw source emits MCP connectors and plugins as runtime-of-record, but their config.patch toggle is a documented follow-up, so each is marked writable: false; only the Gateway tools.allow/tools.deny surface is a confirmed runtime-of-record write today.
  • Single implicit tenant. Every record and the capabilities table carry a tenantId, but it is a dormant seam; no per-tenant filtering is active in v0.2.0.
This documents the v0.2.0 working tree (commit 03b206a). The current npm latest is clawboo@0.1.9, so npx clawboo installs 0.1.9 until the v0.2.0 tag is published. Differences are noted in Known Issues.

See also