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:
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:
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¶
- Creating Event Subscribers -- Build the 10th plugin type
- Cross-Plugin Orchestration -- Coordinate multiple plugins
- Managing a Plugin Ecosystem -- Registry and dependency management