Get started

Plugin SDK

Everything you need to build, test, and distribute third-party plugins for QuoxCORE.

Overview

The QuoxCORE Plugin SDK provides a complete toolkit for building extensions that integrate with the QuoxCORE platform. Plugins can add sidebar entries, register routes, store data, interact with the memory system, emit audit events, and run backend services.

The SDK is published as three npm packages:

PackagePurpose
quox-plugin-sdkCore types, runtime helpers, and testing utilities
create-quox-pluginCLI scaffolding tool for new plugin projects
quox-plugin-viteVite build plugin that externalises shared dependencies and packages the output as a .quoxplugin file

Source code: github.com/quoxai/quox-plugin-sdk

Quick Start

Go from zero to a working plugin in five steps.

1. Scaffold a new plugin

Terminal
npx create-quox-plugin my-plugin

The scaffolder prompts for a display name, category, author, and description, then generates the full project structure: manifest.json, entry point, view component, Vite config, and package.json.

Choose from 8 templates for different plugin types:

Terminal
npx create-quox-plugin my-plugin --template specialist-agent
basic
Hello-world sidebar plugin
Tier 1 (UI only)
monitoring-dashboard
Service polling, vault, NOC tab
Tier 2 (UI + Docker)
fleet-management
Host/container management, safety gates
Tier 2 (UI + Docker)
service-bridge
Third-party integration + specialist agent
Tier 2 (UI + Docker)
ui-feature
Client-side productivity tool
Tier 1 (UI only)
enterprise-extension
Memory API, AOCL, Docker, licensing
Tier 2 (UI + Docker)
specialist-agent
Standalone agent with tools + persona
Tier 1 (UI only)
agentic-team
Multi-agent orchestration, 4 agents, policies
Tier 2 (UI + Docker)

2. Install dependencies

Terminal
cd my-plugin
npm install

3. Develop

Edit src/views/MainView.jsx to build your plugin UI. The entry point at src/index.jsxregisters your routes, sidebar entries, and lifecycle hooks with QuoxCORE.

src/index.jsx
const { React } = window.__quoxSharedDeps;
import MainView from './views/MainView';

window.__quoxPluginRegister('my-plugin', {
  routes: {
    '/plugins/my-plugin': MainView,
  },
  sidebar: [
    { path: '/plugins/my-plugin', label: 'My Plugin', icon: 'puzzle' },
  ],
  hooks: {
    onActivate() {
      console.log('[My Plugin] Activated');
    },
    onDeactivate() {
      console.log('[My Plugin] Deactivated');
    },
  },
});

4. Build

Terminal
npm run build

This produces a dist/ directory containing plugin.esm.js and optionally plugin.css. If adm-zip is installed as a dev dependency, the Vite plugin automatically packages everything into a .quoxplugin file.

5. Install in QuoxCORE

Open your QuoxCORE dashboard, navigate to Settings > Plugins, and click Install Plugin. Upload the .quoxplugin file. QuoxCORE verifies the package signature, displays the requested permissions, and activates the plugin once you confirm.

Plugin Architecture

Third-party plugins run alongside official QuoxCORE plugins but are loaded at runtime rather than baked into the build. The architecture uses two tiers of isolation:

  • Tier 1 (UI only): Frontend code loaded via Module Federation, rendered inside React error boundaries, with SDK access to memory, AEE, routes, and sidebar.
  • Tier 2 (UI + Backend): Everything in Tier 1, plus a Docker container for backend logic, reverse-proxied through the collector gateway with scoped JWT authentication.

.quoxplugin Format

A .quoxplugin file is a ZIP archive with a renamed extension. It contains:

.quoxplugin contents
my-plugin.quoxplugin (ZIP)
├── manifest.json        # Plugin metadata and declarations
├── plugin.esm.js        # ES module bundle (entry point)
├── plugin.css           # Styles (optional)
├── signature.json       # Ed25519 signature (added during review)
├── assets/              # Static assets (optional)
└── backend/             # Docker context (Tier 2 only)
    ├── Dockerfile
    └── dist/
        └── server.js

Shared Dependencies

QuoxCORE provides React, ReactDOM, and React Router DOM as shared dependencies. Your plugin does not bundle these libraries. Instead, the Vite build plugin externalises them and rewrites imports to read from the global window.__quoxSharedDeps object.

Accessing shared deps
// Provided by QuoxCORE at runtime
const { React, ReactDOM, ReactRouterDOM } = window.__quoxSharedDeps;

// Or use the SDK helper
import { getSharedDeps } from 'quox-plugin-sdk';
const { React, ReactDOM, ReactRouterDOM } = getSharedDeps();

The quox-plugin-vite package handles this automatically. You write standard React imports in your source code and they are rewritten at build time.

Route Namespace

All plugin routes are namespaced under /plugins/{pluginId}/. This prevents collisions between plugins and with core QuoxCORE routes. The plugin ID in the manifest determines the namespace.

Route declaration in manifest.json
{
  "routes": {
    "/plugins/my-plugin": "MainView",
    "/plugins/my-plugin/settings": "SettingsView"
  }
}

Plugin Registration

Plugins register with QuoxCORE by calling window.__quoxPluginRegister() with the plugin ID and an exports object. The SDK provides a registerPlugin helper that includes validation:

Using the SDK helper
import { registerPlugin } from 'quox-plugin-sdk';

registerPlugin('my-plugin', {
  routes: {
    '/plugins/my-plugin': MainView,
  },
  sidebar: [
    { path: '/plugins/my-plugin', label: 'My Plugin', icon: 'puzzle' },
  ],
  hooks: {
    onActivate() { /* called when plugin is enabled */ },
    onDeactivate() { /* called when plugin is disabled */ },
  },
});

Manifest Reference

Every plugin must include a manifest.json at the project root. This file declares the plugin's identity, permissions, UI entries, and backend configuration.

FieldTypeRequiredDescription
idstringRequiredUnique plugin identifier in kebab-case. Must match the regex /^[a-z][a-z0-9-]{1,62}[a-z0-9]$/. This becomes the route namespace and storage prefix.
namestringRequiredHuman-readable display name shown in the sidebar, plugin manager, and store listing.
versionstringRequiredSemantic version string (e.g. "1.2.0").
authorstringOptionalAuthor name or organisation.
descriptionstringOptionalShort description of what the plugin does.
categorystringOptionalOne of: Monitoring, Productivity, Communication, AI & Intelligence, Infrastructure, Compliance & Audit, Other.
licensestringOptionalSet to "free" for free plugins (no license key required). Any other value indicates a commercial licence.
permissionsstring[]OptionalArray of permission identifiers the plugin requires. See the Permissions section.
sidebarSidebarItem[]OptionalArray of sidebar entries to register. Each entry has path, label, icon, and optionally minRole and badge.
routesRecord<string, string>OptionalMap of route paths to component names. Paths must be under /plugins/{id}.
minQuoxVersionstringOptionalMinimum compatible QuoxCORE version (semver range).
backendobjectOptionalBackend configuration for Tier 2 plugins. See Backend Plugins.

Sidebar Item Fields

FieldTypeRequiredDescription
pathstringRequiredRoute path for the sidebar link.
labelstringRequiredDisplay text in the sidebar.
iconstringRequiredIcon name from the QuoxCORE icon set (e.g. "puzzle", "zap", "shield").
minRolestringOptionalMinimum user role required to see this entry. One of "runner", "builder", "admin".
badgestringOptionalOptional badge text shown next to the label (e.g. "NEW").

Full Example

manifest.json
{
  "id": "slack-bridge",
  "name": "Slack Bridge",
  "version": "1.2.0",
  "author": "Arun",
  "description": "Bridge Slack channels with QuoxCORE agents",
  "category": "Communication",
  "license": "commercial",
  "permissions": [
    "sidebar",
    "storage",
    "network",
    "memory",
    "notifications",
    "aee",
    "tools"
  ],
  "sidebar": [
    {
      "path": "/plugins/slack-bridge",
      "label": "Slack Bridge",
      "icon": "message-square",
      "minRole": "builder"
    }
  ],
  "routes": {
    "/plugins/slack-bridge": "MainView",
    "/plugins/slack-bridge/settings": "SettingsView"
  },
  "minQuoxVersion": "1.0.0",
  "backend": {
    "image": "slack-bridge-backend:latest",
    "healthPath": "/health",
    "port": 8080
  }
}

Plugin API

QuoxCORE provides a PluginAPI object to each active plugin. This object exposes namespaced methods for storage, configuration, networking, notifications, memory, and audit events. All methods are scoped to the calling plugin and cannot access other plugins' data.

Storage

Plugin-scoped key-value storage backed by localStorage. All keys are automatically prefixed with the plugin ID.

Storage API
// Get a value (returns null if not found)
const value = api.storage.get('lastSync');

// Set a value (any JSON-serialisable type)
api.storage.set('lastSync', new Date().toISOString());

// Remove a value
api.storage.remove('lastSync');

// List all keys belonging to this plugin
const keys = api.storage.keys();
// => ['lastSync', 'preferences', 'cache']

Config

Read and write plugin configuration. Config is persisted server-side and survives plugin updates.

Config API
// Read current config
const config = api.config.get();
// => { apiKey: '...', refreshInterval: 30 }

// Save updated config (merges with existing)
api.config.save({
  refreshInterval: 60,
  notifications: true,
});

Fetch

Make HTTP requests through the QuoxCORE proxy. For Tier 2 plugins, this routes requests to the plugin's own backend container. The path is relative to the plugin's backend base URL.

Fetch API
// GET request to the plugin backend
const response = await api.fetch('/channels');
const data = await response.json();

// POST request with a body
const result = await api.fetch('/send', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ channel: '#alerts', message: 'Hello' }),
});
i
The network permission is required to use the fetch API. QuoxCORE adds authentication headers automatically.

Notifications

Show toast notifications to the user.

Notifications API
// Info notification (default)
api.notify('Sync complete');

// Success notification
api.notify('Message sent', 'success');

// Warning
api.notify('Rate limit approaching', 'warning');

// Error
api.notify('Connection failed', 'error');

The notifications permission is required to call this method.

Memory

Read from and write to the QuoxCORE memory system. Memories saved by a plugin are tagged with the plugin ID and appear in the global memory timeline.

Memory API
// Save a memory entry
await api.memory.save({
  type: 'observation',
  content: 'Detected 3 failed deployments in the last hour',
  tags: ['deployment', 'alert'],
});

// Search existing memories
const results = await api.memory.search('deployment failures', 10);
// => { results: [{ content: '...', score: 0.95, tags: [...] }, ...] }

Requires the memory permission.

AEE Audit Events

Emit audit events to the Agent Envelope Exchange system. All plugin actions are auditable through AEE, providing a full trail of what the plugin did, when, and in response to what.

AEE API
// Emit an audit event
await api.aee.emit('plugin.slack-bridge.message-sent', {
  channel: '#ops',
  messageLength: 142,
  timestamp: new Date().toISOString(),
});

Requires the aee permission. The intent string should follow the pattern plugin.{pluginId}.{action}.

Backend Plugins

Tier 2 plugins include a backend service that runs as a separate Docker container alongside QuoxCORE. This allows plugins to run server-side code, accept webhooks from external services, register agent tools, and maintain persistent connections.

Backend Manifest Fields

FieldTypeDescription
imagestringDocker image name for the backend container.
healthPathstringHTTP path for health checks (default: /health).
portnumberPort the backend listens on (default: 8080).

Collector Proxy

Backend plugin requests are reverse-proxied through the QuoxCORE collector service. The frontend calls api.fetch('/my-endpoint') and the collector routes it to the plugin container at http://plugin-{id}:{port}/my-endpoint, injecting a scoped JWT token for authentication.

Request flow
Browser → api.fetch('/channels')
       → Collector: POST /api/plugins/slack-bridge/channels
       → Plugin Container: GET /channels (with JWT header)

Tool Registration

Backend plugins can register tools that agents can invoke. Tools are declared in the manifest and their handlers run inside the plugin container. When an agent calls a plugin tool, the collector proxies the request to the plugin backend.

Tool declaration in manifest
{
  "backend": {
    "image": "slack-bridge-backend:latest",
    "port": 8080,
    "healthPath": "/health"
  }
}

Requires the tools permission.

Container Isolation

Each backend container runs with strict resource and network constraints:

  • CPU and memory limits enforced by Docker
  • Network access restricted to the plugin gateway only (no raw database or bastion access)
  • Egress limited to domains declared in the manifest's network permissions
  • No host filesystem access
  • Separate process space from QuoxCORE services

Agent Plugins

The specialist-agent template creates a standalone AI agent with its own system prompt, tools, persona, and inline chat sidebar. Agent plugins integrate with QuoxCORE's delegation system — QUOX can route queries to your agent when it matches the agent's capabilities.

Agent Definition

The manifest's agent field defines the agent's capabilities:

manifest.json (agent section)
{
  "agent": {
    "id": "my-agent",
    "displayName": "My Specialist",
    "systemPrompt": "You are a specialist in...",
    "persona": { "voice": "professional", "verbosity": "concise" },
    "tools": [
      {
        "name": "my_tool",
        "description": "Does something specific",
        "input_schema": { "type": "object", "properties": { "query": { "type": "string" } } }
      }
    ],
    "capabilities": ["data-analysis", "report-generation"],
    "delegationScore": 25
  }
}

DEFCON Levels

Agents integrate with QuoxCORE's DEFCON system. Each level restricts what an agent can do:

GREEN
Full autonomy. All tools available.
AMBER
Reduced autonomy. Dangerous tools require approval.
ORANGE
Minimal autonomy. Most actions need HITL.
RED
Read-only. No state-changing operations.

Autonomy Budgets

Each agent has configurable limits that prevent runaway operations:

  • Token budget — maximum tokens per session (input + output)
  • Tool call limit — maximum tool invocations per session
  • Duration limit — maximum wall-clock time per session
  • Daily run count — maximum autonomous runs per 24 hours

When a budget is exceeded, the agent escalates to human-in-the-loop (HITL) approval.

Agentic Teams

The agentic-team template creates a multi-agent orchestration plugin with multiple cooperating agents, shared tools, policies, and a task queue. This is for complex workflows where one agent's output feeds into another's input.

Team Structure

A team plugin defines 2-8 agents with distinct roles, a coordinator that routes tasks, and shared policies governing delegation, escalation, and budget allocation:

manifest.json (team section)
{
  "team": {
    "agents": [
      { "id": "planner", "role": "Break down objectives into tasks" },
      { "id": "executor", "role": "Execute individual tasks via tools" },
      { "id": "reviewer", "role": "Verify outputs and flag issues" },
      { "id": "reporter", "role": "Compile results into reports" }
    ],
    "policies": {
      "maxConcurrentAgents": 2,
      "escalateOnFailure": true,
      "budgetAllocation": "equal"
    }
  }
}

Teams use the same DEFCON levels, autonomy budgets, and HITL approval gates as individual agents — but applied at the team level with configurable per-agent overrides.

Signing and Licensing

All distributable plugins must be cryptographically signed before they can be installed on any QuoxCORE instance. Unsigned or tampered packages are rejected at install time.

Ed25519 Signatures

QuoxCORE uses Ed25519 signatures to verify plugin integrity and authenticity. The signing process works as follows:

  1. The developer builds and submits the .quoxplugin file for review.
  2. After review and approval, the Quox signing service generates a signature over the package contents.
  3. A signature.json file is added to the package containing the Ed25519 signature.
  4. At install time, QuoxCORE verifies the signature against the Quox public key before allowing activation.

Key Generation

Generating an Ed25519 key pair
# Generate a private key
openssl genpkey -algorithm Ed25519 -out quox-plugin-signing.key

# Extract the public key
openssl pkey -in quox-plugin-signing.key -pubout -out quox-plugin-signing.pub

The private key is held by the platform owner and never shared. The public key is bundled with every QuoxCORE installation for offline signature verification.

License Keys

Commercial plugins use license keys in the format QXP-XXXX-XXXX-XXXX-XXXX. The activation flow:

  1. User enters the license key after uploading the plugin.
  2. QuoxCORE calls the Quox license API to activate the key.
  3. The API validates the key, checks the activation count, and returns a signed licence token.
  4. QuoxCORE stores the token locally. All subsequent validation is offline (Ed25519 check).
  5. A periodic phone-home (every 7 days) checks for revocation, with a grace period for offline instances.

Free plugins (where manifest.license is set to "free") skip the license key step entirely and activate immediately on install.

Permissions

Plugins declare the permissions they need in the manifest. Users see these at install time and must approve them before activation. The SDK enforces permissions at runtime.

PermissionGrants Access ToAPI Methods
sidebarAdd entries to the QuoxCORE sidebarregisterPlugin({ sidebar })
storagePlugin-scoped key-value storageapi.storage.get/set/remove/keys
networkHTTP requests through the collector proxyapi.fetch()
memoryRead and write to the QuoxCORE memory systemapi.memory.save/search
notificationsShow toast notifications to the userapi.notify()
aeeEmit audit events to the AEE systemapi.aee.emit()
toolsRegister agent tools (Tier 2 only)Backend tool handlers

Calling an API method without the corresponding permission throws an error. Over-requesting permissions (declaring permissions that the plugin does not use) will be flagged during review.

Security Model

The plugin security model uses five layers of defence in depth.

Layer 1: Review Gate

Every plugin must be reviewed and signed before it can be installed. The Ed25519 signature is verified at install time. Unsigned or tampered packages are rejected outright.

Layer 2: Permission Scoping

Plugins declare the permissions they need. Users approve them at install time. The SDK enforces permissions at runtime, so a plugin cannot access memory, AEE, or the network without the corresponding permission grant.

Layer 3: Frontend Isolation

Plugin frontend code runs inside React error boundaries. A crashing plugin cannot take down the QuoxCORE dashboard. Plugins cannot access internal React context, internal state, or other plugins' components.

Code validation runs at build time and blocks dangerous patterns:

  • eval() and new Function() are blocked
  • innerHTML and dangerouslySetInnerHTML are flagged
  • Direct DOM access outside the plugin mount point is prevented
  • Imports of QuoxCORE internal modules are rejected

Layer 4: Backend Isolation (Tier 2)

Backend containers run with CPU and memory limits, restricted network policies (egress only to declared domains), no host filesystem access, and a separate process space. They communicate with QuoxCORE exclusively through the collector gateway.

Layer 5: Audit Trail

All plugin actions flow through AEE. Every memory write, tool execution, and API call is logged with the plugin ID. The platform owner can revoke a plugin's signature at any time, forcing all QuoxCORE instances to disable it on the next phone-home check.

Storage Namespacing

All storage operations are automatically prefixed with the plugin ID. A plugin with ID slack-bridge calling api.storage.set('token', '...') actually writes to the key plugin:slack-bridge:token. Plugins cannot read or write keys outside their namespace.

Testing

The SDK includes testing utilities at quox-plugin-sdk/testing that provide mock implementations of the plugin API and helpers for setting up the test environment.

createMockPluginApi

Creates a mock PluginAPI object with in-memory storage, no-op config and notification methods, and stub implementations of memory and AEE.

Using the mock API in tests
import { createMockPluginApi } from 'quox-plugin-sdk/testing';

test('saves preferences to storage', () => {
  const api = createMockPluginApi('my-plugin');

  api.storage.set('theme', 'dark');
  expect(api.storage.get('theme')).toBe('dark');

  api.storage.remove('theme');
  expect(api.storage.get('theme')).toBeNull();
});

setupPluginTestEnv / teardownPluginTestEnv

Set up and tear down the global window mocks that plugins rely on. Call these in your test setup and teardown hooks.

Test environment setup
import {
  setupPluginTestEnv,
  teardownPluginTestEnv
} from 'quox-plugin-sdk/testing';

beforeAll(() => {
  setupPluginTestEnv();
});

afterAll(() => {
  teardownPluginTestEnv();
});

test('plugin registers successfully', () => {
  // window.__quoxSharedDeps and window.__quoxPluginRegister
  // are now available
  require('./src/index.jsx');

  const registered = window.__quoxRegisteredPlugins;
  expect(registered['my-plugin']).toBeDefined();
  expect(registered['my-plugin'].routes).toBeDefined();
});

Validation

Run the built-in validation command to check your manifest and plugin structure before submitting for review:

Terminal
npm run validate

This checks that the manifest schema is valid, all declared routes have corresponding components, no forbidden patterns are present in the code, declared permissions are actually used, and the version follows semver.

Size Limits

The following limits apply to plugin packages:

ConstraintLimitNotes
Plugin ID length3–64 charactersLowercase letters, numbers, and hyphens only
Frontend JS bundle2 MBCompressed size of plugin.esm.js
CSS bundle500 KBCompressed size of plugin.css
Total .quoxplugin size (Tier 1)5 MBIncluding all assets
Total .quoxplugin size (Tier 2)50 MBIncluding backend Docker context
manifest.json100 KBTypically 2–5 KB
Backend container memory256 MBDefault limit, configurable by platform admin
Backend container CPU0.5 coresDefault limit, configurable by platform admin