Skip to content

Causal Reasoning

intermediate

Ask 'what if?' and 'why?' to your memories. Engramma builds causal relationships between facts and lets you query them.

Beyond correlation

Most memory systems tell you what is similar. Engramma tells you why things are connected and what would happen if something changed.

This is the difference between "these two facts are related" and "this fact caused that outcome." Engramma builds and maintains a causal graph across your memories, exposed through dedicated /v1/memory/causal/* endpoints.

Three levels of causal queries

Engramma supports three levels of causal reasoning, inspired by Judea Pearl's causal ladder:

Level 1: Association

"What is related to X?"

At this level you ask about the strength and direction of the relationship between two memories. Engramma goes beyond simple similarity — it measures asymmetric causal strength and detects confounders.

import requests

BASE = "https://api.engramma.dev"
HEADERS = {"Authorization": "Bearer <YOUR_KEY>", "Content-Type": "application/json"}

# Level 1: Measure causal strength between two memories
resp = requests.post(
    f"{BASE}/v1/memory/causal/strength",
    headers=HEADERS,
    json={
        "text_a": "Memory limits set too low",
        "text_b": "OOM crashes during deployment"
    }
)
result = resp.json()

print(result)
# {
#   "strength_a_to_b": 0.78,
#   "strength_b_to_a": 0.12,
#   "direction": "a_to_b",
#   "observations_a_to_b": 15,
#   "observations_b_to_a": 3,
#   "confounded": false
# }

You can also pass raw embedding vectors instead of text:

resp = requests.post(
    f"{BASE}/v1/memory/causal/strength",
    headers=HEADERS,
    json={
        "pattern_a": [0.12, -0.45, ...],  # pre-computed embedding
        "pattern_b": [0.33, 0.71, ...]
    }
)

Level 2: Intervention

"What happens if I do X?"

This goes beyond correlation. The /causal/intervene endpoint reasons about the downstream effects of an action based on stored causal relationships.

import requests

BASE = "https://api.engramma.dev"
HEADERS = {"Authorization": "Bearer <YOUR_KEY>", "Content-Type": "application/json"}

# Level 2: Intervention — predict downstream effects
resp = requests.post(
    f"{BASE}/v1/memory/causal/intervene",
    headers=HEADERS,
    json={
        "text": "Increase memory limits to 4GB"
    }
)
result = resp.json()

print(result)
# {
#   "predicted_effects": [
#     {
#       "effect_key": [0.33, 0.71, ...],
#       "predicted_value": [0.55, -0.12, ...],
#       "causal_strength": 0.78
#     },
#     {
#       "effect_key": [0.21, 0.44, ...],
#       "predicted_value": [0.60, -0.08, ...],
#       "causal_strength": 0.65
#     }
#   ]
# }

You can also intervene using raw embeddings by passing cause_key and cause_value instead of text:

resp = requests.post(
    f"{BASE}/v1/memory/causal/intervene",
    headers=HEADERS,
    json={
        "cause_key": [0.12, -0.45, ...],
        "cause_value": [0.88, 0.23, ...]
    }
)

Level 3: Counterfactual

"Would Y have happened if X hadn't occurred?"

The most powerful level. The /causal/counterfactual endpoint reasons about alternative histories using the causal graph.

import requests

BASE = "https://api.engramma.dev"
HEADERS = {"Authorization": "Bearer <YOUR_KEY>", "Content-Type": "application/json"}

# Level 3: Counterfactual — reason about alternative histories
resp = requests.post(
    f"{BASE}/v1/memory/causal/counterfactual",
    headers=HEADERS,
    json={
        "text_cause": "Memory limits set too low",
        "text_effect": "PagerDuty alerts fired during deploy",
        "text_counterfactual": "Memory limits were already increased to 4GB"
    }
)
result = resp.json()

print(result)
# {
#   "counterfactual_effect": [0.11, -0.03, ...],
#   "computable": true
# }

# computable=true means the causal graph has enough structure
# to reason about this counterfactual. The counterfactual_effect
# embedding represents what the effect would have looked like.
Info

When computable is false, it means the causal graph lacks sufficient evidence to reason about the counterfactual. Store more related memories and allow consolidation cycles to run.

You don't need to explicitly tell Engramma which facts are causally related. The engine discovers causal relationships automatically:

  1. Temporal co-occurrence — Facts stored close in time are candidates for causal links
  2. Semantic direction — "X causes Y" patterns are detected in stored text
  3. Retrieval patterns — If querying A frequently leads to accessing B, a link strengthens
  4. Consolidation — During sleep cycles, weak causal links are pruned and strong ones are reinforced
Info

Causal discovery improves over time. The more memories you store and retrieve, the better the causal graph becomes. This is one reason consolidation cycles matter.

Querying the causal graph

Causal direction

Determine whether A causes B or B causes A, with a confidence score:

import requests

BASE = "https://api.engramma.dev"
HEADERS = {"Authorization": "Bearer <YOUR_KEY>", "Content-Type": "application/json"}

resp = requests.post(
    f"{BASE}/v1/memory/causal/direction",
    headers=HEADERS,
    json={
        "text_a": "Code changes without tests",
        "text_b": "Deployment failures"
    }
)
result = resp.json()

print(result)
# {
#   "direction": "a_causes_b",
#   "confidence": 0.87,
#   "confounded": false,
#   "details": {
#     "displacement_consistency": 0.92
#   }
# }

Causal neighbors

Find upstream causes (parents) and downstream effects (children) of a memory:

import requests

BASE = "https://api.engramma.dev"
HEADERS = {"Authorization": "Bearer <YOUR_KEY>", "Content-Type": "application/json"}

resp = requests.post(
    f"{BASE}/v1/memory/causal/neighbors",
    headers=HEADERS,
    json={
        "text": "Deployment failures",
        "direction": "both",
        "min_strength": 0.3
    }
)
result = resp.json()

print(result)
# {
#   "parents": [
#     {"key": [0.44, -0.21, ...], "strength": 0.82},
#     {"key": [0.15, 0.67, ...], "strength": 0.76}
#   ],
#   "children": [
#     {"key": [0.33, 0.12, ...], "strength": 0.91},
#     {"key": [0.28, -0.55, ...], "strength": 0.65}
#   ]
# }

# parents = upstream causes (what leads to deployment failures)
# children = downstream effects (what deployment failures cause)

The direction parameter controls which neighbors are returned:

  • "both" — return both parents and children
  • "parents" — only upstream causes
  • "children" — only downstream effects

Structure discovery

For a broader view of the causal graph, use the structure endpoints:

# Discover the full causal DAG
resp = requests.post(
    f"{BASE}/v1/memory/structure/discover",
    headers=HEADERS,
    json={}
)

# Get the causal graph structure
resp = requests.post(
    f"{BASE}/v1/memory/structure/graph",
    headers=HEADERS,
    json={}
)

Real-world use cases

Use caseHow causal reasoning helps
Incident responseUse /causal/neighbors to trace upstream causes of an outage
Impact analysisUse /causal/intervene to predict effects of a proposed change
Root cause analysisUse /causal/direction to confirm whether A truly causes B
Decision supportUse /causal/counterfactual to evaluate "what if we had done X?"

Causal strength

Each causal link has a strength score (0-1) that indicates how reliably A leads to B:

  • Strong (> 0.8): Near-certain causal relationship. "Memory exhaustion causes OOM crashes."
  • Moderate (0.5-0.8): Likely causal but with exceptions. "Late deployments correlate with incidents."
  • Weak (< 0.5): Possible relationship, needs more evidence. May be pruned during consolidation.

The confounded field in responses indicates whether the engine has detected a potential confounder — a third variable that may be causing both A and B. When confounded: true, interpret the causal strength with caution.

Tip

Causal links strengthen every time the relationship is observed and weaken when evidence contradicts them. Store contradicting facts to update the causal graph — Engramma handles belief revision automatically.

Limitations

Causal reasoning is powerful but has boundaries:

  • Not a knowledge graph — Engramma doesn't store explicit RDF triples. Causal links are learned, not declared.
  • Requires evidence — At least 2-3 related facts are needed before causal links form reliably.
  • Temporal scope — Very old causal links may weaken if never reinforced (by design — the world changes).
  • Correlation risk — Strong correlations may appear as causal links that aren't truly causal. Check the confounded field and use /causal/direction to validate.

Next steps