Plugin Capabilities
Plugins can declare structured capabilities in their manifest. These give the agent typed tools with schemas, lifecycle hooks, slash commands, HTTP routes, AI provider adapters, and user-configurable settings. A single plugin can declare any combination of capabilities simultaneously.
Tools
Declare tools in capabilities.tools[]. Each tool becomes a typed, schema-validated tool available to the agent:
{
"capabilities": {
"tools": [
{
"name": "gws.gmail.triage",
"description": "Triage Gmail inbox -- categorize, prioritize, and draft responses",
"command": "gmail +triage",
"inputSchema": {
"type": "object",
"properties": {
"limit": { "type": "integer", "description": "Max emails to triage" },
"label": { "type": "string", "description": "Gmail label filter" }
}
},
"approval": true,
"timeoutSeconds": 120
}
]
}
}Tool fields
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Tool name exposed to the agent (e.g., "gws.gmail.triage") |
description | string | required | Description for the model to understand when to use this tool |
command | string | required | CLI args appended to the plugin binary (e.g., "gmail +triage") |
inputSchema | object | generic object | JSON Schema for typed input validation |
approval | bool | false | Whether this tool requires user approval before execution |
timeoutSeconds | number | 120 | Maximum execution time in seconds |
How typed tools work
- Plugin installs -- Nebo reads
capabilities.tools[] - Tool definitions are routed through the consolidated STRAP
PluginTool - Agent sees the tool with its schema and description
- On execution: Nebo resolves the plugin binary, runs
<binary> <command>with input as JSON on stdin, returns stdout
The generic plugin(resource, action, command) tool still works alongside structured tools -- it's the fallback for commands not declared in capabilities.
Hooks
Declare lifecycle hooks your plugin wants to subscribe to in capabilities.hooks[]:
{
"capabilities": {
"hooks": [
{
"hook": "tool.pre_execute",
"hookType": "filter",
"priority": 50,
"command": "hooks tool-pre-execute",
"timeoutMs": 500
}
]
}
}Hook fields
| Field | Type | Default | Description |
|---|---|---|---|
hook | string | required | Hook point name (e.g., "tool.pre_execute") |
hookType | string | "action" | "filter" (can modify payload) or "action" (fire-and-forget) |
priority | number | 100 | Lower runs first |
command | string | required | CLI subcommand for the hook handler |
timeoutMs | number | 500 | Timeout in milliseconds |
Hook types
- filter -- can modify or block the operation. The hook receives the payload as JSON on stdin and returns modified JSON on stdout. Returning a non-zero exit code blocks the operation.
- action -- fire-and-forget side-effect. The return value is ignored.
Priority determines execution order: lower numbers run first. Default timeout is 500ms.
Available hook points
Plugins can subscribe to these lifecycle hooks. Filter hooks can modify payloads; action hooks are fire-and-forget.
| Hook | Default type | When |
|---|---|---|
tool.pre_execute | filter | Before a tool runs -- can modify input or block |
tool.post_execute | filter | After a tool completes -- can modify output |
message.pre_send | filter | Before user message is sent to the agent |
message.post_receive | filter | After agent message is received |
memory.pre_store | filter | Before a memory is persisted |
memory.pre_recall | filter | Before memories are retrieved |
session.message_append | action | When a message is added to the session |
prompt.system_sections | filter | During system prompt generation -- can inject sections |
steering.generate | filter | During steering signal generation |
response.stream | action | During response streaming |
agent.turn | action | When an agent turn completes |
agent.should_continue | filter | Decision point to continue or halt a turn |
Commands
Declare slash commands or app commands in capabilities.commands[]:
{
"capabilities": {
"commands": [
{
"name": "/gmail",
"description": "Quick access to Gmail operations",
"command": "gmail",
"slash": true
}
]
}
}Command fields
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Command name (e.g., "/gmail") |
description | string | required | Human-readable description |
command | string | required | CLI subcommand to execute |
slash | bool | false | Register as a slash command in chat |
Routes
Declare HTTP routes your plugin handles (e.g., OAuth callbacks) in capabilities.routes[]:
{
"capabilities": {
"routes": [
{
"path": "/gws/oauth/callback",
"method": "GET",
"command": "auth callback",
"auth": "public"
}
]
}
}Route fields
| Field | Type | Default | Description |
|---|---|---|---|
path | string | required | Route path |
method | string | required | HTTP method (GET, POST, etc.) |
command | string | required | CLI subcommand that handles the request |
auth | string | "jwt" | "public" or "jwt" |
Providers
Declare AI provider adapters for custom model backends in capabilities.providers[]:
{
"capabilities": {
"providers": [
{
"id": "openrouter",
"displayName": "OpenRouter",
"providerType": "model",
"modelsCommand": "models list",
"chatCommand": "chat stream",
"authCommand": "auth setup"
}
]
}
}Provider fields
| Field | Type | Default | Description |
|---|---|---|---|
id | string | required | Provider ID |
displayName | string | required | Display name |
providerType | string | required | "model", "speech", "image", etc. |
modelsCommand | string | required | CLI subcommand to list available models (JSON output) |
chatCommand | string | required | CLI subcommand for streaming chat (NDJSON on stdout) |
authCommand | string | -- | CLI subcommand for auth setup |
Config schema (user settings)
Declare user-configurable settings that render as a form in the UI. Values are stored in plugin_settings and injected as environment variables on every plugin execution.
{
"capabilities": {
"configSchema": [
{
"key": "WORKSPACE_ID",
"label": "Workspace ID",
"description": "Your Asana workspace ID",
"fieldType": "string",
"required": true
},
{
"key": "MAX_RESULTS",
"label": "Max Results",
"fieldType": "number",
"default": "50"
},
{
"key": "API_KEY",
"label": "API Key",
"fieldType": "string",
"required": true,
"secret": true
},
{
"key": "LOG_LEVEL",
"label": "Log Level",
"fieldType": "select",
"options": ["debug", "info", "warn", "error"],
"default": "info"
}
]
}
}Config schema fields
| Field | Type | Default | Description |
|---|---|---|---|
key | string | required | Env var name injected on execution (e.g., "MAX_RESULTS") |
label | string | required | Display label in the settings form |
description | string | "" | Help text |
fieldType | string | "string" | "string", "number", "boolean", or "select" |
default | string | -- | Default value |
required | bool | false | Whether the user must set this field |
secret | bool | false | If true, value is stored with AES-256-GCM encryption |
options | string[] | -- | Available choices for "select" type |
required: true config fields have been set by the user. The UI auto-generates a settings form from configSchema -- users fill it in under Settings > Plugins.Permissions
Plugins can declare a permissions manifest that controls environment variable access, network needs, and execution limits. These are enforced at exec time.
{
"permissions": {
"envAllow": ["HOME", "PATH", "WORKSPACE_ID"],
"envDeny": ["AWS_SECRET_ACCESS_KEY", "GITHUB_TOKEN"],
"network": true,
"maxTimeoutSeconds": 300
}
}Permissions fields
| Field | Type | Default | Description |
|---|---|---|---|
envAllow | string[] | [] (all allowed) | Env vars the plugin may read. Empty means all are allowed. |
envDeny | string[] | [] | Env vars always stripped before execution (security blocklist) |
network | bool | false | Whether the plugin needs network access |
maxTimeoutSeconds | number | 300 | Hard cap on any single execution in seconds |
Common plugin patterns
These patterns show how capabilities combine to form different plugin types.
Connector plugin
Wraps a SaaS API. Declares auth for OAuth/API key login, tools[] for API operations, events[] for webhook-driven watches, and configSchema[] for user settings.
{
"id": "asana",
"slug": "asana",
"capabilities": {
"tools": [
{ "name": "asana.list-tasks", "description": "List tasks", "command": "tasks list" }
],
"configSchema": [
{ "key": "WORKSPACE_ID", "label": "Workspace", "fieldType": "string", "required": true }
]
},
"auth": { "type": "oauth_cli", "label": "Asana account", "commands": { "login": "auth login", "status": "doctor" } },
"events": [
{ "name": "task.created", "description": "New task created", "command": "watch tasks" }
]
}Provider plugin
Adds an AI model backend. Declares providers[] with commands for listing models and streaming chat.
{
"id": "openrouter",
"slug": "openrouter",
"capabilities": {
"providers": [
{
"id": "openrouter",
"displayName": "OpenRouter",
"providerType": "model",
"modelsCommand": "models list",
"chatCommand": "chat stream",
"authCommand": "auth setup"
}
]
}
}Hook plugin
Intercepts agent lifecycle events. Declares hooks[] to filter or observe tool calls, messages, memory, or prompts.
{
"id": "content-filter",
"slug": "content-filter",
"capabilities": {
"hooks": [
{
"hook": "message.pre_send",
"hookType": "filter",
"priority": 10,
"command": "filter message",
"timeoutMs": 200
},
{
"hook": "tool.pre_execute",
"hookType": "filter",
"priority": 50,
"command": "filter tool-input",
"timeoutMs": 500
}
]
}
}Utility plugin
Shared binary that other plugins or skills depend on. No capabilities of its own -- just a binary distributed via env var.
{
"id": "ffmpeg",
"slug": "ffmpeg",
"platforms": {
"darwin-arm64": { "binaryName": "ffmpeg", "sha256": "...", "size": 50000000 }
}
}