Plexus
Last updated for Plexus 0.81.1
An async Python plugin framework with multi-node mTLS networking and pub/sub
event routing. PyPI package: plexus-core.
Plexus loads small, single-responsibility Python classes — plugins — from
disk, drives a deterministic on_load / on_enable / on_disable lifecycle,
and gives them three ways to talk to each other:
- direct method calls —
await self.execute("OtherPlugin", "method", args=...) - 1:N events —
await self.publish_event("event_id", payload=...) - 1:1 ask-by-topic —
result = await self.request_event("event_id", payload=...)
The runtime can stretch across multiple nodes over an mTLS-pinned wire
protocol (NetworkManager), so the same execute / publish_event /
request_event calls transparently fan out to peer machines whose plugins
are flagged remote: true.
A typical deployment in conversational AI, data-pipeline, or event-driven domains wires together base plugins (a Discord bot, a Postgres adapter, an LLM adapter, a TTS pipeline) with orchestrator plugins that hold the business logic — each one a class plus a YAML manifest.
Highlights
- One-class-per-plugin layout. A
plugin.pyplus a declarativeplugin_config.ymldescribing endpoints, events, and subscriptions. - Three call styles. Direct endpoint calls (
execute), 1:N fire-and-forget events (publish_event), and 1:1 topic-routed requests with optional streaming (request_event,request_event_stream). - Hot-swap reloading. Any plugin can be disabled, re-instantiated from config, and re-enabled at runtime — its sockets, background tasks, and event subscriptions tear down and rebuild on a fresh class instance with no downtime for the rest of the cluster.
- Three-tier discipline (project policy). Base plugins wrap one protocol or service; Extension plugins glue bases together; Orchestrator plugins hold business logic.
- Multi-node clustering. Pinned-fingerprint mTLS between peers; remote endpoints and remote subscribers are reachable through the same Plugin base methods that drive local calls.
- Sync and async surfaces. Every cross-plugin call has both an
asyncform and a sync form that bridges to the loop, so plugins written against blocking libraries do not have to twist themselves into coroutines. - Strict but small. Around fifteen public methods on the Plugin base class; everything else is YAML.
Install
Requires Python 3.11+.
From PyPI (recommended for using Plexus as a library):
pip install plexus-core
Optional: add [fastloop] for a faster event loop (uvloop on Linux/macOS,
winloop on Windows). Purely a performance opt-in; everything works without
it.
pip install plexus-core[fastloop]
From source (for developing on the framework itself):
git clone https://github.com/Haflix/Plexus.git
cd Plexus
python -m venv .venv
.venv/Scripts/activate # Windows
# source .venv/bin/activate # Linux / macOS
pip install -e .
cp config.example.yml config.yml
config.yml is gitignored. Edit it to enable the plugins you want.
Quickstart
A plugin lives in its own folder containing two required files: plugin.py
(the class) and plugin_config.yml (declarative metadata). It may ship further
modules or sub-packages alongside them; the loader puts the plugin directory on
sys.path so plugin.py can import them.
Here is a minimal plugin showing the moving parts: lifecycle hooks, a regular
endpoint, a streaming endpoint, a cross-plugin call, and a topic-subscribed
endpoint. The shipped copypasta/ folder has a fuller, runnable version of
these patterns (a two-plugin demo that also covers rate limiting and
capabilities); run it with python copypasta/run_demo.py and see
copypasta/README.md.
A minimal plugin.py
from plexus.utils import Plugin
from plexus.decorators import (
log_errors, async_log_errors,
async_handle_errors, async_gen_log_errors,
)
import asyncio
class AveragePlugin(Plugin):
@log_errors
def on_load(self, *args, **kwargs):
self._logger.debug("AveragePlugin loaded")
self.state = {}
@async_log_errors
async def on_enable(self):
self._logger.debug("AveragePlugin enabled")
@async_log_errors
async def on_disable(self):
self._logger.debug("AveragePlugin disabled")
@async_log_errors
async def example_method(self, value):
return value * 2
@async_handle_errors(default_return=None)
async def call_other_plugin(self, plugin_name, method_name, args):
return await self.execute(plugin_name, method_name, args, hosts="any")
@async_log_errors
async def handle_event(self, event):
# event.topic, event.payload, event.author, event.author_host
return {"received": event.payload, "handled_by": self.plugin_name}
@async_gen_log_errors
async def example_stream(self, count):
for i in range(count):
await asyncio.sleep(0.1)
yield f"Item {i + 1} of {count}"
The matching plugin_config.yml
description: Example plugin demonstrating the Plexus plugin structure
version: 1.0.0
remote: True
arguments:
subscriptions:
handle_event_sub:
topic: "example/event"
target_access_name: handle_event
hosts: "local"
endpoints:
example_method:
internal_name: example_method
remote: True
accessible_by_other_plugins: True
description: Doubles the input value.
arguments:
- name: value
type: any
description: Input value to process
call_other_plugin:
internal_name: call_other_plugin
remote: True
accessible_by_other_plugins: True
description: Call another plugin's method by name and return its result.
arguments:
- name: plugin_name
type: str
description: Name of the plugin to call
- name: method_name
type: str
description: Method to execute on the target plugin
- name: args
type: any
description: Arguments to pass to the method
example_stream:
internal_name: example_stream
remote: True
accessible_by_other_plugins: True
description: Streaming method that yields sequential results.
arguments:
- name: count
type: int
description: Number of items to yield
handle_event:
internal_name: handle_event
remote: True
accessible_by_other_plugins: True
description: Endpoint subscribed to "example/event" via the subscriptions block.
arguments:
- name: event
type: any
description: Event object received from the topic
Run it
The copypasta/ folder ships a runnable version of these patterns. From the repo
root:
python copypasta/run_demo.py
It registers two plugins (SensorPlugin + AveragePlugin), wires them together,
and drives a scripted scenario: readings published as events flow into a running
average, an on-demand cross-plugin execute(), a capability assertion, and the
rate limiter rejecting once a bucket is dry.
To register a plugin in your own app, add it to config.yml and launch
main_application.py:
plugins:
- name: YourPlugin
enabled: true
path: ./path/to/YourPlugin
From any other plugin you can then call an endpoint, or fire an event the plugin declares:
result = await self.execute("YourPlugin", "some_method", args=...)
count = await self.publish_event("some_event", payload={"hello": "world"})
# count == number of subscribers the dispatch was scheduled for
That is the entire surface to write something useful. The rest is just more endpoints, more events, more subscriptions.
Documentation
| File | Audience | Focus |
|---|---|---|
| docs/architecture.md | new readers | mental model, lifecycle, three-tier discipline, hot-swap, shutdown order |
| docs/plugin_authoring.md | plugin authors | full plugin_config.yml schema and a tutorial-style walkthrough |
| docs/api_reference.md | reference users | every Plugin-base method (signature, args, raises) plus decorators |
| docs/notifier.md | event-system users | publish vs request, topic templating, filter chain, runtime subs |
| docs/networking.md | multi-node operators | mTLS, peers, cert pinning, remote semantics, wire protocol |
| docs/configuration.md | operators | full config.yml reference and per-plugin overrides |
| docs/rate_limiting.md | operators | the token-bucket rate limiter: dimensions, rate_limits: config, observability |
| docs/capabilities.md | operators | caller identity + the capabilities: impersonation / system-caller grant model |
Status
Active development. Public API of the Plugin base class is stable;
internal Plexus helpers (_* prefix) are not. See the wire-protocol
table in docs/networking.md for cross-version
compatibility.
Acknowledgements
The early networking and streaming work (0.6.0 through 0.6.5, mid-2025) was a collaboration with 1ckyDev, who wrote the first structure of the networking layer. Development has been solo since.
License
See LICENSE.
Release files for plexus-core 0.81.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| plexus_core-0.81.1.tar.gz | 301.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| plexus_core-0.81.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 617.6 kB
Release files / plexus_core-0.81.1.tar.gz
| Download URL | plexus_core-0.81.1.tar.gz |
|---|---|
| Size | 301.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bc714905445952172043a1602f9dc4cf6ae34f266b92b27989401f183560e5d3
|
|
BLAKE2b-256 checksum How to use checksums |
f57e3fba1445c95f865d929bbbafe5c1f4f39ca32e36642a698534126835017f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logRelease files / plexus_core-0.81.1-py3-none-any.whl
| Download URL | plexus_core-0.81.1-py3-none-any.whl |
|---|---|
| Size | 315.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
99b452f92e767c58382fb42454c2e92b151ab00038caa4a577d0c920227ad414
|
|
BLAKE2b-256 checksum How to use checksums |
f992283dc60f78874e7ee9cdeb21efd8f3aa32198bd40753508b86de599f3a95
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency log