What a Monty plugin tells a cluster, and the registry that carries the answer between nodes.

The API by module — and the same content as llms.txt for a model with no browser — is generated by mix docs and published at d-led.github.io/ex.monty.plugin from main; the released version is on HexDocs.

A run is assembled from modules — the experiment that drives it, the learning modules it steps, the motor policies that decide where to sense next — and they do not all live in the same node. A policy that reads a part's B-rep has to run where the part was opened, because a B-rep handle cannot leave its node. So a cluster has to be able to ask a module what it is before it decides where to call it.

ModuleWhat it answers
MontyPluginthe contract: describe/0 — name, kind, version, placement, needs — and conforms?/1, which names the field that is wrong
MontyPlugin.Registrywhich plugins the cluster offers, by name and kind, over :pg, and the same answer on every node; lookup/3 asks for a name on a named node, which is what an :owner_only plugin needs
%{
  "name" => "follow_chamfers",
  "kind" => :motor_policy,
  "version" => "0.1.0",
  # It reads a part's geometry, and a B-rep handle cannot leave its node.
  "placement" => :owner_only,
  "needs" => [:quiddity]
}

The three kinds are :experiment, :learning_module and :motor_policy; placement is :anywhere or :owner_only. A learning module is advertised rather than routed — it runs thousands of operations a step, so it is compiled where it runs — while an experiment and a policy are called once per step, which is what makes a remote call free.

What it starts

Adding the dependency is the whole of the setup: MontyPlugin.Application starts the one process this package needs — OTP's :pg, under the scope PLUGIN_SCOPE names — before anything that could ask it a question. A forgotten child would otherwise be a registry that answers :noproc on the first call, which says nothing about the plugin and everything about the boot.

:pg and not a Registry: :pg knows a name on every node and forgets it when a node goes down; a Registry names processes on the node that started it.

MontyPlugin.Registry.child_spec/1 is there for a test that wants the process under its own supervisor, not for an application to add again.

Two variables, both read at boot

variablewhat it does
PLUGIN_SCOPEthe :pg scope name (default monty_plugins). Two systems sharing a cluster that must not see each other's plugins differ here, exactly as they differ in node name
PLUGIN_DISCOVERYpg (default) or none. none is a deployment that runs its own built-in work and takes no part in a cluster: registration does nothing and a lookup says so, rather than looking like an empty cluster

A node that is not distributed is a cluster of one, which is a supported deployment — the registry answers with what that node offers and nothing else.

What a provider is

A plugin is a process that answers one call:

def handle_call(:describe, _from, state), do: {:reply, MyPlugin.describe(), state}

The answer is held to MontyPlugin.conforms?/1. A plugin that answers unusably is still listed, with the reason — hiding it would make "nothing there" and "there but unusable" look the same to whoever is choosing.

Install

def deps do
  [
    {:ex_monty_plugin, "~> 0.1"}
  ]
end

Elixir only: no NIF, no geometry, no Monty.

Releasing

Nothing is published from a branch: ci proves a commit, a tag says it is a version. The version lives in VERSION and is written into mix.exs (and the dependency line above) by the script that moves it:

elixir scripts/release.exs bump patch     # 0.1.0 -> 0.1.1, in every file
git add -A && git commit -m "release: 0.1.1" && git push
./scripts/tag_and_push_version.sh          # refuses a dirty tree or red CI

The tag is what .github/workflows/release.yml publishes from, and that job needs the HEX_API_KEY secret on this repository:

gh secret set HEX_API_KEY -R d-led/ex.monty.plugin

Until the secret is there the tag job stops with that sentence rather than with an authentication error from Hex. A tag that disagrees with VERSION stops it too, before anything is built.