Skip to content

Federated vs Individual

The FCC framework supports two scopes for both its object model and its knowledge graph: a single-project local form and a multi-project federated form. They are deliberately different surfaces — not "the same thing scaled up" — and choosing the right one matters because the federated form pays for cross-project coordination with constraints the local form does not have. This page makes the comparison explicit so you can pick the right one without reading both subtree's source code.

The diagram below contrasts the two scopes at a glance.

graph LR
    subgraph LOCAL["Individual project scope"]
        L_OM["ObjectModel<br/>ModelFacade + Repository[T]"]
        L_KG["KnowledgeGraph<br/>9 node + 11 edge types"]
        L_NS[Single namespace fcc:]
    end
    subgraph FED["Federated scope"]
        F_OM["FederatedObjectModel<br/>EntityResolver + ChangeTracker"]
        F_KG["FederatedKnowledgeGraph<br/>local + remote graphs + cross-edges"]
        F_NS[N namespaces with VocabularyMappings]
    end

    L_OM -.assemble.-> F_OM
    L_KG -.merge_federated.-> F_KG
    L_NS -.NamespaceRegistry.-> F_NS

    L_OM --> L_NS
    L_KG --> L_NS
    F_OM --> F_NS
    F_KG --> F_NS

The choice is not a smooth slider — at some point you stop using KnowledgeGraph and start using FederatedKnowledgeGraph, and the collaborators around it change. Below is the side-by-side comparison.

Object model: local vs federated

Concern Individual (objectmodel/) Federated (objectmodel/federation.py + federation/)
Entry point ModelFacade (one per project) assess_cross_project(facades) + EntityResolver.resolve()
Storage RepositoryProtocol[T] per entity type N project facades, each with their own repos
Identity local_id unique within the project (local_id, namespace) pair, plus optional canonical_id
Vocabulary implicit — fields hardcoded in dataclasses explicit — VocabularyMapping(source_id, source_vocabulary, target_id, target_vocabulary, similarity_score)
Search ModelFacade.search(query) over local repos EntityResolver.resolve(local_id, source_ns, target_ns) per namespace pair
Change handling dataclasses are frozen → no in-place change ChangeTracker.record(ModelChange) audit trail across projects
Assessment EvolutionStage enum (DRAFT / FOUNDATIONAL / STRUCTURED / SEMANTIC / FEDERATED) CrossProjectAssessment(cross_project_score, federation_readiness, vocabulary_coverage)
Failure mode a missing entity returns None a missing mapping returns FederatedEntity(resolved=False) — caller decides whether to treat as soft or hard miss

Knowledge graph: local vs federated

Concern Individual (knowledge/graph.py) Federated (knowledge/federation.py)
Container KnowledgeGraph_nodes dict, _edges list, _adjacency map FederatedKnowledgeGraph_local_graph + _remote_graphs dict + _cross_edges list
Node identity node_id unique within graph (node_id, namespace) — uniqueness is per-namespace
Edge types 11 standard (INTERACTS_WITH, DEPENDS_ON, ORCHESTRATES, …) 11 standard plus FEDERATION_LINK, CROSS_NAMESPACE, EQUIVALENT_TO for cross-namespace bridges
Merge KnowledgeGraph.merge(other) returns combined graph FederatedKnowledgeGraph.merge_federated() returns flattened KnowledgeGraph with namespace-prefixed ids
Resolve entity get_node(node_id) resolve_entity(local_id, source_namespace, target_namespace) — returns the equivalent node in the target graph if a EQUIVALENT_TO edge exists
Subgraph subgraph(node_ids) returns view requires merge first; subgraph operates on the merged graph
Serializers OWL, RDF, SKOS, JSON-LD via knowledge/serializers.py same serializers, applied to merged form; namespace prefixes are preserved

When to use which

Use the individual form when:

  • You are a single project with one canonical vocabulary.
  • All entities live in src/<project>/data/ and have stable local ids.
  • You want fast in-process queries with no I/O between subsystems.
  • You do not need to track changes that happen outside the project.

Use the federated form when:

  • You are coordinating ≥ 2 projects (e.g. FCC + PAOM, or FCC + AOME + CONSTEL + Sky-Parlour).
  • The same conceptual entity is identified differently in each project (e.g. "Persona" in PAOM vs "Agent" in CTO).
  • You want a compliance audit trail that survives across project releases.
  • You need cross-project entity search (e.g. "find every governance persona regardless of which project owns it").

Migration path

Going from individual to federated is additive — the local form does not have to change. The steps are:

  1. Define the namespaceNamespaceConfig(namespace="myproject", prefix="mp", base_uri="https://...", version="1.0", description="...") — and register it with NamespaceRegistry.register(config).
  2. Author vocabulary mappings — for each entity that exists in your project AND in another, create a VocabularyMapping row in src/fcc/data/objectmodel/<namespace>_vocabulary_mappings.yaml. The 175 packaged mappings under src/fcc/data/objectmodel/ are the reference templates.
  3. Ship a VocabularyProviderPlugin — see Plugin boundaries. The 12 existing plugins under plugins/fcc-{codename}-plugin/ are reference implementations.
  4. Wrap your ModelFacade with a federation client — call assess_cross_project([your_facade, other_facade, ...]) whenever you want a coordination snapshot.
  5. (Optional) wrap your KnowledgeGraph — call FederatedKnowledgeGraph.register_local(your_graph) and register_remote(namespace, other_graph) for each peer project, then merge_federated() produces the unified view.

The FCCEvolutionStage enum names this trajectory: FOUNDATIONAL (no mappings) → STRUCTURED (typed dataclasses) → SEMANTIC (vocabulary mappings exist) → FEDERATED (active multi-project resolution). A project can sit at STRUCTURED indefinitely; not every project needs to climb to FEDERATED.

Cost of federation

Federation is not free. The added concerns are:

  • Mapping maintenance — every new entity in any peer project needs a mapping row, or it shows up as FederatedEntity(resolved=False) and pollutes assessment scores.
  • Namespace pollution — node ids in the merged graph carry namespace prefixes (paom:persona-001 vs fcc:persona-001); existing query code may need to learn to ignore the prefix.
  • Change-tracking volumeChangeTracker.record(ModelChange) writes one row per cross-project diff. For active projects this is thousands of records per release.
  • Vocabulary drift — when a peer project releases a new schema, your mappings need to be re-validated. The 175 packaged mappings include similarity scores so drift is detectable, but somebody has to look.

The feedback_vocabulary_contract_pattern.md memory captures this: sister projects co-evolve via the VocabularyProviderPlugin pattern so the FCC side stays declarative (YAML mappings) and the sister side stays decoupled (no FCC dependency).

See also