Skip to content

Plugin authoring

Before you start
Prerequisites:
  • Python 3.11 or later
  • magellon-sdk installed (`pip install magellon-sdk`)
  • Docker Desktop (for the docker install method)

This guide walks through writing a Magellon plugin end to end. Verify your setup:

Terminal window
magellon-sdk --version

magellon-sdk plugin scaffold generates a runnable skeleton — 11 files including a manifest.yaml, a working main.py, a PluginBase subclass stub, a Dockerfile, a pyproject.toml, and a unit test:

Terminal window
magellon-sdk plugin scaffold my-fft --category fft
cd my-fft
ls
# manifest.yaml main.py plugin/ Dockerfile pyproject.toml
# requirements.txt tests/ README.md .gitignore

The generated output is lint-clean and pack-clean out of the box — you can pack it immediately without editing. Then iterate.

The --category flag pins which TaskCategory your plugin serves (e.g. fft, ctf, particle_picking, two_d_classification). Browse /plugins for the full catalog.

Two files matter:

  • plugin/compute.py — your algorithm. Pure Python, takes the decoded input dict, returns the result. No SDK glue — keep it unit-testable.
  • plugin/plugin.py — the PluginBase subclass. Maps the incoming TaskMessage to your compute call; emits progress events via reporter.progress(percent, message).
plugin/compute.py
def run_compute(input_data: dict) -> dict:
# Your algorithm here.
return {"result": ...}
# plugin/plugin.py
class MyFftPlugin(PluginBase):
plugin_id = "my-fft"
category = "fft"
def execute(self, input_data, reporter) -> dict:
reporter.started()
reporter.progress(25.0, "loading image")
out = run_compute(input_data)
reporter.progress(90.0, "writing output")
reporter.completed(output_files=[])
return out

The runner handles bus consume / decode / publish-result for you. You won’t touch RMQ or NATS directly.

Edit manifest.yaml to fill in:

manifest_version: "1.1"
plugin_id: my-fft
name: "My FFT — fast power spectrum"
version: 0.1.0
requires_sdk: ">=2.1,<3.0"
category: fft
backend_id: my-fft
# Resources hint the install pipeline uses to pick a host.
resources:
cpu_cores: 1
memory_mb: 512
gpu_count: 0
# Wave 4: declare replica bounds if your plugin is parallelizable.
replicas:
desired: 1
min: 1
max: 4
# Wave 4: how should the runtime auto-restart your plugin?
lifecycle:
restart_policy: on-failure
restart_max_retries: 5
# Install methods, in order of preference.
install:
- method: uv
pyproject: pyproject.toml
requires:
- python: ">=3.11"
- method: docker
dockerfile: Dockerfile
build_context: .
requires:
- docker_daemon: true

See the hub catalog for production examples.

magellon-sdk plugin lint catches DX papercuts before pack:

Terminal window
magellon-sdk plugin lint .
# OK: my-fft — 0 errors, 0 warnings

What it checks:

  • Errors (block the install pipeline): manifest schema invalid, install spec missing Dockerfile / pyproject.toml, replica bounds inconsistent.
  • Warnings: health-check timeout outside [5, 300] seconds.
SDK wheel version mismatch — the #1 install-time trap

If you bundle an SDK wheel under wheels/, it must match your CLI’s SDK version. An older wheel (e.g. < 2.3) is missing magellon_sdk.paths and the plugin will throw ModuleNotFoundError at container start — long after lint passes cleanly.

Fix: rebuild via scripts/rebuild-sdk-wheels.sh and update the filename references in pyproject.toml and requirements.txt. Run magellon-sdk plugin lint . again to confirm the warning is gone.

  • Info (only shown with --strict): missing tags, description, author.

--strict flips warnings to non-zero exit, suitable as a CI gate.

magellon-sdk plugin test is one stop for pre-pack checks:

Terminal window
magellon-sdk plugin test .
# === Linting my-fft ===
# OK: my-fft — 0 errors, 0 warnings
# === Running pytest tests ===
# tests/test_compute.py::test_run_compute_echoes_input PASSED
#
# OK: my-fft — lint + tests passed

It runs lint, then pytest under tests/, then optionally probes /health against a running instance (--http http://127.0.0.1:8000).

Terminal window
magellon-sdk plugin pack .
# Packed my-fft v0.1.0
# archive: my-fft-0.1.0.mpn
# size: 8,392 bytes
# sha256: 4c247020...
# files: 11 (+ 2 manifest aliases)

The .mpn is a deflate-compressed zip with a stamped manifest.yaml at the root, a regenerated archive_id (UUID v7), and SHA-256 checksums for every file inside.

Terminal window
# Upload via the admin dialog at /panel/plugins, OR programmatically:
curl -X POST http://localhost:8000/admin/plugins/install \
-F file=@my-fft-0.1.0.mpn \
-F install_method=docker \
-H "Authorization: Bearer $TOKEN"

The Magellon admin panel surfaces the new plugin on /panel/plugins. The runner is ready as soon as the install completes.

The magellon.org hub is a curated, git-based catalog. Submit a PR to magellon-web:

  1. Drop your .mpn under public/assets/plugins/.
  2. Create src/content/plugins/<plugin_id>-<version>.yaml with the publication metadata (see existing entries for the shape).
  3. Open a PR.
  4. A reviewer verifies the SHA256 against your archive, sets the tier (verified for maintained plugins, community for contributor projects), and merges.

CI/CD redeploys; your plugin appears on /plugins within minutes.

magellon-sdk plugin commands
CommandWhat it does
plugin init <dir>Bare manifest.yaml + README only
plugin scaffold <dir> --category <cat>Full runnable skeleton — recommended starting point
plugin lint <dir> [--strict]Pre-pack validation; --strict fails on warnings (use as CI gate)
plugin validate <path>Schema-only check; accepts a dir, manifest.yaml, or .mpn archive
plugin test <dir> [--http <url>]Lint + pytest + optional /health probe
plugin pack <dir> [-o <out>] [--force]Produce the .mpn archive