Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

When is consensus among independent Cogs required?

Oliphant, 2026, §5.6

The manifest answers with a threshold: consensus_disagreement > 0.25. But the rule does not say what the number measures. Rather than choose for the author, the specification fixes the metric’s type (a fact-by-checker answer matrix of nullable Booleans, mapped to a scalar in [0, 1] against the factored threshold; the codomain is a checked constraint, TYPE-01) and exhibits three type-satisfying metrics with different semantics, evaluated on the same matrix. All three satisfy the type; none is thereby correct. They do not agree on whether the gate fires, and, on the ensemble the recorded run used, two of them cannot fire it at all.

The answer’s base type is a nullable Bool, chosen for type-checking support as much as for clarity. The semantics: given a datum as context, and irrespective of which Cog produced it, each checker answers one question, “do you agree with the datum?”. True is agreement, False is disagreement, and null is cannot tell (the same outcome the Track records as earl:cantTell). Checkers are factored apart from generators on purpose: it is strictly possible for a Cog to produce a datum and still answer False when asked whether that datum is true. One could assume a producer always files True about its own output; the specification does not, and a Cog’s answer about its own datum is one column like any other (clarifications logged with GAP-04 in the open-questions chapter). The matrix is rectangular, and a run with no facts scores zero.

A correctly constructed policy is not the same as a fit-for-purpose policy. What is right for the job here is an open algorithmic policy design question, likely to have no unique correct answer but many viable answers with different implications on incentives, and the right answer is not limited to these three.

1The type, in the model

The abstract definition carries the type signature and five property attributes (what the value is a fraction of, how a null is treated, monotone in dissent, saturates at unanimous dissent, invariant under ensemble size). It splits along one axiom into two branches: a DisagreementMetric measures how much dissent a run carries and is monotone in it; an UndecodabilityMetric measures whether facts resolve and is not. Three concrete definitions specialize the branches, each documenting its semantics and its incentive implication and redefining the attributes its branch leaves open. The split names the under-specification the first version of this chapter only exhibited: the original single class could not distinguish two different quantities that both fit the type (a finding of the second external review, ruled on as a refinement of GAP-04). The table is parsed from the model: each row is one definition with its doc, verbatim.

import sys; sys.path[:0] = [".", ".."]  # the repo root, from either cwd
import exhibits
exhibits.show_metric_definitions()
Loading...

The structural fact sits in the comparator oracle. Its metric slot is typed by the abstract definition and deliberately left unbound (part metric : ConsensusInadequacyMetric; with no concrete binding): the model states that a metric of this type is required and refuses to choose one (GAP-04 in the open-questions chapter). A deployment binds it, and in doing so picks a branch, which is a visible policy decision; the Track records which one a run used, and the matrix it ran on. The sharper punchline comes later in the chapter: two of the three type-satisfying bindings make the gate unreachable on the very ensemble the Track records.

exhibits.show_unbound_metric_slot()
part def ConsensusComparatorOracle :> OracleService {
    doc /* adjudicated: GAP-04 — the metric slot below is typed by
       the abstract ConsensusInadequacyMetric and deliberately left
       unbound: the model states that a metric of this type is
       required and refuses to choose one. A deployment binds it,
       choosing a branch (disagreement or undecodability) as a
       visible policy decision; the Track records which one a run
       used, and the matrix it ran on (GAP-12). */
    part metric : ConsensusInadequacyMetric;
    attribute returnedDisagreement : ScalarValues::Real;
    port reading : ReadingWrite;
}

2The three implementations

The same three semantics as running code, satisfying the declared type:

  • dissent-fraction counts every answer that is not True, null included: honest abstention is punished, so checkers are pushed to affirm rather than say cannot tell. On the paper’s own section 5.5 example (three Cogs, two must agree) it FIRES the gate at 0.25, contradicting the example’s intent.

  • strict-quorum-undecodable counts facts where no answer reaches a quorum of two matching non-null answers; null blocks quorum, so it escalates more, spending expert-review capacity. The quorum is the paper’s two-of-three, deliberately, and the metric is admissible for a three-checker ensemble only (with one checker, unanimous agreement scores 1; with nine, two agreements outvote seven dissents).

  • erasure-aware-undecodable treats null as an erasure, not an error: a lone expressed voice among abstentions decodes, so abstention shifts power to whoever still answers.

Both undecodability metrics are “consistent with the section 5.5 example”, and the reason is not flattering: on three checkers with no abstention they are identically zero (shown below). Consistency with the example is a consequence of never firing, not evidence of fitness.

Could the signature live in the model as a calculation rather than as annotation? Partly. The pinned evaluator accepts a calc def with typed scalar parameters (probed, 2026-09-05); carrying a nullable-Boolean matrix and three alternative bodies through it was not attempted. So the signature is carried as declared attributes in the model and as Python here, and the test suite holds the two equal. That is the same honest division as the wiring rules in chapter 2: the model states, code checks.

import inspect
for f in (exhibits._rectangular, exhibits.dissent_fraction,
          exhibits.strict_quorum_undecodable, exhibits.erasure_aware_undecodable):
    print(inspect.getsource(f))
def _rectangular(matrix):
    """True if there is anything to measure; raises on a ragged matrix."""
    if not matrix:
        return False
    if any(len(fact) != len(matrix[0]) for fact in matrix):
        raise ValueError("answer matrix must be rectangular: one answer per checker per fact")
    return True

def dissent_fraction(matrix):
    if not _rectangular(matrix):
        return 0.0
    dissents = total = 0
    for fact in matrix:
        dissents += sum(1 for v in fact if v is not True)
        total += len(fact)
    return dissents / total

def strict_quorum_undecodable(matrix):
    if not _rectangular(matrix):
        return 0.0
    undecodable = 0
    for fact in matrix:
        expressed = [v for v in fact if v is not None]
        if not expressed or max(expressed.count(v) for v in set(expressed)) < 2:
            undecodable += 1
    return undecodable / len(matrix)

def erasure_aware_undecodable(matrix):
    if not _rectangular(matrix):
        return 0.0
    undecodable = 0
    for fact in matrix:
        expressed = [v for v in fact if v is not None]
        if not expressed or max(expressed.count(v) for v in set(expressed)) * 2 <= len(expressed):
            undecodable += 1
    return undecodable / len(matrix)

3What each metric is, proved

The doc comments above make claims; the attributes make assertions; the cell below checks them. For each property the model declares, the implementation is enumerated over every answer matrix of up to two facts by three checkers (the test suite goes to three facts), and the declared value is compared with what the enumeration finds, with the minimal counterexample wherever a property fails to hold. Two results deserve a sentence each. Flipping one answer from agree to disagree can decrease both undecodability metrics: [T, F, null] scores 1.0 and [F, F, null] scores 0.0, because unanimous disagreement decodes. And a matrix of unanimous disagreement scores 0.0 under both, which is why they are not disagreement metrics and the class had to split.

exhibits.show_metric_properties()
Loading...
declared = computed: 15 of 15 cells agree (the model asserts, the enumeration checks; the test suite repeats this over facts <= 3)

4Two of three bindings cannot fire the gate on this ensemble

The recorded run names three Cogs. With three checkers over a two-valued answer domain, some value occurs at least twice, so the strict quorum is always met, and the majority strictly exceeds half, so the erasure-aware rule always decodes. Both undecodability metrics are identically zero on every answer matrix without an abstention. No threshold makes GATE-02 fire under them; only a null can. This is not “the bindings disagree at the margin”; it is a structural difference in what the gate is capable of doing under each binding, on the exact configuration the Track records. The cell reads the ensemble size from the Track and enumerates.

exhibits.show_gate_reachability()
ensemble size from track/run-001.trig cogs_invoked: 3 checkers; gate fires when the metric > 0.25

dissent-fraction          : no abstention -> values {0, 0.333, 0.667, 1}; with abstention -> values {0, 0.333, 0.667, 1}
strict-quorum-undecodable : no abstention -> values {0}; with abstention -> values {0, 1}
erasure-aware-undecodable : no abstention -> values {0}; with abstention -> values {0, 1}

with 3 checkers and no abstention, GATE-02 cannot fire under: strict-quorum-undecodable, erasure-aware-undecodable
proposition (proved by enumeration, here and in the test suite for every odd ensemble size up to 7): over a two-valued domain, three expressed answers always contain a repeated value, so quorum-2 is met and the majority strictly exceeds half. Both undecodability metrics are identically zero; only abstention can make them nonzero. Their consistency with the section 5.5 example is a consequence of this, not evidence in their favour.

5The convention that binds model to code

The model cannot hold Python, and this walkthrough does not pretend it does. The binding between a part def and the function that implements its logic is a stated, mechanical convention, checked in both directions. A definition’s metric id is derived from its name (DissentFractionMetric becomes dissent-fraction); the same id keys the implementation mapping in exhibits.py and is the value a Track records in vfr:metric. The cell verifies the correspondence is one-to-one — every model definition has an implementation, every implementation has a definition — and that the metric run-001 cited resolves to a bound implementation. In a deployment, the same id is how the comparator oracle’s configuration names the metric it bound into the open slot.

exhibits.show_metric_binding()
Loading...
model <-> implementation binding: OK (3 definitions, 3 implementations)
run-001 recorded metric: strict-quorum-undecodable -> bound implementation: exhibits.strict_quorum_undecodable

6One matrix, three answers

The same answer matrix, the threshold read from the model’s single point of definition, and different gate decisions. The headline matrix is constructed so that no metric lands on the threshold. A second matrix is kept apart as the boundary case: under it one binding scores exactly 0.250, and the decision turns entirely on the comparator, which is the manifest’s own strict >, verbatim from the section 5.6 rule (so its strictness is source text, not a judgment call). The Track records which metric a run actually used; recording is not endorsing.

One more thing the single point of definition conceals: the three metrics do not share a denominator. dissent-fraction is a fraction of answers; both undecodability metrics are fractions of facts. The one number 0.25 therefore means different things under different bindings. The threshold stays where the manifest puts it, on the policy, and each metric now declares its denominator as a model attribute so the difference is a model fact rather than a reading of the code (GAP-11 in the open-questions chapter).

exhibits.evaluate_metrics()
type signature: ConsensusInadequacyMetric = (fact x checker answer matrix of nullable Bool; True = agree, False = disagree, null = cannot tell) -> [0, 1]

same answer matrix for all three metrics (8 facts x 3 checkers; fabricated example data, constructed to separate the metrics):
  f1: True  True  True 
  f2: True  True  True 
  f3: True  True  True 
  f4: True  True  False
  f5: True  False null 
  f6: True  null  null 
  f7: False null  null 
  f8: True  False False
threshold (from the model, single point of definition): > 0.25

dissent-fraction          : 0.417 -> gate fires: True
strict-quorum-undecodable : 0.375 -> gate fires: True
erasure-aware-undecodable : 0.125 -> gate fires: False

the boundary case (a second matrix, kept apart from the headline): one binding lands exactly on the threshold, and the comparator is the manifest's own strict >, verbatim from the section 5.6 rule —
  f1: True  True  True 
  f2: True  True  True 
  f3: True  True  True 
  f4: True  True  False
  f5: True  True  null 
  f6: True  False null 
  f7: False null  True 
  f8: True  null  null 
dissent-fraction          : 0.333 -> gate fires: True
strict-quorum-undecodable : 0.375 -> gate fires: True
erasure-aware-undecodable : 0.250 -> gate fires: False  (exactly on the boundary)

the paper's own section 5.5 example, as one fact (three Cogs classify a document, two agree):
dissent-fraction          : 0.333 -> gate fires: True
strict-quorum-undecodable : 0.000 -> gate fires: False
erasure-aware-undecodable : 0.000 -> gate fires: False

7The recorded run, under all three bindings

The Track records the consensus reading’s metric and its output. Since the second external review it also records its input: the answer matrix the comparator ran on (GAP-12 in the open-questions chapter). So the recorded value can be recomputed by anyone holding the record, and the question this chapter raises can be asked of the run rather than of fabricated data: what would the other two bindings have said about these answers? The matrix is fabricated like the rest of run-001, but it is the run’s, and the audit below is the one an auditor could perform.

exhibits.show_run_001_under_all_bindings()
run-001 recorded: metric strict-quorum-undecodable, value 0.1, matrix 10 facts x 3 checkers:
  f1: True  True  True 
  f2: True  True  True 
  f3: True  True  True 
  f4: True  True  False
  f5: True  True  False
  f6: True  True  False
  f7: True  True  False
  f8: True  True  False
  f9: True  True  False
  f10: True  null  null 

run-001 recomputed under strict-quorum-undecodable: 0.100 -> equals the recorded value: True

the same answers under every binding the model admits:
  dissent-fraction          : 0.267 -> GATE-02 fires: True
  strict-quorum-undecodable : 0.100 -> GATE-02 fires: False  (the binding this run used)
  erasure-aware-undecodable : 0.000 -> GATE-02 fires: False

8How often the choice matters

One matrix proves the bindings can disagree. It says nothing about how often, or where. The sweep below draws answer matrices from a stated generative model (independent cells; abstain with probability p_abstain, otherwise dissent with probability p_dissent), twelve facts by the recorded three checkers, and reports how often the three bindings fail to agree on the gate decision, and how often each fires. The marginal firing rates are the number a policy designer can budget against: the expert-review load each binding would impose on the same stream of runs. The heaviest binding fires about five times as often as the lightest. That is the incentive claim from the bullets above, as a quantity.

The caveat is stated in the cell’s docstring and repeated here: real checkers are correlated, and correlated dissent is exactly what a consensus gate exists to catch, so these numbers are a lower bound on how much the choice matters. A correlated-checker variant is logged as future work in the judgment record.

exhibits.show_metric_sensitivity()
generative model: independent cells, 12 facts x 3 checkers, 500 trials per cell, gate fires when metric > 0.25, seed 20260905

P(bindings disagree about whether GATE-02 fires)
  p_abstain \ p_dissent    0.00   0.10   0.20   0.30   0.40   0.50
                  0.00   0.000  0.000  0.170  0.666  0.934  1.000
                  0.10   0.004  0.140  0.530  0.874  0.950  0.944
                  0.20   0.176  0.568  0.816  0.874  0.816  0.798
                  0.30   0.696  0.870  0.836  0.770  0.694  0.622
                  0.40   0.944  0.906  0.800  0.660  0.600  0.584
                  0.50   0.952  0.788  0.668  0.550  0.500  0.426

marginal firing rate per binding, averaged over the grid (the expert-review load each binding would impose):
  dissent-fraction           0.791
  strict-quorum-undecodable  0.476
  erasure-aware-undecodable  0.149

maximum disagreement 1.000 at p_abstain=0.0, p_dissent=0.5. In the no-abstention row the maximum is 1.000: the reachability proposition showing up statistically, since there the two undecodability bindings never fire and dissent-fraction fires whenever dissent is common
References
  1. Oliphant, T. (2026). The Distributed AI Economy: Intelligence Hubs, Frames, Cogs, Ops, and the Accountability Plane [Techreport]. OpenTeams.