Skip to content

Knowledge Graphs

Duration: 90 minutes Level: Advanced Module: fcc.knowledge

This tutorial teaches you how to build, query, traverse, and export knowledge graphs that represent the FCC persona ecosystem. You will work with the KnowledgeGraph class, its 9 node types and 9 edge types, the 5 builder functions, 4 serialization formats, the FCCOntology schema, and federated knowledge graphs spanning multiple namespaces.

Prerequisites

  • Completed beginner/intermediate tutorials
  • Familiarity with PersonaRegistry, CrossReferenceMatrix, WorkflowActionRegistry
  • Understanding of graph concepts (nodes, edges, adjacency)

Core Concepts

The FCC knowledge graph represents the complete persona ecosystem as a typed, directed graph. Every persona, workflow action, category, constitution, and dimension becomes a node, connected by semantically meaningful edges.

Node Types (9)

NodeType Description
PERSONA An FCC persona (e.g., RC, BC, DE)
WORKFLOW A workflow graph definition
ACTION A workflow action (scaffold, refactor, debug, test, compare, document)
CONCEPT An abstract domain concept
DELIVERABLE A produced output artifact
CATEGORY A persona category (e.g., core, integration, governance)
DIMENSION A persona dimension attribute
CONSTITUTION A governance constitution
ECOSYSTEM_PROJECT An ecosystem project reference

Edge Types (9)

EdgeType Description
INTERACTS_WITH Peer interaction between personas
DEPENDS_ON Dependency relationship
ORCHESTRATES Champion orchestrating team members
CHAMPIONS Champion-of relationship
PRODUCES Persona producing an action
BELONGS_TO Persona belonging to a category
MAPS_TO Vocabulary mapping
GOVERNS Constitution governing a persona
FEDERATION_LINK Cross-namespace federation edge

Building a Knowledge Graph

Manual Construction

Create nodes and edges directly using the KnowledgeGraph class:

from fcc.knowledge.graph import KnowledgeGraph
from fcc.knowledge.models import (
    KnowledgeNode, KnowledgeEdge, NodeType, EdgeType
)

graph = KnowledgeGraph()

# Add persona nodes
rc_node = KnowledgeNode(
    node_id="RC",
    node_type=NodeType.PERSONA,
    label="Research Crafter",
    properties={"category": "core", "fcc_phase": "find"},
)
bc_node = KnowledgeNode(
    node_id="BC",
    node_type=NodeType.PERSONA,
    label="Blueprint Crafter",
    properties={"category": "core", "fcc_phase": "create"},
)
graph.add_node(rc_node)
graph.add_node(bc_node)

# Add a category node
core_cat = KnowledgeNode(
    node_id="cat_core",
    node_type=NodeType.CATEGORY,
    label="core",
)
graph.add_node(core_cat)

# Add edges
graph.add_edge(KnowledgeEdge(
    source_id="RC", target_id="cat_core",
    edge_type=EdgeType.BELONGS_TO,
))
graph.add_edge(KnowledgeEdge(
    source_id="RC", target_id="BC",
    edge_type=EdgeType.INTERACTS_WITH,
    properties={"interaction": "handoff", "strength": 0.9},
))

print(f"Nodes: {graph.node_count}, Edges: {graph.edge_count}")

Using Builder Functions

The fcc.knowledge.builders module provides 5 builder functions that construct graphs from FCC registries:

from fcc.personas.registry import PersonaRegistry
from fcc.personas.cross_reference import CrossReferenceMatrix
from fcc.workflow.actions import WorkflowActionRegistry
from fcc.governance.constitution_registry import ConstitutionRegistry
from fcc.knowledge.builders import (
    build_persona_graph,
    build_crossref_graph,
    build_workflow_graph,
    build_constitution_graph,
    build_full_fcc_graph,
)

# Load registries
registry = PersonaRegistry.from_yaml_directory("src/fcc/data/personas")

# Build individual sub-graphs
persona_graph = build_persona_graph(registry)
print(f"Persona graph: {persona_graph.node_count} nodes, "
      f"{persona_graph.edge_count} edges")

# Build the complete composite graph from all registries
full_graph = build_full_fcc_graph(
    persona_registry=registry,
    # crossref_matrix=matrix,      # optional
    # action_registry=actions,      # optional
    # constitution_registry=consts, # optional
)
print(f"Full graph: {full_graph.node_count} nodes, "
      f"{full_graph.edge_count} edges")

The build_persona_graph function creates: - A PERSONA node for each persona - A CATEGORY node for each unique category - BELONGS_TO edges from persona to category - ORCHESTRATES edges where the persona orchestrates team members - CHAMPIONS edges where the persona champions another

Querying and Traversing

Node and Edge Queries

# Get all persona nodes
personas = full_graph.nodes_by_type(NodeType.PERSONA)
print(f"Persona nodes: {len(personas)}")

# Get all category nodes
categories = full_graph.nodes_by_type(NodeType.CATEGORY)
print(f"Categories: {[c.label for c in categories]}")

# Get all ORCHESTRATES edges
orchestrations = full_graph.edges_by_type(EdgeType.ORCHESTRATES)
print(f"Orchestration edges: {len(orchestrations)}")

# Look up a specific node
rc = full_graph.get_node("RC")
if rc:
    print(f"Found: {rc.label} ({rc.node_type.value})")

Neighbor Traversal

# Find all neighbors of a persona
neighbors = full_graph.neighbors("RC")
for neighbor in neighbors:
    print(f"  {neighbor.node_id}: {neighbor.label} ({neighbor.node_type.value})")

Subgraph Extraction

Extract a focused subgraph containing only specific nodes:

# Extract just the core personas and their relationships
core_ids = {p.node_id for p in personas if p.properties.get("category") == "core"}
core_ids.add("cat_core")  # Include the category node

core_subgraph = full_graph.subgraph(core_ids)
print(f"Core subgraph: {core_subgraph.node_count} nodes, "
      f"{core_subgraph.edge_count} edges")

Graph Merging

Combine two graphs, with nodes from the second overwriting duplicates:

graph_a = build_persona_graph(registry)
# graph_b = build_crossref_graph(matrix)
# merged = graph_a.merge(graph_b)
# print(f"Merged: {merged.node_count} nodes")

Graph Statistics

stats = full_graph.stats()
for key, value in sorted(stats.items()):
    print(f"  {key}: {value}")
# node_count: 122
# edge_count: 315
# nodes_persona: 102
# nodes_category: 20
# edges_belongs_to: 102
# edges_orchestrates: 15
# ...

Serialization and Export

Turtle (OWL/RDF)

from fcc.knowledge.serializers import serialize_turtle

turtle_str = serialize_turtle(full_graph)
print(turtle_str[:500])

# Save to file
with open("fcc_knowledge.ttl", "w") as f:
    f.write(turtle_str)

JSON-LD

from fcc.knowledge.serializers import serialize_jsonld

jsonld_str = serialize_jsonld(full_graph)
print(jsonld_str[:500])

# Save to file
with open("fcc_knowledge.jsonld", "w") as f:
    f.write(jsonld_str)

N-Triples

from fcc.knowledge.serializers import serialize_ntriples

ntriples_str = serialize_ntriples(full_graph)
print(f"Triples: {ntriples_str.count(chr(10))}")

SKOS (Concept Scheme)

The SKOS serializer maps categories to skos:Concept instances with skos:broader/skos:narrower hierarchies:

from fcc.knowledge.serializers import serialize_skos

skos_str = serialize_skos(full_graph)
print(skos_str[:500])

Using an Ontology for Prefix Control

Pass an FCCOntology to serializers for custom namespace prefixes and base URIs:

from fcc.knowledge.schema import FCCOntology, default_ontology

ontology = default_ontology()
print(f"Namespace: {ontology.namespace}")
print(f"Version: {ontology.version}")
print(f"Base URI: {ontology.base_uri}")
print(f"Node types defined: {len(ontology.node_type_definitions)}")
print(f"Edge types defined: {len(ontology.edge_type_definitions)}")

# Serialize with ontology-controlled prefixes
turtle_with_ontology = serialize_turtle(full_graph, ontology=ontology)

FCCOntology Schema

The FCCOntology is a frozen dataclass defining the knowledge graph schema. It specifies valid node types, edge types, and namespace prefix mappings:

from fcc.knowledge.schema import FCCOntology

# Create a custom ontology
custom_ontology = FCCOntology(
    namespace="myproject",
    version="1.0.0",
    base_uri="https://myproject.example.org/ontology/",
    node_type_definitions={
        "persona": {"description": "An agent persona"},
        "category": {"description": "A persona category"},
    },
    edge_type_definitions={
        "interacts_with": {"description": "Peer interaction"},
        "belongs_to": {"description": "Category membership"},
    },
    prefix_map={
        "myproject": "https://myproject.example.org/ontology/",
        "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    },
)

Federated Knowledge Graphs

The FederatedKnowledgeGraph class enables cross-project knowledge graph operations by maintaining a local FCC graph alongside registered remote project graphs:

from fcc.knowledge.federation import FederatedKnowledgeGraph
from fcc.knowledge.models import EdgeType

# Create a federated graph
fkg = FederatedKnowledgeGraph(local_namespace="fcc")

# Register the local FCC graph
fkg.register_local(full_graph)

# Create and register a remote project graph
remote_graph = KnowledgeGraph()
remote_graph.add_node(KnowledgeNode(
    node_id="ext_persona_1",
    node_type=NodeType.PERSONA,
    label="External Analyst",
    namespace="partner_project",
))
fkg.register_remote("partner_project", remote_graph)

# Add cross-namespace edges
fkg.add_cross_edge(
    source_namespace="fcc",
    source_id="RC",
    target_namespace="partner_project",
    target_id="ext_persona_1",
    edge_type=EdgeType.FEDERATION_LINK,
)

# Check registered namespaces
print(f"Namespaces: {fkg.namespaces()}")
# ["fcc", "partner_project"]

# Merge everything into a single graph
merged = fkg.merge_federated()
print(f"Merged: {merged.node_count} nodes, {merged.edge_count} edges")

# Resolve entities across namespaces
resolved = fkg.resolve_entity("RC", "fcc", "partner_project")
if resolved:
    print(f"Resolved: {resolved.label}")

# Get per-namespace statistics
stats = fkg.stats()
print(f"FCC nodes: {stats.get('fcc_nodes', 0)}")
print(f"Partner nodes: {stats.get('partner_project_nodes', 0)}")
print(f"Cross edges: {stats['cross_edges']}")

Serialization and Persistence

Graphs can be serialized to and from plain dictionaries for JSON persistence:

import json

# Serialize to dict
data = full_graph.to_dict()
with open("graph.json", "w") as f:
    json.dump(data, f, indent=2)

# Deserialize from dict
with open("graph.json") as f:
    loaded_data = json.load(f)
restored_graph = KnowledgeGraph.from_dict(loaded_data)
print(f"Restored: {restored_graph.node_count} nodes")

Summary

In this tutorial you learned how to:

  • Create knowledge graphs with 9 node types and 9 edge types
  • Use 5 builder functions to construct graphs from FCC registries
  • Query nodes and edges by type, traverse neighbors, extract subgraphs
  • Export to OWL/RDF (Turtle), JSON-LD, N-Triples, and SKOS formats
  • Define and use the FCCOntology schema for namespace control
  • Build federated knowledge graphs spanning multiple project namespaces

Next Steps

  • RAG Pipeline -- Build retrieval-augmented generation pipelines
  • Semantic Search -- Search personas with natural language
  • Federation -- Cross-project entity resolution and change tracking