# How the LangGraph StateGraph Workflow Orchestrates Analyzer Nodes in SkillSpector

> Learn how the LangGraph StateGraph workflow orchestrates analyzer nodes in NVIDIA SkillSpector. Discover its fan-out pattern and dynamic loading for efficient analysis.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: internals
- Published: 2026-06-18

---

**The LangGraph StateGraph workflow orchestrates analyzer nodes by threading a shared `SkillspectorState` through a deterministic directed acyclic graph, dynamically loading static analyzers from a registry and wiring them in a fan-out pattern from a shared context builder to a centralized meta-analyzer.**

The NVIDIA **SkillSpector** repository implements a modular security scanning pipeline using LangGraph's `StateGraph` to coordinate multiple static analysis routines. This architecture ensures that every analyzer receives identical contextual information while their findings are automatically merged into a unified state. Understanding how the LangGraph StateGraph workflow orchestrates analyzer nodes reveals how the tool maintains consistency across parallel security checks and LLM-based enrichment.

## Core Architecture of the StateGraph

The workflow centers on a single `StateGraph` instance defined in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py). The `create_graph()` function (lines 33-55) initializes the graph using `SkillspectorState` as its shared state schema.

```python
from langgraph.graph import StateGraph, START, END
from skillspector.state import SkillspectorState

def create_graph():
    workflow = StateGraph(SkillspectorState)
    # Node registration and edge wiring follows...

    return workflow.compile()

```

This compiled graph acts as the execution engine, ensuring that each node receives the current state dictionary and returns updates that conform to the `SkillspectorState` TypedDict definition.

## The Four Infrastructure Node Types

The graph contains four core infrastructure nodes that execute in sequence, with analyzers injected dynamically between the context building and meta-analysis phases.

### Input Resolution and Context Building

The pipeline begins with `resolve_input` and `build_context` nodes. The `resolve_input` node (defined in [`src/skillspector/nodes/resolve_input.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/resolve_input.py)) normalizes user-provided paths and creates temporary directories when needed. Immediately following, the `build_context` node (in [`src/skillspector/nodes/build_context.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/build_context.py)) walks the skill directory, constructs a file cache, extracts component metadata, and parses the skill manifest.

These two nodes establish the foundational context that all subsequent analyzers will share.

### Dynamic Analyzer Registry

Between the context building and meta-analysis phases, the graph dynamically loads static analyzer nodes from the registry located at [`src/skillspector/nodes/analyzers/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/__init__.py). This registry exports two critical structures:

- **`ANALYZER_NODE_IDS`**: An ordered list of string identifiers (e.g., `"static_patterns_prompt_injection"`, `"semantic_security_discovery"`) defined in lines 71-92
- **`ANALYZER_NODES`**: A mapping from each identifier to its concrete node callable (e.g., `static_patterns_prompt_injection_node`) defined in lines 94-115

This separation allows the graph construction logic to remain agnostic of specific analyzer implementations while maintaining deterministic execution order.

### Meta-Analysis and Reporting

After all static analyzers complete, the `meta_analyzer` node (in [`src/skillspector/nodes/meta_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/meta_analyzer.py)) performs per-file LLM-based filtering and enrichment. Finally, the `report` node (in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py)) formats the output for the requested CLI format (terminal, JSON, or SARIF).

## Dynamic Analyzer Registration and Fan-Out Pattern

The orchestration relies on a **fan-out/fan-in** pattern implemented in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) (lines 45-51). Rather than hardcoding analyzer connections, the graph construction loops over `ANALYZER_NODE_IDS` to wire edges dynamically:

```python

# From src/skillspector/graph.py

workflow.add_edge(START, "resolve_input")
workflow.add_edge("resolve_input", "build_context")

# Fan-out: connect build_context to every analyzer

for analyzer_id in ANALYZER_NODE_IDS:
    workflow.add_node(analyzer_id, ANALYZER_NODES[analyzer_id])
    workflow.add_edge("build_context", analyzer_id)
    workflow.add_edge(analyzer_id, "meta_analyzer")  # Fan-in

workflow.add_edge("meta_analyzer", "report")
workflow.add_edge("report", END)

```

This pattern ensures that **every analyzer receives the same context** produced by `build_context` while executing independently. The `meta_analyzer` acts as a synchronization point, collecting findings from all analyzers before proceeding.

## State Propagation and Merging

Each node receives the current `SkillspectorState` and returns a typed dictionary (such as `AnalyzerNodeResponse` or `MetaAnalyzerResponse`). The state definition in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py) (lines 46-48) uses `Annotated` types with `operator.add` to specify additive merging:

```python
from typing import Annotated
import operator

class SkillspectorState(TypedDict):
    findings: Annotated[list[Finding], operator.add]
    # ... other fields

```

When analyzers return new findings, the graph automatically merges them into the cumulative list using the addition operator. This additive pattern prevents race conditions and ensures that concurrent analyzer outputs are preserved deterministically.

## CLI Invocation and Execution Flow

The CLI entry point in [`src/skillspector/cli.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/cli.py) (lines 35-37 and 85-95) builds the initial state from command-line arguments and invokes the compiled graph:

```python

# Simplified from src/skillspector/cli.py

state = {
    "input_path": "./my-skill/",
    "output_format": "json",
    "use_llm": True,
}
result_state = graph.invoke(state, config=trace_config)

```

The `invoke` method executes the directed acyclic workflow, returning a final state containing `report_body`, `risk_score`, and aggregated findings. The compiled graph handles the execution order, ensuring that infrastructure nodes run first, followed by parallel analyzer execution, then meta-analysis.

## Summary

- The **LangGraph StateGraph** in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) creates a deterministic DAG using `SkillspectorState` as the shared schema.
- **Dynamic registration** loads analyzer nodes from `ANALYZER_NODE_IDS` and `ANALYZER_NODES` in the analyzers registry.
- A **fan-out** pattern connects `build_context` to all analyzers simultaneously, while a **fan-in** pattern collects results into `meta_analyzer`.
- **Additive state merging** via `Annotated[list[Finding], operator.add]` ensures thread-safe aggregation of findings without data loss.
- The **CLI** invokes the compiled graph with initial state, receiving the final report after the complete orchestration finishes.

## Frequently Asked Questions

### What is the purpose of the fan-out pattern in the LangGraph workflow?

The fan-out pattern allows every static analyzer to receive identical contextual information from the `build_context` node while executing independently. This ensures consistent analysis across all security checks without requiring analyzers to re-implement context gathering logic.

### How does SkillspectorState handle concurrent analyzer updates?

The state uses `Annotated` types with `operator.add` (as defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py) lines 46-48) to specify that list fields like `findings` should be merged additively. When multiple analyzers return results simultaneously, LangGraph automatically combines them into the cumulative state using the addition operation.

### Can I add custom analyzers to the workflow?

Yes. Custom analyzers must be registered in [`src/skillspector/nodes/analyzers/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/__init__.py) by adding their identifier to `ANALYZER_NODE_IDS` and their node callable to `ANALYZER_NODES`. The `create_graph()` function automatically picks up new entries during the loop that wires edges from `build_context` to `meta_analyzer`.

### What happens after the meta_analyzer node completes?

After the `meta_analyzer` node finishes processing and enriching all static findings, the graph transitions to the `report` node. This node formats the final output according to the requested format (terminal, JSON, or SARIF), calculates risk scores, and terminates the workflow at the `END` node.