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:
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
npx create-quox-plugin my-pluginThe 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:
npx create-quox-plugin my-plugin --template specialist-agent2. Install dependencies
cd my-plugin
npm install3. 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.
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
npm run buildThis 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:
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.jsShared 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.
// 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.
{
"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:
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.
Sidebar Item Fields
Full Example
{
"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.
// 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.
// 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.
// 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' }),
});network permission is required to use the fetch API. QuoxCORE adds authentication headers automatically.Notifications
Show toast notifications to the user.
// 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.
// 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.
// 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
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.
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.
{
"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
networkpermissions - 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:
{
"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:
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:
{
"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:
- The developer builds and submits the
.quoxpluginfile for review. - After review and approval, the Quox signing service generates a signature over the package contents.
- A
signature.jsonfile is added to the package containing the Ed25519 signature. - At install time, QuoxCORE verifies the signature against the Quox public key before allowing activation.
Key Generation
# 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.pubThe 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:
- User enters the license key after uploading the plugin.
- QuoxCORE calls the Quox license API to activate the key.
- The API validates the key, checks the activation count, and returns a signed licence token.
- QuoxCORE stores the token locally. All subsequent validation is offline (Ed25519 check).
- 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.
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()andnew Function()are blockedinnerHTMLanddangerouslySetInnerHTMLare 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.
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.
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:
npm run validateThis 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: