Skip to content

Plugin Development Guide

This guide covers how to build, test, and distribute FCC plugins. The plugin system uses Python entry points, so plugins are ordinary Python packages discovered automatically at install time.

Plugin Types

FCC supports 10 plugin types, each registered through its own entry-point group. Every plugin must implement the corresponding abstract base class from fcc.plugins.base.

# Type Entry-Point Group ABC Purpose
1 Personas fcc.plugins.personas PersonaPlugin Contribute additional persona YAML files
2 Engines fcc.plugins.engines EnginePlugin Provide custom simulation engine classes
3 Templates fcc.plugins.templates TemplatePlugin Add Jinja2 template directories for doc generation
4 Scorers fcc.plugins.scorers ScorerPlugin Register quality scoring callables
5 Validators fcc.plugins.validators ValidatorPlugin Add FCC validation rules
6 Providers fcc.plugins.providers AIProviderPlugin Register AI provider client implementations
7 Governance fcc.plugins.governance GovernancePlugin Supply tags and quality gate YAML definitions
8 Scenarios fcc.plugins.scenarios ScenarioPlugin Contribute scenario definition JSON files
9 Workflows fcc.plugins.workflows WorkflowPlugin Contribute workflow graph JSON definitions
10 Subscribers fcc.plugins.subscribers EventSubscriberPlugin Wire event handlers to the EventBus

Creating a Plugin

1. Project layout

fcc-my-plugin/
  pyproject.toml
  src/
    fcc_my_plugin/
      __init__.py
      plugin.py          # ABC implementation
      data/
        personas/        # YAML files (for persona plugins)
        governance/      # tag/gate YAML (for governance plugins)

2. Implement the ABC

Every plugin must expose a plugin_meta() method returning a PluginMeta dataclass:

from fcc.plugins.base import PersonaPlugin, PluginMeta, PluginType
from pathlib import Path

class MyPersonaPlugin(PersonaPlugin):
    def plugin_meta(self) -> PluginMeta:
        return PluginMeta(
            id="fcc-my-personas",
            name="My Persona Pack",
            version="1.0.0",
            plugin_type=PluginType.PERSONAS,
            description="Extra personas for domain X",
            author="Your Name",
            source_package="fcc-my-plugin",
        )

    def get_persona_paths(self) -> list[Path]:
        return [Path(__file__).parent / "data" / "personas" / "custom.yaml"]

Persona plugins

class PersonaPlugin(ABC):
    def plugin_meta(self) -> PluginMeta: ...
    def get_persona_paths(self) -> list[Path]: ...
    def get_dimension_paths(self) -> list[Path]: ...   # optional
    def get_cross_reference_paths(self) -> list[Path]: ...  # optional

Engine plugins

class EnginePlugin(ABC):
    def plugin_meta(self) -> PluginMeta: ...
    def get_engine_class(self) -> type: ...

Template plugins

class TemplatePlugin(ABC):
    def plugin_meta(self) -> PluginMeta: ...
    def get_template_dirs(self) -> list[Path]: ...

Scorer plugins

class ScorerPlugin(ABC):
    def plugin_meta(self) -> PluginMeta: ...
    def get_scorers(self) -> dict[str, Any]: ...

Validator plugins

class ValidatorPlugin(ABC):
    def plugin_meta(self) -> PluginMeta: ...
    def get_validation_rules(self) -> dict[str, Any]: ...

AI Provider plugins

class AIProviderPlugin(ABC):
    def plugin_meta(self) -> PluginMeta: ...
    def get_provider_id(self) -> str: ...
    def get_client_class(self) -> type: ...
    def get_default_model(self) -> str: ...  # optional, defaults to "default"

Governance plugins

class GovernancePlugin(ABC):
    def plugin_meta(self) -> PluginMeta: ...
    def get_tag_paths(self) -> list[Path]: ...          # optional
    def get_quality_gate_paths(self) -> list[Path]: ... # optional

Scenario plugins

class ScenarioPlugin(ABC):
    def plugin_meta(self) -> PluginMeta: ...
    def get_scenario_paths(self) -> list[Path]: ...

Workflow plugins

class WorkflowPlugin(ABC):
    def plugin_meta(self) -> PluginMeta: ...
    def get_workflow_paths(self) -> list[Path]: ...

Event Subscriber plugins

Defined in fcc.messaging.plugin_bridge:

from fcc.messaging.plugin_bridge import EventSubscriberPlugin
from fcc.messaging.bus import EventFilter, Subscriber

class MySubscriberPlugin(EventSubscriberPlugin):
    def plugin_meta(self) -> PluginMeta: ...
    def get_subscribers(self) -> list[tuple[Subscriber, EventFilter | None]]: ...

3. Configure pyproject.toml entry points

Register the plugin class under the correct entry-point group. The key (left side of =) is an arbitrary name; the value (right side) is the dotted import path to the factory function or class.

[build-system]
requires = ["setuptools>=64.0"]
build-backend = "setuptools.build_meta"

[project]
name = "fcc-my-plugin"
version = "1.0.0"
dependencies = ["fcc-agent-team-ext>=0.5.0"]

[project.entry-points."fcc.plugins.personas"]
my-personas = "fcc_my_plugin.plugin:MyPersonaPlugin"

# For governance plugins:
# [project.entry-points."fcc.plugins.governance"]
# my-governance = "fcc_my_plugin.plugin:MyGovernancePlugin"

# For subscriber plugins:
# [project.entry-points."fcc.plugins.subscribers"]
# my-subscriber = "fcc_my_plugin.plugin:MySubscriberPlugin"

The table below shows all entry-point group strings:

Plugin Type Entry-Point Group
Personas fcc.plugins.personas
Engines fcc.plugins.engines
Templates fcc.plugins.templates
Scorers fcc.plugins.scorers
Validators fcc.plugins.validators
Providers fcc.plugins.providers
Governance fcc.plugins.governance
Scenarios fcc.plugins.scenarios
Workflows fcc.plugins.workflows
Subscribers fcc.plugins.subscribers

4. Plugin manifest

The PluginMeta dataclass is the plugin's manifest. It carries:

Field Type Required Description
id str Yes Unique plugin identifier
name str Yes Human-readable name
version str Yes Semver version string
plugin_type PluginType Yes One of the 10 enum values
description str No Short description
author str No Author name
source_package str No PyPI package name
tags tuple[str, ...] No Capability tags

Plugin Discovery

FCC uses importlib.metadata.entry_points() to discover plugins at runtime. The PluginRegistry class handles discovery:

from fcc.plugins.registry import PluginRegistry

registry = PluginRegistry()
registry.discover()

# Get all persona plugins
persona_plugins = registry.get_plugins(PluginType.PERSONAS)

Cross-Plugin Orchestration

The CrossPluginOrchestrator class manages dependencies and interactions between plugins:

from fcc.plugins.orchestration import CrossPluginOrchestrator

orchestrator = CrossPluginOrchestrator.from_yaml(
    dependencies_path="plugin_dependencies.yaml",
    interactions_path="plugin_interactions.yaml",
)

# Check ecosystem health
report = orchestrator.health_report(plugin_registry)

Plugin dependencies are declared in YAML files under src/fcc/data/ecosystem/:

  • plugin_dependencies.yaml -- declares which plugins depend on each other
  • plugin_interactions.yaml -- declares cross-plugin persona interactions

Testing Plugins

Unit tests

Test your plugin class directly:

from fcc_my_plugin.plugin import MyPersonaPlugin

def test_plugin_meta():
    plugin = MyPersonaPlugin()
    meta = plugin.plugin_meta()
    assert meta.id == "fcc-my-personas"
    assert meta.plugin_type == PluginType.PERSONAS

def test_persona_paths_exist():
    plugin = MyPersonaPlugin()
    for path in plugin.get_persona_paths():
        assert path.exists(), f"Missing persona file: {path}"

Integration tests

Verify your plugin is discovered when installed:

from fcc.plugins.registry import PluginRegistry

def test_plugin_discovered():
    registry = PluginRegistry()
    registry.discover()
    plugins = registry.get_plugins(PluginType.PERSONAS)
    assert any(p.plugin_meta().id == "fcc-my-personas" for p in plugins)

Validating contributed data

If your plugin contributes YAML/JSON data files, validate them against the corresponding JSON schemas:

import json
import yaml
from jsonschema import validate
from fcc._resources import get_schema_path

def test_contributed_personas_valid():
    schema_path = get_schema_path("persona")
    with schema_path.open() as f:
        schema = json.load(f)
    with open("src/fcc_my_plugin/data/personas/custom.yaml") as f:
        data = yaml.safe_load(f)
    validate(instance=data, schema=schema)

Error Handling

The plugin system defines five error types in fcc.plugins.errors:

Error When raised
PluginError Base class for all plugin errors
PluginLoadError Plugin entry point cannot be imported
PluginNotFoundError Requested plugin ID not in registry
PluginValidationError Plugin fails validation checks
PluginConflictError Two plugins have conflicting IDs or resources

Publishing to PyPI

  1. Ensure pyproject.toml declares the correct entry points.
  2. Build the package:
    python -m build
    
  3. Upload to PyPI:
    twine upload dist/*
    
  4. Users install with:
    pip install fcc-my-plugin
    
    The plugin is discovered automatically on the next PluginRegistry.discover() call.

Best Practices

  • Use the fcc- prefix for plugin package names (e.g., fcc-paom-plugin).
  • Include capability tags in PluginMeta for governance traceability.
  • Validate contributed YAML/JSON against FCC schemas in your test suite.
  • Pin your dependency on fcc-agent-team-ext to a minimum compatible version.
  • Document your plugin's personas, scorers, or templates in a README.
  • Use GovernancePlugin to register custom quality gates that apply to your contributed personas.