A JavaScript and TypeScript toolkit for causal discovery and causal inference.
The current Step 3 workflow is production-scope within an explicitly bounded, DAG-first contract. It is not presented as a full causal inference framework.
For npm consumers, the public package is @kanaries/causal.
npm install @kanaries/causalpnpm add @kanaries/causalyarn add @kanaries/causalbun add @kanaries/causalIf you are new to the library, start with one of these three paths:
- Discovery only: use
pc,ges,exactSearch,gin,grasp,cdnod,camuv, orrcddirectly. - DAG-first workflow: use
discoverGraph,findAdjustmentSets,identifyEffect,falsifyGraph, andstabilityAnalysis. - Runtime-specific integration: use
@kanaries/causal/nodeor@kanaries/causal/webwhen you need runtime capability helpers.
The current task workflow is production-scope only within its explicit DAG-first boundary. It is appropriate when you already accept a DAG-first structural workflow and need real adjustment, identification, falsification, and stability helpers instead of mock orchestration.
Use these entry points based on the job you are trying to do:
pc,ges,exactSearch,gin,grasp,cdnod,camuv,rcd: run a concrete discovery algorithm directly.discoverGraph(...): wrap an existing discovery algorithm behind a stable task result object.findAdjustmentSets(...)/isAdjustmentSet(...): inspect valid DAG backdoor covariate sets.identifyEffect(...): ask whether a singleton treatment effect is identifiable under the supported DAG-first rules.falsifyGraph(...): run DAG sanity checks plus implied conditional independence validation against observed data.stabilityAnalysis(...): bootstrap an existing discovery configuration and summarize robustness.
If you need full ID, richer graph classes such as PAG/MAG/ADMG, counterfactuals, permutation falsification, or estimator construction, this repository does not currently promise that surface.
This repository is organized as a single Git repo with a pnpm workspace. The
goal is to keep the portable causal core independent from runtime-specific
integrations so the project can support:
- Node.js-only algorithms or accelerators
- browser-safe algorithms
- shared algorithms with different runtime implementations
The publish target is a single npm package:
@kanaries/causal
Public entry points:
@kanaries/causal@kanaries/causal/node@kanaries/causal/web
The internal workspace packages remain private implementation units. They are bundled into the public facade package at build time and are not intended to be published separately.
@causal-js/core: shared graph types, runtime capability metadata, and common algorithm contracts@causal-js/discovery: runtime-agnostic causal discovery APIs and algorithm registry@causal-js/tasks: DAG-first task-oriented workflow APIs for adjustment, identification, falsification, and stability@causal-js/node: Node.js facade and Node-only capability surface@causal-js/web: browser facade and browser/WebGPU capability surface
- Keep graph structures and algorithm contracts portable
- Treat runtime support as explicit metadata, not an afterthought
- Avoid forcing Node.js and browser exports to stay identical
- Add hardware-accelerated implementations behind separate packages or adapters
The first requested algorithm wave is now runnable:
PCCD_NODGESExactSearchGINGRaSPCAM_UVRCD
This baseline has now passed the current V1 acceptance run:
pnpm typecheckpnpm buildpnpm testpnpm test:integration- external regression suite in
Kanaries/causal-parity
Current algorithm status:
| Algorithm | Status |
|---|---|
PC |
selected-path parity |
CD_NOD |
selected-path parity |
GES |
selected-path parity |
ExactSearch |
selected-path parity |
GIN |
selected-path parity |
GRaSP |
selected-path parity |
CAM_UV |
selected-fixture parity, accepted portable smoother boundary |
RCD |
selected-path parity |
The practical meaning is:
PC,CD_NOD,GES,ExactSearch,GIN,GRaSP, andRCDare now covered by the current V1 parity and comparison baselineCAM_UVis accepted for V1 on the current comparison suite, while still keeping a portable smoother instead of a literalpygam.LinearGAMdependency
See v1-status.md for the exact boundaries. See external-regression.md for the heavy regression workflow.
Current public package roles:
@kanaries/causal: portable graph primitives, matrix containers, CI tests, score functions, discovery algorithms, and DAG-first task workflows@kanaries/causal/node: Node-oriented runtime facade@kanaries/causal/web: browser-oriented runtime facade
At the current v1 stage, the Node and Web public facades intentionally overlap a lot. Runtime-specific divergence is currently limited to capability probes, execution planning, and worker-adapter scaffolding.
The recommended DAG-first order is:
discoverGraph(...)findAdjustmentSets(...)identifyEffect(...)falsifyGraph(...)stabilityAnalysis(...)
See tasks/index.md for the full task surface.
See tasks/backend-selection.md before choosing auto, dag-first-mvp, or dag-backdoor-only.
See tasks/operational-readiness.md for release checks and interpretation boundaries.
import { DenseMatrix, FisherZTest, pc } from "@kanaries/causal";
const data = new DenseMatrix(rows);
const result = pc({
data,
ciTest: new FisherZTest(data),
alpha: 0.05,
stable: true,
ucRule: 0,
ucPriority: 2
});
console.log(result.graph);import { DenseMatrix, GaussianBicScore, ges } from "@kanaries/causal";
const data = new DenseMatrix(rows);
const result = ges({
data,
score: new GaussianBicScore(data)
});
console.log(result.cpdag);import { DenseMatrix, FisherZTest, cdnod } from "@kanaries/causal";
const data = new DenseMatrix(rows);
const context = [1, 1, 1, 2, 2, 2];
const result = cdnod({
data,
context,
alpha: 0.05,
createCiTest: (augmentedData) => new FisherZTest(augmentedData),
ucRule: 0,
ucPriority: 2
});
console.log(result.contextNodeIndex);import { DenseMatrix, GaussianBicScore, exactSearch } from "@kanaries/causal";
const data = new DenseMatrix(rows);
const result = exactSearch({
data,
score: new GaussianBicScore(data),
searchMethod: "astar"
});
console.log(result.dag);import { DenseMatrix, gin } from "@kanaries/causal";
const data = new DenseMatrix(rows);
const result = gin({
data,
indepTestMethod: "kci",
alpha: 0.05
});
console.log(result.causalOrder);import {
CausalGraph,
DenseMatrix,
FisherZTest,
GRAPH_KIND,
discoverGraph,
findAdjustmentSets,
identifyEffect,
falsifyGraph,
stabilityAnalysis
} from "@kanaries/causal";
const graph = CausalGraph.fromNodeIds(["X", "Y", "Z"], { kind: GRAPH_KIND.dag });
graph.addDirectedEdge("Z", "X");
graph.addDirectedEdge("Z", "Y");
graph.addDirectedEdge("X", "Y");
const data = new DenseMatrix(
Array.from({ length: 200 }, (_, index) => {
const t = index + 1;
const z = Math.sin(t / 8) + Math.cos(t / 13);
const x = 0.9 * z + Math.sin(t / 5) * 0.03;
const y = -0.8 * z + Math.cos(t / 7) * 0.03;
return [x, y, z];
})
);
const discovered = discoverGraph({
algorithm: "pc",
options: {
data,
ciTest: new FisherZTest(data),
nodeLabels: ["X", "Y", "Z"]
}
});
const adjustment = findAdjustmentSets({
graph: graph.toShape(),
treatment: "X",
outcome: "Y"
});
const identified = identifyEffect({
graph: graph.toShape(),
treatment: "X",
outcome: "Y"
});
const falsified = falsifyGraph({
graph: graph.toShape(),
data,
observedNodeOrder: ["X", "Y", "Z"]
});
const stability = stabilityAnalysis({
discovery: {
algorithm: "pc",
options: {
data,
ciTest: new FisherZTest(data),
nodeLabels: ["X", "Y", "Z"]
}
},
bootstrapSamples: 10,
seed: 42
});
console.log(discovered.summary);
console.log(adjustment.candidateSets);
console.log(identified.method);
console.log(identified.estimandSpec?.expression);
console.log(falsified.overallSummary);
console.log(stability.edgeFrequency);Current Step 3 task-workflow boundary:
- graph-analysis tasks are DAG-first
identifyEffect()supports backdoor, core frontdoor, zero-effect, and current-MVP non-identifiable resultsidentifyEffect()now acceptsbackend?: "auto" | "dag-first-mvp" | "dag-backdoor-only";autoresolves todag-first-mvp, whiledag-backdoor-onlyintentionally restricts identification to zero-effect and backdoor logiclistIdentificationBackends(),listIdentificationBackendsForGraphKind(),supportsIdentificationBackendGraphKind(),listIdentificationBackendDescriptors(), andgetIdentificationBackendDescriptor()expose the currently available identification backends, graph-kind compatibility, and theautoselection contract- explicit backend selection now rejects incompatible graph kinds instead of silently falling back
falsifyGraph()supports sanity checks plus implied conditional independence validation, not full permutation falsificationstabilityAnalysis()is a bootstrap robustness wrapper, not a graph correctness proof- invalid falsification and stability input contracts now fail explicitly instead of producing silent fallback behavior
See tasks/index.md and tasks/step3-checklist.md for the current accepted task-workflow surface. See tasks/result-contract.md for the stable result-object contract. See tasks/end-to-end-workflow.md and examples/step3-task-workflow.ts for the current minimal public workflow example. See tasks/backend-selection.md and tasks/operational-readiness.md for backend pinning guidance, validation commands, and evidence boundaries.
import {
DenseMatrix,
FisherZTest,
detectNodeRuntimeCapabilities,
nodeRuntime,
pc
} from "@kanaries/causal/node";
const data = new DenseMatrix(rows);
const result = pc({
data,
ciTest: new FisherZTest(data)
});
console.log(detectNodeRuntimeCapabilities());
console.log(nodeRuntime.supportsFileSystem);import {
DenseMatrix,
FisherZTest,
detectWebRuntimeCapabilities,
webRuntime,
pc
} from "@kanaries/causal/web";
const data = new DenseMatrix(rows);
const result = pc({
data,
ciTest: new FisherZTest(data)
});
console.log(detectWebRuntimeCapabilities());
console.log(webRuntime.supportsWebWorkers);import { nodeAlgorithmCatalog, isNodeAlgorithmSupported } from "@kanaries/causal/node";
console.log(nodeAlgorithmCatalog.map((entry) => entry.id));
console.log(isNodeAlgorithmSupported("pc"));import { webAlgorithmCatalog, isWebAlgorithmSupported } from "@kanaries/causal/web";
console.log(webAlgorithmCatalog.map((entry) => entry.id));
console.log(isWebAlgorithmSupported("calm"));The accepted V1 surface is intentionally narrower than full causal-learn:
PC: parity is covered on the selectedFisher-Z,Chi-square, andG-squarepathsGES: parity is covered on selectedGaussianBicScoreandBDeuScorepathsCD_NOD: committed path is deterministic domain-varyingFisher-ZExactSearch: committed path is Gaussian exact DAG searchGIN: includes standalone unconditionalkci, not conditional-kernel discoveryCAM_UV: accepted V1 smoother is portable additive spline backfitting, notpygam.LinearGAMRCD: accepted V1 optimizer is portable numeric MLHSICR, not SciPyfmin_l_bfgs_b
See:
For local development in this repository:
pnpm install
pnpm typecheck
pnpm build
pnpm test
pnpm test:integration
pnpm typecheck:examplesIf you change public behavior, graph semantics, CI/score contracts, parity baselines, or regression expectations, also run the heavy external regression suite in causal-parity:
cd ../causal-parity
CAUSAL_JS_SOURCE_ROOT=../causal-js pnpm testUse docs/tasks/operational-readiness.md as the release checklist for the DAG-first workflow surface.
If you want to inspect the publishable facade package locally:
pnpm pack:causalApache-2.0