Skip to content

Plugin System Overview

The N.E.K.O. plugin system is a Python-based plugin framework built on process isolation and async IPC. It has two package types: Plugin for product features and Adapter for external protocol bridges. The former Extension package type has been removed; PluginRouter remains available inside a normal Plugin.

Architecture

┌────────────────────────────────────────────────────┐
│              Main Process (Host)                   │
│  ┌──────────────────────────────────────────────┐  │
│  │   Plugin Host (core/)                        │  │
│  │   - Plugin lifecycle management              │  │
│  │   - Bus system (memory, events, messages)    │  │
│  │   - ZMQ IPC transport                        │  │
│  └──────────────────────────────────────────────┘  │
│  ┌──────────────────────────────────────────────┐  │
│  │   Plugin Server (server/)                    │  │
│  │   - HTTP API endpoints (FastAPI)             │  │
│  │   - Plugin registry                          │  │
│  │   - Message queue                            │  │
│  └──────────────────────────────────────────────┘  │
└────────────────────┬───────────────────────────────┘
                     │ ZMQ IPC
      ┌──────────────┼──────────────┐
      ▼              ▼              ▼
  Plugin A       Plugin B       Adapter D
  (process)      (process)      (process)

Package types

ParadigmImport fromUse caseHow it runs
Pluginplugin.sdk.pluginIndependent features (search, reminders, etc.)Separate process
Adapterplugin.sdk.adapterBridge external protocols (MCP, NoneBot) to internal plugin callsSeparate process with gateway pipeline

When to use which?

  • "I want to add a new standalone feature" → use Plugin
  • "I want to add commands around an existing feature" → use a normal Plugin, or add a PluginRouter inside the existing host when you own it
  • "I want to accept MCP/NoneBot/external protocol calls and route them to plugins" → use Adapter

Start with Plugin. Migrate a former Extension by merging its Router into the owning Plugin or converting it into a standalone Plugin.

Key Features

  • Process isolation — Plugins and Adapters run in separate processes
  • Async support — Both sync and async entry points
  • Result typesOk/Err for type-safe error handling (no exceptions in normal flow)
  • Hook system@before_entry, @after_entry, @around_entry, @replace_entry for AOP
  • Cross-plugin callsself.plugins.call_entry("other_plugin:entry_id") for inter-plugin communication
  • System infoself.system_info for querying host system metadata
  • Plugin storePluginStore for persistent key-value storage
  • Bus systemself.bus reads host state through messages, events, lifecycle, conversations, and memory. Only the first three support watch(); conversations and memory are read-only snapshots. Replayable watcher chains use get() → structured filter(field=value, ...)sort(by=...)limit()watch() and subscribe only to add, del, or change deltas. There is no publish/emit API. Use self.bus.memory.get(...) for recent records and await self.ctx.query_memory(...) for semantic lookup.
  • Dynamic entries — Register/unregister entry points at runtime
  • Hosted UI — Build interactive TSX panels and Markdown guides in the Plugin Manager
  • Static UI — Serve a legacy web UI from your plugin directory
  • Lifecycle hooksstartup, shutdown, reload, freeze, unfreeze, config_change
  • Timer tasks — Periodic execution with @timer_interval
  • Message handlers — React to messages from the host system

Plugin Directory Structure

plugin/plugins/
└── my_plugin/
    ├── __init__.py      # Plugin code (entry point)
    ├── plugin.toml      # Plugin configuration
    ├── config.json      # Optional: custom config
    ├── data/            # Optional: runtime data directory
    ├── ui/              # Optional: hosted TSX panels
    ├── docs/            # Optional: Markdown or TSX guide surfaces
    ├── i18n/            # Optional: plugin-local translations
    └── static/          # Optional: legacy web UI files

Released under the MIT License.