verdict4 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- verdict4-0.2.0/.github/workflows/publish.yml +54 -0
- verdict4-0.2.0/.gitignore +8 -0
- verdict4-0.2.0/CHANGELOG.md +16 -0
- verdict4-0.2.0/LICENSE +21 -0
- verdict4-0.2.0/PKG-INFO +207 -0
- verdict4-0.2.0/README.md +183 -0
- verdict4-0.2.0/examples/json_parser.py +123 -0
- verdict4-0.2.0/examples/multi_file_refactor.py +146 -0
- verdict4-0.2.0/pyproject.toml +42 -0
- verdict4-0.2.0/quaternary/__init__.py +68 -0
- verdict4-0.2.0/quaternary/core.py +109 -0
- verdict4-0.2.0/quaternary/qict.py +766 -0
- verdict4-0.2.0/quaternary/runner.py +250 -0
- verdict4-0.2.0/tests/test_core.py +206 -0
- verdict4-0.2.0/tests/test_qict.py +385 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: Build distributions
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check out the released tag
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
ref: ${{ github.event.release.tag_name }}
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.x"
|
|
24
|
+
|
|
25
|
+
- name: Install build tooling
|
|
26
|
+
run: python -m pip install --upgrade build
|
|
27
|
+
|
|
28
|
+
- name: Build distributions
|
|
29
|
+
run: python -m build
|
|
30
|
+
|
|
31
|
+
- name: Upload distributions
|
|
32
|
+
uses: actions/upload-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: python-package-distributions
|
|
35
|
+
path: dist/
|
|
36
|
+
|
|
37
|
+
publish:
|
|
38
|
+
name: Publish distributions
|
|
39
|
+
needs: build
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
environment:
|
|
42
|
+
name: pypi
|
|
43
|
+
url: https://pypi.org/p/verdict4
|
|
44
|
+
permissions:
|
|
45
|
+
id-token: write
|
|
46
|
+
steps:
|
|
47
|
+
- name: Download distributions
|
|
48
|
+
uses: actions/download-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: python-package-distributions
|
|
51
|
+
path: dist/
|
|
52
|
+
|
|
53
|
+
- name: Publish distributions to PyPI
|
|
54
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to verdict4 are documented here.
|
|
4
|
+
|
|
5
|
+
## 0.2.0 — 2026-09-04
|
|
6
|
+
|
|
7
|
+
- Add the substrate-free `QICTController` with finite-rank termination.
|
|
8
|
+
- Add typed `EvidenceNeed` and `ComputeNeed` payloads for `MAYBE`.
|
|
9
|
+
- Add one-use failure, evidence, computation, and dependency alphabets.
|
|
10
|
+
- Add logical lease expiry as a controller event.
|
|
11
|
+
- Add receipt replay auditing without candidate activations.
|
|
12
|
+
- Preserve the v0.1.0 runner API and legacy string `MAYBE` behavior.
|
|
13
|
+
|
|
14
|
+
## 0.1.0 — 2026-07-17
|
|
15
|
+
|
|
16
|
+
- Initial four-verdict protocol and agent-loop runner.
|
verdict4-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MetaCortex Dynamics
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
verdict4-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: verdict4
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Four-value evaluation for agentic AI loops. Replace pass/fail with NO/YES/MAYBE/IFF.
|
|
5
|
+
Project-URL: Homepage, https://github.com/MetaCortex-Dynamics/verdict4
|
|
6
|
+
Project-URL: Documentation, https://metacortexdynamics.substack.com/p/your-loop-has-two-states-it-needs
|
|
7
|
+
Project-URL: Repository, https://github.com/MetaCortex-Dynamics/verdict4
|
|
8
|
+
Project-URL: Issues, https://github.com/MetaCortex-Dynamics/verdict4/issues
|
|
9
|
+
Project-URL: Paper, https://doi.org/10.5281/zenodo.22309225
|
|
10
|
+
Author-email: MetaCortex Dynamics <Contact@metacortexdynamics.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agentic,ai,evaluation,loop,quaternary,verdict
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# verdict4
|
|
26
|
+
|
|
27
|
+
Four-value evaluation for agentic AI loops.
|
|
28
|
+
|
|
29
|
+
**Replace pass/fail with NO / YES / MAYBE / IFF.**
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
This package provides a four-value `Verdict` enum, a structured `CheckResult`, and a loop runner that handles all four branches.
|
|
34
|
+
|
|
35
|
+
Read the full explanation: [Your Loop Has Two States. It Needs Four.](https://metacortexdynamics.substack.com/p/your-loop-has-two-states-it-needs)
|
|
36
|
+
|
|
37
|
+
The finite-rank controller is specified in [Quaternary Iteration Control for Looped Transformers](https://doi.org/10.5281/zenodo.22309225). The controller is included in this release; looped-transformer substrate integration is in preparation.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
Install directly from GitHub:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install git+https://github.com/MetaCortex-Dynamics/verdict4.git@v0.2.0
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or from source:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/MetaCortex-Dynamics/verdict4.git
|
|
51
|
+
cd verdict4
|
|
52
|
+
pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
After the GitHub release has published the package to PyPI, install the pinned release with:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install verdict4==0.2.0
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Until that release workflow completes, use the GitHub install above.
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from quaternary import Verdict, CheckResult, no, yes, maybe, iff, run
|
|
67
|
+
|
|
68
|
+
# Define your check function — return a CheckResult, not a bool
|
|
69
|
+
def check(output):
|
|
70
|
+
if not output.get("valid"):
|
|
71
|
+
return no("validation failed: missing required field")
|
|
72
|
+
if not output.get("schema_available"):
|
|
73
|
+
return maybe("schema file not found")
|
|
74
|
+
if output.get("depends_on_auth"):
|
|
75
|
+
return iff("auth module deployed")
|
|
76
|
+
return yes()
|
|
77
|
+
|
|
78
|
+
# Define your generator
|
|
79
|
+
def generate(task, *, exclusions):
|
|
80
|
+
# Your agent generates output here
|
|
81
|
+
# `exclusions` carries named reasons from prior NO results
|
|
82
|
+
return {"valid": True, "schema_available": True}
|
|
83
|
+
|
|
84
|
+
# Run the loop
|
|
85
|
+
outcome = run(
|
|
86
|
+
task="build parser",
|
|
87
|
+
generate=generate,
|
|
88
|
+
check=check,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
print(outcome.status) # "accepted" | "held" | "blocked" | "exhausted"
|
|
92
|
+
print(outcome.exclusions) # accumulated NO reasons
|
|
93
|
+
print(outcome.rounds) # how many iterations
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## The four verdicts
|
|
97
|
+
|
|
98
|
+
| Verdict | Meaning | Loop action |
|
|
99
|
+
|---------|---------|-------------|
|
|
100
|
+
| `NO` | Fails a named condition | Add reason to exclusions, retry |
|
|
101
|
+
| `YES` | Satisfies conditions | Accept |
|
|
102
|
+
| `MAYBE` | Cannot evaluate — evidence missing | Hold, gather evidence, recheck |
|
|
103
|
+
| `IFF` | Correct if dependency holds | Bind to dependency, resolve, recheck |
|
|
104
|
+
|
|
105
|
+
## API
|
|
106
|
+
|
|
107
|
+
### Core types
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from quaternary import Verdict, CheckResult, no, yes, maybe, iff
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**`Verdict`** — `IntEnum` with values `NO=0`, `YES=1`, `MAYBE=2`, `IFF=3`
|
|
114
|
+
|
|
115
|
+
**`CheckResult`** — frozen dataclass:
|
|
116
|
+
- `verdict: Verdict`
|
|
117
|
+
- `reason: str | None` — required for `NO`
|
|
118
|
+
- `needed: str | EvidenceNeed | ComputeNeed | None` — required for `MAYBE`
|
|
119
|
+
- `dependency: str | None` — required for `IFF`
|
|
120
|
+
- `meta: dict[str, Any]` — optional domain metadata
|
|
121
|
+
|
|
122
|
+
**Constructors**: `no(reason)`, `yes()`, `maybe(needed)`, `iff(dependency)` — all accept `**meta` kwargs.
|
|
123
|
+
|
|
124
|
+
### Runner
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from quaternary import run, LoopOutcome
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**`run(task, *, generate, check, ...)`** — runs the quaternary loop.
|
|
131
|
+
|
|
132
|
+
Required:
|
|
133
|
+
- `generate(task, *, exclusions) -> output`
|
|
134
|
+
- `check(output) -> CheckResult`
|
|
135
|
+
|
|
136
|
+
Optional:
|
|
137
|
+
- `gather(needed) -> evidence` — called on MAYBE
|
|
138
|
+
- `recheck(output, evidence?) -> CheckResult` — called after gather or dependency resolution
|
|
139
|
+
- `is_resolved(dep) -> bool` — called on IFF
|
|
140
|
+
- `is_failed(dep) -> bool` — called on IFF
|
|
141
|
+
- `max_rounds: int` — default 10
|
|
142
|
+
- `on_hold(output, reason)` — callback when holding
|
|
143
|
+
- `on_blocked(output, dep)` — callback when blocked
|
|
144
|
+
|
|
145
|
+
**`LoopOutcome`**:
|
|
146
|
+
- `status`: `"accepted"` | `"held"` | `"blocked"` | `"exhausted"`
|
|
147
|
+
- `output`: the final output (or None if exhausted)
|
|
148
|
+
- `exclusions`: accumulated NO reasons
|
|
149
|
+
- `rounds`: iterations used
|
|
150
|
+
- `held_reason` / `blocked_by`: why the loop stopped
|
|
151
|
+
- `contradiction`: flagged contradictory exclusion pairs (if any)
|
|
152
|
+
|
|
153
|
+
### Finite-rank controller
|
|
154
|
+
|
|
155
|
+
Version 0.2.0 adds a substrate-free controller for repeated computation. It
|
|
156
|
+
preserves the v0.1.0 runner API while adding typed `MAYBE` needs, finite
|
|
157
|
+
one-use alphabets, dependency bindings, logical leases, and replayable
|
|
158
|
+
accounting receipts.
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from quaternary import (
|
|
162
|
+
ComputeNeed,
|
|
163
|
+
QICTConfig,
|
|
164
|
+
QICTController,
|
|
165
|
+
maybe,
|
|
166
|
+
rank,
|
|
167
|
+
yes,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
config = QICTConfig(compute_alphabet={"next_1", "next_2"})
|
|
171
|
+
controller = QICTController(config)
|
|
172
|
+
initial_rank = rank(config, controller.state)
|
|
173
|
+
|
|
174
|
+
controller.process(maybe(ComputeNeed("next_1")))
|
|
175
|
+
controller.process(maybe(ComputeNeed("next_2")))
|
|
176
|
+
controller.process(yes())
|
|
177
|
+
|
|
178
|
+
assert controller.state.evaluation_count <= initial_rank + 1
|
|
179
|
+
assert controller.audit().valid
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Every evaluation that continues consumes exactly one finite opportunity: a
|
|
183
|
+
new exclusion, an evidence request, another computation, or a dependency.
|
|
184
|
+
Therefore the controller terminates in at most `initial_rank + 1`
|
|
185
|
+
evaluations, without assuming that the evaluator is sound.
|
|
186
|
+
|
|
187
|
+
Receipts contain controller accounting state and transition data. Candidate
|
|
188
|
+
activations are excluded from the receipt state by type.
|
|
189
|
+
|
|
190
|
+
## Examples
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
python examples/json_parser.py
|
|
194
|
+
python examples/multi_file_refactor.py
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Design constraints
|
|
198
|
+
|
|
199
|
+
- **Zero domain logic.** The runner is a protocol over user-supplied callables.
|
|
200
|
+
- **No framework.** The fix is local — an enum and a branch.
|
|
201
|
+
- **Bounded MAYBE.** A second MAYBE after evidence gathering → hold and report. No infinite gather loops.
|
|
202
|
+
- **Exclusion memory.** NO reasons accumulate across rounds. Contradictions are flagged, not resolved.
|
|
203
|
+
- **No dependencies.** Standard library only.
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT — [MetaCortex Dynamics](https://github.com/MetaCortex-Dynamics)
|
verdict4-0.2.0/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# verdict4
|
|
2
|
+
|
|
3
|
+
Four-value evaluation for agentic AI loops.
|
|
4
|
+
|
|
5
|
+
**Replace pass/fail with NO / YES / MAYBE / IFF.**
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
This package provides a four-value `Verdict` enum, a structured `CheckResult`, and a loop runner that handles all four branches.
|
|
10
|
+
|
|
11
|
+
Read the full explanation: [Your Loop Has Two States. It Needs Four.](https://metacortexdynamics.substack.com/p/your-loop-has-two-states-it-needs)
|
|
12
|
+
|
|
13
|
+
The finite-rank controller is specified in [Quaternary Iteration Control for Looped Transformers](https://doi.org/10.5281/zenodo.22309225). The controller is included in this release; looped-transformer substrate integration is in preparation.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
Install directly from GitHub:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install git+https://github.com/MetaCortex-Dynamics/verdict4.git@v0.2.0
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or from source:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/MetaCortex-Dynamics/verdict4.git
|
|
27
|
+
cd verdict4
|
|
28
|
+
pip install -e .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
After the GitHub release has published the package to PyPI, install the pinned release with:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install verdict4==0.2.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Until that release workflow completes, use the GitHub install above.
|
|
38
|
+
|
|
39
|
+
## Quick start
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from quaternary import Verdict, CheckResult, no, yes, maybe, iff, run
|
|
43
|
+
|
|
44
|
+
# Define your check function — return a CheckResult, not a bool
|
|
45
|
+
def check(output):
|
|
46
|
+
if not output.get("valid"):
|
|
47
|
+
return no("validation failed: missing required field")
|
|
48
|
+
if not output.get("schema_available"):
|
|
49
|
+
return maybe("schema file not found")
|
|
50
|
+
if output.get("depends_on_auth"):
|
|
51
|
+
return iff("auth module deployed")
|
|
52
|
+
return yes()
|
|
53
|
+
|
|
54
|
+
# Define your generator
|
|
55
|
+
def generate(task, *, exclusions):
|
|
56
|
+
# Your agent generates output here
|
|
57
|
+
# `exclusions` carries named reasons from prior NO results
|
|
58
|
+
return {"valid": True, "schema_available": True}
|
|
59
|
+
|
|
60
|
+
# Run the loop
|
|
61
|
+
outcome = run(
|
|
62
|
+
task="build parser",
|
|
63
|
+
generate=generate,
|
|
64
|
+
check=check,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
print(outcome.status) # "accepted" | "held" | "blocked" | "exhausted"
|
|
68
|
+
print(outcome.exclusions) # accumulated NO reasons
|
|
69
|
+
print(outcome.rounds) # how many iterations
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## The four verdicts
|
|
73
|
+
|
|
74
|
+
| Verdict | Meaning | Loop action |
|
|
75
|
+
|---------|---------|-------------|
|
|
76
|
+
| `NO` | Fails a named condition | Add reason to exclusions, retry |
|
|
77
|
+
| `YES` | Satisfies conditions | Accept |
|
|
78
|
+
| `MAYBE` | Cannot evaluate — evidence missing | Hold, gather evidence, recheck |
|
|
79
|
+
| `IFF` | Correct if dependency holds | Bind to dependency, resolve, recheck |
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
### Core types
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from quaternary import Verdict, CheckResult, no, yes, maybe, iff
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**`Verdict`** — `IntEnum` with values `NO=0`, `YES=1`, `MAYBE=2`, `IFF=3`
|
|
90
|
+
|
|
91
|
+
**`CheckResult`** — frozen dataclass:
|
|
92
|
+
- `verdict: Verdict`
|
|
93
|
+
- `reason: str | None` — required for `NO`
|
|
94
|
+
- `needed: str | EvidenceNeed | ComputeNeed | None` — required for `MAYBE`
|
|
95
|
+
- `dependency: str | None` — required for `IFF`
|
|
96
|
+
- `meta: dict[str, Any]` — optional domain metadata
|
|
97
|
+
|
|
98
|
+
**Constructors**: `no(reason)`, `yes()`, `maybe(needed)`, `iff(dependency)` — all accept `**meta` kwargs.
|
|
99
|
+
|
|
100
|
+
### Runner
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from quaternary import run, LoopOutcome
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**`run(task, *, generate, check, ...)`** — runs the quaternary loop.
|
|
107
|
+
|
|
108
|
+
Required:
|
|
109
|
+
- `generate(task, *, exclusions) -> output`
|
|
110
|
+
- `check(output) -> CheckResult`
|
|
111
|
+
|
|
112
|
+
Optional:
|
|
113
|
+
- `gather(needed) -> evidence` — called on MAYBE
|
|
114
|
+
- `recheck(output, evidence?) -> CheckResult` — called after gather or dependency resolution
|
|
115
|
+
- `is_resolved(dep) -> bool` — called on IFF
|
|
116
|
+
- `is_failed(dep) -> bool` — called on IFF
|
|
117
|
+
- `max_rounds: int` — default 10
|
|
118
|
+
- `on_hold(output, reason)` — callback when holding
|
|
119
|
+
- `on_blocked(output, dep)` — callback when blocked
|
|
120
|
+
|
|
121
|
+
**`LoopOutcome`**:
|
|
122
|
+
- `status`: `"accepted"` | `"held"` | `"blocked"` | `"exhausted"`
|
|
123
|
+
- `output`: the final output (or None if exhausted)
|
|
124
|
+
- `exclusions`: accumulated NO reasons
|
|
125
|
+
- `rounds`: iterations used
|
|
126
|
+
- `held_reason` / `blocked_by`: why the loop stopped
|
|
127
|
+
- `contradiction`: flagged contradictory exclusion pairs (if any)
|
|
128
|
+
|
|
129
|
+
### Finite-rank controller
|
|
130
|
+
|
|
131
|
+
Version 0.2.0 adds a substrate-free controller for repeated computation. It
|
|
132
|
+
preserves the v0.1.0 runner API while adding typed `MAYBE` needs, finite
|
|
133
|
+
one-use alphabets, dependency bindings, logical leases, and replayable
|
|
134
|
+
accounting receipts.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from quaternary import (
|
|
138
|
+
ComputeNeed,
|
|
139
|
+
QICTConfig,
|
|
140
|
+
QICTController,
|
|
141
|
+
maybe,
|
|
142
|
+
rank,
|
|
143
|
+
yes,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
config = QICTConfig(compute_alphabet={"next_1", "next_2"})
|
|
147
|
+
controller = QICTController(config)
|
|
148
|
+
initial_rank = rank(config, controller.state)
|
|
149
|
+
|
|
150
|
+
controller.process(maybe(ComputeNeed("next_1")))
|
|
151
|
+
controller.process(maybe(ComputeNeed("next_2")))
|
|
152
|
+
controller.process(yes())
|
|
153
|
+
|
|
154
|
+
assert controller.state.evaluation_count <= initial_rank + 1
|
|
155
|
+
assert controller.audit().valid
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Every evaluation that continues consumes exactly one finite opportunity: a
|
|
159
|
+
new exclusion, an evidence request, another computation, or a dependency.
|
|
160
|
+
Therefore the controller terminates in at most `initial_rank + 1`
|
|
161
|
+
evaluations, without assuming that the evaluator is sound.
|
|
162
|
+
|
|
163
|
+
Receipts contain controller accounting state and transition data. Candidate
|
|
164
|
+
activations are excluded from the receipt state by type.
|
|
165
|
+
|
|
166
|
+
## Examples
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
python examples/json_parser.py
|
|
170
|
+
python examples/multi_file_refactor.py
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Design constraints
|
|
174
|
+
|
|
175
|
+
- **Zero domain logic.** The runner is a protocol over user-supplied callables.
|
|
176
|
+
- **No framework.** The fix is local — an enum and a branch.
|
|
177
|
+
- **Bounded MAYBE.** A second MAYBE after evidence gathering → hold and report. No infinite gather loops.
|
|
178
|
+
- **Exclusion memory.** NO reasons accumulate across rounds. Contradictions are flagged, not resolved.
|
|
179
|
+
- **No dependencies.** Standard library only.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT — [MetaCortex Dynamics](https://github.com/MetaCortex-Dynamics)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Worked example: JSON parser — binary vs quaternary convergence.
|
|
3
|
+
|
|
4
|
+
From: "Your Loop Has Two States. It Needs Four."
|
|
5
|
+
https://metacortexdynamics.substack.com/p/your-loop-has-two-states-it-needs
|
|
6
|
+
|
|
7
|
+
Simulates the JSON parser scenario from the post.
|
|
8
|
+
Binary loop: 3 full generations, oscillation.
|
|
9
|
+
Quaternary loop: 1 generation, 1 targeted repair, convergence.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from quaternary import CheckResult, LoopOutcome, Verdict, iff, maybe, no, run, yes
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# --- Simulated component state ---
|
|
18
|
+
|
|
19
|
+
class SimulatedParser:
|
|
20
|
+
"""Simulates a parser that initially fails on nested arrays
|
|
21
|
+
and has a missing schema import."""
|
|
22
|
+
|
|
23
|
+
def __init__(self) -> None:
|
|
24
|
+
self.handles_nested_arrays = False
|
|
25
|
+
self.schema_imported = False
|
|
26
|
+
self.generation_count = 0
|
|
27
|
+
|
|
28
|
+
def generate(self, task: str, *, exclusions: list[str]) -> dict[str, str]:
|
|
29
|
+
self.generation_count += 1
|
|
30
|
+
|
|
31
|
+
# After being told about the nested array issue, fix it
|
|
32
|
+
if "nested array not handled" in exclusions:
|
|
33
|
+
self.handles_nested_arrays = True
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
"parser": "json_parser_v" + str(self.generation_count),
|
|
37
|
+
"handles_nested": str(self.handles_nested_arrays),
|
|
38
|
+
"schema_imported": str(self.schema_imported),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def check(self, output: dict[str, str]) -> CheckResult:
|
|
42
|
+
"""Evaluate each component."""
|
|
43
|
+
# Test 1: basic parsing — always passes
|
|
44
|
+
# Test 2: nested arrays
|
|
45
|
+
if output["handles_nested"] == "False":
|
|
46
|
+
return no("nested array not handled")
|
|
47
|
+
|
|
48
|
+
# Validator: depends on schema import
|
|
49
|
+
if output["schema_imported"] == "False":
|
|
50
|
+
return iff("schema library imported")
|
|
51
|
+
|
|
52
|
+
return yes()
|
|
53
|
+
|
|
54
|
+
def is_resolved(self, dep: str) -> bool:
|
|
55
|
+
if dep == "schema library imported":
|
|
56
|
+
# Simulate: resolve by importing
|
|
57
|
+
self.schema_imported = True
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def is_failed(self, dep: str) -> bool:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
def recheck(self, output: dict[str, str], evidence: object = None) -> CheckResult:
|
|
65
|
+
# After dependency resolved, recheck
|
|
66
|
+
if self.schema_imported and self.handles_nested_arrays:
|
|
67
|
+
return yes()
|
|
68
|
+
return no("still failing after dependency resolution")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run_binary_simulation() -> None:
|
|
72
|
+
"""Simulate binary loop behavior from the post."""
|
|
73
|
+
print("=" * 60)
|
|
74
|
+
print("BINARY LOOP (simulated)")
|
|
75
|
+
print("=" * 60)
|
|
76
|
+
|
|
77
|
+
generations = 0
|
|
78
|
+
for attempt in range(1, 4):
|
|
79
|
+
generations += 1
|
|
80
|
+
print(f"\nRound {attempt}: generate everything from scratch")
|
|
81
|
+
if attempt == 1:
|
|
82
|
+
print(" test 2 fails: nested arrays not handled")
|
|
83
|
+
print(" -> regenerate everything")
|
|
84
|
+
elif attempt == 2:
|
|
85
|
+
print(" test 1 now fails (regression)")
|
|
86
|
+
print(" -> regenerate everything")
|
|
87
|
+
elif attempt == 3:
|
|
88
|
+
print(" schema validator fails: missing import")
|
|
89
|
+
print(" -> regenerate everything")
|
|
90
|
+
|
|
91
|
+
print(f"\nResult: {generations} full generations, oscillation, no convergence")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def run_quaternary() -> None:
|
|
95
|
+
"""Run actual quaternary loop."""
|
|
96
|
+
print("\n" + "=" * 60)
|
|
97
|
+
print("QUATERNARY LOOP")
|
|
98
|
+
print("=" * 60)
|
|
99
|
+
|
|
100
|
+
sim = SimulatedParser()
|
|
101
|
+
|
|
102
|
+
outcome: LoopOutcome = run(
|
|
103
|
+
task="write json parser with schema validation",
|
|
104
|
+
generate=sim.generate,
|
|
105
|
+
check=sim.check,
|
|
106
|
+
recheck=sim.recheck,
|
|
107
|
+
is_resolved=sim.is_resolved,
|
|
108
|
+
is_failed=sim.is_failed,
|
|
109
|
+
max_rounds=5,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
print(f"\nStatus: {outcome.status}")
|
|
113
|
+
print(f"Rounds: {outcome.rounds}")
|
|
114
|
+
print(f"Generations: {sim.generation_count}")
|
|
115
|
+
print(f"Exclusions accumulated: {outcome.exclusions}")
|
|
116
|
+
if outcome.contradiction:
|
|
117
|
+
print(f"Contradictions flagged: {outcome.contradiction}")
|
|
118
|
+
print(f"Output: {outcome.output}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
run_binary_simulation()
|
|
123
|
+
run_quaternary()
|