debatebench 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.
Files changed (199) hide show
  1. debatebench-0.1.0/.github/workflows/release.yml +107 -0
  2. debatebench-0.1.0/.github/workflows/tests.yml +44 -0
  3. debatebench-0.1.0/.gitignore +27 -0
  4. debatebench-0.1.0/.python-version +1 -0
  5. debatebench-0.1.0/ADR-001-orchestration-build-vs-reuse.md +126 -0
  6. debatebench-0.1.0/ADR-002-debatebench-architecture.md +356 -0
  7. debatebench-0.1.0/ADR-003-afm-backend-via-fm-serve.md +132 -0
  8. debatebench-0.1.0/ADR-004-test-framework.md +96 -0
  9. debatebench-0.1.0/ADR-005-transcript-format.md +156 -0
  10. debatebench-0.1.0/ADR-006-team-files-manual-for-now.md +59 -0
  11. debatebench-0.1.0/ADR-007-run-yaml-schema.md +229 -0
  12. debatebench-0.1.0/ADR-008-runtime-baseline.md +76 -0
  13. debatebench-0.1.0/ADR-009-backend-seam-types.md +106 -0
  14. debatebench-0.1.0/ADR-010-turn-rules.md +98 -0
  15. debatebench-0.1.0/ADR-011-response-length.md +112 -0
  16. debatebench-0.1.0/ADR-012-prep-retrieval.md +173 -0
  17. debatebench-0.1.0/ADR-013-judge-scoring.md +202 -0
  18. debatebench-0.1.0/ADR-014-prep-details.md +146 -0
  19. debatebench-0.1.0/ADR-015-fact-check-in-judge.md +223 -0
  20. debatebench-0.1.0/ADR-016-per-phase-length.md +131 -0
  21. debatebench-0.1.0/ADR-017-judge-cli-details.md +202 -0
  22. debatebench-0.1.0/ADR-018-ollama-for-real-model-runs.md +174 -0
  23. debatebench-0.1.0/ADR-019-contradiction-takes-precedence.md +146 -0
  24. debatebench-0.1.0/ADR-020-judge-settings-in-run-yaml.md +210 -0
  25. debatebench-0.1.0/ADR-021-debate-override-flags.md +121 -0
  26. debatebench-0.1.0/ADR-022-default-phase-length.md +137 -0
  27. debatebench-0.1.0/ADR-023-coin-toss-tiebreak.md +128 -0
  28. debatebench-0.1.0/ADR-024-the-factual-test-comes-first.md +158 -0
  29. debatebench-0.1.0/ADR-025-configurable-read-timeout.md +120 -0
  30. debatebench-0.1.0/ADR-026-a-malformed-reply-names-its-repair.md +130 -0
  31. debatebench-0.1.0/ADR-027-event-stream-on-stdout.md +158 -0
  32. debatebench-0.1.0/ADR-028-public-api.md +260 -0
  33. debatebench-0.1.0/ADR-029-real-pypi-release.md +167 -0
  34. debatebench-0.1.0/BACKEND-PROBE-RESULTS.md +482 -0
  35. debatebench-0.1.0/BACKEND-PROBE.md +93 -0
  36. debatebench-0.1.0/BUILD-GUIDE.md +425 -0
  37. debatebench-0.1.0/CLAUDE.md +453 -0
  38. debatebench-0.1.0/JUDGE-VALIDATION.md +400 -0
  39. debatebench-0.1.0/LICENSE +21 -0
  40. debatebench-0.1.0/MODEL-COVERAGE.md +246 -0
  41. debatebench-0.1.0/NOTICE +70 -0
  42. debatebench-0.1.0/OPEN-QUESTIONS.md +404 -0
  43. debatebench-0.1.0/PKG-INFO +525 -0
  44. debatebench-0.1.0/R0-RESULTS.md +507 -0
  45. debatebench-0.1.0/R0-repo-review-session.md +83 -0
  46. debatebench-0.1.0/README.md +502 -0
  47. debatebench-0.1.0/RESULTS.md +185 -0
  48. debatebench-0.1.0/SESSION-2026-09-15.md +111 -0
  49. debatebench-0.1.0/debate-formats-research.md +60 -0
  50. debatebench-0.1.0/examples/README.md +212 -0
  51. debatebench-0.1.0/examples/run-prep.yaml +51 -0
  52. debatebench-0.1.0/examples/run.yaml +90 -0
  53. debatebench-0.1.0/examples/teams/advocate.yaml +32 -0
  54. debatebench-0.1.0/examples/teams/neutral-corpus.jsonl +6 -0
  55. debatebench-0.1.0/examples/teams/neutral.yaml +14 -0
  56. debatebench-0.1.0/examples/teams/skeptic.yaml +9 -0
  57. debatebench-0.1.0/examples/teams/social-democrat-corpus.jsonl +6 -0
  58. debatebench-0.1.0/examples/teams/social-democrat.yaml +12 -0
  59. debatebench-0.1.0/examples/teams/tea-party-corpus.jsonl +6 -0
  60. debatebench-0.1.0/examples/teams/tea-party.yaml +8 -0
  61. debatebench-0.1.0/probe/b0/b0_probe.py +541 -0
  62. debatebench-0.1.0/probe/b0/b0_raw.json +3885 -0
  63. debatebench-0.1.0/probe/b0/download.log +16 -0
  64. debatebench-0.1.0/probe/b0/logs/afm.log +18 -0
  65. debatebench-0.1.0/probe/b0/logs/mistral-small-24b.log +3 -0
  66. debatebench-0.1.0/probe/b0/logs/qwen3-8b.log +67 -0
  67. debatebench-0.1.0/probe/b0/run.log +15 -0
  68. debatebench-0.1.0/probe/b0/summarize.py +95 -0
  69. debatebench-0.1.0/probe/b6/README.md +597 -0
  70. debatebench-0.1.0/probe/b6/baseline-a-d1.json +222 -0
  71. debatebench-0.1.0/probe/b6/baseline-a-d2.json +290 -0
  72. debatebench-0.1.0/probe/b6/baseline-a-d3.json +290 -0
  73. debatebench-0.1.0/probe/b6/baseline-a-d4.json +290 -0
  74. debatebench-0.1.0/probe/b6/baseline-a-d5.json +290 -0
  75. debatebench-0.1.0/probe/b6/condition-b-cold-1.json +202 -0
  76. debatebench-0.1.0/probe/b6/condition-b-cold-2.json +202 -0
  77. debatebench-0.1.0/probe/b6/condition-b-warm-1.json +270 -0
  78. debatebench-0.1.0/probe/b6/condition-b-warm-2.json +270 -0
  79. debatebench-0.1.0/probe/b6/condition-c-cold-1.json +247 -0
  80. debatebench-0.1.0/probe/b6/condition-c-cold-2.json +247 -0
  81. debatebench-0.1.0/probe/b6/condition-c-warm-1.json +158 -0
  82. debatebench-0.1.0/probe/b6/condition-c-warm-2.json +158 -0
  83. debatebench-0.1.0/probe/b6/gate-qwen3-8b-2026-09-14-repeat.json +290 -0
  84. debatebench-0.1.0/probe/b6/gate-qwen3-8b-2026-09-14-s3.json +290 -0
  85. debatebench-0.1.0/probe/b6/gate-qwen3-8b-2026-09-14-s4.json +290 -0
  86. debatebench-0.1.0/probe/b6/gate-qwen3-8b-2026-09-14-s5.json +290 -0
  87. debatebench-0.1.0/probe/b6/gate-qwen3-8b-2026-09-14-s6.json +290 -0
  88. debatebench-0.1.0/probe/b6/gate-qwen3-8b-2026-09-14-s7.json +290 -0
  89. debatebench-0.1.0/probe/b6/gate-qwen3-8b-2026-09-14.json +222 -0
  90. debatebench-0.1.0/probe/b6/ladder-qwen3-0_6b.json +102 -0
  91. debatebench-0.1.0/probe/b6/ladder-qwen3-14b.json +209 -0
  92. debatebench-0.1.0/probe/b6/ladder-qwen3-1_7b.json +97 -0
  93. debatebench-0.1.0/probe/b6/ladder-qwen3-4b.json +191 -0
  94. debatebench-0.1.0/probe/b6/ladder-qwen35-4b.json +233 -0
  95. debatebench-0.1.0/probe/b6/q14-cold-1.json +209 -0
  96. debatebench-0.1.0/probe/b6/q14-cold-2.json +209 -0
  97. debatebench-0.1.0/probe/b6/q14-warm-1.json +215 -0
  98. debatebench-0.1.0/probe/b6/q14-warm-2.json +215 -0
  99. debatebench-0.1.0/probe/b6/rejudge-2026-09-14-a.json +222 -0
  100. debatebench-0.1.0/probe/b6/rejudge-2026-09-14-b.json +290 -0
  101. debatebench-0.1.0/probe/b6/scores-2026-09-14-pre-adr-019.json +287 -0
  102. debatebench-0.1.0/probe/b6/statetest-cold.json +222 -0
  103. debatebench-0.1.0/probe/b6/statetest-warm.json +290 -0
  104. debatebench-0.1.0/probe/b6/transcript-2026-09-14-gate.json +246 -0
  105. debatebench-0.1.0/probe/backend/probe-a-a2recheck-mlx_lm.json +13 -0
  106. debatebench-0.1.0/probe/backend/probe-a-a2recheck-ollama.json +13 -0
  107. debatebench-0.1.0/probe/backend/probe-a-a2recheck-vllm-mlx.json +13 -0
  108. debatebench-0.1.0/probe/backend/probe-a-mlx_lm-budgeted.json +35 -0
  109. debatebench-0.1.0/probe/backend/probe-a-mlx_lm-rerun.json +34 -0
  110. debatebench-0.1.0/probe/backend/probe-a-mlx_lm.json +45 -0
  111. debatebench-0.1.0/probe/backend/probe-a-ollama-qwen3.json +73 -0
  112. debatebench-0.1.0/probe/backend/probe-a-ollama.json +67 -0
  113. debatebench-0.1.0/probe/backend/probe-a-vllm-mlx-a8.json +12 -0
  114. debatebench-0.1.0/probe/backend/probe-a-vllm-mlx-a9.json +16 -0
  115. debatebench-0.1.0/probe/backend/probe-a-vllm-mlx.json +64 -0
  116. debatebench-0.1.0/probe/backend/probe-b-mlx_lm.json +47 -0
  117. debatebench-0.1.0/probe/backend/probe-b-ollama.json +42 -0
  118. debatebench-0.1.0/probe/backend/probe-b-vllm-mlx.json +47 -0
  119. debatebench-0.1.0/probe/backend/probe-b5-mlx_lm.json +37 -0
  120. debatebench-0.1.0/probe/backend/probe_a.py +224 -0
  121. debatebench-0.1.0/probe/backend/probe_b.py +198 -0
  122. debatebench-0.1.0/probe/backend/probe_b5.py +148 -0
  123. debatebench-0.1.0/probe/mirror/README.md +83 -0
  124. debatebench-0.1.0/probe/mirror/mirror-1-scores.json +104 -0
  125. debatebench-0.1.0/probe/mirror/mirror-1-transcript.json +199 -0
  126. debatebench-0.1.0/probe/mirror/mirror-10-scores.json +92 -0
  127. debatebench-0.1.0/probe/mirror/mirror-10-transcript.json +199 -0
  128. debatebench-0.1.0/probe/mirror/mirror-11-scores.json +104 -0
  129. debatebench-0.1.0/probe/mirror/mirror-11-transcript.json +199 -0
  130. debatebench-0.1.0/probe/mirror/mirror-2-scores.INVALID-stale-copy.json +104 -0
  131. debatebench-0.1.0/probe/mirror/mirror-2-transcript.json +199 -0
  132. debatebench-0.1.0/probe/mirror/mirror-3-scores.json +100 -0
  133. debatebench-0.1.0/probe/mirror/mirror-3-transcript.json +199 -0
  134. debatebench-0.1.0/probe/mirror/mirror-4-scores.json +100 -0
  135. debatebench-0.1.0/probe/mirror/mirror-4-transcript.json +199 -0
  136. debatebench-0.1.0/probe/mirror/mirror-5-scores.json +104 -0
  137. debatebench-0.1.0/probe/mirror/mirror-5-transcript.json +199 -0
  138. debatebench-0.1.0/probe/mirror/mirror-6-scores.INVALID-stale-copy.json +104 -0
  139. debatebench-0.1.0/probe/mirror/mirror-6-transcript.INVALID-stale-copy.json +199 -0
  140. debatebench-0.1.0/probe/mirror/mirror-7-scores.json +100 -0
  141. debatebench-0.1.0/probe/mirror/mirror-7-transcript.json +199 -0
  142. debatebench-0.1.0/probe/mirror/mirror-8-scores.json +96 -0
  143. debatebench-0.1.0/probe/mirror/mirror-8-transcript.json +199 -0
  144. debatebench-0.1.0/probe/mirror/mirror-9-scores.json +100 -0
  145. debatebench-0.1.0/probe/mirror/mirror-9-transcript.json +199 -0
  146. debatebench-0.1.0/probe/scale/weak-debater.yaml +22 -0
  147. debatebench-0.1.0/probe/scale/weak-run.yaml +31 -0
  148. debatebench-0.1.0/probe/scale/weak-scores.json +104 -0
  149. debatebench-0.1.0/probe/scale/weak-transcript.json +163 -0
  150. debatebench-0.1.0/probe/validation/human_ceiling.py +91 -0
  151. debatebench-0.1.0/probe/validation/judge-scores-qwen3-8b-full.json +5059 -0
  152. debatebench-0.1.0/probe/validation/judge-scores-qwen3-8b-subset.json +947 -0
  153. debatebench-0.1.0/probe/validation/judge-scores-timing.json +83 -0
  154. debatebench-0.1.0/probe/validation/run_judge.py +187 -0
  155. debatebench-0.1.0/probe/validation/score_agreement.py +110 -0
  156. debatebench-0.1.0/pyproject.toml +45 -0
  157. debatebench-0.1.0/src/debatebench/__init__.py +5 -0
  158. debatebench-0.1.0/src/debatebench/api.py +242 -0
  159. debatebench-0.1.0/src/debatebench/backend.py +56 -0
  160. debatebench-0.1.0/src/debatebench/cli.py +198 -0
  161. debatebench-0.1.0/src/debatebench/config.py +500 -0
  162. debatebench-0.1.0/src/debatebench/event_stream.py +116 -0
  163. debatebench-0.1.0/src/debatebench/events.py +59 -0
  164. debatebench-0.1.0/src/debatebench/judge_cli.py +200 -0
  165. debatebench-0.1.0/src/debatebench/judging.py +726 -0
  166. debatebench-0.1.0/src/debatebench/openai_compat.py +151 -0
  167. debatebench-0.1.0/src/debatebench/orchestrator.py +161 -0
  168. debatebench-0.1.0/src/debatebench/prompts.py +129 -0
  169. debatebench-0.1.0/src/debatebench/retrieval.py +171 -0
  170. debatebench-0.1.0/src/debatebench/transcript.py +321 -0
  171. debatebench-0.1.0/src/debatebench/yaml_loader.py +80 -0
  172. debatebench-0.1.0/tests/conftest.py +26 -0
  173. debatebench-0.1.0/tests/fakes.py +59 -0
  174. debatebench-0.1.0/tests/fixtures/run.yaml +20 -0
  175. debatebench-0.1.0/tests/fixtures/sources/args-me.jsonl +9 -0
  176. debatebench-0.1.0/tests/fixtures/sources/debatesum.jsonl +3 -0
  177. debatebench-0.1.0/tests/fixtures/teams/conservative.yaml +5 -0
  178. debatebench-0.1.0/tests/fixtures/teams/liberal-climate-corpus.jsonl +3 -0
  179. debatebench-0.1.0/tests/fixtures/teams/liberal.yaml +6 -0
  180. debatebench-0.1.0/tests/helpers.py +21 -0
  181. debatebench-0.1.0/tests/test_api.py +235 -0
  182. debatebench-0.1.0/tests/test_cli.py +149 -0
  183. debatebench-0.1.0/tests/test_config.py +343 -0
  184. debatebench-0.1.0/tests/test_event_stream.py +166 -0
  185. debatebench-0.1.0/tests/test_fact_check.py +250 -0
  186. debatebench-0.1.0/tests/test_judge_cli.py +302 -0
  187. debatebench-0.1.0/tests/test_judging.py +452 -0
  188. debatebench-0.1.0/tests/test_layering.py +36 -0
  189. debatebench-0.1.0/tests/test_length.py +195 -0
  190. debatebench-0.1.0/tests/test_live_afm.py +239 -0
  191. debatebench-0.1.0/tests/test_live_judge.py +238 -0
  192. debatebench-0.1.0/tests/test_openai_compat.py +153 -0
  193. debatebench-0.1.0/tests/test_orchestrator.py +273 -0
  194. debatebench-0.1.0/tests/test_output.py +147 -0
  195. debatebench-0.1.0/tests/test_packaging.py +93 -0
  196. debatebench-0.1.0/tests/test_prep.py +310 -0
  197. debatebench-0.1.0/tests/test_prompts.py +68 -0
  198. debatebench-0.1.0/tests/test_timeout.py +68 -0
  199. debatebench-0.1.0/uv.lock +219 -0
@@ -0,0 +1,107 @@
1
+ # ADR-029: publish to PyPI with no PyPI credential anywhere. The job proves it
2
+ # is this repository to PyPI over OIDC, so there is no token to store, rotate,
3
+ # leak or revoke — which is the specific failure this project has already had.
4
+ #
5
+ # Before the first run, the account owner must create a *pending publisher* at
6
+ # https://pypi.org/manage/account/publishing/ binding:
7
+ # project debatebench
8
+ # owner robert-e-roy
9
+ # repository debatebench
10
+ # workflow release.yml
11
+ # environment pypi
12
+ # Until that exists the upload step fails to authenticate, which is the intended
13
+ # failure mode: nothing can publish by accident.
14
+ name: release
15
+
16
+ # A *published GitHub Release*, not a tag push (ADR-029 §3): pushing a tag by
17
+ # mistake must not publish anything, and a version on PyPI cannot be replaced.
18
+ # No workflow_dispatch: a manual run would publish from whatever main happens to
19
+ # be, which is the same accident §3 rules out for a stray tag push, by another
20
+ # route. Re-running a failed release means a new version, because PyPI will not
21
+ # take 0.1.0 twice however the first attempt went.
22
+ on:
23
+ release:
24
+ types: [published]
25
+
26
+ permissions:
27
+ contents: read
28
+
29
+ jobs:
30
+ build:
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v7
34
+ # Pinned exactly: setup-uv publishes no floating major tag (see tests.yml).
35
+ - uses: astral-sh/setup-uv@v10.1.0
36
+ with:
37
+ python-version: "3.11"
38
+
39
+ # The suite runs here too. The release artifact and the tested tree are
40
+ # then the same checkout, rather than two things assumed to match.
41
+ - run: uv sync --locked
42
+ - run: uv run --locked pytest -q
43
+
44
+ - name: Build the wheel and sdist from the tagged commit
45
+ run: uv build
46
+
47
+ - name: Refuse metadata PyPI would reject
48
+ run: uvx twine check dist/*
49
+
50
+ - uses: actions/upload-artifact@v7
51
+ with:
52
+ name: dist
53
+ path: dist/
54
+
55
+ publish:
56
+ needs: build
57
+ runs-on: ubuntu-latest
58
+ # Named so the trusted-publisher binding can require it, and so a reviewer
59
+ # can be added later without changing this file.
60
+ environment:
61
+ name: pypi
62
+ url: https://pypi.org/p/debatebench
63
+ permissions:
64
+ id-token: write # the OIDC token; this is the whole authentication story
65
+ steps:
66
+ - uses: actions/download-artifact@v8
67
+ with:
68
+ name: dist
69
+ path: dist/
70
+
71
+ - uses: pypa/gh-action-pypi-publish@v1.14.2
72
+
73
+ # twine check validates metadata, not that the thing installs and runs. B7's
74
+ # gate clause was "install from an index and run", and that is what proved
75
+ # determinism across a packaging boundary — so the release repeats it against
76
+ # real PyPI. This cannot prevent a bad upload; it tells us within a minute
77
+ # whether to yank, which is the only remedy ADR-029 §6 leaves.
78
+ verify:
79
+ needs: publish
80
+ runs-on: ubuntu-latest
81
+ steps:
82
+ - uses: actions/checkout@v7
83
+ - uses: actions/setup-python@v6
84
+ with:
85
+ python-version: "3.11"
86
+
87
+ - name: Install from real PyPI into a venv that has never seen the source
88
+ run: |
89
+ version="$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
90
+ echo "waiting for debatebench==$version to be installable"
91
+ cd "$(mktemp -d)" # out of the checkout, so nothing resolves locally
92
+ python -m venv venv
93
+ # PyPI's index can lag the upload by under a minute; retry rather than
94
+ # fail a release on CDN timing.
95
+ for attempt in 1 2 3 4 5 6 7 8 9 10; do
96
+ if ./venv/bin/pip install --no-cache-dir "debatebench==$version"; then
97
+ installed=yes; break
98
+ fi
99
+ echo "attempt $attempt failed; retrying in 15s"
100
+ sleep 15
101
+ done
102
+ [ "${installed:-no}" = yes ] || { echo "::error::debatebench==$version never became installable"; exit 1; }
103
+
104
+ ./venv/bin/debate --help > /dev/null
105
+ ./venv/bin/judge --help > /dev/null
106
+ ./venv/bin/python -c "from debatebench.api import debate, judge, load_run; from debatebench.transcript import package_version; print('installed version:', package_version())"
107
+ echo "debatebench==$version installs from PyPI and both commands run"
@@ -0,0 +1,44 @@
1
+ # CLAUDE.md said "CI is deferred to B7" from B0 until 2026-09-16. B7 finished on
2
+ # 2026-09-14 without a workflow because there was no remote to run one; the repo
3
+ # now exists, so this closes it.
4
+ name: tests
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ pytest:
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ # pyproject declares >=3.11 and classifies 3.11 and 3.12. Both run.
22
+ python-version: ["3.11", "3.12"]
23
+
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+
27
+ # Pinned exactly: astral-sh/setup-uv publishes no floating `v10` tag —
28
+ # only v10.0.0, v10.0.1, v10.1.0 — so `@v10` fails to resolve at all.
29
+ # actions/checkout does publish `v7`, hence the difference above.
30
+ - uses: astral-sh/setup-uv@v10.1.0
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+ enable-cache: true
34
+
35
+ # --locked, not --frozen: a uv.lock that has drifted from pyproject should
36
+ # fail here rather than be silently re-resolved into something untested.
37
+ - run: uv sync --locked
38
+
39
+ # DEBATEBENCH_LIVE_TESTS is deliberately unset, so every test runs against
40
+ # the scripted FakeBackend (ADR-004) and the live-model tests skip. CI has
41
+ # no Ollama, no AFM and no GPU, and a suite that needed one would be a
42
+ # suite nobody could run.
43
+ - name: pytest (fake backend only; live-model tests skip)
44
+ run: uv run --locked pytest -q
@@ -0,0 +1,27 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ .venv/
5
+ .pytest_cache/
6
+
7
+ # Build artifacts (`uv build` writes here). Ignored by a global excludes file on
8
+ # the machine this was developed on, which does not survive a clone.
9
+ dist/
10
+ build/
11
+ *.egg-info/
12
+
13
+ # Editors / macOS
14
+ *.swp
15
+ .DS_Store
16
+
17
+ # Run artifacts. The README tells people to run `debate examples/run.yaml`,
18
+ # whose `output:` resolves next to the run.yaml — so a transcript, its rotated
19
+ # `.1` backup, and the score file all land inside examples/ on a plain clone.
20
+ examples/transcript.json
21
+ examples/transcript.json.1
22
+ examples/scores.json
23
+ examples/scores.json.1
24
+ examples/transcript-prep.json
25
+ examples/transcript-prep.json.1
26
+ examples/scores-prep.json
27
+ examples/scores-prep.json.1
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,126 @@
1
+ # ADR-001: Debate Orchestration Layer — Build vs. Reuse
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-09-10
5
+ **Amended:** 2026-09-11 — rationale wording and cross-references corrected against
6
+ the R0 results; the decision itself is unchanged.
7
+ **Context session:** R0 repo review (aragora-debate, arbgjr/multi-agent-debate,
8
+ paolodalprato/agent-discussion-arena, rd-serendipity/ai-debate-arena). The brief and
9
+ full results are in this repo, as `R0-repo-review-session.md` and `R0-RESULTS.md`
10
+ (moved from `~/Projects/DebateKit/` on 2026-09-11); scores cited below come from
11
+ the latter.
12
+
13
+ ## Decision
14
+
15
+ Build our own orchestration layer from scratch (~400–500 lines, zero required
16
+ dependencies). Do not adopt or fork any of the four reviewed projects. Lift three
17
+ narrow, attributed pieces; take three design ideas; design against four observed
18
+ anti-patterns.
19
+
20
+ "Zero required dependencies" applies to the orchestration layer itself — the phase
21
+ loop, event dispatch and backend `Protocol` that R0 sized at ~400–500 lines. Config
22
+ parsing sits outside that layer and can't be stdlib-only (ADR-002's config is YAML,
23
+ and Python's standard library has no YAML parser). The HTTP adapter can stay
24
+ stdlib-only (`urllib` under `asyncio.to_thread`, which anti-pattern 4 below permits)
25
+ or take an async client; ADR-003 leaves that open.
26
+
27
+ ## Why
28
+
29
+ None of the four reviewed projects fits, and not for one shared reason. Two
30
+ (`aragora-debate`, `arbgjr/multi-agent-debate`) are built to make agents converge
31
+ toward agreement, and in both that objective is hardcoded into the turn loop
32
+ itself, not exposed as a setting (R0 consensus-separation scores 2/5 and 1/5).
33
+ `rd-serendipity/ai-debate-arena` has no consensus logic at all (5/5) but engineering
34
+ we can't build on. `agent-discussion-arena` has no importable orchestration code at
35
+ all. Our requirement — sustained, non-converging adversarial debate with per-side
36
+ model/budget asymmetry — is not a configuration difference from consensus-seeking;
37
+ it's the opposite objective.
38
+
39
+ The clearest single data point: the project with the best engineering
40
+ (`aragora-debate` — clean `Agent` ABC, excellent standalone event system, verified
41
+ MIT license) has the worst fit on the one axis that matters most, separation from
42
+ consensus machinery (2/5). The project with the best fit on that axis
43
+ (`rd-serendipity`, true 1v1, no consensus logic anywhere) has the worst engineering
44
+ — LangChain-bound to September 2024 pins, fully synchronous, dead two years, no
45
+ tests. Nothing scored well on both. That is the whole decision.
46
+
47
+ ## What we take (with attribution — all four repos are MIT)
48
+
49
+ | From | What | Form |
50
+ |---|---|---|
51
+ | `aragora-debate/events.py` | Typed `EventType` enum + `DebateEvent` dataclass, sync+async dispatch, per-callback exception isolation | Design lift, ~120 lines, reimplemented |
52
+ | `arbgjr/multi-agent-debate` `protocols.py` | Single-method `async generate(...)` `Protocol` as the LLM backend seam (not a multi-method ABC) | Design lift, ~20 lines |
53
+ | `aragora-debate/evidence.py` | Stdlib-only, LLM-free, five separately-scored dimensions (citation density, specificity, evidence diversity, temporal relevance, logical chain) | Conditional lift — see the fact-checker bullet under "Decisions this locks in" on naming (R0 flag #5) |
54
+ | `paolodalprato/agent-discussion-arena` | Phase model: `Opening → Round 1..N → Verdict`, verdict as a distinct terminal phase, not a vote folded into the loop | Idea only, no code |
55
+ | `paolodalprato/agent-discussion-arena` | One `openai-compatible` adapter with configurable `base_url` serves MLX (`mlx_lm.server`), Ollama, LM Studio, and OpenAI itself | Idea only, no code |
56
+ | `rd-serendipity/ai-debate-arena` | Alternating initiative — swap who opens each round so neither side keeps first-mover advantage | Idea only, ~2 lines when implemented |
57
+
58
+ ## Anti-patterns to design against (each observed as a real bug in a reviewed repo)
59
+
60
+ 1. **Never let a phase failure be silent.** Aragora's `_run_propose`/`_run_vote` use
61
+ `return_exceptions=True` and `continue` past a failed side with only a log
62
+ warning; `_pick_winner` then defaults to `agents[0]`, so a one-sided debate
63
+ still emits a confident-looking result. **Our rule: every phase must produce a
64
+ response from every configured side, or the run hard-fails and `--output` is
65
+ not written.**
66
+ 2. **Never key transcript state by side alone.** Aragora's `_proposals` dict is
67
+ overwritten each round, so `result.proposals` only ever holds the final round.
68
+ **Our rule: key all transcript state by `(round, side)`.**
69
+ 3. **Never blend judge dimensions into one number, and never return 0 on a parse
70
+ failure.** rd-serendipity's judge asks for a single 0–100 score blending
71
+ relevance, coherence, and fact-check, parsed by `RegexParser(r"(\d+)")`, which
72
+ grabs the *first* integer anywhere in the output — a reply starting "Criteria
73
+ 1..." scores 1 — and returns 0 on any parse failure, indistinguishable from a
74
+ genuinely terrible argument. **Our rule: score rubric dimensions separately;
75
+ treat a parse failure as an error, never a score.**
76
+ 4. **Never call a synchronous SDK client inside `async def`.** All four of
77
+ Aragora's provider agents construct sync clients (`anthropic.Anthropic`,
78
+ `openai.OpenAI`, etc.) and call them directly inside `async def` with no
79
+ `await` and no `asyncio.to_thread` — so `asyncio.gather` across agents
80
+ delivers zero real concurrency and blocks the event loop. For a tool also
81
+ driving a live dashboard, this would stall the UI. **Our rule: use a real
82
+ async client, or wrap sync calls in `asyncio.to_thread`.**
83
+
84
+ ## Decisions this locks in (changes/sharpens the prior plan)
85
+
86
+ - **Two commands (`debate` / `judge`) stays as designed.** Aragora's fusion of
87
+ judging into the debate loop is exactly what forces its wasted vote-phase calls.
88
+ No reviewed project argues for merging the two.
89
+ - **`debate` writes to `--output` file only stays as designed.** No conflicts
90
+ found. Aragora's `DebateResult.to_dict()` + `DecisionReceipt.to_markdown()` is a
91
+ reasonable precedent for pairing a machine-readable and human-readable view of
92
+ one run in the output schema.
93
+ - **Phases must be data, not control flow.** Both Aragora and arbgjr hardcode
94
+ their phase sequence as literal branches in the loop body — Aragora even has a
95
+ `Phase` enum its own loop ignores, with two dead members. **Our loop iterates a
96
+ list of phases; it does not branch on round number.**
97
+ - **Per-side budgets are enforced by the orchestrator, per phase — not by each
98
+ backend's constructor.** Aragora's `max_tokens`/`temperature` are genuine
99
+ per-agent constructor args, but every provider's `vote()` hardcodes its own
100
+ 512-token, 0.3-temperature budget regardless of what was configured, so the
101
+ configured budget silently doesn't hold across every phase. For a benchmarking
102
+ tool, an unenforced budget claim is worse than no budget claim.
103
+ - **Fast fact-checker vs. slow end-of-debate judge split is vindicated.** No
104
+ reviewed project implements this separation. Aragora's `evidence.py` is the
105
+ nearest analogue but is pure regex over surface form (citation patterns, data
106
+ patterns, vague-vs-specific phrasing) with zero LLM calls and zero verification
107
+ of truth — a confidently fabricated citation with a recent year and a
108
+ percentage scores *high*. **If adopted, it is labeled "evidence hygiene," not
109
+ "fact-check," and sits alongside a real fact-checker, never in place of one.**
110
+ - **New invariant, not previously in the plan:** every phase must produce a
111
+ response from every configured side, or the run aborts and no output file is
112
+ written. (See anti-pattern 1.)
113
+
114
+ ## Consequences
115
+
116
+ - We own and maintain the full orchestration loop (~400–500 lines). No external
117
+ dependency risk from a project whose roadmap has nothing to do with ours.
118
+ - We do not get Aragora's four cloud-provider integrations for free, but we didn't
119
+ want them as the primary path anyway (MLX-first).
120
+ - Building the `Agent`/backend seam as a single-method protocol (per arbgjr's
121
+ shape) keeps an MLX implementation to one method rather than three, avoiding the
122
+ work an Aragora-based `Agent` subclass would require (structured `Critique`/
123
+ `Vote` parsing out of a local model).
124
+ - Attribution required in our source/NOTICE file for the two design lifts
125
+ (`events.py` shape, `LLMProviderProtocol` shape) and the conditional
126
+ `evidence.py`-derived evidence-hygiene scorer, per each project's MIT license.
@@ -0,0 +1,356 @@
1
+ # ADR-002: DebateBench — Project Scope and Architecture
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-09-11
5
+ **Supersedes:** nothing. **Depends on:** ADR-001 (orchestration build vs. reuse)
6
+ **Amended:** 2026-09-11 — cross-references, dataset facts and wording corrected. B0's
7
+ hardware findings added to "Hardware", withdrawing the `free` offload option. An
8
+ offline requirement for model servers added to "Backend abstraction".
9
+
10
+ ## What this is
11
+
12
+ `debatebench` is an open-source Python CLI for running structured, multi-turn,
13
+ adversarial LLM debates and scoring them against a fixed rubric, with a
14
+ post-hoc fact-check pass inside `judge` (ADR-015; real-time per-turn
15
+ fact-checking is the Swift app's feature, not this repo's). It generalizes into a broader pattern (see the MetaTool notes,
16
+ `~/Projects/metatool.md`, outside this repo) but this ADR scopes the concrete first
17
+ implementation.
18
+
19
+ A Swift port, `DebateKit`, is a later, separate effort once this design stops
20
+ changing session to session (see "Language split" below). Not started yet.
21
+
22
+ ## Why it exists
23
+
24
+ Built to evaluate MLX model + grounding-dataset combinations for a debate-format
25
+ Mac app, but scoped from the start to be generally useful for comparing LLMs on
26
+ structured, verifiable, multi-turn tasks — not tied to the app.
27
+
28
+ ## Language split: Python first, Swift later
29
+
30
+ - **Python** owns everything exploratory: model/dataset evaluation, retrieval,
31
+ rubric iteration, fact-checking logic. Mature ML tooling (`mlx-lm`, `datasets`,
32
+ dataset ecosystem) makes iteration fast here.
33
+ - **Swift** (`DebateKit`, future) owns the shipped product: privacy-first,
34
+ on-device, no bundled Python runtime, consistent with the rest of the
35
+ HomesteadAI portfolio. Ported only once the CLI's design has stabilized —
36
+ porting a moving target wastes the port.
37
+ - Publish both source repos on GitHub. Python side is a genuine standalone
38
+ open-source tool (MIT). Swift side's license is a separate, later decision —
39
+ likely source-available/non-commercial rather than MIT, since its purpose is
40
+ auditability and trust for the paid app, not general reuse.
41
+
42
+ ## Naming
43
+
44
+ - Python package: `debatebench` (PyPI name confirmed unclaimed as of this
45
+ session). `bench` was chosen over `-cli` because it correctly signals
46
+ "measures and compares models," matching existing conventions in this space
47
+ (`lm-eval-harness`, similar `*-eval`/`*-bench` tools).
48
+ - Swift library (future): `DebateKit`, consistent with existing `Kit`-suffixed
49
+ libraries (CorpusKit, JsonHelpKit).
50
+ - Neither collides with any of the ten-plus adjacent "debate"-named projects
51
+ checked this session (`debate`, `aragora-debate`, `arbiter-debate`,
52
+ `ai-debate-chat`, `multi-model-debate`, `autodebater`, `debating-machine`,
53
+ and the four in the R0 review).
54
+
55
+ ## CLI shape: two commands, not one
56
+
57
+ - **`debate`** — generates a transcript. Writes structured JSON to an explicit
58
+ output file only — the required `output:` path in `run.yaml`, since `debate`
59
+ takes no flags (ADR-007 "CLI invocation"). All logging/errors go to stderr, never to the
60
+ output file — the file must always be valid, parseable JSON, never relying on
61
+ shell redirection to keep it clean.
62
+ - **`judge`** — scores a transcript (from a file, not a pipe requirement) against
63
+ the rubric plus fact-check. Kept separate from `debate` deliberately: lets the
64
+ same transcript be re-judged with a different judge model, or re-scored after
65
+ a rubric change, without regenerating the debate. (Aragora's fused
66
+ debate+judge loop is a cautionary example — it burns a mandatory vote-phase
67
+ call every round even when unwanted, precisely because judging isn't
68
+ separable from generation in its design.)
69
+
70
+ ## Config: two file types, different lifespans
71
+
72
+ **`run.yaml`** — one per debate run, ephemeral (schema finalized in ADR-007):
73
+ ```yaml
74
+ topic: "..."
75
+ format:
76
+ phases: [prep, opening:short, rebuttal:long, retort:medium, rebuttal:long, conclusion:short]
77
+ teams:
78
+ - team: teams/liberal.yaml
79
+ side: pro
80
+ model: qwen3-8b
81
+ base_url: http://127.0.0.1:8080/v1
82
+ budget: 2000
83
+ prep_budget: 1500
84
+ - team: teams/conservative.yaml
85
+ side: con
86
+ model: mistral-small
87
+ base_url: http://127.0.0.1:8081/v1
88
+ budget: 2000
89
+ prep_budget: 1500
90
+ sources: [args-me, debatesum]
91
+ seed: 42
92
+ output: transcript.json
93
+ ```
94
+
95
+ **`teams/*.yaml`** — durable, reusable, hand-authored for now (see ADR-006 —
96
+ the earlier "PersonaForge-compatible, zero conversion step" claim here was
97
+ checked against PersonaKit's actual schema and found false; aligning the two
98
+ is real future work, not started):
99
+ ```yaml
100
+ id: liberal-climate
101
+ name: "Progressive Climate Advocate"
102
+ voice: "direct, urgency-driven, cites institutional consensus"
103
+ stance: liberal
104
+ corpus: liberal-climate-corpus.jsonl
105
+ values: [collective-action, precaution, equity]
106
+ ```
107
+
108
+ `corpus` is a JSONL file resolved relative to the team file's own directory,
109
+ not `run.yaml`'s — see ADR-014 §1 for the row format and why.
110
+
111
+ **Deliberately no `model` field in team files.** A team file defines identity
112
+ (voice, corpus, stance, values), not runtime execution. Keeping `model`/`budget`
113
+ in `run.yaml` as per-side overrides means the same team can debate under
114
+ different hardware/model constraints across different runs without editing its
115
+ identity file — that reasoning holds regardless of PersonaForge (see ADR-006).
116
+
117
+ ## Phase structure
118
+
119
+ Phases are **data, not control flow** — the orchestration loop iterates a
120
+ configured phase list; it does not branch on round number in the loop body (both
121
+ `aragora-debate` and `arbgjr/multi-agent-debate` hardcode their phase sequence as
122
+ control flow — see ADR-001).
123
+
124
+ Default phase list: `[prep(optional), opening, rebuttal, retort, rebuttal,
125
+ conclusion]`. `prep` is a bounded-budget research phase — analogous to real
126
+ debate prep time — that produces a recorded, per-side evidence set *before* any
127
+ argument is written. This sharpens fact-checking: a claim is checked against what
128
+ a side actually gathered during its own prep, not an open-ended post-hoc search,
129
+ making "cited something you never had" a distinct, catchable failure mode from
130
+ "cited something real but misrepresented it."
131
+
132
+ Initiative (who goes first each round) alternates by design, so neither side
133
+ keeps a first-mover advantage across the whole debate.
134
+
135
+ ## Asymmetry is a first-class feature, not an edge case
136
+
137
+ Different teams may run different models under different reasoning/token budgets
138
+ (chess-clock analogy). This is core to the product's value, not a config nicety
139
+ — it's what makes "does a bigger, slower model beat a faster, leaner one" a real,
140
+ answerable, watchable question. Budgets are **enforced by the orchestrator, per
141
+ phase** — never left to a backend's own constructor defaults, which is exactly
142
+ how Aragora's budget claims silently failed to hold across every phase (see
143
+ ADR-001).
144
+
145
+ ## Backend abstraction
146
+
147
+ Single-method `async` `Protocol` as the LLM seam (lifted design from
148
+ `arbgjr/multi-agent-debate`), not a multi-method ABC. One `openai-compatible`
149
+ adapter with a configurable `base_url` (idea from `agent-discussion-arena`)
150
+ covers MLX (`mlx_lm.server`), Ollama, LM Studio, and OpenAI itself through one
151
+ code path.
152
+
153
+ **Dev default model is Apple Foundation Models (AFM)**, or another very small
154
+ model — instant, free, no MLX load overhead, no memory pressure. Used while
155
+ building and testing the CLI plumbing itself (config parsing, phase loop,
156
+ output-file discipline, judge scoring format). Real MLX candidate models are
157
+ reserved for once the harness is proven and the actual question becomes "is this
158
+ debate any good," not "does the pipe work."
159
+
160
+ **Never call a synchronous SDK client inside `async def`** without
161
+ `asyncio.to_thread` — observed as a real, unnoticed bug (fake concurrency) in all
162
+ four of `aragora-debate`'s cloud-provider agents (see ADR-001).
163
+
164
+ **Model servers run offline — a requirement, not an option.** Every model server
165
+ runs with `HF_HUB_OFFLINE=1`, and anything else that fetches from the Hugging
166
+ Face Hub (such as `datasets` for `sources`, later) uses its equivalent offline
167
+ setting. B0 found `mlx_lm.server` contacts huggingface.co on every start to check
168
+ the model revision. That contradicts the offline-capable, privacy-first
169
+ positioning this tool is built around. In offline mode, already-downloaded
170
+ models resolve from the local cache (verified 2026-09-11). Downloading a model is
171
+ a separate, explicit step.
172
+
173
+ ## Judge design
174
+
175
+ Five independently-scored rubric dimensions, never blended into one number:
176
+ argument quality & logic (30), evidence grounding (25), steelman fidelity (20),
177
+ rebuttal effectiveness (15), clarity (10). Steelman fidelity is the explicit
178
+ tiebreaker, given the app's core premise.
179
+
180
+ Rebuttal effectiveness is tracked via a **structured hit-ledger**
181
+ (open/conceded/rebutted/dodged per point — idea from `arbiter-debate`), not a
182
+ subjective number alone.
183
+
184
+ A narrow, separate **fallacy-detector** check (idea from `autodebater`'s
185
+ "Bullshit Detector" judge) is a candidate fast signal, same tier as the
186
+ fact-checker — not yet decided whether it's folded into fact-checking or kept as
187
+ its own pass.
188
+
189
+ A candidate **stance-consistency** check (idea surfaced reviewing
190
+ `mlburnham/Political_DEBATE_large_v1.0`, a 0.4B DeBERTa NLI classifier — name
191
+ is a backronym, "DeBERTa Algorithm for Textual Entailment," unrelated to
192
+ adversarial debate) is a second candidate fast signal: cheap enough to run
193
+ after every turn without competing for resources with the debater models,
194
+ verifying a side's turn still entails its assigned stance rather than drifting
195
+ or conceding. Not yet decided whether this is a third fast pass alongside
196
+ fact-checking and the fallacy detector, or folded into one of them.
197
+
198
+ **Validation data for the rubric itself, before trusting any judge model
199
+ choice:**
200
+ - `ibm-research/argument_quality_ranking_30k` (IBM-Rank-30k; CC-BY-3.0) — 30,497 crowd-written
201
+ arguments across 71 genuinely contested policy topics (same domain as this
202
+ app, not academic claims or trivia), each with a human quality score (WA and
203
+ MACE-P methods) and stance label. Use to correlate a candidate judge's
204
+ argument-quality scores against real human judgment before trusting it.
205
+ - `ibm-research/debate_speeches` (CDLA-Permissive-2.0) — the HF release
206
+ (`opening_speeches` config) holds 948 opening speeches across 114 topics from
207
+ nine sources, human-written and machine-generated, each rated by crowd
208
+ annotators. **Resolved 2026-09-13 by reading the paper and the data:** the
209
+ 631-speech / 76-topic figure *is* the paper's subset. It removes **78**
210
+ control speeches — not 81 — plus two topics lacking both Arg-Human sources.
211
+ Better still, the authors publish that filtered set directly as
212
+ `noystl/speech-quality-dataset` (CDLA-Permissive-2.0, one `data.csv`), so
213
+ there is no need to re-derive it from the 948-row release and no chance of
214
+ filtering it differently than they did.
215
+
216
+ Verified locally against that file: 631 speeches, 76 topics, **exactly 15**
217
+ labelers each (not 5–30), 9,465 individual ratings on a 1–5 Likert scale held
218
+ per-annotator rather than pre-averaged, mean 605.6 words per speech. Mean
219
+ score by source forms the gradient a judge has to reproduce: Human expert
220
+ 4.19, Project Debater 4.03, Arg-Human1 3.79, Arg-Human2 3.67, Arg-GPT2 3.40,
221
+ Speech-GPT2 3.22, Arg-Search 3.12, Summit 2.96. Purpose-built for benchmarking whether an LLM judge's scores track human
222
+ judgment at the speech/turn level (closer to our unit of judging than
223
+ Rank-30k's isolated arguments). A recent paper, "Benchmarking LLM Judges via
224
+ Debate Speech Evaluation," used this exact dataset for this exact task — read
225
+ it for methodology before designing our own validation pass.
226
+ - `tasksource/logical-fallacy` (Jin et al. 2022) — 3,761 labeled examples
227
+ across 13-14 fallacy types, real naturalistic argument text, includes a
228
+ `LogicClimate` challenge subset specifically for climate-policy claims. Real
229
+ train/test/dev splits; multiple independent models already fine-tuned on it.
230
+ Candidate training/validation set for the fallacy-detector check above.
231
+ **License: none found, but the authors' README explicitly says "Feel free to
232
+ access our data in the `data/` folder."** No LICENSE file, HF lists
233
+ `unknown`. Treat this as two separate permissions, not one: sufficient for
234
+ private use (downloading it ourselves to validate a fallacy-detector or judge
235
+ against) but not for redistribution — nothing grants the right to bundle or
236
+ republish the data as part of a `debatebench` release. Keep for validation
237
+ use; do not ship any of it in this repo without a clearer grant.
238
+
239
+ ## Fact-checking is separate from judging, and runs on a different clock
240
+
241
+ **Superseded for the Python CLI by ADR-015 (2026-09-12).** The per-turn,
242
+ real-time fact-check described below is now a feature of the future Swift
243
+ app (`DebateKit`), attaching to the CLI's existing event seam. In this repo,
244
+ fact-checking is a post-hoc pass inside `judge`, checked against the
245
+ recorded evidence, not "the world" — see ADR-015 §2 for what that means
246
+ concretely. The bullets are kept as the description of the Swift-side
247
+ feature and of the evidence-hygiene rule, which still holds.
248
+
249
+ - **Fact-check** (Swift app): fast, narrow, per-claim, runs at the end of every
250
+ turn in real time — verifies a claim against the side's own recorded Prep
251
+ evidence.
252
+ - **Judge**: slower, holistic, runs once over the complete transcript at the end.
253
+ In the CLI, the fact-check pass runs here too (ADR-015 §3, a second call).
254
+ - Regex/heuristic "evidence hygiene" (citation density, specificity — as in
255
+ Aragora's `evidence.py`) is not a substitute for either of the above and, if
256
+ used at all, must be labeled "evidence hygiene," never "fact-check" — it scores
257
+ surface form, not truth, and a confidently fabricated citation with a
258
+ plausible-looking year and percentage would score *high* on it.
259
+
260
+ ## Hard invariant
261
+
262
+ Every configured phase must produce a response from every side, or the run
263
+ aborts with nothing written to the `output:` path (ADR-007). A silently one-sided debate producing a
264
+ confident-looking verdict is the worst failure mode for a benchmarking tool
265
+ (observed as a real, unflagged bug in a reviewed competitor — see ADR-001).
266
+
267
+ ## Hardware
268
+
269
+ MLX-first. Running two debater models plus a judge/fact-checker concurrently on
270
+ one Mac (target dev machine: M2 Pro, 32GB unified memory) is a real memory- and
271
+ compute-contention risk, not just a capacity question — Apple Silicon's GPU and
272
+ memory bandwidth are shared, so three "simultaneous" models likely serialize
273
+ rather than truly parallelize. Options on the table, not yet decided: offload the
274
+ judge/fact-checker to a remote host already running inference (`free`, via
275
+ Ollama) — **withdrawn by B0, see below** — or load/unload models per turn rather
276
+ than keeping all three resident.
277
+ **Needs its own M0-style hardware probe (real peak memory + turn latency
278
+ measurement) before any assumption here is trusted.** ("M0" is the MLXProbe
279
+ session-M0 throwaway probe; results at `~/Projects/MLXProbe/RESULTS.md`.)
280
+
281
+ ### B0 findings (2026-09-11)
282
+
283
+ B0 measured this on the dev machine (`RESULTS.md`). What it changes here:
284
+
285
+ - **`free` is not a separate host: it's the dev machine itself.** The "offload
286
+ to `free`" option above offloads nothing, so it's withdrawn unless a genuinely
287
+ separate host is named. This isn't a minor correction. It invalidates a
288
+ specific option this ADR proposed, and it would have been silently
289
+ load-bearing for the rest of the build if nobody had tried to reach the host.
290
+ Two earlier reachability checks during B0 got it wrong before it was
291
+ confirmed.
292
+ - **The judge never needs to be co-resident.** The judge runs once, post-hoc,
293
+ over the finished transcript (see "Fact-checking is separate from judging"),
294
+ so it can load after the debaters unload. That decision now does real
295
+ load-bearing work: the "three models at once" concern applies only to a
296
+ fact-checker that has to run live. This follows from the design; B0 didn't
297
+ measure it.
298
+ - **The fact-checker hardware problem is resolved for v1 by ADR-015**, not by
299
+ finding a host: the fact-check pass now runs inside `judge`, post-hoc, so it
300
+ loads when the judge does, after the debaters have unloaded. The "no
301
+ designated offload target" finding stands as a fact about this machine, but
302
+ nothing in this repo needs one anymore. The live per-turn version that
303
+ would need one is the Swift app's concern (ADR-015 §1).
304
+ - **The debater pair is doubtful and unmeasured, not ruled out.**
305
+ Mistral-Small-24B alone tripped B0's safety cut-off (critical memory pressure
306
+ alongside normal desktop use) before the pair and concurrency stages ran. A
307
+ quiet-machine rerun comes before anything downstream trusts the pair.
308
+ - **Load/unload per turn looks expensive at long contexts**, but that's inferred:
309
+ a cold 4,000-token prompt took about 25 s on the 8B model, and a full swap cycle
310
+ wasn't measured.
311
+
312
+ Prep and argument phases may warrant different inference engines — MLX
313
+ generally wins decode-heavy short-turn work, but long-document Prep ingestion is
314
+ a prefill-heavy workload where llama.cpp/Ollama have shown faster prompt-eval in
315
+ some benchmarks. Worth benchmarking separately, not assumed to inherit the same
316
+ engine choice.
317
+
318
+ ## Scope discipline
319
+
320
+ Stay narrow: structured multi-turn comparison + rubric scoring + fact-checking.
321
+ No consensus/voting logic anywhere in the design — consensus-seeking built into the
322
+ turn loop is what ruled out `aragora-debate` and `arbgjr/multi-agent-debate` (see
323
+ ADR-001). No formal
324
+ verification (Z3/SymPy-style, as in `arbiter-debate`) — domain-mismatched for
325
+ non-formalizable topics like policy debates. Resist becoming "the debate app's
326
+ backend" — frame and build it as a general-purpose structured-comparison tool
327
+ that the debate app happens to be the first real user of. Aragora's own
328
+ self-documented scope creep (~25% of its codebase admitted by its own authors
329
+ not to serve its core thesis) is the cautionary example to actively design
330
+ against. (The source for the ~25% figure isn't recorded yet — R0's aragora health
331
+ section documents the platform's breadth but not this number. Cite it or drop the
332
+ number.)
333
+
334
+ ## Data sourcing — no scraping user-generated debate platforms
335
+
336
+ Reviewed several live consumer debate platforms (DebateWise.org, DebateArt.com,
337
+ VersyTalks.com) as competitive-landscape research — genuinely useful for
338
+ understanding the market (all three determine a winner via crowd voting, not a
339
+ rubric judge, which remains a real point of difference for this project) but
340
+ **none of their content is a candidate data source.** Unlike the academic
341
+ datasets used elsewhere in this ADR (IBM-Rank-30k, DebateSum, args.me, etc.,
342
+ all released under explicit open licenses for reuse), these are user-generated
343
+ content platforms whose terms of service almost certainly prohibit scraping and
344
+ bulk reuse, with no indication any of them hold rights to sublicense user
345
+ content for training or grounding purposes. Quality or topical relevance never
346
+ overrides this — a dataset failing the licensing gate is disqualified
347
+ regardless of how well it would otherwise fit. Any future dataset candidate
348
+ gets checked for an explicit reuse license before anything else, same as every
349
+ dataset actually adopted so far.
350
+
351
+ ## Attribution
352
+
353
+ Two design lifts (`events.py` shape from `aragora-debate`, `Protocol` shape from
354
+ `arbgjr/multi-agent-debate`) and a conditional third (`evidence.py`-derived
355
+ evidence-hygiene scorer, if adopted) require attribution in a `NOTICE` file — all
356
+ source projects are MIT.