@takk/bayesconsensus
v1.0.0
Published
Dawid-Skene consensus for multi-agent and multi-LLM disagreement: estimate the true label and per-agent reliability with calibrated confidence. Zero dependencies, node-free core, TypeScript-first.
Maintainers
Readme
BayesConsensus
Universal, zero-runtime-dependency NPM library and CLI that resolves disagreement across a fleet of agents, judges, or models with the Dawid-Skene model. It estimates each voter's reliability and the true answer at the same time, from the votes alone, and returns a consensus label with a calibrated confidence and a per-agent reliability audit, for Massive Intelligence (IM) systems.
BayesConsensus is the consensus layer for multi-agent Massive Intelligence (IM). When you ask several agents, judges, or models the same question and they disagree, you need a single answer you can defend. Plain majority vote is the reflexive choice, and it is blind: it counts heads, so a bloc of unreliable voters outvotes a smaller set of reliable ones, and it returns a bare tally with no honest measure of how sure to be. BayesConsensus uses the right primitive. The Dawid and Skene (1979) expectation-maximization algorithm estimates, simultaneously and with no ground truth, the latent true label of every item AND a confusion matrix for every agent, the diagonal of which is that agent's reliability. It then returns the maximum-a-posteriori consensus label with a calibrated confidence, weighting each vote by how reliable its caster has proven to be. It is the jury foreman for a fleet of non-human entities.
Core promise: zero required runtime dependencies, two-line setup, reliability-weighted consensus instead of a blind majority, a calibrated confidence and full posterior on every verdict, a per-agent confusion matrix as a reliability audit, missing votes treated as first-class, online updates as votes arrive, calibration measured by simulation rather than asserted, a tamper-evident SHA-256 audit trail, a node-free core that runs in Node, edge runtimes, and the browser, ESM plus CJS dual distribution, and a framework-agnostic tool adapter that binds to any MCP server or tool-calling backend with no hard dependency.
Why BayesConsensus
Aggregating votes from a fleet of agents is an estimation problem, not a counting problem. You want the answer the reliable voters point to, and you want to know which voters are reliable, without ever being told the right answers in advance. The two reflexive approaches both fail. Majority vote (count every voter equally) lets an unreliable bloc win and reports a tally that is not a probability, three of four agents saying "safe" is not a 75 percent chance of safe. Trusting one designated expert throws away the wisdom in the rest of the fleet and has no recourse when the expert is wrong. BayesConsensus uses the correct primitive: it infers each agent's reliability from the pattern of agreement and disagreement across many items, and weights every vote by that reliability while it infers the true labels, in one coupled estimation.
What sets it apart from counting votes:
- Weights by reliability, not by headcount. Each agent gets a confusion matrix learned from the data, and its votes count in proportion to its estimated reliability. A rubber-stamp agent that always says the same thing is down-weighted automatically; a consistently accurate agent pulls hard. The reliable minority can overrule the unreliable majority.
- Learns who is reliable with no answer key. Reliability is recovered by expectation-maximization from the votes alone, never from labelled ground truth. You do not tell BayesConsensus which agents are good; it finds out from the structure of their agreement across items.
- Returns a calibrated confidence, not a tally. Every verdict carries the full posterior over labels, and the winning probability is a calibrated confidence you can threshold, route on, or escalate from, not a raw fraction of votes.
- Audits every agent. The per-agent confusion matrix and a single reliability score are first-class outputs. This is how you find the rubber stamp, the contrarian, and the silently-degrading model in your fleet.
- Treats missing votes as first-class. Agents need not vote on every item, and an item need not be seen by every agent. The model sums only over the votes that were actually cast, never imputes or pads, so a sparse, real-world voting matrix is the normal case.
- Updates online. Observe votes as your agents report them and query the current verdict at any time; the fit runs lazily and the calibrated confidence sharpens as evidence accumulates. The full state is the label space plus the votes, so a snapshot round-trips through JSON with no hidden state.
- Measures its own calibration. A confidence is only trustworthy if it matches the empirical accuracy at that confidence, so calibration is measured by simulation, drawing a fleet from known reliabilities, fitting the model, and scoring accuracy and Brier against the truth, not asserted.
- Proves what was decided. A tamper-evident SHA-256 hash-chained audit trail of consensus verdicts and decisions, the evidence a review asks for when a non-human entity resolved a disagreement on your behalf.
- Checks its own assumption. Dawid-Skene assumes agents err independently given the true label, the assumption a fleet of near-identical models breaks. The built-in diversity diagnostic measures the error correlation across the fleet, reports the effective number of independent voters, and warns when the agreement is lockstep, so an inflated confidence is caught rather than trusted.
- Updates incrementally. A streaming
OnlineConsensuskeeps running sufficient statistics and folds each batch in with a warm-started EM step, so reliability evolves as data arrives; optional exponential forgetting lets it adapt when an agent's accuracy drifts, rather than averaging the drift away. - Tells you when not to use it. When every agent is equally weak, there is no reliability signal to learn and majority vote is the better baseline; the benchmark measures exactly this regime and the model documents it rather than overclaiming.
Install
pnpm add @takk/bayesconsensus
# or: npm install @takk/bayesconsensus
# or: yarn add @takk/bayesconsensus
# or: bun add @takk/bayesconsensusThe core has zero required runtime dependencies. Every @takk sibling is an optional peer; install only what you compose with.
Quickstart
import { ConsensusModel } from "@takk/bayesconsensus";
// Four agents label whether a code snippet is safe. Three are competent; one is a rubber stamp.
const jury = new ConsensusModel({ labels: ["safe", "unsafe"] });
jury.observe("snippet-1", "reviewer-a", "unsafe");
jury.observe("snippet-1", "reviewer-b", "unsafe");
jury.observe("snippet-1", "reviewer-c", "unsafe");
jury.observe("snippet-1", "rubber-stamp", "safe"); // always says "safe", whatever the truth
const verdict = jury.consensus("snippet-1");
console.log(verdict.label, verdict.confidence, verdict.distribution);
// "unsafe", ~0.94, { safe: 0.06, unsafe: 0.94 } -- the rubber stamp's dissent is down-weightedobserve folds one vote in (an agent assigning a label to an item). consensus returns that item's maximum-a-posteriori label, its calibrated confidence, and the full posterior, fitting the Dawid-Skene model lazily on first query. The verdict weights each vote by the caster's learned reliability, so a single unreliable dissenter does not flip a confident answer.
Aggregate the whole fleet, and audit every agent
import { ConsensusModel } from "@takk/bayesconsensus";
const jury = new ConsensusModel({ labels: ["yes", "no"] }).observeVotes(votes);
const report = jury.aggregate(); // fit once, return every item's verdict and every agent's reliability
report.converged; // did EM converge
report.classPrior; // the inferred prevalence of each label across the fleet
report.items; // per item: consensus label, confidence, full posterior
report.agents; // per agent: confusion matrix, accuracy, per-class accuracy
const stamp = jury.reliability("rubber-stamp");
stamp.accuracy; // a single reliability score in [0, 1]
stamp.confusionMatrix; // the learned K x K matrix; the diagonal is per-class accuracy
stamp.perClassAccuracy; // accuracy broken down by true labelaggregate fits the model once and returns a posterior for every item plus a reliability report for every agent. reliability exposes one agent's learned confusion matrix and accuracy. This is the audit a multi-agent system needs: it surfaces the rubber stamp, the contrarian, and the model whose accuracy is quietly drifting.
Majority vote versus Dawid-Skene
import { fitDawidSkene, majorityVote } from "@takk/bayesconsensus";
const mv = majorityVote(votes); // the blind baseline: count heads, ties broken deterministically
const fit = fitDawidSkene(votes, { // the reliability-weighted estimate
labels: ["a", "b", "c"],
mode: "soft", // "soft" (default, calibrated) or "hard" (Fast Dawid-Skene, faster)
});
fit.consensus.get("item-1"); // { item, label, confidence, distribution }
fit.confusion; // per-agent confusion matrices
fit.classPrior; // inferred label prevalence
fit.converged; // convergence of the marginal log-likelihoodUse majorityVote for the free baseline and fitDawidSkene for the functional core when you want the raw fit rather than the ConsensusModel facade.
Online updates, and serialization for free
import { ConsensusModel } from "@takk/bayesconsensus";
const jury = new ConsensusModel({ labels: ["approve", "reject"] });
jury.observe("pr-42", "agent-1", "approve");
jury.observe("pr-42", "agent-2", "reject");
jury.consensus("pr-42").confidence; // ~0.50, a genuine split
jury.observe("pr-42", "agent-3", "approve");
jury.observe("pr-42", "agent-4", "approve");
jury.consensus("pr-42").confidence; // sharpens as agreement accrues
// The full state is the label space plus the votes; a snapshot is plain JSON.
const restored = ConsensusModel.fromSnapshot(JSON.parse(JSON.stringify(jury.snapshot())));Observe votes as they arrive and query at any time. The fit is lazy and the confidence tightens with evidence. A snapshot is the label space and the votes, so it serializes and restores with no hidden state.
Check the fleet for independence
Dawid-Skene assumes agents err independently given the true label. A fleet of near-identical models breaks that assumption, and their agreement then overstates the evidence, inflating the confidence. The diversity diagnostic measures it.
import { ConsensusModel, diversityReport, temperDistribution } from "@takk/bayesconsensus";
const model = new ConsensusModel({ labels: ["yes", "no"] }).observeVotes(votes);
const report = model.diversity();
report.meanAgreement; // raw lockstep, reference-free; near 1 means the fleet votes together
report.meanErrorCorrelation; // how often agents are wrong on the same items, the independence-breaking pattern
report.effectiveVoters; // N / (1 + (N - 1) * correlation): the headcount discounted for correlation
report.confidenceDiscount; // effectiveVoters / N, the factor to temper an over-confident verdict by
report.independenceHolds; // false when the agents fail together
report.warning; // a plain-language caution, present only when independence is doubtful
// Discount an over-confident verdict back toward honesty.
const verdict = model.consensus("item-1");
const honest = temperDistribution(verdict.distribution, report.confidenceDiscount);The strongest form, diversityReport(votes, reference), takes a trusted reference labelling (a small labeled set) and measures error correlation directly. Without ground truth, meanAgreement is the blunt, always-identifiable signal: a unanimous-but-wrong fleet of similar models cannot be told apart from a unanimous-and-right one from the votes alone, so high agreement among models that share training data is itself the warning.
Stream votes with OnlineConsensus
ConsensusModel re-fits from scratch on each query. OnlineConsensus is the true incremental fit: it keeps running sufficient statistics and folds each batch in with a warm-started EM step, so reliability evolves as data arrives. Optional exponential forgetting lets it adapt when an agent's accuracy drifts.
import { OnlineConsensus } from "@takk/bayesconsensus";
const online = new OnlineConsensus({ labels: ["yes", "no"], forgetting: 0.7 });
// Fold a batch in; get the verdicts for that batch, scored with the just-updated model.
const verdicts = online.update([
{ item: "claim-1", agent: "model-a", label: "yes" },
{ item: "claim-1", agent: "model-b", label: "no" },
]);
online.reliability("model-a"); // evolves with every batch
online.classPrior(); // the current label prevalenceWith forgetting below 1 the accumulated counts decay before each batch, so recent behavior outweighs the stale past, the model tracks an agent that degrades rather than averaging it with its better history.
Entry points
Fourteen subpath exports, each importable on its own. The core is node-free; only node touches a Node built-in.
| Import | What it gives you |
|---|---|
| @takk/bayesconsensus | The full toolkit barrel: the facade, the Dawid-Skene core, confusion and majority primitives, calibration, the diversity diagnostic, streaming, the adapter, and the audit chain. |
| @takk/bayesconsensus/consensus | ConsensusModel, the facade most callers use: observe, fit, consensus, reliability, aggregate, diversity, snapshot. |
| @takk/bayesconsensus/dawidskene | The Dawid-Skene EM core: fitDawidSkene, the log-space E-step and M-step, soft and hard modes. |
| @takk/bayesconsensus/confusion | Confusion-matrix construction, diagonal Dirichlet smoothing, row-normalization, and the log form. |
| @takk/bayesconsensus/vote | The branded ids and the VoteSet collector that builds the indexed vote matrix EM consumes. |
| @takk/bayesconsensus/majority | majorityVote and weighted-vote baselines, also the deterministic EM initializer. |
| @takk/bayesconsensus/calibration | Simulation-measured accuracy versus majority, Brier score, and reliability bins. |
| @takk/bayesconsensus/diversity | diversityReport and temperDistribution, the error-correlation and effective-voter independence check. |
| @takk/bayesconsensus/online | OnlineConsensus, the streaming incremental fit with optional exponential forgetting. |
| @takk/bayesconsensus/adapter | The framework-agnostic tool descriptor and JSON-safe handler, name bayes_consensus. |
| @takk/bayesconsensus/audit | Append-only SHA-256 hash-chained audit log of consensus verdicts, via Web Crypto. |
| @takk/bayesconsensus/node | JSON and CSV loaders for vote files. |
| @takk/bayesconsensus/edge | The node-free core, re-exported for edge runtimes and the browser. |
A tool for a non-human entity
The adapter exposes BayesConsensus as a framework-agnostic tool a non-human entity can call to resolve disagreement across the ensemble it operates. The descriptor shape, a name, a description, a JSON Schema, and a handler, matches what MCP servers and tool-calling APIs expect, and input arriving from a model is parsed defensively. Only votes (item, agent, label) are accepted, never any other agent state.
import { bayesConsensusTool } from "@takk/bayesconsensus/adapter";
// Register bayesConsensusTool with your MCP server or tool-calling API; its name is "bayes_consensus".
const result = bayesConsensusTool.handler({
votes: [
{ item: "claim-1", agent: "model-a", label: "true" },
{ item: "claim-1", agent: "model-b", label: "true" },
{ item: "claim-1", agent: "model-c", label: "false" },
],
labels: ["true", "false"],
query: "claim-1", // omit to get the full report instead of one item
});This is the loop a non-human entity (NHE) closes over the fleet of agents it coordinates: it collects disagreeing votes, weights them by learned reliability, and returns a defensible verdict with a confidence, never trusting the loudest bloc by default. It is the disagreement-resolution primitive an ensemble of non-human entities needs, the piece beyond OpenClaw, Hermes Agent, and Claude Code.
Tamper-evident audit trail
import { AuditChain, verifyChain } from "@takk/bayesconsensus/audit";
const chain = new AuditChain();
await chain.append({ kind: "consensus", item: "claim-1", label: "true", confidence: 0.93 });
await chain.append({ kind: "decision", item: "claim-2", label: "false" });
await verifyChain(chain.toArray()); // { valid: true }, until any entry is alteredEach consensus verdict and each decision is recorded append-only and chained to the previous one through a SHA-256 hash of its canonical form. Any later edit, deletion, or reordering breaks the chain and verifyChain reports the first broken entry. The seal is an integrity seal, not a digital signature: it proves the record was not tampered with, not who produced it. The chain uses the Web Crypto API, so the audit surface runs in Node, edge runtimes, and the browser.
CLI
BayesConsensus ships a command-line tool that runs the real model, with every number produced by execution.
# Fit the model and print every item's consensus verdict from a votes file.
npx @takk/bayesconsensus aggregate votes.json --labels yes,no
# Print one item's consensus label, confidence, and full posterior as JSON.
npx @takk/bayesconsensus consensus votes.json item-1 --labels yes,no --json
# Print each agent's learned reliability and confusion matrix.
npx @takk/bayesconsensus reliability votes.json --labels yes,no
# Verify a sealed audit chain.
npx @takk/bayesconsensus audit-verify chain.jsonA votes file is a JSON array of { item, agent, label } or a CSV with item,agent,label columns. Exit codes are explicit: 0 ok, 2 usage or input error, 20 a broken audit chain. See examples/ for runnable demos and benchmarks/ for the value benchmark.
The math, in one paragraph
Items i, agents j, and classes c and k. Each item has a latent true label, and each agent has a confusion matrix theta_j where theta_j[c,k] is the probability that agent j votes k when the truth is c, so the diagonal is the agent's per-class reliability. A class prior gives each label's prevalence. Expectation-maximization alternates two steps. The E-step computes, for every item, the posterior over true labels by combining the log class prior with the log-likelihood of every observed vote under the current confusion matrices, entirely in log-space and normalized per item by log-sum-exp, so raw probabilities are never multiplied; the winning label is the consensus and its posterior probability is the calibrated confidence. The M-step re-estimates each agent's confusion matrix as the reliability-weighted, Dirichlet-smoothed tally of how it voted against the current soft labels, and sets the class prior to the mean of the soft labels. The matrices are seeded by a diagonal-favouring Dirichlet prior and the labels are initialized from the majority vote with the M-step run first, both for determinism and to avoid label switching. The marginal observed-data log-likelihood is monotone non-decreasing across iterations and is the convergence signal. Missing votes are summed over only where present, never imputed. A faster hard-assignment variant (Fast Dawid-Skene) collapses each E-step posterior to its argmax; the soft variant is the default because it stays calibrated.
Benchmark
benchmarks/ simulates fleets of seven agents voting on 150 items, sweeps four reliability regimes, and averages over 25 seeds per regime. It scores plain majority vote and Dawid-Skene against the known ground truth. Higher accuracy is better.
| Regime | Majority vote | Dawid-Skene | Advantage | |---|---|---|---| | mixed-binary (reliable minority vs unreliable bloc) | 81.4% | 95.4% | +14.0 pp | | mixed-ternary (heterogeneous reliability, 3 classes) | 89.2% | 96.0% | +6.7 pp | | uniform-strong (every agent competent) | 98.6% | 98.6% | +0.0 pp | | uniform-weak (every agent equally weak) | 71.3% | 64.8% | -6.5 pp |
Dawid-Skene's advantage is largest where reliability varies across the fleet, exactly the case majority vote handles worst: a reliable minority against an unreliable majority bloc. Where every agent is equally competent there is no reliability signal and the two methods tie. Where every agent is equally weak there is still no signal to learn, and Dawid-Skene can latch onto noise and underperform plain majority, so the honest guidance is to use the simple baseline when you expect a uniform fleet. Numbers are measured, not asserted. Run it yourself: pnpm run build && node benchmarks/value-benchmark.mjs.
A second caveat is structural: Dawid-Skene assumes agents err independently given the truth. Models that share training data make correlated mistakes, so a fleet of near-identical models can be confidently wrong together. Favour genuinely diverse voters, and read the calibration output before trusting a high confidence.
Quality
- 86 tests across 16 suites, all passing under Vitest on Node 20, 22, and 24, including an adversarial hardening suite (degenerate votes, thousands of votes on one item, a 12-label space, and invariants over 20 random scenarios).
- Coverage: statements 97.14%, branches 86.30%, functions 97.71%, lines 96.89%.
- Lint clean under Biome.
- Typecheck clean under TypeScript in maximum strict mode (
exactOptionalPropertyTypes,useUnknownInCatchVariables,noUncheckedIndexedAccess,noPropertyAccessFromIndexSignature). publintclean and@arethetypeswrong/cligreen across all fourteen subpaths.size-limitunder budget on every bundle (brotli core 6.74 kB, diversity 986 B, online 1.89 kB).- Distribution smoke test exercising the compiled ESM and CJS artifacts and the compiled CLI spawned as a single Node process.
- Zero required runtime dependencies; a node-free core that runs in Node, edge runtimes, and the browser; dual ESM plus CJS; TypeScript-first.
See SPEC.md for the formal specification, public surface, and stability promise.
FAQ
Why not just take the majority vote? Because majority vote counts every agent equally, so an unreliable bloc outvotes a reliable minority, and it returns a tally rather than a probability. BayesConsensus learns each agent's reliability from the pattern of agreement across items and weights votes accordingly, then reports a calibrated confidence. The benchmark shows it beating majority by up to 14 percentage points where reliability varies across the fleet.
How does it learn reliability with no ground truth? By expectation-maximization. It alternates between estimating the true labels given current per-agent reliabilities and re-estimating those reliabilities given the current label estimates, until the likelihood converges. Agreement structure across many items is enough to recover who is reliable, no answer key required.
Is the confidence a real probability?
Yes. It is the posterior probability of the winning label under the fitted model, computed in log-space and normalized per item, not a fraction of votes. Calibration is measured by simulation (see measureCalibration), and the benchmark reports the Brier score.
When should I NOT use it?
When you expect every agent to be equally reliable. With no spread in reliability there is no signal for the model to exploit, and on a uniformly weak fleet it can underperform plain majority. Be cautious too when your voters are near-identical models that share training data, since their errors are correlated and the independence assumption is violated; run model.diversity() to measure that correlation, read the effective number of independent voters, and discount the confidence with temperDistribution when the fleet is lockstep.
Do agents have to vote on every item? No. Missing votes are first-class. The model sums only over the votes that were actually cast, so a sparse voting matrix, the normal case for a real fleet, is handled directly.
Does this work in Cloudflare Workers, Vercel Edge, Bun, and Deno?
Yes. The core is node-free; the audit seal uses the Web Crypto API, not node:crypto. Import @takk/bayesconsensus or @takk/bayesconsensus/edge anywhere with Web Crypto. Only @takk/bayesconsensus/node requires Node.
Contributing
See .github/CONTRIBUTING.md for the contributor guide. Substantive proposals open a GitHub Issue first; trivial fixes can go straight to a PR. All commits require DCO sign-off (git commit -s). Non-trivial contributions are governed by the Contributor License Agreement.
Community and support
- Issues and feature requests. Open a GitHub issue at
davccavalcante/bayesconsensus/issues. Include the package version, a minimal reproduction, expected versus actual behavior, and where relevant theconsensus()oraggregate()output. - Security disclosures. Do not open public issues for vulnerabilities. Follow the responsible-disclosure flow in
SECURITY.md, contact[email protected](or[email protected]) with the[SECURITY]prefix. - Code of Conduct. This project follows the Contributor Covenant 2.1. Participation in any BayesConsensus space implies agreement.
- Contributions. All non-trivial contributions go through the Contributor License Agreement. Tests, lint, typecheck, and build must be green before review (
pnpm verify).
Author
Created by David C Cavalcante, [email protected] (preferred), [email protected] (Takk relay), linkedin.com/in/hellodav, x.com/davccavalcante, takk.ag.
BayesConsensus is part of a broader portfolio of NPM packages targeting Massive Intelligence (IM) native infrastructure for 2026-2030, built at Takk Innovate Studio. Resolving disagreement across a fleet of non-human entities, with a defensible verdict and a measured confidence, is foundational infrastructure for that era, the layer that turns a crowd of agents into a jury.
Related research by the author
The architectural philosophy behind BayesConsensus, treating a fleet of agents as a jury whose members have different and learnable reliability rather than as interchangeable voters, echoes the author's research frameworks:
- MAIC (Massive Artificial Intelligence Consciousness), a systemic intelligence framework designed to coordinate, supervise, and govern large-scale Massive Intelligence ecosystems, providing global context awareness, alignment, and orchestration across multiple models, agents, and decision layers.
- HIM (Hybrid Entity Intelligence Model), a hybrid intelligence layer that integrates Massive Intelligence systems with human-defined logic, rules, heuristics, and strategic intent, interpreting objectives and structuring decision-making before and after model execution.
- NHE (Noumenal Higher-order Entity), a non-human cognitive entity with a defined functional identity and operational agency within a Massive Intelligence ecosystem, operating through coordinated intelligence layers while maintaining a non-anthropomorphic identity.
These frameworks are published independently of BayesConsensus and are separate works:
- Research papers: The Soul of the Machine, Beyond Consciousness in LLMs, The Cave of Silence.
- PhilPapers profile: David Cortes Cavalcante.
- Hugging Face: TeleologyHI.
- GitHub: davccavalcante, Takk8IS.
Sponsors
Join the journey as the portfolio continues to ship Massive Intelligence (IM) native infrastructure. Your support is the cornerstone of this work.
- Sponsor on GitHub: github.com/sponsors/davccavalcante
- USDT (TRC-20):
TS1vuhMAhFpbd7y68cu5ZtP9PsXVmZWmeh
Privacy
BayesConsensus runs entirely inside your own process and infrastructure. It makes no outbound calls to the author, collects no telemetry, and ships no analytics. The only state it holds is the votes you feed it. See PRIVACY.md for the full data-handling notice, including how the optional file loaders read vote files from disk.
License
Licensed under the Apache License 2.0. See LICENSE for the full text and NOTICE for attribution and third-party component licenses. You may use, modify, and distribute the code under the terms of that license, including its patent grant and attribution requirements.
