Skip to content

LYRA Knowledge Graph Integration

Duration: 90 minutes Level: Advanced Module: fcc.knowledge.lyra_bridge, fcc.api.knowledge Bridge: v1.5.0 stable promotion

This tutorial teaches you how to integrate with LYRA — the graph-of-thought retrieval service in the FCC ecosystem — through the stable fcc.api.knowledge surface. You will learn the three-method protocol (graph_of_thought_query, vocab_lookup, harmonic_expand), how the functional mock uses SKOS-proxy edges over a seed KnowledgeGraph, how to supply your own KG to the mock for testing, and how GraphRAG consumes LYRA for augmentation.

What LYRA is (and is not)

LYRA (Linked Yields for Reasoning + Argumentation) is the graph-of-thought navigation service in the FCC ecosystem. Where POLARIS preserves artifacts, LYRA navigates them — given a starting concept, LYRA returns reasoned paths through neighboring nodes with argumentation traces explaining why each hop was chosen.

The lyra.api package is not yet published; lyra_available() returns False in every current environment. The sentinel is wired in advance so the real adapter can slot in without any import-site churn. Until then, get_lyra_bridge() returns a functional LyraMockBridge backed by a minimal KnowledgeGraph seed. That means your code works today against the mock, and will seamlessly upgrade to live LYRA when it publishes.

LYRA is a read service. It does not mutate the knowledge graph; it traverses one. Mutations live in your ordinary KnowledgeGraph and FederatedKnowledgeGraph APIs.

The stable surface — fcc.api.knowledge

v1.5.0 promoted the LYRA bridge from fcc.plugins.v1_5_preview into the canonical fcc.api.knowledge surface. Public symbols carry the same 1.x SemVer backward-compatibility guarantee as the rest of fcc.api.*.

# Preferred — stable top-level API
from fcc.api import knowledge

# Equivalent — direct module import
from fcc.knowledge.lyra_bridge import (
    get_lyra_bridge,
    LyraBridgeProtocol,
    LyraMockBridge,
    NoOpLyraBridge,
    lyra_available,
)

The preview path fcc.plugins.v1_5_preview.lyra_bridge still works through v1.5.x with a DeprecationWarning emitted on import. The preview path is removed in v1.6.0.

The three-method protocol

@runtime_checkable
class LyraBridgeProtocol(Protocol):
    def graph_of_thought_query(self, query: str, depth: int = 3) -> dict: ...
    def vocab_lookup(self, term: str, namespace: str = "") -> list[str]: ...
    def harmonic_expand(self, concept: str) -> list[str]: ...

Three methods. The production adapter, the functional mock, and the no-op fallback all implement exactly this surface.

graph_of_thought_query — the BFS walker

graph_of_thought_query(query, depth) walks the backing graph BFS from every node matching query up to depth hops. It returns a dict with four keys:

{
    "query": str,
    "depth": int,
    "nodes": list[dict],   # visited node descriptors
    "edges": list[dict],   # traversed edges
    "paths": list[list],   # [source_id, target_id] pairs
}

This is the exact shape GraphRAG consumes in its LYRA-augmentation stage.

from fcc.api import knowledge

bridge = knowledge.get_lyra_bridge()

result = bridge.graph_of_thought_query("persona", depth=2)
print(f"visited {len(result['nodes'])} nodes")
print(f"traversed {len(result['edges'])} edges")
for edge in result["edges"][:5]:
    print(f"  {edge['source_id']} --{edge['edge_type']}--> {edge['target_id']}")

vocab_lookup — term resolution

vocab_lookup(term, namespace) returns node IDs whose id or label contains term (case-insensitive). The optional namespace argument filters to that namespace only.

from fcc.api import knowledge

bridge = knowledge.get_lyra_bridge()

# Search globally
ids = bridge.vocab_lookup("persona")
print(f"global matches: {ids}")

# Search within the seed namespace only
ids_seed = bridge.vocab_lookup("persona", namespace="lyra-seed")
print(f"seed namespace matches: {ids_seed}")

Use this to resolve user-supplied strings into canonical graph ids before handing them to a downstream consumer like GraphRAG or a KG query.

harmonic_expand — SKOS-proxy expansion

harmonic_expand(concept) returns related concept IDs linked to the input via SKOS-proxy edges. The mock implementation maps the SKOS hierarchy onto the FCC EdgeType enum:

SKOS role FCC EdgeType proxy
broader DEPENDS_ON
related MAPS_TO
narrower INTERACTS_WITH

These mappings live in the mock bridge as module constants — when the real LYRA publishes, it will use native SKOS.broader/narrower/related but the method signature is the same.

from fcc.api import knowledge

bridge = knowledge.get_lyra_bridge()

related = bridge.harmonic_expand("persona")
print(f"related concepts: {related}")
# ['riscear', 'discernment', 'fcc'] — per the seed graph

If no matching concept exists, the method returns [] (graceful degradation, never raises).

The seed graph

LyraMockBridge() with no arguments builds a minimal seed KnowledgeGraph with seven concepts and six edges:

persona --depends_on--> riscear       (broader: persona is a subtype of R.I.S.C.E.A.R.)
persona --maps_to--> discernment      (related)
discernment --interacts_with--> curiosity    (narrower)
discernment --interacts_with--> humility     (narrower)
persona --depends_on--> fcc
workflow --maps_to--> fcc

This seed is sufficient to exercise every method of the protocol. For realistic demos, pass your own KG:

from fcc.knowledge.lyra_bridge import LyraMockBridge
from fcc.knowledge.builders import build_full_fcc_graph

full_graph = build_full_fcc_graph()
bridge = LyraMockBridge(graph=full_graph)

result = bridge.graph_of_thought_query("research crafter", depth=2)
print(f"walked {len(result['nodes'])} nodes of the full FCC KG")

Mock vs real — the factory

get_lyra_bridge() returns the live adapter when lyra.api is importable and the functional LyraMockBridge otherwise.

from fcc.api.knowledge import get_lyra_bridge, lyra_available

print(f"LYRA installed? {lyra_available()}")  # False until lyra publishes
bridge = get_lyra_bridge()                     # works regardless
print(f"bridge: {type(bridge).__name__}")      # LyraMockBridge today

For tests that want explicit control, construct the mock directly with a specific graph:

from fcc.knowledge.lyra_bridge import LyraMockBridge
from fcc.knowledge.graph import KnowledgeGraph
from fcc.knowledge.models import EdgeType, KnowledgeEdge, KnowledgeNode, NodeType

g = KnowledgeGraph()
g.add_node(KnowledgeNode(
    node_id="a", node_type=NodeType.CONCEPT, label="Alpha", namespace="test",
))
g.add_node(KnowledgeNode(
    node_id="b", node_type=NodeType.CONCEPT, label="Beta", namespace="test",
))
g.add_edge(KnowledgeEdge(source_id="a", target_id="b", edge_type=EdgeType.DEPENDS_ON))

bridge = LyraMockBridge(graph=g)
assert bridge.vocab_lookup("alpha", namespace="test") == ["a"]
assert "b" in bridge.harmonic_expand("alpha")

The NoOpLyraBridge is the empty-behaviour fallback for verifying that your code tolerates no-op responses — use it only when you specifically need the non-functional stand-in.

GraphRAG augmentation — how it integrates

GraphRAG's fifth stage calls lyra_bridge.graph_of_thought_query(text, depth=max_hops) and folds the returned nodes, edges, and paths into its result. The LYRA call happens even when you do not explicitly pass a bridge — GraphRAG resolves the default bridge lazily on the first query.

from fcc.knowledge.builders import build_full_fcc_graph
from fcc.rag.graphrag import GraphRAG
from fcc.knowledge.lyra_bridge import LyraMockBridge

# Without explicit bridge — lazy default
pipe = GraphRAG(knowledge_graph=build_full_fcc_graph())

# With explicit bridge (e.g. custom-seeded for deterministic tests)
seeded = LyraMockBridge(graph=build_full_fcc_graph())
pipe_explicit = GraphRAG(
    knowledge_graph=build_full_fcc_graph(),
    lyra_bridge=seeded,
)

LYRA-sourced nodes enter the result at a baseline score of 0.25 — below every Zachman-filter survivor and persona-boosted node, but above the defensive "no info" floor. This preserves the invariant that KG-sourced answers outrank external augmentation when the KG has relevant information.

To disable LYRA augmentation entirely (useful for A/B comparison):

class NullBridge:
    def graph_of_thought_query(self, q, depth=3):
        return {}   # empty -> extra_nodes empty -> no augmentation
    def vocab_lookup(self, t, ns=""): return []
    def harmonic_expand(self, c): return []

pipe_no_lyra = GraphRAG(
    knowledge_graph=build_full_fcc_graph(),
    lyra_bridge=NullBridge(),
)

See the GraphRAG deep dive for the full GraphRAG pipeline and how augmentation composes with the other stages.

A realistic cross-namespace walk

A GraphRAG query typically surfaces persona and workflow nodes from the local FCC KG. LYRA adds cross-namespace concepts from linked vocabularies. The mock exercises the same path locally:

from fcc.knowledge.lyra_bridge import LyraMockBridge
from fcc.knowledge.builders import build_full_fcc_graph
from fcc.knowledge.graph import KnowledgeGraph
from fcc.knowledge.models import EdgeType, KnowledgeEdge, KnowledgeNode, NodeType

# 1. Start with the full FCC KG
fcc_graph = build_full_fcc_graph()

# 2. Add a few "external" concepts that LYRA might surface
external = KnowledgeGraph()
for node in fcc_graph.all_nodes():
    external.add_node(node)
for edge in fcc_graph.all_edges():
    external.add_edge(edge)

# External namespace concepts
external.add_node(KnowledgeNode(
    node_id="ext-governance",
    node_type=NodeType.CONCEPT,
    label="Enterprise Governance",
    namespace="ext-vocab",
))
external.add_node(KnowledgeNode(
    node_id="ext-audit",
    node_type=NodeType.CONCEPT,
    label="Third-Party Audit",
    namespace="ext-vocab",
))
external.add_edge(KnowledgeEdge(
    source_id="ext-governance",
    target_id="ext-audit",
    edge_type=EdgeType.MAPS_TO,
))

# 3. LYRA walks the combined graph
bridge = LyraMockBridge(graph=external)
result = bridge.graph_of_thought_query("governance", depth=2)
for node in result["nodes"]:
    print(f"  [{node['namespace']}] {node['node_id']}: {node['label']}")

When live LYRA lands, this same call routes to the real lyra.api.LyraAdapter and surfaces concepts from every federated namespace LYRA has indexed.

Combining with bidirectional federation

LYRA reads from a graph; BidirectionalSync writes to one. A typical pattern wires them together: federation pushes vocabulary changes across namespaces, and LYRA surfaces the newly-shared terms via vocab_lookup and harmonic_expand.

# Pseudocode — actual wiring depends on your FederationRegistry setup
from fcc.api import knowledge
from fcc.federation.bidirectional import BidirectionalSync

def on_term_added(change_event, lyra_bridge, sync):
    # Let bidirectional sync propagate the new term
    sync.tracker_for(change_event.namespace).track_change(change_event.model_change)
    sync.sync_to(target_namespace="partner",
                 source_namespace=change_event.namespace)

    # Immediately verify LYRA can see the term (via its vocab)
    hits = lyra_bridge.vocab_lookup(change_event.term)
    assert change_event.entity_id in hits

See the bidirectional federation patterns tutorial for the write path and the GraphRAG deep dive for the read path.

Graceful degradation

Every LYRA bridge method must be tolerant of:

  • Empty inputs. graph_of_thought_query("") returns a result with empty nodes, edges, paths. vocab_lookup("") returns [].
  • Unknown terms. vocab_lookup("does-not-exist") returns [].
  • No matching concept. harmonic_expand("does-not-exist") returns [].
  • Missing bridge. When LYRA is not installed, get_lyra_bridge() returns LyraMockBridge rather than raising.

GraphRAG depends on this graceful degradation — it wraps the LYRA call in a try/except and degrades to a KG-only result on any exception. Your own code should do the same.

Troubleshooting

harmonic_expand returns empty on a concept that exists. The concept node is probably not connected via a SKOS-proxy edge. Inspect the graph's edges for DEPENDS_ON, MAPS_TO, and INTERACTS_WITH from the concept. If the seed graph has no such edges, the method correctly returns [].

graph_of_thought_query returns zero nodes. Either the query matches no node labels (_nodes_matching is case-insensitive substring — verify the term is actually present), or the matched nodes are isolated and depth=0 returns only the seeds. Try depth=3 to walk further.

LYRA results appear in GraphRAG but not in the KG. That is correct. LYRA-augmented nodes come from the bridge's backing graph (which may differ from GraphRAG's primary KG). Use result.nodes to get the full merged set.

Tests break when lyra.api is installed. The factory switches to the live adapter. Pin tests to the mock by constructing it explicitly: LyraMockBridge(graph=my_test_graph) instead of calling get_lyra_bridge().

Warnings from NoOpLyraBridge. Someone instantiated NoOpLyraBridge explicitly (which emits UserWarning on every call). Switch to LyraMockBridge if you want a functional mock; keep NoOpLyraBridge only for the narrow case of verifying degraded-mode behavior.

Summary

In this tutorial you learned how to:

  • Use the stable fcc.api.knowledge surface for LYRA integration
  • Call the three protocol methods: graph_of_thought_query, vocab_lookup, harmonic_expand
  • Understand the SKOS-proxy edge mapping used by LyraMockBridge
  • Seed the mock with a custom KnowledgeGraph for realistic tests
  • Compose LYRA with GraphRAG for graph-of-thought augmentation
  • Combine LYRA with BidirectionalSync for read + write ecosystem patterns
  • Handle graceful degradation so downstream code never crashes on empty inputs

Next steps

  • Build a FederatedKnowledgeGraph that spans FCC + partner namespaces, then seed LyraMockBridge with it to preview what live LYRA will return
  • Write integration tests that assert lyra_available() is True in environments where you expect the live bridge — catches accidental mock-in-production
  • Watch for the lyra.api package publication — once it lands, no code changes are required on the FCC side
  • Codename decoder — docs/ecosystem/codename-decoder.md
  • LYRA bridge chapter stub — publications/chapters/integ/lyra-bridge.qmd
  • Source — src/fcc/knowledge/lyra_bridge.py, src/fcc/plugins/v1_5_preview/lyra_bridge.py
  • Class diagram — docs/architecture/class-diagrams/lyra-bridge.md
  • GraphRAG integration — src/fcc/rag/graphrag.py (stages 5–6)