FCC Vocabulary Provider Pattern¶
The VocabularyProviderPlugin pattern was added in v1.2.1 to give downstream projects a clean, non-runtime-coupled way to contribute vocabularies to the FCC object model. This page walks through the pattern, the authoring process, and the conformance tests.
The reference code lives under:
src/fcc/objectmodel/provider_registry.py— plugin protocol & registrysrc/fcc/objectmodel/vocabulary_loader.py— YAML → domain entity loadersrc/fcc/objectmodel/mapping.py—VocabularyMappingfrozen dataclasssrc/fcc/objectmodel/federation.py— cross-project federationsrc/fcc/data/objectmodel/*_vocabulary_mappings.yaml— 19+ shipped mapping files
Why a Plugin, Not a Direct Import¶
Before v1.2.1, a downstream project that wanted to share vocabulary with FCC had two bad choices:
- Import FCC at runtime — tight coupling, circular dependency risk, version fragility
- Fork FCC — maintenance burden, no path to upstream improvements
The VocabularyProviderPlugin pattern solves this by inverting the dependency:
- The downstream project publishes a YAML mapping + a Python plugin shim
- The FCC framework discovers the plugin at startup via
importlib.metadataentry points - Neither project imports the other at runtime
This is the same contract pattern used for Anthropic/OpenAI provider plugins, evaluator scorers, and event subscribers.
The PluginProviderProtocol¶
Every plugin provider must satisfy the runtime-checkable protocol at src/fcc/objectmodel/provider_registry.py:
@runtime_checkable
class PluginProviderProtocol(Protocol):
def provider_name(self) -> str: ...
def provider_type(self) -> str: ...
def is_available(self) -> bool: ...
def activate(self, config: dict[str, Any]) -> None: ...
def deactivate(self) -> None: ...
def health_check(self) -> dict[str, Any]: ...
For vocabularies, provider_type() returns "vocabulary_provider".
Authoring a VocabularyProviderPlugin¶
The simplest possible plugin:
# myproject/fcc_plugin.py
from fcc.objectmodel.provider_registry import PluginProviderProtocol
from fcc.objectmodel.vocabulary_loader import load_vocabulary_from_yaml
from pathlib import Path
class MyProjectVocabularyProvider:
def __init__(self) -> None:
self._loaded = False
self._mappings = None
def provider_name(self) -> str:
return "myproject"
def provider_type(self) -> str:
return "vocabulary_provider"
def is_available(self) -> bool:
return self._loaded
def activate(self, config: dict) -> None:
yaml_path = Path(config.get("mapping_path") or self._default_path())
self._mappings = load_vocabulary_from_yaml(yaml_path)
self._loaded = True
def deactivate(self) -> None:
self._mappings = None
self._loaded = False
def health_check(self) -> dict:
return {
"status": "ok" if self._loaded else "inactive",
"mapping_count": len(self._mappings or []),
}
def mappings(self):
return self._mappings
def _default_path(self) -> Path:
return Path(__file__).parent / "data" / "myproject_vocabulary_mappings.yaml"
Register via your package's pyproject.toml:
[project.entry-points."fcc.plugins"]
myproject_vocabulary = "myproject.fcc_plugin:MyProjectVocabularyProvider"
FCC's provider registry will discover the plugin at startup. No FCC code changes needed.
Authoring the YAML Mapping¶
A vocabulary mapping file contains concepts, synonyms, external identifiers, and relationships. The canonical shape:
vocabulary:
name: myproject
version: 1.0.0
namespace: https://example.org/myproject/ontology#
source: MyProject v2.3
license: CC-BY-4.0
concepts:
- id: myproject:Customer
label: Customer
definition: "A party who purchases goods or services."
synonyms: [Client, Buyer, Subscriber]
external_ids:
fibo: "fibo:Party"
schema_org: "schema:Person"
broader: myproject:Party
narrower: [myproject:RetailCustomer, myproject:EnterpriseCustomer]
examples: ["A household buying groceries"]
Fields external_ids are the bridge to federated projects. When a peer vocabulary declares fibo:Party as a concept, the EntityResolver can walk from myproject:Customer to the FIBO definition via the shared external ID.
The VocabularyMapping Dataclass¶
load_vocabulary_from_yaml() returns a list of VocabularyMapping frozen dataclasses:
@dataclass(frozen=True)
class VocabularyMapping:
id: str
label: str
definition: str
synonyms: tuple[str, ...]
external_ids: Mapping[str, str]
broader: str | None
narrower: tuple[str, ...]
examples: tuple[str, ...]
namespace: str
These are consumed by:
- The knowledge graph builder (
src/fcc/knowledge/builders.py) - The federated entity resolver (
src/fcc/federation/resolver.py) - The RAG grounding layer (
src/fcc/rag/pipeline.py) - The object model facade (
src/fcc/objectmodel/facade.py)
Validation¶
Run the strict validator before shipping:
The validator checks:
- YAML parses and matches the schema
- No duplicate concept IDs
- Every
broader/narrowerlink resolves - Every
external_idnamespace is known to FCC's federation registry - Every concept has a non-empty definition
Findings map to quality gate QG-OBM-VOC-001 (vocabulary completeness).
Testing Conformance¶
The project ships a conformance test harness that downstream plugin authors can reuse:
import pytest
from fcc.objectmodel.provider_registry import PluginProviderProtocol
from myproject.fcc_plugin import MyProjectVocabularyProvider
def test_protocol_conformance():
provider = MyProjectVocabularyProvider()
assert isinstance(provider, PluginProviderProtocol)
def test_activates_cleanly():
provider = MyProjectVocabularyProvider()
provider.activate(config={})
assert provider.is_available()
health = provider.health_check()
assert health["status"] == "ok"
assert health["mapping_count"] > 0
def test_round_trips_yaml(tmp_path):
provider = MyProjectVocabularyProvider()
provider.activate(config={})
mappings = provider.mappings()
assert all(m.id.startswith("myproject:") for m in mappings)
The v1.2.1 release verified this test battery against the athenium (8/8) and mnemosyne (8/8) providers. Use it as a template.
Reference Implementations¶
The best way to learn the pattern is to read working code. Three recommended reference plugins:
- Athenium — 8 concepts, financial-instrument vocabulary,
src/fcc/data/objectmodel/athenium_vocabulary_mappings.yaml - Mnemosyne — 8 concepts, memory/knowledge vocabulary,
src/fcc/data/objectmodel/mnemosyne_vocabulary_mappings.yaml - CTO — larger vocabulary mapping against the CTO object model,
src/fcc/data/objectmodel/cto_vocabulary_mappings.yaml
Open each YAML, then trace the load path through vocabulary_loader.py → provider_registry.py → federation.py.
Plugin Versioning¶
A vocabulary carries three version fields you must manage:
| Field | Location | Meaning |
|---|---|---|
| Package version | pyproject.toml |
Plugin code version |
| Vocabulary version | YAML vocabulary.version |
Content version |
| Mapping version | Per-concept version (optional) |
Per-concept revision |
Bump the vocabulary version on any concept addition, removal, or definitional change. Synonyms and examples can be added without a version bump.
Semantic-version breaking: concept removal, ID change, broader re-link that changes hierarchy depth.
Semantic-version additive: new concept, new synonym, new external_id.
Semantic-version patch: definition wording, example addition.
The Vocabulary Authority Hierarchy¶
When two vocabularies conflict on the same concept, FCC resolves by authority tier:
- Tier 1 — FCC-core canonical (e.g. personas, workflows)
- Tier 2 — constellation vertical (e.g. ophiuchus for healthcare)
- Tier 3 — downstream project plugins (e.g. athenium)
- Tier 4 — ad-hoc user extensions
Higher tiers override lower tiers. Within a tier, conflicts are reported as findings; the auditor (see For Auditors) adjudicates.
Related Reading¶
- Namespace Registration — registering your vocabulary under a federation namespace
- Ontology Integration — adapting an external ontology
- RAG Pipeline Guide — using vocabulary to ground retrieval
- Object model module docs — implementation reference
- Plugin system docs — broader plugin architecture
- For Developers: Plugin Development — Python plugin authoring in general