Open the live demo → · CausalRAG interactive decision runtime, built and deployed by GitHub Actions.\n\n# CausalRAG v0.3 — from retrieval to causal decision
CausalRAG is a causal decision runtime for agents operating under uncertainty.
It started as causal-graph-enhanced RAG. In v0.3, retrieval is only one way to acquire evidence. The runtime now maintains explicit competing hypotheses, chooses what to observe or intervene on, updates beliefs from outcomes, and can value another experiment by whether it is expected to improve the eventual decision.
Goal
↓
Belief state + competing hypotheses
↓
Candidate observations / interventions
↓
Runtime decision layer
├─ Bayesian information gain
├─ expected value of sample information (EVSI)
├─ intervention consequence utility
└─ capability cost / risk / reversibility
↓
Observe / Retrieve / Ask / Intervene / Wait
↓
Outcome
↓
Posterior + world-model update
↓
Repeat or Stop
The central distinction is simple:
The model proposes. The runtime decides. The world corrects both.
An LLM can suggest hypotheses and actions, but it does not own the belief state, capability economics, Bayesian update, or deployment consequence utility.
A normal agent loop can repeatedly do:
LLM → tool → result → LLM
CausalRAG v0.3 adds explicit semantics around why another action is worth taking:
- Beliefs are explicit and defeasible. Competing hypotheses live in a
CausalWorldModel, not only in prompt context. - Experiments have declared observation models.
ExperimentContractcan representP(outcome | hypothesis, action)and enables exact Bayesian posterior updates. - Information and decision value are separated. EIG asks how much uncertainty an experiment removes. EVSI asks whether the information is expected to change the best downstream decision enough to justify its cost.
- Interventions have consequence utilities.
InterventionContractevaluates actions under the current posterior rather than trusting model self-scores. - Deployment owns preferences.
DecisionPreferencescan change consequence utility without changing the reasoner, the tool implementation, or the causal world model. - Behavior is benchmarked, not just prose. HiddenWorld measures success, calibration, experiment cost, and causal regret over seeded stochastic episodes.
The legacy CausalRAGPipeline remains available as an optional compatibility layer.
Python 3.10+ is required.
The default install is the lightweight causal runtime. It does not install Torch, sentence-transformers, or FAISS.
git clone https://github.com/hippoley/CausalRAG.git
cd CausalRAG
pip install -e .Add optional capability layers only when needed:
pip install -e ".[rag]" # lightweight document + causal retrieval
pip install -e ".[api]" # FastAPI / HTTP server
pip install -e ".[local-embeddings]" # sentence-transformers + PyTorch
pip install -e ".[faiss]" # FAISS vector backend
pip install -e ".[evaluation]" # pandas + Ragas evaluation
pip install -e ".[full]" # hosted-runtime capability layersFor the default OpenAI-backed reasoner and hosted embedding provider:
export OPENAI_API_KEY=...Core CI exercises Python 3.10 and 3.12 independently from the optional API and RAG gates.
from causalrag import ToolSpec, create_agent
def read_co2(room: str):
return {"room": room, "co2_ppm": 1180}
agent = create_agent(
tools=[
ToolSpec(
name="read_co2",
description="Read current CO2 concentration in a room.",
handler=read_co2,
cost=0.01,
risk=0.0,
reversible=True,
metadata={"kind": "observe"},
)
]
)
result = agent.run(
"Determine whether the bedroom needs more ventilation and what to verify first",
max_steps=6,
)
print(result.answer)
print(result.to_dict()["decisions"])The runtime records candidate actions, scores, selected action, observations, beliefs, and transitions separately.
When an observation has a known or estimated likelihood model, declare an ExperimentContract:
from causalrag.experiments import ExperimentContract, OutcomeLikelihood
pressure_test = ExperimentContract(
experiment_id="filter_pressure_test",
outcomes=[
OutcomeLikelihood("high", {"H1": 0.90, "H2": 0.15}),
OutcomeLikelihood("normal", {"H1": 0.10, "H2": 0.85}),
],
)The runtime can then compute Bayesian expected information gain and update the posterior exactly when an outcome arrives. The LLM does not get a second chance to independently reinterpret the same evidence and double-count it.
Without a valid likelihood contract, the runtime falls back to weaker hypothesis-discrimination or model-estimated information signals rather than pretending it has Bayesian semantics.
Information gain alone is not enough. An experiment can reduce uncertainty without changing the decision.
CausalRAG v0.3 can place experiments and interventions on the same expected-value axis when both contracts are available:
best action now
vs.
expected best action after observing one more sample
For an experiment:
EVSI =
E_outcome [ best intervention value after posterior update ]
- best intervention value now
net value of sampling = EVSI - experiment cost
For an intervention, expected utility is evaluated under the current posterior and its capability cost is subtracted. The runtime can therefore decide whether another observation is worth buying or whether it should act now.
The current implementation is one-step value of information, not a general POMDP solver.
Different deployments can value the same physical outcome differently. That preference should not live in the LLM prompt.
from causalrag import DecisionPreferences, create_agent
preferences = DecisionPreferences(
intervention_utilities={
"repair_fan": {
"H1": -1.0,
"H2": 1.0,
"H3": -1.0,
},
"clear_duct": {
"H1": -1.0,
"H2": -1.0,
"H3": 1.0,
},
}
)
agent = create_agent(
tools=my_tools,
reasoner=my_reasoner,
decision_preferences=preferences,
)Ownership is intentionally separated:
ExperimentContract = what observations are likely under each hypothesis
InterventionContract = default consequence semantics
DecisionPreferences = how this deployment values intervention outcomes
ToolSpec = capability cost / risk / reversibility
Reasoner = proposes candidates
Runtime = performs arbitration
World = returns outcomes and updates evidence
DecisionPreferences creates effective runtime contracts without mutating the original tool objects.
HiddenWorld: evaluate agency as behavior
HiddenWorld hides the true mechanism and gives the agent a finite diagnostic/intervention budget. The environment alone knows the truth; the agent receives priors, experiment contracts, capabilities, costs, and observations.
The first HVAC scenario contains three competing hidden mechanisms and noisy diagnostic tests. It is intentionally small: the purpose is to establish a reproducible behavioral benchmark contract before moving to hidden graph/SCM discovery.
A fixed stochastic suite can be run without an API key:
python examples/hidden_world_suite_demo.py
python examples/hidden_world_policy_comparison.py
python examples/hidden_world_risk_sweep.pyThe table below is not a universal performance claim. It is the measured result on the repository's fixed 30-episode suite: three hidden mechanisms × seeds 0..9.
| policy | success | Brier ↓ | probes | cost | causal regret ↓ |
|---|---|---|---|---|---|
| greedy EIG | 0.800 | 0.340 | 2.10 | 0.346 | 0.296 |
| conservative EIG | 0.867 | 0.247 | 2.90 | 0.388 | 0.272 |
| decision value | 0.833 | 0.292 | 2.00 | 0.334 | 0.251 |
| random probe | 0.733 | 0.403 | 2.33 | 0.357 | 0.373 |
| cheapest probe | 0.633 | 0.390 | 2.73 | 0.344 | 0.461 |
The benchmark also exposed a useful non-monotonic utility frontier. On the same episodes, assigning increasing loss to a wrong intervention produced:
| wrong-action loss | success | Brier ↓ | probes | cost | causal regret ↓ |
|---|---|---|---|---|---|
| 0.0 | 0.833 | 0.292 | 2.00 | 0.334 | 0.251 |
| 0.5 | 0.833 | 0.292 | 2.00 | 0.334 | 0.251 |
| 1.0 | 0.900 | 0.176 | 2.63 | 0.366 | 0.216 |
| 2.0 | 0.867 | 0.225 | 2.60 | 0.368 | 0.251 |
The useful result is not that 1.0 is a globally correct constant. It is that policy behavior can exhibit a phase change as consequence utility changes, and making a system "more risk averse" is not monotonically better.
python examples/hypothesis_falsification_demo.py
python examples/bayesian_experiment_demo.py
python examples/decision_preferences_demo.py
python examples/hidden_world_demo.py
python examples/hidden_world_suite_demo.py
python examples/hidden_world_policy_comparison.py
python examples/hidden_world_risk_sweep.pyThese demos exercise the causal runtime without requiring a hosted reasoning model.
RAG remains useful as an evidence capability.
pip install -e ".[rag]"from causalrag import create_agent
agent = create_agent(
documents=[
"Opening a window increases air exchange and can lower indoor CO2.",
"More occupants increase indoor CO2 when ventilation is unchanged.",
"Outdoor particulate pollution can make opening windows undesirable.",
]
)
result = agent.run(
"Find the safest leverage point for reducing indoor CO2 and what evidence should be checked first.",
max_steps=6,
)By default, vector retrieval and causal-graph normalization share one hosted embedding provider with NumPy in-memory cosine search. Local sentence-transformers and FAISS remain opt-in.
pip install -e ".[rag,local-embeddings]"agent = create_agent(
documents=my_documents,
embedding_provider_name="local",
embedding_model="all-MiniLM-L6-v2",
)pip install -e ".[rag,faiss]"agent = create_agent(
documents=my_documents,
vector_backend="faiss",
)The runtime does not require a vendor-specific model contract:
agent = create_agent(
tools=my_tools,
reasoner=my_reasoner,
belief_updater=my_belief_updater,
decision_preferences=my_preferences,
)This supports deterministic policies, local models, test doubles, domain-specific planners, or future RL/planning policies. The core path can run without an API key when a custom reasoner is supplied.
causalrag --version
causalrag --helpRun an agent over documents:
causalrag agent \
--goal "Which variable is the highest-leverage intervention and what should I verify first?" \
--input ./docs \
--embedding-provider openai \
--embedding-model text-embedding-3-small \
--vector-backend memory \
--max-steps 8Build a reusable index:
causalrag index --input ./docs --output ./indexLegacy one-shot retrieval remains available:
causalrag query --index ./index --query "How can A lead to B?"pip install -e ".[api]"
causalrag serve --host 0.0.0.0 --port 8000GET /healthPOST /agent/run
Content-Type: application/json
{
"goal": "Find the safest leverage point for reducing indoor CO2",
"max_steps": 8,
"model": "gpt-5.6-terra",
"provider": "openai",
"embedding_provider": "openai",
"embedding_model": "text-embedding-3-small",
"vector_backend": "memory"
}If the request includes documents, install both optional layers:
pip install -e ".[api,rag]"The response contains the final answer, decision trace, observations, beliefs/hypotheses, and recorded transitions.
Stores explicit causal beliefs, competing hypotheses, evidence, and action→observation transitions. The implementation is intentionally lightweight so future backends can include Bayesian SCMs, temporal models, simulators, neural world models, or hybrids.
Declares discrete outcomes and their likelihood under competing hypotheses. Enables Bayesian EIG and exact posterior updates.
Declares consequence utility of an intervention under each causal hypothesis.
Deployment-owned overrides for consequence utility. The reasoner cannot rewrite them.
Supported action kinds:
observe
retrieve
ask
intervene
wait
stop
Stores uncertainty, all candidates, runtime score breakdown, selected action, and belief snapshot before action.
Stores executed action → observation history, forming the base for future causal/experiment memory.
┌──────────────────────────────────────────────┐
│ CausalAgent │
│ Goal → Propose → Decide → Act → Observe │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ Causal Decision Runtime │
│ hypotheses · Bayes · EIG · EVSI · utility │
│ capability cost/risk · DecisionPreferences │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ Explicit World Model │
│ beliefs · evidence · transitions · outcomes │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ Environment │
│ retrieval · APIs · sensors · humans │
│ simulators · software · robots │
└──────────────────────────────────────────────┘
The LLM is a Reasoner: it can propose hypotheses/actions and explain them. It is not the world model or the final action authority.
causalrag/
├── agent/ # loop, state, actions, user-facing runtime
├── world_model/ # beliefs, hypotheses, evidence, transitions
├── reasoning/ # reasoner adapters, updates, runtime policy
├── experiments/ # experiment/intervention contracts, Bayes, EVSI, preferences
├── benchmarks/ # HiddenWorld, stochastic suites, policy comparisons
├── tools/ # capability registry and runtime metadata
├── embeddings/ # hosted/local/custom embedding abstraction
├── causal_graph/ # optional causal extraction + graph layer
├── retriever/ # optional vector/hybrid retrieval
├── reranker/ # optional causal reranking
├── interface/ # optional HTTP surfaces
└── pipeline.py # legacy one-shot RAG compatibility
Existing code can continue to use:
from causalrag import create_pipeline
pipeline = create_pipeline()
pipeline.index([
"Climate change causes rising sea levels.",
"Rising sea levels increase coastal flooding."
])
result = pipeline.run("How can climate change lead to coastal flooding?")
print(result["answer"])For new projects, prefer create_agent().
- explicit competing hypotheses and falsification targets
- runtime-owned hypothesis discrimination
ExperimentContractlikelihood models- Bayesian expected information gain
- exact posterior updates without LLM double-counting
InterventionContractconsequence utility- one-step EVSI / observe-vs-intervene decision value
- deployment-owned
DecisionPreferences - runtime enforcement of capability cost/risk/reversibility
- deterministic and seeded stochastic HiddenWorld episodes
- Brier calibration, probe cost, and causal-regret metrics
- fixed-world policy tournament and utility-sensitivity sweeps
- no-key causal decision demos
- optional RAG compatibility rather than RAG as the core ontology
See CHANGELOG.md for the release summary.
The next layers should extend the same contracts rather than replace them:
- Temporal effects and observation windows — action lag, persistence, and first-class
WAITsemantics. - Causal/experiment memory — learn from
(state, action, next_state)and reuse intervention outcomes across similar contexts. - Hidden graph / SCM discovery — move HiddenWorld from discrete mechanism identification to unknown structure and latent variables.
- Finite-horizon value of information — replace one-step EVSI with multi-step planning when the extra complexity is justified.
- Drift and regime change — detect when previously learned mechanisms stop predicting outcomes.
- Protocol adapters — MCP/A2A/API/sensor adapters remain replaceable capability transports rather than core ontology.
The intended invariant is:
experience → causal belief → decision value → action → new experience
MIT. See LICENSE.