Configuration Reference#

Plugins are configured in a YAML file. You pass the file path when creating the PluginManager:

manager = PluginManager("plugins/config.yaml")

Or set it via environment variable:

export PLUGINS_CONFIG_FILE=plugins/config.yaml
export PLUGINS_ENABLED=true

YAML Structure#

plugin_dirs:
  - ./plugins

plugins:
  - name: content_filter
    kind: my_app.plugins.ContentFilterPlugin
    version: "1.0.0"
    description: "Blocks prohibited content in tool arguments"
    author: "platform-team"
    hooks:
      - tool_pre_invoke
    tags:
      - security
      - content
    mode: sequential
    on_error: fail
    priority: 10
    conditions:
      - server_ids: [prod-gateway]
        tenant_ids: [tenant-a, tenant-b]
    config:
      blocked_patterns:
        - "DROP TABLE"
        - "rm -rf"

Plugin Fields#

FieldTypeDefaultDescription
namestrrequiredUnique plugin identifier
kindstrrequiredFully qualified class path (e.g., my_app.plugins.MyPlugin), "external" for remote plugins, or "isolated_venv" for venv-isolated plugins
versionstrSemantic version
descriptionstrHuman-readable description
authorstrPlugin author
hookslist[str][]Hook types this plugin handles
tagslist[str][]Searchable tags
modestrsequentialExecution mode — see Execution Modes
on_errorstrfailError behavior: fail, ignore, disable
priorityint100Execution order within mode (lower = higher priority)
conditionslist[]When the plugin should execute
capabilitieslist[str][]Declared capabilities for extension access
configdictPlugin-specific settings passed to the constructor
max_content_sizeint10000000Maximum payload size in bytes
mcpobjectMCP client config (for external plugins)
grpcobjectgRPC client config (for external plugins)
unix_socketobjectUnix socket client config (for external plugins)

Plugin Directories#

plugin_dirs lists directories that CPEX adds to the Python path for plugin discovery. Use this when your plugin classes live outside the main application package:

plugin_dirs:
  - ./plugins
  - ./vendor/plugins

Conditions#

Conditions restrict when a plugin executes. If conditions are set and none match, the plugin is skipped for that invocation.

conditions:
  - server_ids: [prod-gateway, staging-gateway]
    tenant_ids: [tenant-a]
  - tools: [web_search, code_exec]

Available condition fields:

FieldTypeDescription
server_idsset[str]Match specific server IDs
tenant_idsset[str]Match specific tenant IDs
toolsset[str]Match specific tool names
promptsset[str]Match specific prompt names
resourcesset[str]Match specific resource URIs
agentsset[str]Match specific agent IDs
user_patternslist[str]Match user patterns
content_typeslist[str]Match content types

Multiple conditions are OR’d — the plugin runs if any condition matches. Fields within a single condition are AND’d.


Plugin-Specific Config#

The config dict is passed to your plugin’s constructor via PluginConfig.config. You access it in __init__:

from pydantic import BaseModel
from cpex.framework import Plugin, PluginConfig


class FilterConfig(BaseModel):
    blocked_patterns: list[str]
    case_sensitive: bool = False


class ContentFilterPlugin(Plugin):
    def __init__(self, config: PluginConfig):
        super().__init__(config)
        self._filter = FilterConfig.model_validate(config.config)

Validating with a Pydantic model gives you type safety and clear error messages if the YAML config is malformed.


Environment Variables#

VariableDefaultDescription
PLUGINS_ENABLEDfalseEnable the plugin framework
PLUGINS_CONFIG_FILEplugins/config.yamlPath to plugin configuration
PLUGINS_PLUGIN_TIMEOUT30Max execution time per plugin (seconds)
PLUGINS_EXECUTION_POOLMax concurrent tasks (semaphore limit)
PLUGINS_DEFAULT_HOOK_POLICYallowDefault policy for hooks without explicit rules: allow or deny

Legacy Mode Migration#

If you are upgrading from an older version of CPEX, these mode names are automatically migrated:

Legacy ModeCurrent Equivalent
enforcesequential
permissivetransform
enforce_ignore_errorsequential + on_error: ignore

Complete Example#

plugin_dirs:
  - ./plugins

plugins:
  # Policy enforcement — blocks dangerous tools
  - name: tool_policy
    kind: plugins.security.ToolPolicyPlugin
    version: "1.0.0"
    hooks:
      - tool_pre_invoke
    mode: sequential
    priority: 10
    on_error: fail
    conditions:
      - server_ids: [prod-gateway]
    config:
      blocked_tools:
        - admin_delete
        - raw_sql_exec

  # PII redaction — cleans arguments before tools run
  - name: pii_redactor
    kind: plugins.privacy.PIIRedactionPlugin
    version: "1.0.0"
    hooks:
      - tool_pre_invoke
      - tool_post_invoke
    mode: transform
    priority: 20
    on_error: ignore

  # Audit logging — async, never blocks
  - name: audit_logger
    kind: plugins.observability.AuditLogPlugin
    version: "1.0.0"
    hooks:
      - tool_pre_invoke
      - tool_post_invoke
      - prompt_pre_fetch
    mode: fire_and_forget
    priority: 100
    on_error: ignore

  # Experimental policy — dry-run only
  - name: new_content_policy
    kind: plugins.experimental.ContentPolicyV2
    version: "0.1.0"
    hooks:
      - tool_pre_invoke
    mode: audit
    priority: 15

This pipeline runs in order: tool_policy (sequential, blocks) → pii_redactor (transform, modifies) → new_content_policy (audit, observes) → audit_logger (fire_and_forget, logs in background).