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 insemantica/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 wrapperCausalChainAnalyzerclass fromsemantica/context/causal_analyzer.py - Direction control uses
"upstream"for ancestors (target-facing edges) and"downstream"for descendants (source-facing edges) - Results return as
Decisionmodel objects withcausal_distancemetadata 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →