How to Perform Causal Chain Analysis in Semantica's ContextGraph

Semantica's ContextGraph enables causal chain analysis by storing decisions as typed nodes and causal relationships as canonical edges—such as "CAUSED" or "INFLUENCED"—which you can traverse upstream or downstream using get_causal_chain() or the CausalChainAnalyzer class.

Causal chain analysis traces how decisions influence one another through an in-memory graph structure. In the semantica-agi/semantica repository, the ContextGraph implementation provides native methods to record decision nodes, establish causal links between them, and retrieve complete ancestor or descendant chains for audit and explanation purposes.

Understanding the ContextGraph Data Model

Decision Nodes

The foundation of causal analysis rests on properly typed nodes. Decisions are stored with the node type "decision" and contain structured metadata including content, category, scenario, confidence, and timestamp.

In semantica/context/context_graph.py, create these nodes using graph.add_node() or the higher-level graph.record_decision() helper method. Each decision receives a unique identifier that serves as the anchor for causal relationships.

Causal Edge Types

Relationships between decisions are represented as edges drawn from a canonical set. The recognized causal edge types include "CAUSED", "INFLUENCED", and "PRECEDENT_FOR", along with present-tense aliases such as "causes" and "influences".

These edge types are normalized internally using the _CAUSAL_EDGE_ALIASES mapping (defined at lines 5330-5345 in context_graph.py). This guarantees that edges added with any alias remain visible during traversal operations.

Building the Causal Graph

Adding Decision Nodes

Before establishing causal links, populate the graph with decision nodes. Each node requires a unique identifier and metadata capturing the decision context.

from semantica.context.context_graph import ContextGraph

g = ContextGraph()
g.add_node("dec_001", "decision", content="Approve loan",
           category="loan", scenario="first-time buyer",
           confidence=0.94, timestamp="2024-01-10T09:00:00")
g.add_node("dec_002", "decision", content="Increase rate",
           category="loan", scenario="risk review",
           confidence=0.88, timestamp="2024-01-12T14:30:00")

Establishing Causal Relationships

Connect nodes using graph.add_causal_relationship(source_id, target_id, relationship_type). This method accepts any canonical type or alias and automatically normalizes the spelling before storage.


# dec_001 caused dec_002, dec_002 influenced dec_003

g.add_causal_relationship("dec_001", "dec_002", "CAUSED")
g.add_causal_relationship("dec_002", "dec_003", "INFLUENCED")

The edge becomes visible to traversal operations through the _CAUSAL_TRAVERSAL_TYPES set defined in the source code.

Querying Causal Chains

Direct Graph Method

For immediate traversal, call graph.get_causal_chain(decision_id, direction="downstream", max_depth=10) directly on your ContextGraph instance.

The implementation in semantica/context/context_graph.py (lines 3918-4000) uses a breadth-first search with a FIFO queue (deque) to explore neighbors. It respects the max_depth parameter, skips the starting decision itself, and filters edges against _CAUSAL_TRAVERSAL_TYPES to ensure only causal relationships are followed.

The method returns a list of Decision model objects (defined in semantica/context/decision_models.py) ordered by causal distance. Each object contains a causal_distance field in its metadata indicating the hop count from the origin.

chain = g.get_causal_chain("dec_001", direction="downstream", max_depth=5)
for d in chain:
    print(f"{d.decision_id} → {d.content} (distance={d.metadata['causal_distance']})")

Using the CausalChainAnalyzer Class

For advanced analysis, instantiate CausalChainAnalyzer from semantica/context/causal_analyzer.py. This wrapper provides the same core traversal logic plus additional utilities such as time-bounded tracing via trace_at_time() and richer diagnostic output.

from semantica.context.causal_analyzer import CausalChainAnalyzer

analyzer = CausalChainAnalyzer(graph_store=g)
upstream = analyzer.get_causal_chain("dec_003", direction="upstream", max_depth=5)

for d in upstream:
    print(d.decision_id, d.content)

Implementation Details

Edge Type Normalization

Whether you specify "CAUSED" or "causes", the system stores and retrieves the relationship consistently. The _CAUSAL_EDGE_ALIASES dictionary ensures that no edge becomes invisible during causal chain analysis due to spelling variations.

Traversal Algorithm

The get_causal_chain method implements breadth-first search using Python's collections.deque. It traverses only edges whose types belong to the internal _CAUSAL_TRAVERSAL_TYPES set, effectively ignoring non-causal edges that might coexist in the graph.

Direction Handling

Directionality determines which edges the algorithm follows:

  • Upstream (ancestors): Follows edges where the current node is the target
  • Downstream (descendants): Follows edges where the current node is the source

This directional logic allows you to trace either the root causes or the cascading consequences of any decision.

Complete Working Example

from semantica.context.context_graph import ContextGraph
from semantica.context.causal_analyzer import CausalChainAnalyzer

# 1. Create graph and add decisions

g = ContextGraph()
g.add_node("dec_001", "decision", content="Approve loan",
           category="loan", confidence=0.94, timestamp="2024-01-10T09:00:00")
g.add_node("dec_002", "decision", content="Increase rate",
           category="loan", confidence=0.88, timestamp="2024-01-12T14:30:00")
g.add_node("dec_003", "decision", content="Reject loan",
           category="loan", confidence=0.97, timestamp="2024-01-15T11:15:00")

# 2. Declare causal relationships

g.add_causal_relationship("dec_001", "dec_002", "CAUSED")
g.add_causal_relationship("dec_002", "dec_003", "INFLUENCED")

# 3. Retrieve downstream chain via direct method

chain = g.get_causal_chain("dec_001", direction="downstream", max_depth=5)
for d in chain:
    print(f"{d.decision_id} → {d.content} (distance={d.metadata['causal_distance']})")

# Output:

# dec_002 → Increase rate (distance=1)

# dec_003 → Reject loan (distance=2)

# 4. Retrieve upstream chain via analyzer

analyzer = CausalChainAnalyzer(graph_store=g)
upstream = analyzer.get_causal_chain("dec_003", direction="upstream")
for d in upstream:
    print(d.decision_id, d.content)

# dec_002 Increase rate

# dec_001 Approve loan

Summary

  • Decision nodes use the type "decision" and store structured metadata in semantica/context/context_graph.py
  • Causal edges belong to the canonical set ("CAUSED", "INFLUENCED", "PRECEDENT_FOR") with automatic alias normalization via _CAUSAL_EDGE_ALIASES
  • Traversal methods include the direct graph.get_causal_chain() and the wrapper CausalChainAnalyzer class from semantica/context/causal_analyzer.py
  • Direction control uses "upstream" for ancestors (target-facing edges) and "downstream" for descendants (source-facing edges)
  • Results return as Decision model objects with causal_distance metadata indicating hop count from the origin

Frequently Asked Questions

What edge types does Semantica recognize for causal chain analysis?

Semantica recognizes "CAUSED", "INFLUENCED", and "PRECEDENT_FOR" as canonical types, plus aliases such as "causes" and "influences". The system normalizes these automatically using the _CAUSAL_EDGE_ALIASES mapping in context_graph.py, ensuring consistent traversal regardless of which alias you use when creating relationships.

How do I trace decision consequences versus decision origins?

Use direction="downstream" to find consequences (descendants) where the starting decision is the source of influence, or direction="upstream" to find origins (ancestors) where the starting decision is the target of prior decisions. The algorithm follows edges directionally based on this parameter.

Can I limit how far the causal chain traverses?

Yes, the max_depth parameter controls traversal depth in both the direct graph method and the CausalChainAnalyzer class. The breadth-first search implementation respects this limit strictly, preventing excessive computation on large graphs while still returning ordered results by causal distance.

What data structure does get_causal_chain return?

The method returns a list of Decision objects defined in semantica/context/decision_models.py. Each object contains the original decision properties plus a causal_distance field in its metadata dictionary indicating the number of hops from the starting decision. The list is ordered by this distance, from nearest to farthest.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →