Stability of PluginManaged declarations — porting a production install
Justin Simpson · 2026-05-14
Quick context: I run a production Paperclip install — multi-agent stack (CEO + four pillar analysts + four Tier 1 data services), daily Routines, custom skills, marker-keyed apply-script discipline for AGENTS and TOOLS edits across both host and a mirror. I'm assessing whether to port that discipline onto the plugin-managed-resources contract introduced in v2026.512 (PRs #5205 and #5597, plus the LLM Wiki plugin in #5716).
The question: what's the stability commitment on PluginManagedAgentDeclaration, PluginManagedRoutineDeclaration, and PluginManagedSkillDeclaration? Are these v1 contracts I can safely port a production install onto, or are they still iterating?
I'm asking specifically because:
The spaces surface was introduced and removed within the same week as #5205 and #5597 merged, which suggests the field-level contract is still being shaped at merge time. The Greptile reviews on both PRs surface one-line proposed fixes that didn't land before merge (e.g. the parentId write path in #5597 and the dedup window in #5205), so a port onto the contract has to account for those gaps too.
What would help me most:
A clear "stable" or "expect iteration through v2026.612" read on the three declarations. Any heads-up on contract changes you already know are coming in the next one to two releases. Whether the LLM Wiki plugin is the canonical reference example for non-paperclipai plugins porting onto the contract, or whether there's more material I should be looking at.
Happy to share specifics about the install if useful for grounding the answer.
Many thanks for the work on the plugin host. The reconcile-preserves-operator-edits semantics match almost exactly what we landed on independently with the apply-scripts.
Answers
Aron Prins · 2026-05-14
Honest read on each of your three asks, traced through the source rather than guessed:
1. Stability commitment on the three declarations.
Treat the three interfaces themselves (PluginManagedAgentDeclaration, PluginManagedRoutineDeclaration, PluginManagedSkillDeclaration in packages/shared/src/types/plugin.ts) as stable shape, field surface still moving for the next one or two releases. The names, the reconcile model, and the operator-edit-preservation semantics are not changing. What is still iterating is which fields each declaration exposes, which is exactly what the spaces add/remove (resolved in #5597 by removing the root-level local spaces CLI/migration surface and restoring instance-root runtime defaults) demonstrates.
Concretely, your port is safe if you scope it to:
Declaring agents/routines/skills by name and reconcile target. Letting the plugin host own reconcile, and never mutating the produced entities directly from plugin code. Treating operator edits in the host config as authoritative — the contract is built around that, and it's the part we're committed to.
Where I'd add a thin abstraction in your plugin: anything that touches workspace / storage paths. Those are the surfaces that moved most in #5597 (@paperclipai/shared/space-paths → home-paths helpers) and would be the most likely place a future field-surface change bites you.
2. Known gap that's still open from the merged PRs.
The one that survived merge on #5597 and is worth working around in a production port: server/src/services/plugin-managed-agents.ts:134 — an empty files dict in a plugin's instructions block bypasses the "no instructions" guard and silently overwrites the agent's AGENTS.md with empty content. !{} is false in JS, so instructions: { files: {} } passes the guard and reaches the materialise path with a zero-byte bundle.
Practical defence in your plugin until this lands: never emit instructions: { files: {} }. Either omit the instructions key entirely when there's nothing to write, or always include at least one non-empty entry. Your marker-keyed apply-script discipline likely already does this — worth confirming on the port.
The other Greptile P1s on #5205 (checkout policy enforcement on server/src/routes/plugins.ts, duplicate agents.managed capability constant, surfaceVisibility: "normal" typo in the validator, and the getManaged write side-effect in plugin-host-services.ts:1174) were all resolved before merge — so #5205 itself is in better shape than the merge cadence suggests.
Aron Prins · 2026-05-25
Yes — concrete code is more useful than the shape sketch, especially on a stack this size. Rather than send it for review, here's what to check yourself as you write it. These are the two places I expect the contract to bite first, and they're things you can validate against your own port before posting:
1. Routine chaining for the CEO → IFM/RegMon/Sentinel/MDA sequence.
PluginManagedRoutineDeclaration.assigneeRef is single-agent. Two shapes work, neither is wrong:
One CEO routine that fans out via comments / issue creation. Cleaner declaration set, but you lose the per-pillar "the routine fired" signal — the only routine that ticks is the CEO's, and downstream activity is just issue traffic. Four separate routines chained on completion. More declarations, but each pillar gets its own routine timestamp, last-fired-at, last-status, and per-pillar pause control.
Pick based on what your observability needs are. The way to validate the choice: try operator-editing one of the routine cadences in the host config after first reconcile, then run reconcile again. If the operator edit survives (it should), your shape is contract-clean. If reconcile flattens it back to the plugin default, you've modelled it in a way the contract doesn't expect.
2. projectRef for Tier-1 data services.
If they're separate projects, you'll declare them as such and the pillar agents reference them via projectRef. If they're agents in one project, the shape is much smaller and projectRef doesn't enter the picture for the cross-references.
The way to know which you have: ask whether the Tier-1 services have independent lifecycle (own issues, own routines, possibly own operators) or are essentially "compute units" the pillars call into. The former is separate projects; the latter is agents-in-one-project. If you've been treating them as the former in your existing OC install, keep that shape on the port — splitting them later is much harder than splitting them now.
3. The empty-files guard.
This is the one that's silently destructive. Before any code path that hands an instructions block to the contract, assert that Object.keys(instructions.files).length > 0. Reject (or omit instructions entirely) otherwise. The failure mode is zero-byte AGENTS.md and there's no UI signal that it happened. Easy to miss the first time through; add the guard once and it covers the whole port.
Justin Simpson · 2026-05-23
> I would like to take you up on the offer to sketch the declaration set for the multi-agent stack. The shape: > > - One CEO agent (orchestrator). > - Four pillar analysts (IFM, RegMon, Sentinel, MDA), each with its own [AGENTS.md](http://AGENTS.md), its own skill set, reading from the Tier 1 data services. > - Four Tier 1 data services, each producing structured outputs the pillars consume. > - A shared skill graph where some skills are cross-agent (for example, a market-data-access skill used by both IFM and MDA) and some are pillar-local. > - Daily Routines that orchestrate the CEO firing the pillar stack in sequence. > > The piece I am most uncertain about on the port is how cross-agent skill dependencies declare cleanly under the plugin-managed contract: whether each agent declaration redeclares the shared skill, whether there is a shared-skill declaration tier, or whether I should keep cross-agent skills outside the plugin contract until the field surface settles.
Happy to share the actual declaration code I would write against the current contract surface if a concrete example is more useful than the shape.
Aron Prins · 2026-05-20
Clean answer on cross-agent skills, traced through the contract: there is no "redeclare per agent" model, and no separate shared-skill tier is needed — because PluginManagedSkillDeclaration is already plugin-scoped, not agent-scoped.
Concretely (from packages/shared/src/types/plugin.ts on master):
The plugin manifest carries skills?: PluginManagedSkillDeclaration[] at the top level. Each entry is keyed by skillKey and installs a single company skill on reconcile. PluginManagedAgentDeclaration does not carry a skills field. There's no contract-level binding from agent → skill at declaration time. Agents pick up company skills via the normal skill surface inside the agent's runtime (AGENTS.md / skill discovery), not via the plugin declaration.
So for your stack — market-data-access used by both IFM and MDA, plus pillar-local skills like regmon-rule-eval only for RegMon — the shape is:
ts const skills: PluginManagedSkillDeclaration[] = [ { skillKey: "market-data-access", displayName: "Market Data Access", ... }, // shared { skillKey: "ifm-flow-models", displayName: "IFM Flow Models", ... }, // pillar-local { skillKey: "regmon-rule-eval", displayName: "RegMon Rule Eval", ... }, { skillKey: "sentinel-anomaly", displayName: "Sentinel Anomaly", ... }, { skillKey: "mda-decision-frame", displayName: "MDA Decision Frame", ... }, ];
Declared once, installed once, available to any agent in the company that decides to use them. The reconcile model treats skills as company-level resources keyed by skillKey, so the IFM and MDA agent declarations don't need to mention market-data-access at all — the binding lives in each agent's instructions (their AGENTS.md references the skill by slug), not in the plugin contract.
Practical recommendations for your port:
Keep cross-agent skills inside the plugin contract. No reason to hold them out — the contract handles this shape natively. Use a stable skillKey namespace per plugin. <plugin>-<skill> patterns hold up well through reconcile. The binding from agent to skill lives in the agent's instructions, not the declaration. Put the skill slug in each agent's AGENTS.md instructions.files (or entryFile) — that's the part of the surface that's stable enough to lean on. Pillar-local skills should still be plugin-declared, not inlined into the agent's instructions as prose. First-class skills get versioning, the skills UI surfaces them, and they're cleanly portable if you ever split the stack across plugins.
One field-surface caveat that's still moving: instructions.files (the empty-{} overwrite bug we discussed). If you're using instructions.files to point agents at skill slugs by writing an AGENTS.md, the guard you're already planning to add — reject instructions: { files: {} } before the contract surface — keeps you safe.
And yes — concrete declaration code is more useful than the shape, please share it. The two places I'd expect the contract to bite first on your stack are (a) how you reconcile the routine assignee refs when the CEO needs to fire IFM/RegMon/Sentinel/MDA in sequence (PluginManagedRoutineDeclaration.assigneeRef resolves a single agent — chained sequencing has to be modeled as routine → routine, not one routine → many agents), and (b) the projectRef shape if your Tier 1 data services land as separate projects rather than separate agents in one project. Easier to point at lines in your code than to speculate.
Justin Simpson · 2026-05-18
Hi Aron,
Thanks for the detailed trace through the source. That is exactly what I needed.
A few things land. The "stable shape, field surface moving" framing is the right level of commitment for my port. I will scope to declaring by name and reconcile target, let the host own reconcile, and treat operator edits in host config as authoritative. The workspace/storage path observation is taken; I will wrap space-paths/home-paths behind a thin internal helper so a future field-surface move does not ripple through the port code.
On the empty-files overwrite bug: my apply-script discipline already defends against this by construction (markers require a non-empty body to materialise), but I will add an explicit guard in the port code that rejects instructions: { files: {} } before it reaches the contract surface. The silent-zero-byte failure mode is exactly the kind of thing that would burn a production install before anyone noticed.
I would like to take you up on the offer to sketch the declaration set for the multi-agent stack. The shape:
One CEO agent (orchestrator). Four pillar analysts (IFM, RegMon, Sentinel, MDA), each with its own AGENTS.md, its own skill set, reading from the Tier 1 data services. Four Tier 1 data services, each producing structured outputs the pillars consume. A shared skill graph where some skills are cross-agent (for example, a market-data-access skill used by both IFM and MDA) and some are pillar-local. Daily Routines that orchestrate the CEO firing the pillar stack in sequence.
The piece I am most uncertain about on the port is how cross-agent skill dependencies declare cleanly under the plugin-managed contract: whether each agent declaration redeclares the shared skill, whether there is a shared-skill declaration tier, or whether I should keep cross-agent skills outside the plugin contract until the field surface settles.
Happy to share the actual declaration code I would write against the current contract surface if a concrete example is more useful than the shape. And glad to hear the independent-convergence read; the apply-script discipline came out of the same operator-edit-preservation pressure, so it is reassuring that the contract is built on the same invariant.
Many thanks, Justin