Skip to content

GraphRAG + Zachman Filter

This diagram traces a single :class:GraphRAG query from free-form input text through to a fully-populated :class:GraphRAGResult. The entry point is GraphRAG.query(text, k, persona_id, zachman_cell, max_hops) at src/fcc/rag/graphrag.py:166, which sequences seed retrieval, BFS graph expansion, Zachman-cell filtering, persona-aware scoring, LYRA augmentation, and context assembly into one deterministic pipeline. GraphRAG is v1.5.0 Pillar B; this is the request-path developers reach for when they need "find every relevant node, but keep only the ones that matter to THIS persona in THIS Zachman cell." ADR-011 captures the decision to couple the Zachman classifier with the graph walk.

The sequence below shows one query end to end, including the graceful fallback to the in-process :class:LyraMockBridge when the live LYRA adapter is unavailable.

sequenceDiagram
    participant Caller
    participant GraphRAG
    participant SemanticRetriever
    participant KnowledgeGraph
    participant PersonaRegistry
    participant LyraBridge

    Caller->>GraphRAG: query(text, k, persona_id, zachman_cell, max_hops)
    alt retriever configured
        GraphRAG->>SemanticRetriever: retrieve(text, k)
        SemanticRetriever-->>GraphRAG: top_k RetrievalResult
    else no retriever
        GraphRAG->>KnowledgeGraph: match labels(text)
        KnowledgeGraph-->>GraphRAG: seed_ids
    end
    GraphRAG->>KnowledgeGraph: all_edges, all_nodes
    KnowledgeGraph-->>GraphRAG: edges, nodes
    Note over GraphRAG: BFS expand from seeds up to max_hops
    opt zachman_cell is not None
        GraphRAG->>GraphRAG: _apply_zachman_filter(visited, cell)
        Note over GraphRAG: drop nodes whose<br/>zachman_cell does<br/>not match coord<br/>or perspective
    end
    GraphRAG->>PersonaRegistry: resolve persona_id (optional)
    PersonaRegistry-->>GraphRAG: match anchors for scoring
    Note over GraphRAG: _score_nodes: +0.5 label hit,<br/>+0.4 persona match, +0.1 baseline
    GraphRAG->>LyraBridge: graph_of_thought_query(text, depth=max_hops)
    alt lyra.api importable
        LyraBridge-->>GraphRAG: {nodes, edges, paths}
    else fallback
        Note over LyraBridge: LyraMockBridge walks<br/>seed KG when<br/>lyra.api absent
        LyraBridge-->>GraphRAG: {nodes, edges, paths} (mock)
    end
    opt zachman_cell not None
        GraphRAG->>GraphRAG: filter LYRA extras by Zachman
    end
    GraphRAG->>GraphRAG: assemble_context(result, max_tokens=2000)
    GraphRAG-->>Caller: GraphRAGResult(nodes, edges, paths, scored, context_text)

Three failure modes are worth noting. An empty knowledge graph short-circuits the query and returns an empty GraphRAGResult without any further work — GraphRAG.query checks node_count == 0 up front. A missing zachman_cell skips both the pre-LYRA and post-LYRA filter steps without any exception. A LYRA bridge that raises is caught inside _apply_lyra_augment and treated as an empty augmentation — the query continues with KG-only results. This is the "graceful degradation" the module docstring guarantees.

Persona awareness is a scoring signal, not a filter. Even when persona_id is supplied, nodes that do not mention the persona still appear in the result — they just score lower. Combined with the Zachman filter (which IS a hard gate), this lets callers say "anything in this cell, ranked by this persona" instead of "only this-persona nodes."

Steps in detail

  1. Caller to GraphRAG: query — Free-form text plus optional filters arrive at the single entry point.
  2. Seed retrieval — Either via the configured SemanticRetriever (mapping chunk IDs back to KG node IDs) or via a case-insensitive substring match over node labels when no retriever is configured.
  3. BFS expansion_bfs_expand(seed_ids, max_hops) walks up to max_hops layers out from each seed, recording traversed edges and frontier-shortest paths.
  4. Zachman filter (pre-LYRA)_apply_zachman_filter drops visited nodes whose properties["zachman_cell"] does not match the target cell's coordinate or perspective.
  5. Persona-aware scoring_score_nodes assigns each surviving node a deterministic score in [0, 1]: +0.5 for a query-text hit, +0.4 for persona alignment, +0.1 baseline, clamped at 1.0.
  6. LYRA augmentation_apply_lyra_augment calls LyraBridge.graph_of_thought_query(text, depth=max_hops) and folds distinct nodes / edges / paths back in. The bridge resolves lazily via get_lyra_bridge() and falls back to LyraMockBridge when lyra.api is not importable.
  7. Zachman filter (post-LYRA) — Extras go through the same cell match so the filter contract holds end to end.
  8. Context assemblyassemble_context(result, max_tokens=2000) rolls the top-scored nodes into a bullet list truncated at a 4-chars-per-token budget.
  9. Return — A frozen :class:GraphRAGResult with nodes, edges, paths, scored, and context_text.

See also

  • Entry point: src/fcc/rag/graphrag.py:166 (GraphRAG.query)
  • LYRA bridge: src/fcc/knowledge/lyra_bridge.py, src/fcc/plugins/v1_5_preview/lyra_bridge.py
  • Zachman classifier: src/fcc/classification/zachman.py (ZachmanCell, Perspective)
  • Related class diagram: ../class-diagrams/lyra-bridge.md, ../class-diagrams/six-pillars-overview.md
  • ADR: docs/decisions/ADR-011_*.md (GraphRAG+Zachman), ADR-012 (CRDT) for neighbouring context
  • Pillar overview: ../class-diagrams/six-pillars-overview.md