Add registry-centric plugins RFD with sub-RFDs#253
Conversation
Introduces the umbrella RFD for generalizing Symposium's plugin system around registries, with child RFDs covering: plugin model, where clause syntax, discovery policy, plugin defaults, user-managed plugins, fixed-point resolution, and custom registries. Co-authored-by: Claude <claude@anthropic.com>
Replace the previous registry/source-based design with a PM-centric model:
- Plugins identified by canonical `pm:name:version` triples
- PMs implement four operations: resolve, search, fetch, list-deps
- Three-layer state: config (requirements) → cache (resolved) → agent dirs (active)
- Flat predicates replace namespaced `where.{cargo,predicates}` tables
- Enterprise control via crate-level PM overrides
Delete old sub-RFDs (plugin-model, where-clauses, discovery, plugin-defaults,
user-managed-plugins, fixed-point-resolution, custom-registries, preview docs)
and plan three new ones: PM interface, discovery & sync, user-managed plugins.
Co-authored-by: Claude <claude@anthropic.com>
Restructure main README as user-facing (As a user / As a crate author) followed by Key concepts (Plugins / Package managers) and FAQ. Add five sub-RFDs aligned with the new design: - PM interface: JSON-RPC over stdio, separate binaries, only path built-in - Cargo PM: resolve format, fetch via cargo toolchain, list-deps from lockfile - Plugin model: Symposium.toml structure, defaults, predicates, chained plugins - Discovery & sync: list-deps → search two-phase algorithm, prompt UX - User-managed plugins: symposium use/remove/status, config format, scoping Key design decisions captured: - Package-id is a tuple (pm, name, version), no mandated string format - PMs communicate via JSON-RPC over stdio (agent-client-protocol SDK) - symposium CLI is the primary command (cargo agents is an alias) - depends-on shorthand reuses PM's resolve format - Symposium.toml is the only config mechanism (no Cargo.toml metadata) - Recommendations PM participates in discovery via search, not list-deps Co-authored-by: Claude <claude@anthropic.com>
| | `resolve` | opaque TOML value (from `source.<pm>`) | set of package-ids | manifest processing | | ||
| | `search` | partial query string | set of package-ids + metadata | `symposium use` | | ||
| | `fetch` | package-id | directory with plugin content | sync/install | | ||
| | `list-deps` | workspace directory | set of package-ids | auto-discovery | |
There was a problem hiding this comment.
Note: We also need something for scanning installed packages and checking whether there are available updates.
| * `[[hooks]]` identifies hooks. Symposium allows you to define vendor-neutral hooks that work for any vendor or vendor-specific hooks that target a particular agent (e.g., Claude Code or Codex). | ||
| * `[[installable]]` identifies *installable content*, which can be referenced by MCP servers or hooks (which need an executable). An easy option is to package your content as a cargo package that will be cargo-install'd and managed by Symposium, but there are other options. | ||
|
|
||
| #### Predicates |
There was a problem hiding this comment.
Note: I still think custom predicates are useful, it's possible they should be rolled into the package manager interface, though (and maybe even namespaced, e.g., cargo:foo(...) would invoke "custom predicate foo with ... on the cargo package manager".
|
|
||
| ### Package managers | ||
|
|
||
| A package manager (PM) is a pluggable backend that knows how to find, resolve, fetch, and enumerate plugins from a particular ecosystem. Each PM is a separate binary that Symposium invokes — installed as an `[[installable]]` from either the recommendations repository or the user's root config. The `path` PM is built into the Symposium binary itself (since it just reads local directories), but `cargo`, `git`, and any future PMs (npm, pypi, etc.) are separate binaries. |
There was a problem hiding this comment.
We have to define where the package managers come from. I am imagining that we always load symposium-recommendations and that it can define a set of additional package managers (which would be another operation in the list below). Those too can define add'l package managers recursively.
|
|
||
| ### Dependency discovery | ||
|
|
||
| Symposium will automatically scan the dependencies of your project to find relevant plugins. This scan is done by executing `symposium sync`. Users can also configure Symposium to automatically sync every time an agent executes in their workspace. |
There was a problem hiding this comment.
Symposium to automatically sync every time an agent executes in their workspace.
Hmm, yes, with the caveat that we don't do work when we don't need to (like if the Cargo.toml hasn't changed).
| @@ -0,0 +1,339 @@ | |||
| # Registry-centric plugin distribution | |||
There was a problem hiding this comment.
Talked on Zulip, but I wonder if it's worth mentioning somewhere (maybe a separate RFD) that cargo agents more or less forwards to symposium
|
|
||
| See the [discovery & sync sub-RFD](./discovery-sync/README.md) for the detailed algorithm. | ||
|
|
||
| ### Workspace-local extensions |
There was a problem hiding this comment.
So, I'm thinking. We're fetching crate sources to get the plugins? If you have a workspace, the uploaded source is the sub-directories, right? So, if you have skills in the top-level directory, those aren't uploaded to crates.io? Unless you add them?
(I might be completely wrong here - I actually don't know.)
In either case: the (pm, name, version) canonicalization means that the name is the crate name, not workspace name (but this means that two crates in the same workspace might resolve the same plugin).
| SKILL.md | ||
| ``` | ||
|
|
||
| ### Explicit use |
There was a problem hiding this comment.
We never really settled whether symposium use means that the plugin is always enabled or not.
I think the answer should be no, and that the predicates ultimately define when the plugin is enabled.
Thinking about it, the depends-on predicate is actually more accurate than the crates predicate:
Let's say you have crateX with a plugin. There are two cases where this gets activated:
- If you depend on
crateX - You
use crateX
Now, if the plugin has a depends-on(crateY) predicate, I wouldn't now expect use crateX to turn that on if you don't also depend on crateY.
For recommendations, I think we want to have a depends-on(*) for default activation, and then no depends means that you need a manual activation.
There was a problem hiding this comment.
It feels a little hard to explain, but I think it would just work pretty naturally.
There was a problem hiding this comment.
My expectation is that: predicates always apply.
I do not think we need something quite so so complex for your crateX scenario. I think if you add a cargo/crateX/Symposium.md file with no predicates into the recommendations repository, then...
- people can do
symposium use crateXand it will work because we'll find it when we search - if
crateXappears in their dependencies, we will also search and find it
In other words, the default when you put something into the recommendations repository at cargo/foo is that it is activated if foo appears in the dependencies or it is explicitly used.
If you have content that you only want to have when it is explicitly used, you can gate that particular content under a used() predicate (and similarly for the case where you only want the content when it appears in your dependencies, then you would put in-dependencies() or whatever I called that... maybe we want to rename it).
| ``` | ||
| symposium-recommendations/ | ||
| ... | ||
| cargo/ |
There was a problem hiding this comment.
I had a thought about conflicting registries, but I think that the recommendations repo is going to be some set of known registries.
There was a problem hiding this comment.
Yeah, I think it's going to be fine in practice, I decided not to worry about it.
|
|
||
| ### Publishing a plugin not associated with a crate | ||
|
|
||
| The `symposium-recommendations` repository can also be used to publish centralized plugins that don't have an associated crate or whatever. For example, this might be used to distribute a collection of skills from a github repository or to distribute a tool whose installation is not managed by Symposium. To do that, you simply add to the directory called `symposium`: |
There was a problem hiding this comment.
Wondering: I think that this can just be abstracted into being able to support any path or git repo as a package manager.
There was a problem hiding this comment.
Hmm, I imagine so. We could make it configurable in your config?
|
|
||
| The plugin itself and each of its subsections can be gated with a `predicates = [...]` field. When a plugin is installed, the content is only *activated* if the predicate matches. | ||
|
|
||
| Common predicates include: |
There was a problem hiding this comment.
Some new ones here; I'd like to know their use-cases.
| source.path = "skills" | ||
|
|
||
| [[skills]] | ||
| predicates = ["workspace()"] |
There was a problem hiding this comment.
Oh, that's what this is for...
Well, you would seemingly want a "not-workspace" predicate then? I don't think you want to enable skills for users when developing.
There was a problem hiding this comment.
You can do that today because we support not(workspace()) or whatever but I'm unconvinced you ever want this.
What does this PR do?
Introduces an umbrella RFD for generalizing Symposium's plugin system around registries, with child RFDs covering: plugin model, where clause syntax, discovery policy, plugin defaults, user-managed plugins, fixed-point resolution, and custom registries.