Skip to content

RFC: machine-readable memory-slot ownership — plugins.slots.<slot>.owner #70823

Description

@plur9

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:

  1. 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.
  2. 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.
  3. 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:

{
  "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" } 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🌊 off-meta tidepoolIssue quality rating does not apply to this item.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions