Date: July 17, 2026
Scope: Full plugin isolation system — Capability Broker, RPC protocol, worker_threads sandboxing, and developer documentation.
This rewrite transforms the Advanced Discord Bot plugin system from a fully trusted, process-shared model into a Core / Broker / Worker isolation architecture. Plugins now run in sandboxed worker_threads with capability-gated RPC access to Discord, database, and hooks — they never touch the real client, db, or process.env directly.
The result: any installed plugin has limited, auditable reach instead of full root-level access to the bot.
| File | Purpose |
|---|---|
core/rpc/methods.js |
RPC method catalog — 47 methods organized by category (db.*, discord.*, hooks.*, model.*, scheduler.*), each declaring its required capability. Includes _resolveArgs mappings for positional-to-named-param conversion. |
core/rpc/broker.js |
CapabilityBroker — Central enforcement point. Receives RPC requests, validates capabilities, executes operations in the Core process, returns serialized results. Extends EventEmitter for hook:forward and cron:tick events. |
core/rpc/worker-manager.js |
WorkerManager — Spawns and manages worker_threads workers. Handles crash recovery (max 3 restarts), broker event forwarding, and clean shutdown. |
core/rpc/worker-bootstrap.js |
Worker entry point — Self-contained bootstrap that runs inside each worker thread. Creates an RpcClient, builds a shim ctx (with db, hooks, discord, logger proxies), loads the plugin, and listens for command:execute events. Exports createShimContext for testing. |
core/rpc/worker-client.js |
RpcClient — IPC transport for workers. Wraps parentPort with request/response correlation (call()), event subscriptions (on()), and cleanup (destroy()). |
core/rpc/protocol.js |
Message types — Constants and validation for IPC message shapes (rpc:request, rpc:response, rpc:event, worker:ready, worker:error). |
core/rpc/metrics.js |
MetricsCollector — Tracks call counts, durations, error rates, and resource usage per plugin. Exposes global and per-plugin metrics with health summaries. |
core/rpc/resource-limits.js |
ResourceTracker — Monitors per-plugin CPU time and call frequency. Enforces configurable limits (max CPU ms, max calls per minute). Lazy parentPort import so it loads safely in the main process. |
core/capabilities.js |
Capability validation — Validates plugin.json capabilities structure (category → string[] mapping). |
| File | Purpose |
|---|---|
plugin.json |
Template manifest with isolation: true, capabilities block, and all standard fields. |
index.js |
Complete dual-mode plugin example — works both in direct mode (uses ctx.client) and isolated mode (uses ctx.discord.*). Demonstrates commands, events, models, hooks, and scheduler usage. |
README.md |
Developer-facing guide for the template plugin. |
The full architecture spec — motivation, three-tier process model, capability broker design, resource limits, migration path, and locked decisions.
Added to the existing PluginManager class:
enableIsolation()— CreatesCapabilityBroker+WorkerManager, wires Discord event forwarding and RPC handlers._forwardDiscordEvents()— Listens to 11 Discord client events, serializes payloads (safe JSON round-trip forGuildMember,Message, etc.), and broadcasts to all workers viaWorkerManager.broadcastEvent()._serializeDiscordEvent(eventName, args)— Extracts only safe, serializable fields from Discord.js objects. Falls back to{ _unserializable: true }on failure._registerIsolationRpcHandlers()— Patchesbroker.handleRequestto intercept three RPCs from workers:plugin.registerCommand— Creates proxy execute functions that serialize interactions and call back to workers.plugin.registerEvent— Acknowledges registration (events flow via forwarding).plugin.defineModel— Registers model schemas in the broker.
_serializeInteraction(interaction)— Serializes Discord interactions (id, type, commandName, options, guildId, user, member) for IPC.loadPlugin()— Now checksuseIsolationflag (isolationEnabled && !builtin && manifest.isolation !== false) and routes to_loadPluginDirect()or_loadPluginInWorker()._loadPluginDirect(plugin, pluginState, logger)— Extracted from the oldloadPlugin()body (legacy path)._loadPluginInWorker(plugin, pluginState, caps, logger)— CallsworkerManager.spawnWorker(), setspluginState.isolated = true.unloadPlugin()— Now terminates workers for isolated plugins viaworkerManager.terminateWorker().
- Added
IS_WORKERguard so the file doesn't crash whenrequire()d from the main process (e.g., in tests). createShimContext(rpc)now takesrpcas a parameter (was referencing an undefined variable before).- Added
command:executelistener in the worker — when Core sends a serialized interaction, the worker routes it to the registered command'sexecute()and posts back the response. buildInteractionProxy(rpc)— Lightweight proxy that routesinteraction.reply(),interaction.editReply(),interaction.followUp()throughdiscord.sendRichMessageRPC.- Removed dead
buildParams()function and unuseddeferredRepliesvariable. - Single consolidated
IS_WORKERblock (no duplicateRpcClientormain()definitions).
- Changed
parentPortfrom a top-levelrequire('worker_threads').parentPortto a lazygetParentPort()getter. - This prevents
TypeError: Cannot read properties of undefinedwhen the module is loaded in the main process (whereparentPortisundefined).
Rewrote the entire developer guide (690 lines changed):
- Documents both direct mode (legacy, full
ctx.clientaccess) and isolated mode (new, RPC-only). - Explains the
plugin.jsonisolationflag andcapabilitiesblock. - Provides a migration guide: which
ctxmethods map to which RPC calls. - Lists all available
ctx.discord.*methods and when to use them. - Documents
ctx.db.*supported methods (21 methods). - Explains resource limits and how to stay within them.
- Includes a complete working plugin example with commands, events, and models.
┌─────────────────────────────────────────────────┐
│ CORE PROCESS (never runs plugin code) │
│ - Holds: Discord token, DB connection, API keys │
│ - Owns: CapabilityBroker (47 RPC methods) │
│ - Owns: WorkerManager (lifecycle, crash recov.) │
│ - Forwards: Discord events → workers (serialized)│
└───────────┬───────────────────────────────────────┘
│ IPC (structured messages only)
┌────────┼────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│Plugin │ │Plugin │ │Plugin │ ← each: own worker_threads
│ A │ │ B │ │ C │ with resourceLimits
│worker │ │worker │ │worker │ (maxOldGenerationSizeMb, etc.)
└───────┘ └───────┘ └───────┘
| What plugins could do before | What they can do now |
|---|---|
Read process.env (tokens, secrets) |
Nothing — env is not passed to workers |
require('fs'), require('child_process') directly |
Nothing — workers are V8 isolates with controlled module resolution |
Call ctx.client.ban(), ctx.client.deleteChannel() |
Only ctx.discord.* methods their manifest declares |
require('mongoose') and touch any collection |
Only ctx.db.* methods (21 methods), scoped to their own data |
| Allocate unbounded memory | Hard-killed by resourceLimits (configurable per plugin) |
| Run forever | Per-call wall-clock timeouts (15s default for commands) |
All 33 existing tests pass. No regressions introduced.
| Test Suite | Status |
|---|---|
test/capabilities.test.js |
✅ All pass |
test/permissions.test.js |
✅ All pass |
test/registry-version.test.js |
✅ All pass |
| Module loading (all 9 RPC + PluginManager) | ✅ All load cleanly |
| Category | Files | Lines Changed |
|---|---|---|
New: core/rpc/ |
9 files | ~1,800 lines |
New: core/capabilities.js |
1 file | ~60 lines |
New: plugins/adb-plugin-template/ |
3 files | ~350 lines |
New: docs/superpowers/plans/ |
1 file | ~200 lines |
Modified: core/PluginManager.js |
1 file | +333 lines |
Modified: core/rpc/worker-bootstrap.js |
1 file | ~400 lines |
Modified: core/rpc/resource-limits.js |
1 file | ~10 lines |
Modified: CREATE-PLUGIN.md |
1 file | ~690 lines |
| Total | ~19 files | ~3,800 lines |
-
worker_threadsfirst, containers deferred — Cheaper per-session, ms-scale startup, blocks the highest-severity risks (secret exfiltration, unbounded memory). Container tier reserved for futureriskTierfield. -
Default capability set: absolute zero — A plugin gets nothing until its manifest explicitly declares capabilities and the server owner approves them.
-
Dual-mode plugins — Existing plugins work in direct mode (no changes needed). New plugins can opt into isolation via
"isolation": trueinplugin.json. -
No breaking changes for existing plugins — The old
ctx.client/ctx.dbinterface still works. Isolation is opt-in per plugin. -
Broker patches for command/event registration — Workers register commands by sending RPC to Core, which creates proxy execute functions that call back to workers for actual execution. This is the bridge between worker-side plugin code and Core-side Discord interactions.
- Docker-per-plugin containers — Deferred to a future
riskTierfield. Not needed until there's a plugin that requires OS-level CPU/pid quota enforcement. - Per-plugin porting marathon — Only the core isolation infrastructure was built. Existing plugins (automod, autorole, welcome) continue to work in direct mode.
- Static AST scanning — Recommended as a Phase 6 addition to the submission pipeline, not implemented in this session.
In index.js (bot entry point), add before pluginManager.loadAll():
if (process.env.PLUGIN_ISOLATION === 'true') {
pluginManager.enableIsolation();
}Or unconditionally:
pluginManager.enableIsolation();
await pluginManager.loadAll();Any plugin with "isolation": false in its plugin.json (or the core plugin) will continue to load in the main process.