Skip to content

Managing plugins

This is the operator’s guide to the Plugins panel (/panel/plugins). It covers the day-to-day actions you take through the UI plus the failures you’ll see and how to fix them. For the architectural overview see Plugins, and for writing your own see Plugin authoring.

Open the admin panel and click Plugins in the left rail (or navigate to /panel/plugins). The page is built around three things:

  1. System stats strip — one-line CPU / RAM / GPU / Network summary at the top. Click anywhere on the strip (or the chevron on the right) to expand it into the full 4-card detail view; click again to collapse. Collapsed by default — most operators spend their time on the inventory below, not the host metrics.

  2. Two tabs:

    • Installed — every plugin CoreService knows about, whether running or not. This is what you spend most of your time on.
    • Downloaded.mpn archives staged on disk but not yet installed. Use this for offline / air-gapped installs.
  3. Two install entry points in the page header:

    • Browse hub — fetches the catalog at https://demo.magellon.org/v1/index.json and lets you one-click install any plugin. CoreService downloads the .mpn server-side, verifies SHA256, then runs the install pipeline.
    • Upload archive — drag-and-drop a local .mpn. Same install pipeline, different source of bytes. Use this for a plugin you packed yourself with magellon-sdk plugin pack, or any plugin that isn’t on the hub.
  1. Click Browse hub.
  2. Filter by category or search by name in the dialog.
  3. Click Install on the plugin card.
  4. The install dialog auto-downloads the archive, verifies SHA256, and inspects the manifest. The Install method dropdown shows every method the manifest declares (uv, docker, …), with the host-supported one pre-selected.
  5. Pick a method (or accept the default) and click Install.
  1. Click Upload archive.
  2. Drop the .mpn onto the dropzone.
  3. Same inspection + method-pick flow as the hub install.
  4. Click Install.

The success banner shows the install_dir (where the plugin’s code lives) and a Logs disclosure with the install pipeline’s stdout.

MethodWhen to useWhat CoreService does
uvPure-Python plugin, dev iterationCreates a venv at <plugins_dir>/<plugin_id>/, runs uv sync, and launches the plugin via the supervisor (systemd-user on Linux, Popen on Windows/macOS dev).
dockerAnything with non-Python deps (GPU, system libs) or when you want full isolationRuns docker build then docker run -d. Lifecycle (pause, scale, restart) goes through the docker CLI.

If both are supported, docker is usually the operator’s choice for production and uv for dev. The manifest author’s preferred method is pre-selected by default.

Each installed plugin renders as a card on the Installed tab. The card layout, top to bottom:

  • Name + version + chips — plugin’s name, semver, install method (docker / uv), install location, replica count for multi-replica deployments.
  • Description + developer — from the manifest.
  • Status chips — Installed / Enabled / Live / Healthy / Default. Green chip = condition is true. Red chip = something needs attention (Installed=False or Healthy=False). Grey outlined = intentional or transient state (Enabled=False, Live=False).
  • Footer actions — the controls you’ll click most.
ControlWhat it does
Enabled switchToggles the plugin between “dispatchable” and “skipped”. When disabled, no target_backend-less dispatch will land on this plugin. Use it to take a backend out of rotation without uninstalling.
Set as defaultPins this plugin as the default backend for its category. Shows only when there’s a sibling with the same category. Becomes a green Default chip when active.
▶ StartBoots the plugin process (uv) or container (docker). Disabled when already running.
⏸ PauseDocker-only. Freezes the container via cgroups (SIGSTOP) so memory stays resident but CPU drops to 0. Use for “park this without losing warm caches”. Greyed out for uv installs — stop them instead.
■ StopSends SIGTERM, then escalates. Disabled when already stopped.
↻ RestartStop + start, in one click. Useful after a .env or settings push.
👁 Open detailNavigates to the per-plugin runner page (see below).
⌄ ReplicasExpands an inline drawer listing per-instance heartbeat freshness. Docker plugins only.
🗑 UninstallStops the plugin and removes its install directory / container. Asks for confirmation.

The toolbar above the grid has a text search (matches name, plugin_id, description) and a row of subject filter chips (consumes image, produces particle_stack, …). The chips come from your installed plugins’ category definitions and let you find every plugin that takes a particular subject as input or produces it as output.

Below the grid you may see an Inactive (N) section, collapsed by default. These are orphan announce records — plugins that briefly heartbeated on the bus and then went silent (a one-off external worker, a crashed sidecar, a test harness). They persist in the DB catalog until their heartbeat-expiration window passes.

You can safely ignore them. They render with a dashed border and reduced opacity so they don’t clutter the primary inventory.

Click the eye icon on any card (or navigate to /panel/plugins/<category>/<plugin>) to open the runner page:

┌── Header strip ────────────────────────────────────────┐
│ Name · v · category · method · Live/Stopped │
│ Description · install dir · http endpoint │
│ [▶ Start] [⏸ Pause] [■ Stop] [↻ Restart] │
│ Queues: [tasks_queue ↗] [out_tasks_queue ↗] │
├── [Workspace] [Logs] ──────────────────────────────────┤
│ (selected tab content) │
└────────────────────────────────────────────────────────┘

The Workspace tab is your test bed for this specific plugin. Two panels:

  • Settings (left) — Transport picker (Bus vs. Sync), then the plugin’s input schema rendered as a form via SchemaForm. Field types, defaults, descriptions, examples, and any ui_* hints all come from the plugin’s input model — there’s no per-plugin UI code in the React app.
  • Activity (right) — appears the moment you click Run: ProgressTracker, live wire envelopes (the actual TaskMessage going out + TaskResultMessage coming back), and the parsed result. Before the first Run, this column is hidden and the Settings column spans the full page.

Transport choice:

  • Bus — submits via POST /plugins/<id>/jobs; the task goes through RabbitMQ. Progress events stream back over Socket.IO. Use for anything long-running or batch-friendly.
  • Sync — posts to POST /dispatch/<category>/run; CoreService routes to the plugin’s /execute HTTP endpoint and waits. Use for sub-second interactions (the picker’s threshold slider, FFT previews). Only available when the plugin declares Capability.SYNC.

Live tail of the plugin’s stdout/stderr:

  • Docker installs read docker logs --tail N <container>.
  • uv installs tail <install_dir>/app.log (Popen) or journalctl --user (systemd).

Controls in the header:

  • Live / disconnected chip — Socket.IO stream state.
  • Pause — stop the live tail without losing the buffered lines. Useful when a noisy plugin is preventing you from reading what’s already there.
  • Clear — wipe the streamed buffer (initial one-shot tail stays).

The log content uses the natural width of the panel; we ship COLUMNS=200 to the container’s environment so Rich-formatted Python loggers don’t hard-wrap at 80 columns.

The header carries two pill buttons:

Queues: [particle_picking_tasks_queue ↗] [particle_picking_out_tasks_queue ↗]

Each opens the queue in the RabbitMQ Management UI (default http://localhost:15672/). Use them when you want to:

  • See how many tasks are pending dispatch.
  • Inspect a stuck message (Get Messages → Ack).
  • Purge a queue full of stale test traffic.

The login is the same as the broker’s (rabbit / behd1d2 in the default dev compose; check app_settings_dev.yaml for your deployment).

"plugin did not announce within 30.0s; rolled back"

The install pipeline waited 30 seconds for the plugin to send a heartbeat over RabbitMQ and gave up. Three common causes:

  1. Docker network mismatch — the plugin container started on Docker’s default bridge network, but RabbitMQ / NATS live on docker_magellon-network. The container can’t resolve rabbitmq:5672. Fix: set MAGELLON_DOCKER_NETWORK=docker_magellon-network in CoreService’s environment and restart it.

  2. Broker URL points at localhost — CoreService’s app_settings_*.yaml has HOST_NAME: localhost, which works for CoreService running natively but is wrong for plugin containers (localhost inside the container is the container itself). Fix: set MAGELLON_PLUGIN_BROKER_URL=amqp://rabbit:behd1d2@rabbitmq:5672/ in CoreService’s environment.

  3. Plugin’s PluginInfo.name doesn’t match the manifest plugin_id — the liveness check looks for the manifest slug (fft); if the plugin announces with a different name (FFT Plugin), they never match. Fix: edit get_info() to use the manifest plugin_id as the name.

If you packed the plugin yourself and are still hitting #3, also check that the bundled SDK wheel under wheels/ is current — older wheels (< 2.3) are missing magellon_sdk.paths and will throw ModuleNotFoundError at container start. Rebuild via scripts/rebuild-sdk-wheels.sh.

Step events / live progress are silent

The Logs tab shows the plugin is healthy and processing tasks, but the Workspace tab’s ProgressTracker stays empty during a run.

This usually means the plugin can reach RabbitMQ but not NATS. Set MAGELLON_PLUGIN_NATS_URL=nats://nats:4222 for docker plugins on the magellon network.

NATS is optional — the RMQ-only path still drives the Socket.IO progress channel, but per-step events from the plugin go via NATS when it’s available.

Stale tasks in `<category>_tasks_queue`

Hammering the dev stack tends to leave a backlog of half-formed test messages in the input queue. A freshly-installed plugin will consume the backlog first and may exhaust its announce window on FileNotFoundError retries.

Fix: Click the tasks_queue pill in the runner page header → RMQ Management UI → Purge the queue → Restart the plugin.

"Installed but not announcing" alert on a card

The plugin’s DB row is healthy but no heartbeat is arriving. Open the runner page’s Logs tab — the boot error usually shows up there within the first 10 seconds.

Magellon runs cleanly on Windows with the brokers (RMQ + NATS + MySQL + Dragonfly) in Docker and CoreService running natively in a venv.

Minimum environment for docker-mode plugin install

Set these in the same PowerShell session where you launch CoreService — env vars are per-session on Windows.

Terminal window
$env:MAGELLON_DOCKER_NETWORK = "docker_magellon-network"
$env:MAGELLON_PLUGIN_BROKER_URL = "amqp://rabbit:behd1d2@rabbitmq:5672/"
$env:MAGELLON_PLUGIN_NATS_URL = "nats://nats:4222"
$env:NATS_URL = "nats://127.0.0.1:4222"
$env:MAGELLON_HOME_DIR = "C:/magellon/home"
$env:PYTHONIOENCODING = "utf-8"
$env:PYTHONUNBUFFERED = "1"
# Then launch CoreService in the same shell:
.\venv\Scripts\python.exe -u -m uvicorn main:app `
--host 127.0.0.1 --port 8000

MAGELLON_DOCKER_NETWORK and MAGELLON_PLUGIN_BROKER_URL / MAGELLON_PLUGIN_NATS_URL are the only plugin-install-specific env vars. The rest are general Windows-direct-run hygiene. To persist them across sessions, use [Environment]::SetEnvironmentVariable(...).