verdict4
Four-value evaluation for agentic AI loops.
Replace pass/fail with NO / YES / MAYBE / IFF.
Most agentic loops evaluate with a boolean. Pass or fail. That works when every non-pass condition is actually failure. But it is not. Some outputs cannot be evaluated yet because evidence is missing. Some outputs are correct only if another result holds. A boolean check compresses both into failure and destroys correct work.
This package provides a four-value Verdict enum, a structured CheckResult, and a loop runner that handles all four branches.
Read the full explanation: Your Loop Has Two States. It Needs Four.
The finite-rank controller is specified in Quaternary Iteration Control for Looped Transformers. The controller is included in this release; looped-transformer substrate integration is in preparation.
Install
Install directly from GitHub:
pip install git+https://github.com/MetaCortex-Dynamics/verdict4.git@v0.2.0
Or from source:
git clone https://github.com/MetaCortex-Dynamics/verdict4.git
cd verdict4
pip install -e .
After the GitHub release has published the package to PyPI, install the pinned release with:
pip install verdict4==0.2.0
Until that release workflow completes, use the GitHub install above.
Quick start
from quaternary import Verdict, CheckResult, no, yes, maybe, iff, run
# Define your check function — return a CheckResult, not a bool
def check(output):
if not output.get("valid"):
return no("validation failed: missing required field")
if not output.get("schema_available"):
return maybe("schema file not found")
if output.get("depends_on_auth"):
return iff("auth module deployed")
return yes()
# Define your generator
def generate(task, *, exclusions):
# Your agent generates output here
# `exclusions` carries named reasons from prior NO results
return {"valid": True, "schema_available": True}
# Run the loop
outcome = run(
task="build parser",
generate=generate,
check=check,
)
print(outcome.status) # "accepted" | "held" | "blocked" | "exhausted"
print(outcome.exclusions) # accumulated NO reasons
print(outcome.rounds) # how many iterations
The four verdicts
| Verdict | Meaning | Loop action |
|---|---|---|
NO |
Fails a named condition | Add reason to exclusions, retry |
YES |
Satisfies conditions | Accept |
MAYBE |
Cannot evaluate — evidence missing | Hold, gather evidence, recheck |
IFF |
Correct if dependency holds | Bind to dependency, resolve, recheck |
API
Core types
from quaternary import Verdict, CheckResult, no, yes, maybe, iff
Verdict — IntEnum with values NO=0, YES=1, MAYBE=2, IFF=3
CheckResult — frozen dataclass:
verdict: Verdictreason: str | None— required forNOneeded: str | EvidenceNeed | ComputeNeed | None— required forMAYBEdependency: str | None— required forIFFmeta: dict[str, Any]— optional domain metadata
Constructors: no(reason), yes(), maybe(needed), iff(dependency) — all accept **meta kwargs.
Runner
from quaternary import run, LoopOutcome
run(task, *, generate, check, ...) — runs the quaternary loop.
Required:
generate(task, *, exclusions) -> outputcheck(output) -> CheckResult
Optional:
gather(needed) -> evidence— called on MAYBErecheck(output, evidence?) -> CheckResult— called after gather or dependency resolutionis_resolved(dep) -> bool— called on IFFis_failed(dep) -> bool— called on IFFmax_rounds: int— default 10on_hold(output, reason)— callback when holdingon_blocked(output, dep)— callback when blocked
LoopOutcome:
status:"accepted"|"held"|"blocked"|"exhausted"output: the final output (or None if exhausted)exclusions: accumulated NO reasonsrounds: iterations usedheld_reason/blocked_by: why the loop stoppedcontradiction: flagged contradictory exclusion pairs (if any)
Finite-rank controller
Version 0.2.0 adds a substrate-free controller for repeated computation. It
preserves the v0.1.0 runner API while adding typed MAYBE needs, finite
one-use alphabets, dependency bindings, logical leases, and replayable
accounting receipts.
from quaternary import (
ComputeNeed,
QICTConfig,
QICTController,
maybe,
rank,
yes,
)
config = QICTConfig(compute_alphabet={"next_1", "next_2"})
controller = QICTController(config)
initial_rank = rank(config, controller.state)
controller.process(maybe(ComputeNeed("next_1")))
controller.process(maybe(ComputeNeed("next_2")))
controller.process(yes())
assert controller.state.evaluation_count <= initial_rank + 1
assert controller.audit().valid
Every evaluation that continues consumes exactly one finite opportunity: a
new exclusion, an evidence request, another computation, or a dependency.
Therefore the controller terminates in at most initial_rank + 1
evaluations, without assuming that the evaluator is sound.
Receipts contain controller accounting state and transition data. Candidate activations are excluded from the receipt state by type.
Examples
python examples/json_parser.py
python examples/multi_file_refactor.py
Design constraints
- Zero domain logic. The runner is a protocol over user-supplied callables.
- No framework. The fix is local — an enum and a branch.
- Bounded MAYBE. A second MAYBE after evidence gathering → hold and report. No infinite gather loops.
- Exclusion memory. NO reasons accumulate across rounds. Contradictions are flagged, not resolved.
- No dependencies. Standard library only.
License
MIT — MetaCortex Dynamics
Metadata
Release files for verdict4 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| verdict4-0.2.0.tar.gz | 19.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| verdict4-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.9 kB
Release files / verdict4-0.2.0.tar.gz
| Download URL | verdict4-0.2.0.tar.gz |
|---|---|
| Size | 19.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
35fc03e570900feafa7565150fee199d5bb6ee4ad863b8869ce68d2743806a7f
|
|
BLAKE2b-256 checksum How to use checksums |
123f6248947998b7938eb0193f87d87a021fc7c624b02726db4c7070de30b68f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.
Transparency logRelease files / verdict4-0.2.0-py3-none-any.whl
| Download URL | verdict4-0.2.0-py3-none-any.whl |
|---|---|
| Size | 14.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c03fcbbaa98c017aeb14560f73c9b83a126ee9b716d0ea39f49016e7225d2618
|
|
BLAKE2b-256 checksum How to use checksums |
738ba57154d5c44a278aa0e3d919e6d59eebd791f4ac41f08e9dd38f5982f1d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.
Transparency log