Skip to content

Building Your First FCC Plugin

The FCC plugin system uses Python entry points to discover and load extensions at runtime. This tutorial shows how to create a persona plugin package from scratch, register it via pyproject.toml, and verify it loads into the PluginRegistry.

Plugin Types

FCC supports 10 plugin types, each with its own entry-point group and abstract base class:

Plugin Type Entry-Point Group ABC Purpose
Personas fcc.plugins.personas PersonaPlugin Contribute persona YAML definitions
Engines fcc.plugins.engines EnginePlugin Custom simulation engines
Templates fcc.plugins.templates TemplatePlugin Jinja2 template directories
Scorers fcc.plugins.scorers ScorerPlugin Quality scoring functions
Validators fcc.plugins.validators ValidatorPlugin Validation rules
Providers fcc.plugins.providers AIProviderPlugin AI provider client implementations
Governance fcc.plugins.governance GovernancePlugin Tags and quality gates
Scenarios fcc.plugins.scenarios ScenarioPlugin Scenario definitions
Workflows fcc.plugins.workflows WorkflowPlugin Workflow graph definitions
Subscribers fcc.plugins.subscribers EventSubscriberPlugin Event bus subscribers

Project Structure

Create a new Python package for your plugin. Here is the recommended layout for a persona plugin:

fcc-plugin-devops/
├── pyproject.toml
├── src/
│   └── fcc_plugin_devops/
│       ├── __init__.py
│       ├── plugin.py
│       └── data/
│           └── personas/
│               └── devops_personas.yaml

Step 1: Define the Persona YAML

Create your persona definitions in src/fcc_plugin_devops/data/personas/devops_personas.yaml:

personas:
  - id: "CICD"
    name: "CI/CD Pipeline Engineer"
    fcc_phase: "Create"
    role_title: "Continuous Integration Specialist"
    category: "devops"
    color: "#FF6F00"
    riscear:
      role: "Designs and maintains CI/CD pipeline documentation"
      inputs:
        - "Pipeline configuration files"
        - "Deployment runbooks"
        - "Infrastructure-as-code templates"
      style: "Precise, procedural, with clear step-by-step instructions"
      constraints:
        - "All pipeline stages must have rollback procedures"
        - "Secrets must never appear in documentation"
      expected_output:
        - "Pipeline architecture diagrams"
        - "Stage-by-stage deployment guides"
        - "Troubleshooting decision trees"
      archetype: "The Reliable Builder"
      responsibilities:
        - "Document pipeline stages and their dependencies"
        - "Maintain deployment procedure accuracy"
        - "Review pipeline changes for documentation impact"
      role_skills:
        - "Infrastructure-as-code fluency"
        - "Container orchestration knowledge"
      role_collaborators:
        - "RB (Runbook Crafter)"
        - "BC (Blueprint Crafter)"
      role_adoption_checklist:
        - "Pipeline architecture documented"
        - "Rollback procedures verified"
        - "Secret scanning configured"

Step 2: Implement the Plugin Class

Create src/fcc_plugin_devops/plugin.py:

from pathlib import Path

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


class DevOpsPersonaPlugin(PersonaPlugin):
    """Plugin that contributes DevOps-focused personas to FCC."""

    def plugin_meta(self) -> PluginMeta:
        return PluginMeta(
            id="fcc-plugin-devops",
            name="FCC DevOps Personas",
            version="0.1.0",
            plugin_type=PluginType.PERSONAS,
            description="CI/CD, SRE, and platform engineering personas",
            author="Your Name",
            source_package="fcc-plugin-devops",
            tags=("devops", "cicd", "sre"),
        )

    def get_persona_paths(self) -> list[Path]:
        """Return paths to YAML files containing persona definitions."""
        data_dir = Path(__file__).parent / "data" / "personas"
        return list(data_dir.glob("*.yaml"))

Plugin Meta Requirements

The id, name, and version fields are required. The plugin_type must match the entry-point group where the plugin is registered. The PluginRegistry validates these fields during discovery.

Step 3: Configure Entry Points

In your pyproject.toml, register the plugin under the appropriate entry-point group:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "fcc-plugin-devops"
version = "0.1.0"
description = "DevOps personas for the FCC Agent Team Framework"
requires-python = ">=3.10"
dependencies = [
    "fcc-agent-team-ext>=0.4.0",
]

[project.entry-points."fcc.plugins.personas"]
devops = "fcc_plugin_devops.plugin:DevOpsPersonaPlugin"

The entry-point format is:

[project.entry-points."<group>"]
<name> = "<module>:<class>"

Where <group> is one of the fcc.plugins.* groups from the table above.

Step 4: Install and Discover

Install your plugin in editable mode alongside FCC:

pip install -e .

Then discover it from the FCC plugin registry:

from fcc.plugins.registry import PluginRegistry
from fcc.plugins.base import PluginType

registry = PluginRegistry()
result = registry.discover()

print(f"Discovered: {result.discovered}")
print(f"Loaded: {result.loaded}")
print(f"Errors: {result.errors}")

# Access your plugin
plugin = registry.get_plugin("fcc-plugin-devops")
meta = plugin.plugin_meta()
print(f"Plugin: {meta.name} v{meta.version}")

# Get persona YAML paths
paths = plugin.get_persona_paths()
for path in paths:
    print(f"  Persona file: {path.name}")

Step 5: Validate the Plugin

The registry can validate that your plugin correctly implements its expected ABC:

errors = registry.validate("fcc-plugin-devops")
if errors:
    for err in errors:
        print(f"  Validation error: {err}")
else:
    print("Plugin is valid")

Validate all discovered plugins at once:

all_errors = registry.validate_all()
for plugin_id, plugin_errors in all_errors.items():
    if plugin_errors:
        print(f"{plugin_id}: {plugin_errors}")

Optional Plugin Methods

Persona plugins can optionally provide dimension profiles and cross-reference data:

class DevOpsPersonaPlugin(PersonaPlugin):
    # ... plugin_meta and get_persona_paths as above ...

    def get_dimension_paths(self) -> list[Path]:
        """Optional: Return paths to dimension profile YAML files."""
        dims_dir = Path(__file__).parent / "data" / "dimensions"
        if dims_dir.exists():
            return list(dims_dir.glob("*.yaml"))
        return []

    def get_cross_reference_paths(self) -> list[Path]:
        """Optional: Return paths to cross-reference YAML files."""
        xref_dir = Path(__file__).parent / "data"
        xref_file = xref_dir / "cross_reference.yaml"
        if xref_file.exists():
            return [xref_file]
        return []

Error Handling

The plugin system defines a hierarchy of exceptions:

Exception When Raised
PluginNotFoundError Requested plugin ID does not exist
PluginLoadError Plugin entry point failed to import or instantiate
PluginConflictError Two plugins declare the same ID
PluginValidationError Plugin fails ABC validation
from fcc.plugins.errors import PluginNotFoundError

try:
    registry.get_plugin("nonexistent-plugin")
except PluginNotFoundError as e:
    print(f"Not found: {e.plugin_id}")

Next Steps