cascade-mcp 0.1.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.
@@ -0,0 +1,34 @@
1
+ # Large simulation outputs (regenerable — run gen_agent_logs.py / test_mcp_wrapper.py)
2
+ agent_logs.csv
3
+ agent_logs_mcp.csv
4
+
5
+ # Logs
6
+ *.log
7
+ gen_progress.log
8
+
9
+ # Python
10
+ __pycache__/
11
+ *.py[cod]
12
+ *.egg-info/
13
+ .eggs/
14
+ dist/
15
+ build/
16
+
17
+ # Virtual environments
18
+ .venv/
19
+ venv/
20
+ env/
21
+
22
+ # IDE / editor
23
+ .idea/
24
+ .vscode/
25
+ *.swp
26
+ *.swo
27
+ *~
28
+
29
+ # OS
30
+ Thumbs.db
31
+ .DS_Store
32
+
33
+ # Claude / agent settings (local-only)
34
+ .claude/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pedro Clemente-Turrubiates
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.
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: cascade-mcp
3
+ Version: 0.1.0
4
+ Summary: Cascade-resolution routing for concurrent multi-agent writes, exposed as an MCP server.
5
+ Project-URL: Homepage, https://github.com/clemente-turrubiates/cascade-mcp
6
+ Project-URL: Repository, https://github.com/clemente-turrubiates/cascade-mcp
7
+ Author: Pedro Clemente-Turrubiates
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Pedro Clemente-Turrubiates
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: agents,conflict-resolution,mcp,model-context-protocol,occ,routing
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Topic :: Software Development :: Libraries
35
+ Requires-Python: >=3.10
36
+ Requires-Dist: mcp>=1.0
37
+ Description-Content-Type: text/markdown
38
+
39
+ # cascade-mcp
40
+
41
+ [![PyPI](https://img.shields.io/pypi/v/cascade-mcp.svg)](https://pypi.org/project/cascade-mcp/)
42
+ [![Python](https://img.shields.io/pypi/pyversions/cascade-mcp.svg)](https://pypi.org/project/cascade-mcp/)
43
+ [![CI](https://github.com/clemente-turrubiates/cascade-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/clemente-turrubiates/cascade-mcp/actions/workflows/ci.yml)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
45
+
46
+ Cascade-resolution routing for concurrent multi-agent writes — a resolution
47
+ router that decides, per conflict, whether a write **wins**, **forks** to a
48
+ human, or must be **recomputed**, plus an MCP server that exposes the router as
49
+ tools and a stress-test suite that proves the behavior can't be cherry-picked.
50
+
51
+ ## What's here
52
+
53
+ The core question: when many agents write to the same field over a dependency
54
+ DAG, how do you resolve conflicts without either silently committing wrong
55
+ values (pure cascade) or overpaying in wasted re-runs (pure OCC)? The **hybrid**
56
+ policy routes zero-tolerance fields to OCC and tolerant fields to a
57
+ provenance-weighted cascade. Every conflict lands in one of a few arms:
58
+
59
+ - **WINNER** — a live (non-stale) write wins on authority → confidence. No
60
+ re-run, no human. This is the win over OCC.
61
+ - **FORK** — two+ fresh writes tie; defer to a human/high-tier agent instead of
62
+ silently dropping one.
63
+ - **RECOMPUTE** — every competing write is premise-stale; there's no correct
64
+ value to pick, so re-run. Here you're no better than OCC.
65
+
66
+ ## Layout
67
+
68
+ ```
69
+ cascade/ importable package
70
+ cascade_routing.py core resolution router (OCC vs cascade vs hybrid)
71
+ server.py MCP stdio server wrapping the router as tools
72
+ cascade_sim.py standalone go/no-go regime simulator
73
+ scripts/ data-generation / audit utilities
74
+ gen_agent_logs.py emit agent_logs.csv across the regime × policy grid
75
+ audit_cherrypick.py adversarial read of agent_logs.csv
76
+ validate_logs.py quick sanity checks on a generated CSV
77
+ tests/ verification suite
78
+ test_agent_logs.py 43-check self-consistency + usability suite over the CSV
79
+ test_mcp_wrapper.py routes the regime grid through the MCP wrapper and
80
+ re-runs the suite to prove the wrapper preserves behavior
81
+ ```
82
+
83
+ Large simulation outputs (`agent_logs.csv`, `agent_logs_mcp.csv`, ~900 MB each)
84
+ are regenerable and are gitignored.
85
+
86
+ ## Requirements
87
+
88
+ - Python ≥ 3.10 (developed on 3.13)
89
+ - [`mcp`](https://pypi.org/project/mcp/) — installed automatically as a dependency
90
+
91
+ ## Install & attach to an MCP client
92
+
93
+ Once published to PyPI, no clone or virtualenv is needed — [`uvx`](https://docs.astral.sh/uv/)
94
+ runs the server in an ephemeral environment:
95
+
96
+ ```
97
+ uvx cascade-mcp
98
+ ```
99
+
100
+ To attach the router to **Claude Desktop** or **Cursor**, add this to your
101
+ `claude_desktop_config.json`:
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "cascade": {
107
+ "command": "uvx",
108
+ "args": ["cascade-mcp"]
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ The MCP server exposes five tools: `configure`, `read_state`, `propose_update`,
115
+ `churn`, `get_field`.
116
+
117
+ ## Usage (from source)
118
+
119
+ Clone the repo and run everything **from the repo root**.
120
+
121
+ Run the MCP server (stdio):
122
+
123
+ ```
124
+ python -m cascade.server
125
+ ```
126
+
127
+ Run the standalone simulator:
128
+
129
+ ```
130
+ python -m cascade.cascade_sim
131
+ ```
132
+
133
+ Generate the stress-test CSV (writes UTF-8 — pipe via a POSIX shell, **not**
134
+ PowerShell `>`, which re-encodes to UTF-16 and corrupts the file):
135
+
136
+ ```
137
+ python scripts/gen_agent_logs.py > agent_logs.csv
138
+ ```
139
+
140
+ Verify the generated CSV:
141
+
142
+ ```
143
+ python -m tests.test_agent_logs # 43-check suite
144
+ python scripts/audit_cherrypick.py # adversarial cross-checks
145
+ ```
146
+
147
+ Verify the MCP wrapper preserves the router's behavior end-to-end (wire-protocol
148
+ smoke test → regime grid through the wrapper → re-run the suite):
149
+
150
+ ```
151
+ python -m tests.test_mcp_wrapper
152
+ ```
@@ -0,0 +1,114 @@
1
+ # cascade-mcp
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/cascade-mcp.svg)](https://pypi.org/project/cascade-mcp/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/cascade-mcp.svg)](https://pypi.org/project/cascade-mcp/)
5
+ [![CI](https://github.com/clemente-turrubiates/cascade-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/clemente-turrubiates/cascade-mcp/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ Cascade-resolution routing for concurrent multi-agent writes — a resolution
9
+ router that decides, per conflict, whether a write **wins**, **forks** to a
10
+ human, or must be **recomputed**, plus an MCP server that exposes the router as
11
+ tools and a stress-test suite that proves the behavior can't be cherry-picked.
12
+
13
+ ## What's here
14
+
15
+ The core question: when many agents write to the same field over a dependency
16
+ DAG, how do you resolve conflicts without either silently committing wrong
17
+ values (pure cascade) or overpaying in wasted re-runs (pure OCC)? The **hybrid**
18
+ policy routes zero-tolerance fields to OCC and tolerant fields to a
19
+ provenance-weighted cascade. Every conflict lands in one of a few arms:
20
+
21
+ - **WINNER** — a live (non-stale) write wins on authority → confidence. No
22
+ re-run, no human. This is the win over OCC.
23
+ - **FORK** — two+ fresh writes tie; defer to a human/high-tier agent instead of
24
+ silently dropping one.
25
+ - **RECOMPUTE** — every competing write is premise-stale; there's no correct
26
+ value to pick, so re-run. Here you're no better than OCC.
27
+
28
+ ## Layout
29
+
30
+ ```
31
+ cascade/ importable package
32
+ cascade_routing.py core resolution router (OCC vs cascade vs hybrid)
33
+ server.py MCP stdio server wrapping the router as tools
34
+ cascade_sim.py standalone go/no-go regime simulator
35
+ scripts/ data-generation / audit utilities
36
+ gen_agent_logs.py emit agent_logs.csv across the regime × policy grid
37
+ audit_cherrypick.py adversarial read of agent_logs.csv
38
+ validate_logs.py quick sanity checks on a generated CSV
39
+ tests/ verification suite
40
+ test_agent_logs.py 43-check self-consistency + usability suite over the CSV
41
+ test_mcp_wrapper.py routes the regime grid through the MCP wrapper and
42
+ re-runs the suite to prove the wrapper preserves behavior
43
+ ```
44
+
45
+ Large simulation outputs (`agent_logs.csv`, `agent_logs_mcp.csv`, ~900 MB each)
46
+ are regenerable and are gitignored.
47
+
48
+ ## Requirements
49
+
50
+ - Python ≥ 3.10 (developed on 3.13)
51
+ - [`mcp`](https://pypi.org/project/mcp/) — installed automatically as a dependency
52
+
53
+ ## Install & attach to an MCP client
54
+
55
+ Once published to PyPI, no clone or virtualenv is needed — [`uvx`](https://docs.astral.sh/uv/)
56
+ runs the server in an ephemeral environment:
57
+
58
+ ```
59
+ uvx cascade-mcp
60
+ ```
61
+
62
+ To attach the router to **Claude Desktop** or **Cursor**, add this to your
63
+ `claude_desktop_config.json`:
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "cascade": {
69
+ "command": "uvx",
70
+ "args": ["cascade-mcp"]
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ The MCP server exposes five tools: `configure`, `read_state`, `propose_update`,
77
+ `churn`, `get_field`.
78
+
79
+ ## Usage (from source)
80
+
81
+ Clone the repo and run everything **from the repo root**.
82
+
83
+ Run the MCP server (stdio):
84
+
85
+ ```
86
+ python -m cascade.server
87
+ ```
88
+
89
+ Run the standalone simulator:
90
+
91
+ ```
92
+ python -m cascade.cascade_sim
93
+ ```
94
+
95
+ Generate the stress-test CSV (writes UTF-8 — pipe via a POSIX shell, **not**
96
+ PowerShell `>`, which re-encodes to UTF-16 and corrupts the file):
97
+
98
+ ```
99
+ python scripts/gen_agent_logs.py > agent_logs.csv
100
+ ```
101
+
102
+ Verify the generated CSV:
103
+
104
+ ```
105
+ python -m tests.test_agent_logs # 43-check suite
106
+ python scripts/audit_cherrypick.py # adversarial cross-checks
107
+ ```
108
+
109
+ Verify the MCP wrapper preserves the router's behavior end-to-end (wire-protocol
110
+ smoke test → regime grid through the wrapper → re-run the suite):
111
+
112
+ ```
113
+ python -m tests.test_mcp_wrapper
114
+ ```
@@ -0,0 +1,7 @@
1
+ """cascade — cascade-resolution routing router, MCP server, and simulator.
2
+
3
+ Modules:
4
+ cascade_routing core resolution router (WINNER/FORK/RECOMPUTE/OCC arms)
5
+ server MCP stdio server wrapping the router as tools
6
+ cascade_sim standalone regime simulator
7
+ """
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ cascade_routing.py — pure OCC vs pure cascade vs per-field HYBRID.
4
+ Fair head-to-head: forked/committed fields keep contending (no freeze), so
5
+ conflict volumes are comparable across policies. Tracks BOTH costs:
6
+ recomputes wasted expensive re-runs (OCC overpays these under churn)
7
+ silent_errors committed-but-actually-wrong values (cascade risks these)
8
+
9
+ Each field has a TRUE tolerance (drift its answer can absorb). Hybrid routes
10
+ zero-tolerance fields to OCC (safe) and tolerant fields to the semantic cascade
11
+ with materiality = measured tolerance. tol_safety>1 models OVER-estimating it.
12
+ fresh_loser_redo_prob models the assumption that a fresh loser re-runs anyway.
13
+ """
14
+ from __future__ import annotations
15
+ import math
16
+ import random
17
+ from dataclasses import dataclass
18
+ from collections import Counter
19
+
20
+ @dataclass
21
+ class Field:
22
+ id: str; level: int; deps: list
23
+ rev: int = 0; value: float = 1.0
24
+ true_tol: float = 0.25; policy: str = "cascade"; materiality: float = 0.20
25
+
26
+ @dataclass
27
+ class Write:
28
+ tier: int; conf: float; read_rev: dict; read_val: dict
29
+
30
+ @dataclass
31
+ class Config:
32
+ n_levels: int = 3; fields_per_level: int = 6; deps_per_field: int = 3
33
+ conf_levels: tuple = (0.5, 0.7, 0.85, 0.95, 0.99)
34
+ rounds: int = 12000
35
+ source_write_prob: float = 0.25; contention_prob: float = 0.30
36
+ width: tuple = (2, 3); lag: int = 4; value_drift: float = 0.08
37
+ frac_zero_tol: float = 0.30; zero_tol: float = 0.01; gen_tol: float = 0.25
38
+ policy: str = "hybrid"; global_materiality: float = 0.20
39
+ route_threshold: float = 0.05; tol_safety: float = 1.0
40
+ # multiplicative log-normal noise on the per-field tolerance ESTIMATE.
41
+ # tol_safety is systematic bias; tol_est_noise is the honest "you measured
42
+ # it, but imperfectly" spread. At >0 the hybrid over-estimates on ~half its
43
+ # fields even with tol_safety=1 -> silent errors appear (the safety=1 zero
44
+ # is a perfect-knowledge artifact, not a property of the design).
45
+ tol_est_noise: float = 0.0
46
+ fresh_loser_redo_prob: float = 0.0; seed: int = 0
47
+
48
+ def drift(w, F):
49
+ return max((abs(F[d].value/v0 - 1.0) if v0 else 0.0 for d, v0 in w.read_val.items()), default=0.0)
50
+ def rev_stale(w, F):
51
+ return any(F[d].rev > s for d, s in w.read_rev.items())
52
+ def bump(f, rng, cfg):
53
+ f.rev += 1; f.value *= (1.0 + rng.gauss(0.0, cfg.value_drift))
54
+
55
+ def build(cfg, rng):
56
+ F, by = {}, []
57
+ for lvl in range(cfg.n_levels):
58
+ ids = [f"L{lvl}_{i}" for i in range(cfg.fields_per_level)]; by.append(ids)
59
+ for fid in ids:
60
+ deps = [] if lvl == 0 else rng.sample(
61
+ [x for p in by[:lvl] for x in p], min(cfg.deps_per_field, cfg.fields_per_level*lvl))
62
+ f = Field(fid, lvl, deps)
63
+ if lvl > 0:
64
+ f.true_tol = cfg.zero_tol if rng.random() < cfg.frac_zero_tol else cfg.gen_tol
65
+ measured = f.true_tol * cfg.tol_safety
66
+ if cfg.tol_est_noise > 0.0:
67
+ measured *= math.exp(rng.gauss(0.0, cfg.tol_est_noise))
68
+ if cfg.policy == "occ": f.policy = "occ"
69
+ # occ_value: OCC decision rule (commit-any-fresh, all losers
70
+ # rerun, no fork/authority) on the VALUE predicate. Isolates the
71
+ # staleness-predicate win from the routing/arbitration win.
72
+ elif cfg.policy == "occ_value":
73
+ f.policy, f.materiality = "occ_value", cfg.global_materiality
74
+ elif cfg.policy == "cascade": f.policy, f.materiality = "cascade", cfg.global_materiality
75
+ else:
76
+ if measured < cfg.route_threshold: f.policy = "occ"
77
+ else: f.policy, f.materiality = "cascade", measured
78
+ F[fid] = f
79
+ return F
80
+
81
+ def resolve(f, grp, F, cfg, rng):
82
+ if f.policy == "occ":
83
+ fresh = [w for w in grp if not rev_stale(w, F)]
84
+ if fresh: return True, len(grp) - 1, 0, "OCC_COMMIT"
85
+ return False, len(grp), 0, "OCC_ALLABORT"
86
+ if f.policy == "occ_value":
87
+ # value predicate + OCC accounting: commit one fresh, ALL losers rerun
88
+ # (no free adoption), no fork/authority. Can leak, because a flat global
89
+ # materiality has no idea of this field's true tolerance.
90
+ fresh = [w for w in grp if drift(w, F) <= f.materiality]
91
+ if not fresh: return False, len(grp), 0, "OCC_ALLABORT"
92
+ silent = 1 if drift(fresh[0], F) > f.true_tol else 0
93
+ return True, len(grp) - 1, silent, "OCC_COMMIT"
94
+ m = f.materiality
95
+ fresh = [w for w in grp if drift(w, F) <= m]; n_stale = len(grp) - len(fresh)
96
+ if not fresh: return False, len(grp), 0, "RECOMPUTE"
97
+ redo = n_stale + sum(1 for _ in range(len(fresh) - 1) if rng.random() < cfg.fresh_loser_redo_prob)
98
+ bt = min(w.tier for w in fresh); top = [w for w in fresh if w.tier == bt]
99
+ if len(top) > 1:
100
+ bc = max(w.conf for w in top); top = [w for w in top if w.conf == bc]
101
+ if len(top) > 1: return True, redo, 0, "FORK"
102
+ silent = 1 if drift(top[0], F) > f.true_tol else 0
103
+ return True, redo, silent, "WINNER"
104
+
105
+ def run(cfg):
106
+ rng = random.Random(cfg.seed); F = build(cfg, rng)
107
+ src = [f for f in F.values() if f.level == 0]; der = [f for f in F.values() if f.level > 0]
108
+ topo = sorted(F.values(), key=lambda f: f.level); pending, since, M = {}, {}, Counter()
109
+ for r in range(cfg.rounds):
110
+ for f in src:
111
+ if rng.random() < cfg.source_write_prob: bump(f, rng, cfg)
112
+ for f in der:
113
+ if f.id in pending: continue
114
+ if rng.random() < cfg.contention_prob:
115
+ grp = [Write(2, rng.choice(cfg.conf_levels),
116
+ {d: F[d].rev for d in f.deps}, {d: F[d].value for d in f.deps})
117
+ for _ in range(rng.randint(*cfg.width))]
118
+ pending[f.id] = grp; since[f.id] = r
119
+ for f in topo:
120
+ g = pending.get(f.id)
121
+ if g is None or r - since[f.id] < cfg.lag: continue
122
+ committed, redo, silent, arm = resolve(f, g, F, cfg, rng)
123
+ M["conflicts"] += 1; M["recomputes"] += redo; M["silent_errors"] += silent; M[arm] += 1
124
+ if committed: M["commits"] += 1; bump(f, rng, cfg) # keeps contending; no freeze
125
+ del pending[f.id]; del since[f.id]
126
+ return M
127
+
128
+ def rep(label, M):
129
+ c = M["conflicts"] or 1; cm = M["commits"] or 1
130
+ print(f" {label:<28} {M['recomputes']/c:4.2f} recompute/conflict "
131
+ f"{M['recomputes']/cm:5.2f} recompute/commit "
132
+ f"silent_err {M['silent_errors']:>4} conflicts {M['conflicts']:>6}")
133
+
134
+ def main():
135
+ print("=" * 104)
136
+ print("POLICY HEAD-TO-HEAD (lag=4; 30% price-like tol~0.01, 70% estimate-like tol~0.25)")
137
+ print("=" * 104)
138
+ print("\n[10] pure OCC(rev) vs OCC(value) vs pure CASCADE(0.20) vs HYBRID(routed)")
139
+ rep("pure OCC (rev-staleness)", run(Config(policy="occ")))
140
+ rep("OCC (value-staleness 0.20)", run(Config(policy="occ_value", global_materiality=0.20)))
141
+ rep("pure CASCADE (global 0.20)", run(Config(policy="cascade", global_materiality=0.20)))
142
+ rep("HYBRID (measured tol)", run(Config(policy="hybrid")))
143
+ print(" OCC(value) vs OCC(rev): the throughput gain that is JUST the predicate,")
144
+ print(" not the routing. OCC(value) silent_err > 0: the predicate alone is unsafe.")
145
+
146
+ print("\n[10b] HYBRID under NOISY (imperfect) tolerance measurement, tol_safety=1")
147
+ print(" the safety=1 zero-leak is a perfect-knowledge artifact; noise leaks:")
148
+ for nz in (0.0, 0.5, 1.0):
149
+ rep(f"hybrid tol_est_noise={nz:.1f}", run(Config(policy="hybrid", tol_est_noise=nz)))
150
+
151
+ print("\n[11] survives the 'fresh loser adopts winner free' assumption being switched off?")
152
+ for p in (0.0, 0.5, 1.0):
153
+ rep(f"hybrid redo_prob={p:.1f}", run(Config(policy="hybrid", fresh_loser_redo_prob=p)))
154
+
155
+ print("\n[12] over-estimating tolerance: PURE CASCADE (bites hard) vs HYBRID (only cascade-routed fields)")
156
+ for s in (1.0, 2.0, 5.0):
157
+ rep(f"cascade tol_safety={s:.0f}", run(Config(policy="cascade", global_materiality=0.20 * s)))
158
+ for s in (1.0, 2.0, 5.0):
159
+ rep(f"hybrid tol_safety={s:.0f}", run(Config(policy="hybrid", tol_safety=s)))
160
+ print(" Measure tolerance conservatively (safety<=1) -> silent_err stays 0.")
161
+ print("=" * 104)
162
+
163
+ if __name__ == "__main__":
164
+ main()