Summary
Today, a plugin that claims an OpenClaw slot — e.g. plugins.slots.memory = "plur-claw" — can only be detected as the owner of that slot by string-comparing the slot value against its own plugin ID. That comparison works for happy-path installs, but it collapses as soon as two plugins want the same slot: neither side can distinguish "nobody claimed it yet" from "this was claimed by another plugin that I should not displace" without either (a) hard-coding a list of known plugin IDs for that slot, or (b) writing string-compare ladders that silently break when a new memory plugin ships.
This RFC proposes a small, backwards-compatible schema extension so slot ownership becomes a first-class, machine-readable fact: every slot value records who claimed it and when, not just a plugin ID.
Motivating example — plur-claw setup --repair
@plur-ai/claw just shipped (v0.9.10) a setup --repair mode that re-runs only the activation-chain steps that doctor reports as failing. One branch of that repair logic deliberately refuses to overturn a memory-slot assignment owned by another plugin:
// packages/claw/src/setup.ts — runRepair()
if (slotFailed && !slots.memory) {
slots.memory = PLUGIN_ID // safe: slot is unset, claim it
}
// if slots.memory is set but to a DIFFERENT plugin, repair is a no-op
// — this is a human judgment call, not repair's to override.
The refusal itself is correct. The implementation of that refusal is brittle:
- Detection of "owned by another plugin" relies on
slots.memory !== PLUGIN_ID && !!slots.memory — i.e. "the string is something, and it isn't me". That works with one known memory plugin in the ecosystem. It doesn't scale.
doctor's failure message (plugins.slots.memory is <X>, expected <Y>) can't tell the user when the other plugin claimed the slot, or whether the claim was from a fresh install vs a stale config entry. Without that, the user has to hand-diff their config history to decide whether to keep the current owner.
- OpenClaw itself has no invariant to enforce: nothing stops a misbehaving plugin from silently writing
slots.memory = "itself" on top of a prior claim, because the slot value has no audit trail.
The same pattern will show up for every future memory plugin, and for every other slot (context, whatever ships next) as soon as there's a second implementation.
Proposal
Evolve plugins.slots.<slot> from a bare string to either a string or an object with an explicit owner field:
Bare-string form ("slots": { "memory": "plur-claw" }) remains accepted on read — it's normalized to { owner: "plur-claw" } with claimed_at / claimed_by_version absent. No flag day, no existing config breaks.
What this unlocks
- Slot-ownership check becomes a single field read.
slots.memory.owner === MY_PLUGIN_ID replaces the current typeof slots.memory === 'string' && slots.memory === PLUGIN_ID string compare. Plugins authoring repair/doctor logic no longer have to encode the shape of the slot value in every probe site.
- Doctor output gets actionable specificity.
plur-claw doctor can say plugins.slots.memory owned by <X> since 2026-04-01 (v2.3.0) instead of just plugins.slots.memory is <X>, expected plur-claw. That's enough information for a user to decide "that claim is stale, I want plur-claw" vs "that's the plugin I picked last week, keep it".
- OpenClaw core can enforce a claim invariant. The gateway (or
openclaw plugins install) can reject a slot write that would silently overwrite a prior owner, unless the caller passes --force or the previous owner's plugin is no longer present in plugins.entries. Today, nothing prevents that overwrite.
- Telemetry / debugging gets a provenance anchor.
claimed_at + claimed_by_version are enough to answer "is this slot assignment from the user's install two weeks ago, or from a postinstall script that ran five minutes ago?" without asking the user to paste their shell history.
What this does not propose
- No change to the set of slots OpenClaw recognizes.
- No change to plugin discovery, enablement, or the
plugins.entries shape.
- No requirement that plugins author the object form — plugins that keep writing strings continue to work; the object form is the canonical read shape after OpenClaw normalizes.
Migration
- Read-side normalization (OpenClaw core, one PR). Everywhere OpenClaw reads
plugins.slots.<slot>, coerce a bare string "X" to { owner: "X" }. Ship this first — it's a pure parser change, zero config migrations required.
- Write-side canonicalization (OpenClaw core, follow-up PR).
openclaw plugins install <pkg> and the plugin register flow start writing the object form (with claimed_at = install time, claimed_by_version = pkg version). Existing bare strings are rewritten to the object form on the first write that touches that slot. No eager migration: configs that are never touched stay untouched.
- Slot-write invariant (OpenClaw core, follow-up PR). Reject overwriting
slots.<slot>.owner from plugin A to plugin B unless (a) A is no longer present in plugins.entries, or (b) the caller passes an explicit --force / allow_override: true. Prevents silent slot capture.
- Plugin-side adoption (opt-in, per-plugin). Plugins that probe slot ownership (
plur-claw doctor, any future equivalent) switch to slots.<slot>.owner. The bare-string path keeps working until every plugin in the ecosystem has migrated.
Alternatives considered
- Keep slot values as bare strings; publish a convention. Simplest possible change, but doesn't solve the invariant problem (anyone can still silently overwrite) and doesn't give
doctor any new information to surface. The current pain is the absence of provenance, not the syntactic shape.
- Separate top-level
plugins.slot_history ledger. More flexible (full change log), but much bigger blast radius — every slot mutation site has to append, readers have to tail. The 80% win is knowing who owns the slot right now; full history is a future extension, not the MVP.
- Make
slots.<slot> an array of plugin IDs, with index 0 being the active owner. Preserves ordering for fallback chains but obscures the common case (one owner) behind array-indexing boilerplate at every read site.
Asking for
- Is this something the OpenClaw core team would accept a PR for?
- If yes: should the read-side normalization land on its own first, or bundled with the write-side canonicalization?
- Is the
{ owner, claimed_at, claimed_by_version } field set the right minimum, or would you prefer a different shape (e.g. just owner, with timing left for a separate change)?
Happy to open the read-side normalization PR as a starting point if there's appetite.
Filed from the plur-ai/plur side as slice F of plur-ai/plur#51. Prior slices on our side (A–E) have shipped the concrete per-step activation chain (doctor, setup --repair) that motivates this RFC — happy to walk through that code if useful.
Summary
Today, a plugin that claims an OpenClaw slot — e.g.
plugins.slots.memory = "plur-claw"— can only be detected as the owner of that slot by string-comparing the slot value against its own plugin ID. That comparison works for happy-path installs, but it collapses as soon as two plugins want the same slot: neither side can distinguish "nobody claimed it yet" from "this was claimed by another plugin that I should not displace" without either (a) hard-coding a list of known plugin IDs for that slot, or (b) writing string-compare ladders that silently break when a new memory plugin ships.This RFC proposes a small, backwards-compatible schema extension so slot ownership becomes a first-class, machine-readable fact: every slot value records who claimed it and when, not just a plugin ID.
Motivating example —
plur-claw setup --repair@plur-ai/clawjust shipped (v0.9.10) asetup --repairmode that re-runs only the activation-chain steps thatdoctorreports as failing. One branch of that repair logic deliberately refuses to overturn a memory-slot assignment owned by another plugin:The refusal itself is correct. The implementation of that refusal is brittle:
slots.memory !== PLUGIN_ID && !!slots.memory— i.e. "the string is something, and it isn't me". That works with one known memory plugin in the ecosystem. It doesn't scale.doctor's failure message (plugins.slots.memory is <X>, expected <Y>) can't tell the user when the other plugin claimed the slot, or whether the claim was from a fresh install vs a stale config entry. Without that, the user has to hand-diff their config history to decide whether to keep the current owner.slots.memory = "itself"on top of a prior claim, because the slot value has no audit trail.The same pattern will show up for every future memory plugin, and for every other slot (
context, whatever ships next) as soon as there's a second implementation.Proposal
Evolve
plugins.slots.<slot>from a bare string to either a string or an object with an explicitownerfield:{ "plugins": { "slots": { "memory": { "owner": "plur-claw", "claimed_at": "2026-04-23T21:14:00Z", "claimed_by_version": "0.9.10" } } } }Bare-string form (
"slots": { "memory": "plur-claw" }) remains accepted on read — it's normalized to{ owner: "plur-claw" }withclaimed_at/claimed_by_versionabsent. No flag day, no existing config breaks.What this unlocks
slots.memory.owner === MY_PLUGIN_IDreplaces the currenttypeof slots.memory === 'string' && slots.memory === PLUGIN_IDstring compare. Plugins authoring repair/doctor logic no longer have to encode the shape of the slot value in every probe site.plur-claw doctorcan sayplugins.slots.memory owned by <X> since 2026-04-01 (v2.3.0)instead of justplugins.slots.memory is <X>, expected plur-claw. That's enough information for a user to decide "that claim is stale, I want plur-claw" vs "that's the plugin I picked last week, keep it".openclaw plugins install) can reject a slot write that would silently overwrite a prior owner, unless the caller passes--forceor the previous owner's plugin is no longer present inplugins.entries. Today, nothing prevents that overwrite.claimed_at+claimed_by_versionare enough to answer "is this slot assignment from the user's install two weeks ago, or from a postinstall script that ran five minutes ago?" without asking the user to paste their shell history.What this does not propose
plugins.entriesshape.Migration
plugins.slots.<slot>, coerce a bare string"X"to{ owner: "X" }. Ship this first — it's a pure parser change, zero config migrations required.openclaw plugins install <pkg>and the plugin register flow start writing the object form (withclaimed_at= install time,claimed_by_version= pkg version). Existing bare strings are rewritten to the object form on the first write that touches that slot. No eager migration: configs that are never touched stay untouched.slots.<slot>.ownerfrom plugin A to plugin B unless (a) A is no longer present inplugins.entries, or (b) the caller passes an explicit--force/allow_override: true. Prevents silent slot capture.plur-claw doctor, any future equivalent) switch toslots.<slot>.owner. The bare-string path keeps working until every plugin in the ecosystem has migrated.Alternatives considered
doctorany new information to surface. The current pain is the absence of provenance, not the syntactic shape.plugins.slot_historyledger. More flexible (full change log), but much bigger blast radius — every slot mutation site has to append, readers have to tail. The 80% win is knowing who owns the slot right now; full history is a future extension, not the MVP.slots.<slot>an array of plugin IDs, with index 0 being the active owner. Preserves ordering for fallback chains but obscures the common case (one owner) behind array-indexing boilerplate at every read site.Asking for
{ owner, claimed_at, claimed_by_version }field set the right minimum, or would you prefer a different shape (e.g. justowner, with timing left for a separate change)?Happy to open the read-side normalization PR as a starting point if there's appetite.
Filed from the plur-ai/plur side as slice F of plur-ai/plur#51. Prior slices on our side (A–E) have shipped the concrete per-step activation chain (
doctor,setup --repair) that motivates this RFC — happy to walk through that code if useful.