Skip to content

Plugin registry: no discovery index, and bare-name resolution 404s against the official registry layout #448

Description

@scttfrdmn

Summary

spawn has no plugin registry — only a bare-name→URL convention hardcoded in
pkg/plugin/registry.go. There is no index to discover plugins from, no way for a
third-party repo to publish one discoverably, and (bug, below) the bare-name path
the resolver builds doesn't match the layout the official registry actually uses.

Found while building a downstream consumer (aws-hf-model-deploy, a HuggingFace→AWS
inference deployer) that wants to ship its own spore.host plugin — an
inference-server plugin (vLLM / SGLang / llama.cpp) installed onto a spawned GPU
instance. There's currently no supported way to publish that so users can find it.

Verified 2026-07-27 against spawn @ current main and spore-plugins @ 97d8d97.


Bug first: bare-name resolution 404s against the official registry

fetchGitHubSpec (pkg/plugin/registry.go:119) builds:

url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s/plugin.yaml",
	owner, repo, gitRef, name)

For the official registry that's spore-host/spore-plugins/main/<name>/plugin.yaml.
But spore-plugins stores plugins under plugins/<name>/ — as its own
AUTHORING.md:81 instructs ("Create plugins/<your-plugin-name>/plugin.yaml") and
as spawn's own pkg/plugin/validate.go:243 comment already assumes
("plugins/<name>/plugin.yaml").

$ curl -s -o /dev/null -w "%{http_code}\n" \
    https://raw.githubusercontent.com/spore-host/spore-plugins/main/tailscale/plugin.yaml
404
$ curl -s -o /dev/null -w "%{http_code}\n" \
    https://raw.githubusercontent.com/spore-host/spore-plugins/main/plugins/tailscale/plugin.yaml
200

So every documented bare-name install fails. From spawn plugin --help and
spore-plugins/README.md:

spawn plugin install tailscale --instance <id> --config tag=tag:spore
spawn plugin install globus-personal-endpoint@v1.0.0 --instance <id>

Both resolve to a 404 path. registry_test.go only covers ParseRef (string
parsing) — nothing exercises the constructed URL, which is why this is silent.

The github:user/repo/name form has the mirror-image problem: it forces
<name>/plugin.yaml at the repo root, so a third-party repo cannot use the
plugins/<name>/ layout that the official registry and AUTHORING.md prescribe.
The two forms are mutually inconsistent.

Minimal fix (independent of the registry work below): try plugins/<name>/plugin.yaml
then fall back to <name>/plugin.yaml, and add a test that asserts the URL.


The actual ask: a registry index

Even with the path fixed, install is the only thing that works. Concretely missing:

  1. No discovery. spawn plugin list lists plugins installed on an instance
    (cmd/plugin.go:52). There is no spawn plugin search / spawn plugin list --available. A user cannot find out that spore-sync exists without reading
    the repo.
  2. No index file. spore-plugins has no manifest of its contents — just
    AUTHORING.md, CLAUDE.md, CONCEPTS.md, LICENSE, README.md, plugins/.
    Resolution is convention-only, so a typo is indistinguishable from a plugin that
    doesn't exist yet, and there's no metadata (description, supported platforms,
    required IAM, maintainer, latest version) available without fetching each
    plugin.yaml blindly by guessed name.
  3. No third-party publishing story. A repo like aws-hf-model-deploy can host a
    plugin.yaml and tell users to type the full
    github:scttfrdmn/aws-hf-model-deploy/<name> ref, but it cannot register so the
    plugin is findable, and users get
    warning: installing plugin from unverified source (registry.go:100) with no
    path to ever clearing that.
  4. No version listing. name@v1.2.0 pins to a git tag, but nothing enumerates
    available tags, so a user can't discover what versions exist or what the latest is.

Suggested shape

An index file in spore-pluginsindex.json (or registry.yaml) at the repo
root, generated in CI from plugins/*/plugin.yaml:

{
  "generatedAt": "2026-07-27T00:00:00Z",
  "schemaVersion": 1,
  "plugins": [
    {
      "name": "tailscale",
      "version": "v1.0.0",
      "description": "Join the instance to a Tailscale tailnet",
      "path": "plugins/tailscale/plugin.yaml",
      "source": "spore-host/spore-plugins",
      "verified": true
    },
    {
      "name": "hf-inference-server",
      "version": "v0.1.0",
      "description": "vLLM/SGLang/llama.cpp OpenAI-compatible endpoint",
      "source": "scttfrdmn/aws-hf-model-deploy",
      "path": "plugin/hf-inference-server/plugin.yaml",
      "verified": false
    }
  ]
}

Third-party entries are pointers, not vendored copies — the index carries
source + path and the resolver fetches from that repo, so plugin code stays
with its owner while remaining discoverable. That mirrors the "recipe, not cake"
pattern already used in libs/catalog, and the static-JSON-on-a-schedule pattern
already used by hf-bedrock-map.

Then:

  • spawn plugin search [query] / spawn plugin list --available reads the index
  • spawn plugin info <name> shows description, version, config schema, source
  • resolution consults index.json for path, removing the hardcoded convention
    (and fixing the bug above structurally rather than by guessing two paths)
  • verified: true only for spore-host-owned entries, which gives the existing
    unverified-source warning a real meaning instead of a dead end

Two things worth deciding explicitly, both of which affect whether a downstream
repo can rely on this:

  • Trust model for third-party entries. Listing in the index is an implicit
    endorsement even with verified: false, and install runs arbitrary shell on the
    instance. A PR-review gate for index entries is probably the minimum; commit-SHA
    pinning rather than branch refs would be stronger.
  • Caching + offline. The index should be cached locally with a visible age
    (same discipline as hf-bedrock-map's generatedAt) so search isn't a hard
    network dependency, and so a stale index is distinguishable from a missing plugin.

Happy to contribute the index generator and the search/info commands if this
shape is agreeable — I need it downstream either way, and would rather build it
here than work around it. If you'd prefer the path bug split into its own issue,
say so and I'll file it separately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions