Every plugin requires a manifest.json file. This reference documents every field, type, and API available to plugin developers.
| Field | Type | Description |
|---|---|---|
name |
string | Unique plugin identifier. Use kebab-case (e.g. my-plugin). This is the primary key in the plugin database. |
version |
string | Semantic version (e.g. 1.0.0). |
description |
string | Short description shown in the marketplace and plugin list. |
kind |
"esm" | "mcp" | "wasm" |
Plugin runtime kind. Determines how the plugin is loaded. |
entryPoint |
string | Path to the module, URL, or WASM binary. Relative to the manifest location. |
runtime |
"deno" | "wasm" |
Execution target. deno for ESM/MCP, wasm for WebAssembly. |
capabilities |
string[] | Declared permissions and extension points. See Capabilities. |
| Field | Type | Description |
|---|---|---|
author |
string | Author name or organization. |
homepage |
string | Plugin homepage URL. |
license |
string | SPDX license identifier (e.g. MIT). |
repository |
string | Source repository URL. |
hash |
string | SHA-256 hash of the entry point content for integrity verification. |
signature |
string | Optional GPG/JWT signature for trust verification. |
dependencies |
Record<string, string> | Other plugins required, keyed by name with semver constraints. |
peerDependencies |
Record<string, string> | Host CortexPrism version constraint (e.g. { "cortex": ">=1.0.0" }). |
tools |
ToolDeclaration[] | Tool definitions (names, params). ESM plugins provide the implementation in code. |
cliCommands |
CliCommandDeclaration[] | CLI subcommand specifications. |
ui |
UiContribution | UI panels, widgets, and settings forms. |
config |
ConfigContribution | Config schema extensions and defaults. |
events |
string[] | Event types the plugin subscribes to. See Plugin Events. |
JavaScript/TypeScript modules loaded via dynamic import(). The entry point module exports tools, lifecycle hooks, providers, and CLI commands. This is the most common and flexible plugin type.
Entry point example: ./mod.ts (relative to manifest location)
Model Context Protocol servers. The entryPoint is a URL to an MCP server. CortexPrism creates a synthetic tool mcp_<pluginName> that wraps JSON-RPC 2.0 calls to the server.
Entry point example: https://my-mcp-server.example.com/rpc
MCP tool interface:
Tool name: mcp_<pluginName>
Params:
- method (string, required) — JSON-RPC method to call
- params (object, required) — method parameters
Each call sends POST <entryPoint> with body:
{ "jsonrpc": "2.0", "id": 1, "method": "<method>", "params": { ... } }MCP plugins declare their tools in the manifest under the tools field for documentation, but the actual execution is handled by the MCP protocol bridge.
WebAssembly modules. The entryPoint is a .wasm binary path. The WASM runtime expects two exports: plugin_init() and plugin_get_capabilities(). Host functions provided to the WASM instance include env.log, env.http_request, and env.store_get/env.store_set.
Note: WASM tool execution is currently stubbed and not yet implemented.
Capabilities serve dual purpose: they declare what extension points the plugin uses AND what permissions it needs.
| Value | Description |
|---|---|
tools |
Plugin provides Tool[] — tools are registered into the global agent tool registry |
cli:commands |
Plugin provides CLI subcommands via cortex <name> |
ui:panel |
Plugin provides a Web UI panel/tab served by the CortexPrism HTTP server |
ui:widget |
Plugin provides a dashboard widget (HTML, chart, or table) |
config:schema |
Plugin extends the config schema with new settings sections |
config:provider |
Plugin provides an LLM provider factory (registered for agent use) |
memory:store |
Plugin provides a custom memory backend (replaces default vector store) |
memory:embedder |
Plugin provides an embedding provider (replaces default embedding model) |
events:listener |
Plugin subscribes to the event bus — must be paired with the events manifest field |
middleware:pre |
Plugin provides pre-execution middleware (runs before tools execute, can reject/modify args) |
middleware:post |
Plugin provides post-execution middleware (runs after tools execute, can modify results) |
| Value | Description |
|---|---|
fs:read |
Read filesystem access |
fs:write |
Write filesystem access |
fs:list |
Directory listing |
fs:edit |
File editing |
fs:delete |
File deletion |
fs:search |
File searching |
shell:run |
Shell command execution |
network:fetch |
Outbound HTTP requests |
net:outbound |
General outbound network access |
net:inbound |
Inbound network (listening) |
db:read |
Database read access |
db:write |
Database write access |
ESM plugins export from their entry point module. The loader picks up these exports by name:
| Export | Type | Description |
|---|---|---|
tools |
Tool[] |
Tool implementations. Each tool has a definition and execute function. |
cliCommands |
CliCommandDeclaration[] |
CLI subcommand declarations (redundant with manifest; prefer declaring in manifest). |
providers |
Record<string, ProviderFactory> |
LLM provider factories keyed by provider kind. Each factory receives plugin config and returns a provider instance with name, defaultModel, complete(), and stream() methods. |
onLoad |
(ctx: PluginContext) => Promise<void> |
Called when the plugin module is imported. Use for initialization. |
onUnload |
(ctx: PluginContext) => Promise<void> |
Called when the plugin is disabled/removed. Use for cleanup. |
onConfigChange |
(key: string, value: unknown, ctx: PluginContext) => Promise<void> |
Called when a config value changes at runtime. |
Plugins can also implement an extended PluginLifecycle interface with these hooks (exported by name from the module):
| Hook | Signature | Called When |
|---|---|---|
onInstall |
(ctx: PluginContext) => Promise<void> |
Plugin is first installed (manifest stored, files staged) |
onLoad |
(ctx: PluginContext) => Promise<void> |
Module is loaded/imported into memory |
onActivate |
(ctx: PluginContext) => Promise<void> |
Plugin transitions to active status — tools registered, providers bound, events subscribed |
onDeactivate |
(ctx: PluginContext) => Promise<void> |
Plugin begins disabling — before tools are unregistered |
onUnload |
(ctx: PluginContext) => Promise<void> |
Module is being unloaded from memory |
onUninstall |
(ctx: PluginContext) => Promise<void> |
Plugin is being permanently removed (database row deleted, files cleaned) |
onConfigChange |
(key: string, value: unknown, ctx: PluginContext) => Promise<void> |
A single config value changes at runtime |
Every lifecycle hook and tool execution receives a PluginContext with these properties:
The plugin's unique name (from the manifest name field).
Absolute filesystem path to the plugin's directory under ~/.cortex/data/plugins/<name>/.
Key-value persistence scoped to this plugin. Data survives plugin reloads.
interface PluginStateStore {
get(key: string): Promise<string | null>;
set(key: string, value: string): Promise<void>;
delete(key: string): Promise<void>;
list(): Promise<Record<string, string>>;
}Typed access to the plugin's config section in ~/.cortex/config.json.
interface PluginConfigStore {
get<T = unknown>(key: string): Promise<T | null>;
set<T = unknown>(key: string, value: T): Promise<void>;
getAll(): Promise<Record<string, unknown>>;
}Scoped logger that prefixes messages with [plugin:<name>].
interface PluginLogger {
info(msg: string): void;
warn(msg: string): void;
error(msg: string): void;
debug(msg: string): void;
}Register/unregister tools at runtime (beyond what's exported from the module).
interface HostApi {
registerTool(tool: Tool): void;
unregisterTool(name: string): void;
}Plugins that export tools must implement the Tool interface:
interface Tool {
definition: ToolDefinition;
execute(args: Record<string, unknown>, context: ToolContext): Promise<ToolCallResult>;
}
interface ToolDefinition {
name: string;
description: string;
params: ToolParam[];
capabilities: ToolCapability[];
}
interface ToolParam {
name: string;
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
description: string;
required?: boolean;
enum?: string[]; // restrict values to this set
}
interface ToolCallResult {
toolName: string;
success: boolean;
output: string;
error?: string;
durationMs: number;
}
interface ToolContext {
sessionId: string; // current agent session ID
workingDir: string; // current working directory
agentId: string; // agent executing this tool
workspaceDir: string; // agent workspace root
approvalGate?: (tool: string, command: string) => Promise<boolean>;
}
type ToolCapability =
| 'fs:read' | 'fs:write' | 'fs:list' | 'fs:edit' | 'fs:delete'
| 'fs:search' | 'shell:run' | 'network:fetch' | 'db:read' | 'db:write';Important: The ToolDefinition.capabilities array declares what permissions this specific tool needs at runtime. This is validated against the plugin's declared capabilities in the manifest.
Plugins subscribe to events by declaring "events:listener" in capabilities and listing event types in the events manifest field (e.g. "events": ["session:start", "tool:post-execute"]).
The globalEventBus singleton dispatches these event types:
| Event Type | Payload | Emitted When |
|---|---|---|
session:start |
{ sessionId: string } |
A new agent session begins |
session:end |
{ sessionId: string } |
An agent session ends |
tool:pre-execute |
{ toolName: string, args: Record<string, unknown> } |
Before any tool executes |
tool:post-execute |
{ toolName: string, result: unknown } |
After any tool executes |
llm:pre-call |
{ provider: string, model: string } |
Before an LLM API call |
llm:post-call |
{ provider: string, model: string, tokensIn: number, tokensOut: number } |
After an LLM API call |
agent:turn-start |
{ sessionId: string, turnId: string } |
Start of an agent reasoning turn |
agent:turn-end |
{ sessionId: string, turnId: string, response: string } |
End of an agent reasoning turn |
config:change |
{ key: string, value: unknown } |
A global or plugin config value changes |
daemon:status |
{ daemon: string, status: 'up' | 'down' } |
A background daemon status changes |
Plugins can also subscribe programmatically via the globalEventBus:
import { globalEventBus } from 'cortex/plugins';
export const onLoad = async (ctx: PluginContext) => {
globalEventBus.on('tool:post-execute', (event) => {
ctx.logger.info(`Tool ${event.toolName} completed`);
});
};Pre-execution middleware runs before any tool executes. The middleware can:
- Reject the tool call by returning a failure result
- Modify arguments before execution
- Log or audit tool usage
Export preMiddleware from your module:
export const preMiddleware = async (toolName: string, args: Record<string, unknown>, ctx: PluginContext) => {
if (toolName === 'dangerous_tool') {
return { allowed: false, reason: 'Blocked by safety plugin' };
}
return { allowed: true, args }; // optionally modified args
};Post-execution middleware runs after any tool executes. The middleware can:
- Modify the result/output
- Log execution metadata
- Trigger side effects (notifications, webhooks)
Export postMiddleware from your module:
export const postMiddleware = async (toolName: string, result: ToolCallResult, ctx: PluginContext) => {
ctx.logger.info(`Tool ${toolName} completed in ${result.durationMs}ms`);
return result; // optionally modified result
};{
"tools": [
{
"name": "my_tool",
"description": "What this tool does",
"params": [
{ "name": "input", "type": "string", "description": "Input value", "required": true },
{ "name": "mode", "type": "string", "description": "Operation mode", "required": false, "enum": ["fast", "accurate", "balanced"] }
]
}
]
}| Field | Type | Description |
|---|---|---|
name |
string | Parameter name (camelCase) |
type |
"string" | "number" | "boolean" | "object" | "array" |
Parameter type |
description |
string | Human-readable description |
required |
boolean | Whether the parameter is required (default: false) |
enum |
string[] | Allowed values for string parameters |
{
"cliCommands": [
{
"name": "my-cmd",
"description": "My custom command",
"args": [
{ "name": "target", "type": "string", "description": "Target to operate on", "required": true }
],
"options": [
{ "name": "verbose", "type": "boolean", "description": "Verbose output", "flag": "-v" }
]
}
]
}| Field | Type | Description |
|---|---|---|
name |
string | Subcommand name (kebab-case) |
description |
string | Shown in cortex --help |
args |
object[] | Positional arguments |
args[].name |
string | Argument name |
args[].type |
string | Argument type |
args[].description |
string | Argument description |
args[].required |
boolean | Whether required |
options |
object[] | Flag options (--name, -v) |
options[].name |
string | Option name (long form without --) |
options[].type |
string | Option type |
options[].description |
string | Option description |
options[].flag |
string | Short flag (e.g. -v, -o) |
{
"ui": {
"panels": [
{ "id": "my-panel", "title": "My Panel", "icon": "star", "htmlPath": "./ui/panel.html" }
],
"widgets": [
{ "id": "my-widget", "title": "My Widget", "type": "html", "config": {} }
],
"settings": [
{
"section": "General",
"fields": [
{ "key": "apiKey", "label": "API Key", "type": "secret", "defaultValue": "", "description": "Your API key for authentication" },
{ "key": "maxRetries", "label": "Max Retries", "type": "number", "defaultValue": 3, "description": "Maximum retry attempts" },
{ "key": "enabled", "label": "Enabled", "type": "boolean", "defaultValue": true },
{
"key": "mode", "label": "Mode", "type": "select",
"defaultValue": "auto",
"options": [
{ "label": "Automatic", "value": "auto" },
{ "label": "Manual", "value": "manual" }
]
}
]
}
]
}
}| Field | Type | Description |
|---|---|---|
id |
string | Unique panel identifier |
title |
string | Tab title in the Web UI |
icon |
string | Icon name (optional) |
htmlPath |
string | Path to HTML file relative to manifest |
| Field | Type | Description |
|---|---|---|
id |
string | Unique widget identifier |
title |
string | Widget title |
type |
"html" | "chart" | "table" |
Widget rendering type |
config |
object | Widget-specific configuration |
| Type | Description |
|---|---|
text |
Single-line text input |
number |
Numeric input |
boolean |
Checkbox toggle |
select |
Dropdown with options array |
secret |
Password field (masked input) |
| Field | Type | Description |
|---|---|---|
key |
string | Config key name |
label |
string | Display label |
type |
"text" | "number" | "boolean" | "select" | "secret" |
Input type |
defaultValue |
any | Default value |
options |
{ label: string, value: string }[] |
Choices for select type |
description |
string | Help text (optional) |
{
"config": {
"providers": [
{ "kind": "my-provider", "label": "My Provider", "defaultModel": "my-model-v1" }
],
"settings": {
"defaultEndpoint": "https://api.example.com"
}
}
}| Field | Type | Description |
|---|---|---|
providers |
object[] | LLM provider declarations |
providers[].kind |
string | Provider identifier used in config |
providers[].label |
string | Display name |
providers[].defaultModel |
string | Default model name |
settings |
object | Default config values for this plugin |
| Level | Sandbox | Permissions |
|---|---|---|
untrusted |
Worker sandbox | Limited to declared permissions |
signed |
Worker sandbox | Broader permissions based on signature |
trusted |
In-process | Full declared permissions |
Trust level is set at install time and can be changed via cortex plugins permissions <name>.
| Status | Description |
|---|---|
unloaded |
Plugin not currently loaded in memory |
loading |
Plugin module is being imported/initialized |
active |
Plugin is loaded, tools registered, running normally |
unloading |
Plugin is being disabled and cleaned up |
error |
Plugin failed to load or encountered a fatal error |
Plugins are stored in the CortexPrism database. Each plugin gets a row with these columns:
| Column | Type | Description |
|---|---|---|
name |
TEXT | Primary key — plugin identifier |
version |
TEXT | Current installed version |
type |
TEXT | Plugin kind (esm, mcp, wasm) |
runtime |
TEXT | Execution target (deno, wasm) |
entry |
TEXT | Entry point path/URL |
manifest_json |
TEXT | Full manifest as JSON string |
declared_permissions |
TEXT | Comma-separated capabilities |
effective_permissions |
TEXT | Resolved permissions after overrides |
author |
TEXT | Author name |
description |
TEXT | Plugin description |
license |
TEXT | SPDX license |
source |
TEXT | Install source URL/path |
integrity_hash |
TEXT | SHA-256 hash of entry point |
enabled |
INTEGER | 0 or 1 |
status |
TEXT | Current PluginStatus |
trust_level |
TEXT | Trust level (untrusted, signed, trusted) |
error_message |
TEXT | Last error message if status is error |
load_attempts |
INTEGER | Number of load attempts |
installed_at |
TEXT | ISO timestamp |
updated_at |
TEXT | ISO timestamp |
last_load_at |
TEXT | ISO timestamp |
dependencies_json |
TEXT | JSON of dependencies and peerDependencies |
config_schema_json |
TEXT | JSON of ui.settings for form rendering |