Version: v1.0.5 | Architecture Manual & Operational Bible
CausalNerve is an advanced, production-grade structural causal inference and real-time observability framework. It differs from traditional machine learning (which relies on static correlations) by explicitly modeling the data-generating process as a dynamic Directed Acyclic Graph (DAG).
- Continuous Structural Evolution: The causal graph is not static. It mutates over time as the system shifts regimes.
- Physically Grounded Interventions: Uses Pearl's
do-calculusto isolate variables, cut incoming causal pathways, and simulate counterfactual outcomes. - Lyapunov Stability: Evaluates the structural integrity ("energy") of the graph to predict catastrophic breakdown or regime shift.
- Bootstrap:
nerve.fit(historical_data)learns the latent foundational DAG. - Telemetry Streaming:
nerve.step(obs)ingests(1, N)real-time vectors, dynamically updating edge weights and calculating structural leakage. - Memory Archival:
record_snapshot()logs topological states into a timeline. - Intervention: Anomalies trigger
why()(Root Cause Analysis), followed bydo()(Surgery), androllout()(Prediction). - Observability: The state is serialized and mounted to the reactive WebGL dashboard.
Copy and paste this snippet into your terminal. It sets up a real-time causal runtime without any configuration.
import time
import numpy as np
from causalnerve import CausalNerve
from causalnerve.datasets import SyntheticStreamGenerator
# 1. Initialize the Causal Runtime
nerve = CausalNerve(nodes=6, state_dim=32)
# 2. Learn the Latent Graph Foundation
print("Learning baseline structural graph...")
historical_data = np.array(list(SyntheticStreamGenerator.stable(n_cycles=150)))
nerve.fit(historical_data)
# 3. Real-Time Telemetry Streaming
print("Streaming live telemetry and computing causal leakage...")
for obs in SyntheticStreamGenerator.with_drift(n_cycles=100):
res = nerve.step(obs)
print(f"Cycle {res.cycle} | Causal Leakage: {res.leakage:.4f} | Graph Changed: {res.graph_changed}")
time.sleep(0.1)python -m venv .venv
source .venv/bin/activate # Linux/Mac
.venv\Scripts\activate # Windows
pip install causalnerve==1.0.5 causalnerve-observe==1.0.5- Math Engines:
numpy>=2.0,scipy,torch(for heavy latent routing). - UI Engine:
gradio>=6.10,fastapi,plotly. - Version Strictness: You must lock to exactly
v1.0.5across the stack. Mixingv1.0.4core withv1.0.5dashboard triggers fatal serialization failures.
The causalnerve-observe module binds to local ports (default 7860). In cloud/Docker environments:
- Set
GRADIO_SERVER_NAME="0.0.0.0". - Map ports strictly (
-p 7860:7860). - Port Collisions: If
OSError: Cannot find empty portoccurs, manually passport=7865into theobserve()bootloader.
The master orchestrator. Holds the latent weights, adjacency matrix, and mathematical engines.
- Lifecycle: Init -> Fit -> Step (Infinite Loop) -> Do/Why (On Demand).
- Memory Implications: Retains the running state vector. Does not automatically store full history (this is outsourced to the Replay Engine to prevent OOM errors).
The DVR of the causal system.
- Internal Role: Maintains a chronologically ordered array of
GraphSnapshotobjects andRevisionRecordobjects. - Performance Impact: High. Recording every cycle will exhaust RAM. Must downsample (e.g.,
if cycle % 10 == 0:).
The long-term clustering engine.
- Internal Role: Archives highly specific "regimes" (e.g., "ICU Shock Protocol", "Engine Turbine Overheating"). Uses latent space distances (
retrieve_similar) to match current telemetry against historical catastrophes.
- Internal Role: Pure deterministic data generation for testing. Yields stable oscillations or forced drifts.
β οΈ STRICT RULE ALERT: CausalNerve enforces exact Keyword Arguments (kwargs). Bypassing kwargs or passing positional arguments leads to fatalTypeErrors.
- Syntax:
nerve.why(target="2") - Strict Rule:
targetmust be a string representing the index. - Returns: Dictionary containing
"confidence".
- Syntax:
nerve.do(node=3, value=1.5) - Internal Effect: Mutates the adjacency matrix. Zeros out the targeted column, isolating the node.
- Syntax:
replay_engine.record_snapshot(cycle=10, adjacency=[(0,1,0.5)], leakage=0.01, v_energy=2.0) - Strict Rule:
adjacencyMUST be a list of 3-tuples(u, v, weight). Passing a 2D matrix triggers aValueErrorunpack crash in the dashboard.
- Strict Rule: You MUST pass the
rationalekwarg. Example:rationale="Administered meds".
The causalnerve-observe dashboard is built on Gradio using a flat component hierarchy and WebGL Plotly traces for maximum hydration speed.
The dashboard is stateless. It derives its entire initial layout by probing the nerve instance passed into it. If the nerve instance lacks bound properties, the dashboard will silently generate blank plots or throw hidden tracebacks.
Before calling observe(), you must execute this sequence:
# 1. Bind the history timeline
nerve.replay_engine = my_replay_engine
# 2. Bind the current temporal location
nerve.current_cycle = total_cycles_run
# 3. Bind human-readable strings
nerve.preset_name = "Enterprise Production System"
nerve.node_labels = {0: "Sensor_A", 1: "Sensor_B"}
# 4. Patch Dashboard Version Drift (v1.0.5 compatibility)
from causalnerve.memory import GraphDiff
if not hasattr(GraphDiff, "edges_stable"):
GraphDiff.edges_stable = property(lambda self: getattr(self, "stable_edges", []))
for snap in my_replay_engine.snapshots:
if not hasattr(snap, "active_alarms"):
snap.active_alarms = []
# 5. Boot
observe(nerve, launch=True, port=7865)- Why it works: The dashboard iterates over
nerve.replay_engine.snapshots. - Performance Trap: The dashboard uses a batched WebGL scatterplot to render the causal graph. If you record snapshots every single cycle for 10,000 cycles, the slider will load 10,000 WebGL states into browser memory. Scale down sampling to 1 snapshot per 100 cycles for enterprise loads.
When executing nerve.do(), the graph structure is modified. You must explicitly log this modification to the UI using replay_engine.record_revision(edit_type="do", ...) so that the dashboard narrative reflects the human intervention.
Do not run the dashboard blockingly in the main execution thread of a high-frequency system.
- Process A (Data Ingestion): Reads Kafka/MQTT streams, pushes to Redis.
- Process B (CausalNerve Engine): Pulls from Redis, runs
nerve.step(), writes Snapshots to PostgreSQL/Disk. - Process C (Observatory): A read-only replica of the
CausalNerveobject is passed toobserve(). It periodically fetches states from the DB.
- Symptom: Dashboard boots, but "Live Causal Graph" tab shows a red "Error" box.
- Traceback:
ValueError: too many values to unpack (expected 3)indashboard.py:97. - Diagnosis: You fed
np.random.rand(N,N)intorecord_snapshot. - Recovery: Iterate over your matrix and convert to tuples:
[(i, j, float(mat[i,j])) for i in range(N) for j in range(N) if mat[i,j] > threshold].
- Symptom: Dragging the dashboard slider crashes the UI rendering.
- Traceback:
AttributeError: 'GraphDiff' object has no attribute 'edges_stable' - Diagnosis: The v1.0.5 module renamed this property.
- Recovery: Apply the Monkeypatch defined in Section 5.2.
- Symptom: Dashboard hangs indefinitely when clicking "Execute Intervention".
- Diagnosis: The
nerve.rollout()method is being called in an async worker thread while the main loop is heavily utilizing the sametorchgraph. - Recovery: Pause background telemetry ingestion while executing heavy counterfactual rollouts, or deepcopy the
nerveinstance.
- Downsample Topology: Causal graphs mutate slower than standard time-series data. Run
nerve.step()every tick, but only runrecord_snapshot()every 50-100 ticks. - Memory Leaks: Do not retain infinite references in
StructuralReplayEngine.snapshots. Cap the list size:self.snapshots = self.snapshots[-1000:].
- Batched WebGL Rendering: Inside
dashboard.py:_render_graph(), edges are NOT rendered as individual Plotly traces. They are flattened into a single list separated byNone(e.g.[x1, x2, None, x3, x4, None]). This is a genius optimization that drops rendering time from 500ms to 2ms per graph update. - Dashboard Scope Bug: Inside
dashboard.py:run_intervention, the code explicitly callsnerve.rollout(...)utilizing actual structural math, replacing the old mock logic. Ensurenerveremains globally accessible within the UI context.
import time
import numpy as np
from causalnerve import CausalNerve
from causalnerve.datasets import SyntheticStreamGenerator
from causalnerve.memory import StructuralReplayEngine, GraphDiff
from causalnerve_observe import observe
# 1. ORCHESTRATION SETUP
nerve = CausalNerve(nodes=6, state_dim=32)
replay = StructuralReplayEngine(snapshot_interval=10)
historical_data = np.array(list(SyntheticStreamGenerator.stable(n_cycles=150)))
nerve.fit(historical_data)
# 2. THE TELEMETRY LOOP
# Simulating a patient drifting into shock (dropping BP)
streaming = np.array(list(SyntheticStreamGenerator.with_drift(n_cycles=30)))
for cycle in range(30):
res = nerve.step(streaming[cycle])
# Only record every 10 cycles (Optimization Rule #1)
if cycle % 10 == 0:
# Converting internal structures to strict Edge Lists
adj_list = [(0, 1, 0.8), (3, 1, 0.5), (4, 2, 0.6)]
replay.record_snapshot(cycle, adj_list, res.leakage, 3.0)
# 3. INTERVENTION LIFECYCLE
rca = nerve.why(target="1") # Why did BP drop?
nerve.do(node=3, value=1.5) # Intervene: Administer Vasopressor
# Audit the human decision
replay.record_revision(
cycle=30, edit_type="add", edge=(3, 1), confidence=0.95,
v_before=4.5, v_after=1.2, accepted=True, rationale="Administered medication"
)
# 4. DASHBOARD INTEGRATION
# Monkeypatch UI bugs
for snap in replay.snapshots: snap.active_alarms = [1] if snap.cycle >= 20 else []
if not hasattr(GraphDiff, "edges_stable"):
GraphDiff.edges_stable = property(lambda s: getattr(s, "stable_edges", []))
# Bind Context
nerve.replay_engine = replay
nerve.current_cycle = 30
nerve.preset_name = "ICU Bed 4"
nerve.node_labels = {0: "HR", 1: "BP", 2: "SpO2", 3: "Vasopressor", 4: "RR", 5: "Temp"}
# Boot isolated port
observe(nerve, launch=True, port=7865)- π΄ MUST NEVER DO: Pass positional arguments to
do(),why(), orwith_drift(). Always usekwarg=value. - π΄ MUST NEVER DO: Launch the dashboard without binding
nerve.replay_engine. - π’ MUST DO: Pass adjacency structures to snapshots strictly as
[(u,v,w)]tuples. - π’ MUST DO: Downsample your
record_snapshot()calls in high-frequency environments. - π‘ DANGEROUS: Calling
.rollout()with massive horizons (>5000) inside a dashboard callback. It will freeze the worker.
[SyntheticStream] ---> [CausalNerve.fit()] ---> [Baseline Weights Established]
|
[Real-Time Kafka] ---> [CausalNerve.step()] ---> [Leakage Computed]
|
+---> [record_snapshot] ---> [StructuralReplayEngine]
|
[Intervention Request] ---> [nerve.do()] |
| |
[Dashboard Boot] <-------------+------------------------------------+
Evaluated on NASA C-MAPSS FD001 (Engines 81-100).
| Method | SHD β | Det. Delay β | Runtime β | Online? |
|---|---|---|---|---|
| CausalNerve | 0.0 Β± 0.0 | 221.7 Β± 60.3 | 83 ms | Yes |
| PCMCI | 105.8 Β± 9.3 | N/A (offline) | 4613 ms | No |
| VAR-LiNGAM | 20.0 Β± 0.0 | N/A (offline) | 1 ms | No |
| Granger | 158.9 Β± 13.7 | N/A (offline) | 780 ms | No |
@software{causalnerve2026,
author = {S, Guru Prasaath},
title = {CausalNerve: A Real-Time Adaptive Causal Runtime for Continuously Evolving Dynamical Systems},
year = {2026},
url = {https://github.com/causalnerve/causalnerve}
}
## License
MIT License
