An evidence-control plane for consequential agent actions. It lets clear policy decisions move quickly, demands verifiable evidence when uncertainty matters, counts independent evidence rather than repeated AI opinions, and prevents unsupported actions from reaching a runtime. It is vendor-neutral and can be embedded without replacing an existing model or agent framework.
Experimental preview: ready for simulation, adversarial evaluation, and shadow observation. It is not a production-security release. Production enforcement requires a real verifier, authorized adapters, durable state, protected runtime paths, and independent review.
ReleaseWarden is the first narrow commercial adapter: evidence-backed production deployment approval for GitHub Actions. GitHub calls the App before a protected environment deploys; ReleaseWarden authenticates the webhook, collects check runs for the exact commit, applies deterministic requirements, groups evidence by explicitly configured independence domain, and returns a Gate decision.
The default shadow mode records what ReleaseWarden would decide while sending
GitHub a non-blocking approval. enforce mode sends the actual approval or
rejection. Neither mode treats repeated checks from one configured control
domain as multiple independent roots. Start with the offline, zero-effect test:
python3 -m minority_prophet.release_warden simulate \
--policy examples/releasewarden/policy.shadow.json \
--payload examples/releasewarden/deployment-request.json \
--checks examples/releasewarden/checks.pass.jsonSee RELEASEWARDEN.md for GitHub App registration, the
one-repository test, handoff instructions, known limitations, and the exact
point where a private-repository test requires GitHub Enterprise.
What you are testing: whether the same MP Gate logic remains fail-closed when evidence is copied, contradictory, malformed, weak, or bound to the wrong action—and whether its shadow decisions reveal useful differences from your existing workflow. You are not testing signature security: the laboratory verifier is deliberately simulated and clearly labeled.
People and coding agents can attack the engine with their own JSON or JSONL—no API key or real runtime required:
python -m minority_prophet.byom_cli attack \
--evidence examples/byom/messy-evidence.jsonl \
--policy examples/byom/policy.json \
--feedback byom-feedback.jsonAttack and shadow commands are simulation-only and always perform zero runtime
effects. Shadow mode consumes copies of workflow events and reports agreements
and disagreements without joining the execution path. Read
AGENT-QUICKSTART.md for the agent handoff contract,
input formats, adversarial mission, shadow adapter, privacy-safe feedback, and
the explicit path from evaluation to an installed enforcement integration.
Refuse manufactured consensus. A drop-in gate for multi-agent systems that counts independent, attested evidence roots instead of voices — so seven agents repeating one unverified guess, with the repetition on record, can never outvote two agents who actually checked.
Research status: REPLICA results pending canonical re-runs. The gate is a reference implementation; its library tests do not establish production safety.
Nine messages say a deploy is SAFE? Voices: SAFE 7 — UNSAFE 2
Seven of them trace to one security scan. Roots: SAFE 1 — UNSAFE 2
Two trace to independent failing test runs. Verdict: BLOCK (flip cost: 1 forged root)
Multi-agent consensus is currently counted by voice, and voices are free: copies, re-broadcasts, summaries, sybils, and conformity-injection attacks (e.g. MAD-Spear) all inflate agreement without adding evidence. This gate implements evidence-root aggregation, whose core properties are machine-checked under the stated model, not tuned:
- T2 — Copy invariance: duplicating a claim whose parent is recorded can never change the verdict. The hypothesis is part of the theorem: a copy with no recorded parent is indistinguishable from a new root and is counted as one.
- T1 — Immunity: given side-consistent attestations and an unchanged set of
origins, the verdict is invariant under arbitrary corruption of who-copied-whom
among the rest. Lineage accuracy between claims is irrelevant; only origins
matter.
- The origin-set hypothesis is load-bearing and was previously omitted here. Corruption that creates or destroys an origin — orphaning a claim so it becomes a root, or attaching a root under a parent — is outside T1, and the verdict may change. What T1 buys is indifference to who copied whom, not indifference to how many independent origins exist. Tolerance to origin-set error is a separate theorem, T5, with its own bound: a verdict survives if its margin exceeds the number of origin-set changes.
- See
FORMAL.md. The normative statement of every hypothesis is the theorem ledger in the research repository, not this list.
- T4 — Margin flip condition: every decision ships with its attack price,
and there are two prices, because there are two attacks. Message volume is
worthless against both.
flip_budget— forgery: fabricating new independent attested roots on the losing side. Each moves the margin one unit, soflip_budgetforgeries force abstention and one more reverses.conversions_to_reverse— compromise: stealing the key of a root that already supports the winner and flipping it. That root leaves the winning side and joins the losing one, so each action moves the margin two units. This price is roughly halfflip_budget, and it is the relevant one when the threat is key compromise. Quotingflip_budgetalone overstates the attacker's cost by ~2× (research counterexample CE-03).abstention_reachable_by_conversion— false at oddflip_budget. Conversions move the margin in steps of two and so preserve its parity: from an odd margin the attacker can never land on a tie. Abstention — the safe outcome — is simply not on the compromise path, and the cheapest attack goes straight to a confidently wrong verdict. Do not assume a thin margin degrades to "don't know"; at odd margins it does not.
Proof texts, an exhaustive machine verifier (all worlds ≤ 6 claims, 121,944 rewirings, 100k randomized instances, zero violations), benchmarks against Dawid–Skene / truth-discovery baselines, and the research paper draft live in the Minority Prophet research repository.
authorize_continuous_effect is the narrow authority lane. It consumes a
Border-stamped border-intent-continuity/v0.1 receipt, rechecks its signature,
audience, expiry, and revocation status, atomically consumes its nonce, and
compares it with the exact candidate effect immediately before the runtime.
Any mismatch or unavailable verifier/revocation/replay dependency returns a
fail-closed block decision. The bundled in-memory nonce store is test-only;
production must inject a durable atomic store.
This does not make Gate a translation layer. Mandate defines authority, Border proves that authority survived and narrowed across translations, and Gate only decides whether the exact final effect may occur now.
TrustAllVerifierbelow is an unsafe testing fixture for the bundled example. It must be replaced by a fail-closed production verifier before real use.
from minority_prophet import decide, TrustAllVerifier
import json
envelopes = [json.loads(l) for l in open("examples/pr482.jsonl")]
d = decide(envelopes, TrustAllVerifier(), proceed_side=1, min_flip_budget=1.0)
print(d.action) # "block"
print(d.roots_for, d.roots_against) # 1 2
print(d.flip_budget) # 1.0 <- FORGED roots needed to change this
print(d.conversions_to_reverse) # 1 <- COMPROMISED roots needed. Usually the
# smaller number, and the one to plan against.
print(d.diagnostics["abstention_reachable_by_conversion"])
# False <- odd margin: a compromise attack cannot
# produce a tie, only a wrong answer.Replace the one line in your orchestrator that says
if enough_agents_agree: proceed with a call to decide(). Three actions
come back: proceed, block, or escalate (abstention / thin margin /
no verifiable evidence — a reason to ask a human, never a reason to proceed).
For belief formation, analysis, or any use that must not imply execution,
call assess() instead. It returns an action-neutral evidence verdict and
flip budget. decide() is the separate policy layer that interprets that
assessment as proceed, block, or escalate.
examples/evidence_sensitive_authorization.py demonstrates the narrow case
where this assessment adds information that ordinary authorization does not:
seven agents approve a production deployment, but all seven copied one scan;
two independent test runs reject it. The example produces this flow:
9 voices -> Minority Prophet assessment (1 SAFE root, 2 UNSAFE roots)
-> separate authority policy (BLOCK)
-> exact-action runtime boundary (0 effects)
Minority Prophet does not grant authority in this example. It reports the
structure and strength of the evidence. A separate provider-owned policy
interprets that assessment, and the runtime independently enforces the
result. Run it from the repository root with
python -m examples.evidence_sensitive_authorization.
The same runnable example includes a harder comparison. Twelve SAFE voices include six copies, two invalid forgeries, one expired observation, one claim bound to the wrong action, and two valid independent roots. Three fresh, bound, independently grounded UNSAFE roots oppose them. Head count, signature checking alone, and signature-plus-subject-plus-freshness still choose SAFE; independent-root assessment chooses UNSAFE and the runtime executes zero effects. This is an illustrative adversarial case, not a population-level accuracy benchmark.
Gate does not require a memory system. When an action explicitly depends on a
remembered approval, observation, or prior event, callers may pass a neutral
Memory Evidence Interoperability Profile to selective_decide together with
the expected proposition, memory-object, request, and action digests.
The consumer is deliberately asymmetric:
- malformed, incomplete, unverifiable, or unknown evidence escalates;
- revoked, expired, replayed, or incorrectly bound evidence blocks;
- accepted memory proceeds only to the ordinary evidence assessment;
- memory never grants authority and never overrides a deterministic denial.
The neutral profile is defined in the Minority Prophet research repository. Identity, signature, controller, and revocation providers remain external to Gate. The caller must persist consumed nonces across processes if replay protection is required; passing an in-memory set is only a local demonstration.
TrustAllVerifier is for testing only. The guarantees above are
conditional on attestation: origins must be unforgeable, which means
signatures checked by a real production verifier implementing
the 3-state AttestationVerifier interface (root / derived / invalid).
Wire yours via CallbackVerifier. Rules the adapter enforces regardless:
-
Default-derived: any claim without a verified fresh-root attestation is presumed an echo. Copying is the presumption; independence requires proof. Unverifiable claims are quarantined and contribute zero roots.
-
Zero evidence ⇒ escalate: no verifiable roots is never a green light.
-
Side-consistency at signing: assertion and origin are fused in one signed unit, so a claim cannot be re-labeled to the other side later.
-
Verifier independence: a verifier is not trusted merely because it is a third party. Its rules must be transparent, it must remain independent of the evidence producer, it must expose uncertainty, and it must be unable to mint, alter, or promote the evidence it verifies.
The current Gate consumes a verifier's
root/derived/invalidclassification; it does not discover shared organizational control by itself. Deployments must not returnrootmerely because producer and verifier use different names, keys, services, or labels. When an upstream verifier returnsderivedorinvalid, the Gate does not count an independent root; when evidence is empty, tied, or below policy margin, it escalates rather than converting uncertainty into permission. The HVI-1 research study defines the future control-domain experiment; it is not a present implementation claim.
For a decision about a specific subject, call decide(..., decision_subject="job:123"). Matching bound roots determine
flip_budget and the min_flip_budget policy threshold. A legacy root without a subject remains
visible during migration at the configurable unbound_root_weight=0.5, but
never increases the strength score; this makes migration-mode flip_budget
conservative. Set unbound_root_weight=0.0 for the documented strict end
state.
Units, when weights are in play. With any root weight other than 1.0,
flip_budget is a quantity of root mass, not a count of roots — it can come
back 1.5, and half a forged root is not something an attacker can buy. Every
verdict therefore reports which it is:
| field | meaning |
|---|---|
flip_budget_unit |
"independent roots" or "weighted root mass" |
flip_budget_is_root_count |
True only when the number is a countable number of roots |
conversions_to_reverse is always a count of actions, so it stays an integer in
both modes and is the safer number to threshold on.
On T5, and what a threshold does not inherit from it. Earlier revisions of
this file called min_flip_budget "the T5 safety floor". T5 states that a
verdict with |margin| > k survives k root-set errors — but only between
worlds that are side-consistent and carry the same assertions. That second
hypothesis is necessary, not decorative: the research repository's CE-02
falsifies the version without it, and T5_needs_assert_fixed compiles a witness
where a single side conversion flips a margin-2 verdict at zero root-set
error.
So T5 covers misattribution — a root dropped, duplicated, or wrongly identified, with no assertion changed. It does not cover compromise, which changes assertions by definition. Measured on this aggregator, five supporting roots against two:
2 supporting roots removed (assertions preserved) -> verdict survives
2 supporting roots flipped (assertions changed) -> verdict REVERSED
Same k. Setting min_flip_budget from T5 buys protection against the first
column and none against the second. Price compromise with
conversions_to_reverse, which is what it is for.
Subject and observed_at live in the signed Entry Stamp predicate, not in an
issuer manifest: manifests answer may this identity testify?; stamps answer
is this observation current and about this case? Derived claims must retain
their root subject or are quarantined. Default freshness is configurable:
probes decay with a five-minute half-life; start events expire after 24 hours;
origins without a policy do not decay; a missing timestamp under a policy is
expired.
What this gate does NOT do: verify signatures itself (bring a verifier),
decide which tool boundaries count as roots (that's your runtime's stamping
policy), or protect root signing keys (that's ops). The attack surface it
leaves is exactly: compromise of root signing keys — stated per-decision
by flip_budget.
reconcile() answers a different operational question: many status views,
one job state. It counts receipt roots rather than board voices and returns
None as unverified on a tie or a configured thin margin.
from minority_prophet import reconcile
import json
status_claims = [json.loads(line) for line in open("examples/state_reconciliation.jsonl")]
state = reconcile(status_claims, freshness={"half_life_s": 600})
print(state.state, state.flip_budget) # exited, ~2.0Rule: receipts are roots, everything else is derived, and derived claims never outrank a root. Freshness applies to roots only; a root without an observation time under a freshness policy is excluded conservatively.
selective_decide keeps explicit deterministic policy primary. Clear allows
and denies do not invoke the provenance challenger. Only an action marked
evidence-sensitive (or returned as review) enters independent-root
assessment. Contradictory evidence blocks; balanced, missing, or thin evidence
escalates to a separately authorized human by default. Escalation is not
permission, and evidence cannot override a deterministic deny.
An operator may opt unresolved decisions into a fourth selective outcome:
request_evidence. This means not yet. The protected action remains frozen
while an external orchestrator asks the agent to collect specified kinds of
evidence and return them for another complete Gate evaluation.
from minority_prophet import (
CollectorRoute, DeterministicDecision,
EvidenceRequestPolicy, EvidenceRequirement,
TrustAllVerifier, selective_decide,
)
collection = EvidenceRequestPolicy(
requirements=(EvidenceRequirement(
"fresh-tests",
"Collect fresh test-run receipts bound to this action",
CollectorRoute(
"requesting-agent-tests", "requesting_agent", "test.execution",
"candidate_evidence",
("repository.read", "test.run"),
),
("test.receipt",),
),),
max_rounds=2,
)
primary = DeterministicDecision(
"review", "current evidence required", policy_id="deploy-policy-v3",
)
first = selective_decide(
primary, [], TrustAllVerifier(),
decision_subject="deploy:123",
action_digest="sha256:exact-action-payload",
evidence_request_policy=collection,
)
assert first.action == "request_evidence"
assert first.evidence_request.grants_authority is False
# The orchestrator performs only separately authorized collection, retains the
# original request in trusted state, and returns new attested envelopes.
final = selective_decide(
primary, returned_envelopes, production_verifier,
decision_subject="deploy:123",
action_digest="sha256:exact-action-payload",
evidence_request_policy=collection,
prior_evidence_request=first.evidence_request,
)Each challenge is bound to the exact action digest, decision subject, policy identifier, policy contents, and collection round. It includes a hash-chain ID, neutral evidence requirements, collection scopes, an item cap, and a round cap. The requirements describe observations to obtain; they must not tell the agent which conclusion to prove. When the round budget is exhausted, the route becomes human escalation. A contradictory result blocks normally.
When using the bundled runtime contract, pass RuntimeAction.binding_digest as
action_digest; it covers the action ID, type, target, and payload digest.
The allowed collection list is not an authority grant. Each search, read,
probe, or test still needs authorization from the surrounding system. The
protected runtime rejects request_evidence as non-final and performs zero
effects. Keep issued challenges in a trusted durable ledger: the bundled hash
identifies and binds request contents but is not a signature and cannot
authenticate state supplied solely by an untrusted agent.
EvidenceControlPlane closes the orchestration gap for an embedded install. It
sits in front of one consequential runtime boundary and runs this bounded loop:
frozen action
-> deterministic policy + Minority Prophet Gate
-> proceed / block / escalate -----------------------> runtime adapter
-> request_evidence -> authorized collectors -> verifier -> Gate again
Only a final Gate outcome reaches RuntimeController. A collection request,
collector permit, returned artifact, or verification artifact cannot execute
the protected action. Every challenge retains the exact
RuntimeAction.binding_digest, and the controller preserves the existing
exactly-once runtime boundary.
from minority_prophet import (
CandidateEvidenceBridge, EvidenceControlPlane, EvidenceControlPolicy,
RuntimeController, VerifiedEvidenceBatch,
)
plane = EvidenceControlPlane(
evidence_router,
CandidateEvidenceBridge(production_attestation_verifier),
RuntimeController(),
)
outcome = plane.run(
frozen_runtime_action,
EvidenceControlPolicy(
deterministic_policy, evidence_request_policy,
decision_subject="deploy:123",
requester_control_domain="agent:orchestrator",
),
VerifiedEvidenceBatch(initial_envelopes, production_attestation_verifier),
customer_runtime_adapter,
)CandidateEvidenceBridge is the light path: candidate assertions return to
Gate and are classified by the injected verifier. TrustAllVerifier is not a
deployment verifier. A hardened installation may put Minority Prophet Border
or another admission service on the evidence-input side. Admitted evidence
still returns to Gate for the decision; Border never replaces Gate or runtime
enforcement.
The shim does not require a particular model, agent framework, cloud, evidence service, or runtime. Integrators implement the small collector, verifier, and runtime protocols.
The experimental preview now includes the smallest production-shaped path for assembling several agents' work without trusting their declarations:
tool/API interception -> durable case -> signed receipts -> verifier
-> MP Gate -> in-process or HTTP runtime
AuthenticatedSqliteCaseStore groups asynchronous submissions under one
frozen action and policy, rejects claim-ID rebinding, survives process restart,
and detects modified or missing stored envelopes. It is an assembly boundary,
not an authority source: callers decide when a case is ready and only Gate can
authorize the protected runtime.
SignedReceiptVerifier is a fail-closed local reference verifier. It
authenticates the complete envelope with an operator-managed issuer key and
binds it to a decision subject, source root, issuer, and control domain. The
first accepted use of a root_id can count as a root. Later uses count only as
declared derivations from an already accepted member of that family; an
undeclared reuse is quarantined rather than manufactured into another vote.
Run the harmless end-to-end example:
PYTHONPATH=. python3 examples/reference_signed_interception.pyIt assembles three agent claims, collapses one repeated source into an echo, passes two independent signed roots to Gate, and invokes an allowlisted demo tool exactly once. No network service or model key is required.
The reference verifier uses shared-secret HMAC keys so an adopter can exercise
the complete contract locally. It is not a universal identity system. For
cross-organization production use, replace it with CallbackVerifier backed
by asymmetric signatures, workload identity, Border, Nxtlinq, or the
customer's attestation service. The verifier instance is scoped to one case.
Its independence guarantee is only as strong as the trusted issuer's canonical
assignment of root_id; signing two IDs for one underlying source would
manufacture two roots and is therefore a verifier-boundary failure.
Gate evidence decisions and autonomy are separate axes. Gate still returns
proceed, block, request_evidence, or escalate. Only a final proceed
may be resolved through an authenticated owner mandate into one of five
increasing autonomy levels:
| Level | Gate release | Runtime effect |
|---|---|---|
observe |
report the decision | none |
recommend |
recommend the frozen action | none |
prepare |
validate and prepare the exact action | none |
act |
execute inside the mandate | exactly one permitted attempt |
emergency_act |
notify the owner, then execute bounded containment | exactly one permitted attempt |
AutonomyMandate binds the decision subject, maximum level, action types,
targets, expiry, minimum independent roots, minimum attack price, optional
exact action digests, reversibility requirement, and emergency permission.
Requesting a level above the mandate is capped. An expired, weak, unbound, or
out-of-scope mandate falls back to observe and performs no protected effect.
block, escalate, and request_evidence can never be upgraded into action.
from minority_prophet import AutonomyLevel, AutonomyMandate
owner_mandate = AutonomyMandate(
mandate_id="containment-v1",
decision_subject="incident:742",
max_level=AutonomyLevel.ACT,
allowed_action_types=("isolate",),
allowed_targets=("staging-server-7",),
expires_at="2026-09-01T00:00:00Z",
min_flip_budget=2.0,
min_roots_for=2,
require_reversible=True,
)
outcome = plane.run(
frozen_runtime_action, policy, initial_evidence, runtime,
autonomy_mandate=owner_mandate,
requested_autonomy=AutonomyLevel.ACT,
)The mandate object is not self-authenticating. Border, Nxtlinq, IAM, or another
trusted mandate store must authenticate and revoke production mandates before
they reach this layer. emergency_act additionally requires a notifier; if
notification fails, execution fails closed.
Each EvidenceRequirement carries an explicit CollectorRoute. Policy—not the
requesting model—selects one of four neutral collector kinds:
| Kind | Use |
|---|---|
requesting_agent |
Return candidate_evidence for ordinary Gate verification |
epistemic_service |
Return a verification_artifact; an MP adapter may serve this route |
human |
Produce a human_handoff, never a fabricated evidence root |
program |
Return candidate evidence or a verification artifact, as policy specifies |
EvidenceRouter selects adapters by route ID, collector kind, and capability.
It rejects same-control-domain collectors when the route requires independence,
and it requires a separately bound CollectionAuthorization before calling any
adapter. An allowed collection action never authorizes the protected action.
An epistemic-service result cannot carry an assertion: it informs the verifier
how to classify the original evidence and is never counted as another vote.
The router automatically writes hash-chained audit events for challenge receipt,
route planning, authorization, dispatch intent, and collection return/failure;
record_gate_decision appends the subsequent Gate handback. Returned evidence
is stored separately and only its digest enters the audit log. The optional
JSONL backend flushes and fsyncs each event; unresolved dispatches fail closed
on retry.
See EVIDENCE-ROUTING.md for the adapter contract,
policy mapping, audit events, and production boundary.
Evidence-sensitive policy may independently require both attack prices:
min_flip_budget for newly forged opposing root mass and
min_conversions_to_reverse for compromised winning roots. The latter is
optional for backward compatibility. When configured, a missing or sub-threshold
conversion price escalates; it can never inherit the deterministic allow.
This routing contract is an execution-safety policy, not a claim that the provenance method is globally more accurate than ordinary authorization rules.
Two reference runtime integrations exercise the neutral boundary: an allowlisted in-process tool adapter and an idempotent HTTP adapter. Both bind the exact payload digest and idempotency key. The HTTP target and any in-process handler must persist idempotency outcomes to provide crash-safe exactly-once effects; the reference controller's in-memory ledger alone does not provide that production guarantee.
v0.1.1 — reference implementation. Aggregator core is conformance-tested
against spec-derived vectors and backed by exhaustively machine-checked
theorems; the ACP adapter and gate policy are new and seeking adversarial
review. Not yet battle-tested against hostile inputs in production.
Issues, attack attempts, and counterexamples are actively invited — a
counterexample to T1/T2 within the stated preconditions is a research
result, and we will credit and publish it.
minority_prophet/aggregator.py # machine-checked core: Claim, EvidenceGraph, aggregate()
minority_prophet/adapter_acp.py # envelopes -> verified Claims (security model here)
minority_prophet/gate.py # decide(): proceed / block / escalate + flip_budget
minority_prophet/evidence_request.py # bounded request-evidence challenge contract
minority_prophet/evidence_audit.py # append-only hash-chained event log
minority_prophet/evidence_router.py # neutral collector routing and dispatch
minority_prophet/case_store.py # durable asynchronous multi-agent case assembly
minority_prophet/receipt_verifier.py # fail-closed signed-receipt reference verifier
minority_prophet/autonomy.py # mandate-bound Gate autonomy profiles
minority_prophet/reconcile.py # reconcile(): many status sources, one state
minority_prophet/runtime_adapter.py # neutral prepare/execute-once/prevent boundary
minority_prophet/runtime_integrations.py # in-process and idempotent HTTP adapters
examples/pr482.jsonl # the 9-message scenario from the paper
tests/ # unit + end-to-end attack tests + conformance vectors
See CITATION.cff. Research: the minority-prophet repository (paper draft,
proofs, benchmarks E1–E8b).