From e2bc0450332d8f4f1b669e47576aef2aa80ce40e Mon Sep 17 00:00:00 2001 From: Mark Lavercombe Date: Sat, 18 Jul 2026 00:13:25 +1000 Subject: [PATCH 1/2] Add Python version resolving launcher --- README.md | 8 ++- ringer | 25 +++++++ tests/test_launcher.py | 160 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+), 3 deletions(-) create mode 100755 ringer create mode 100644 tests/test_launcher.py diff --git a/README.md b/README.md index 529e36c..448cd29 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,13 @@ mkdir -p ~/.config/ringer && cp config.sample.toml ~/.config/ringer/config.toml ```bash # optional but recommended: teach your agent to route work through ringer -./ringer.py install-agent +./ringer install-agent ``` 4. Run the demo: ```bash -./ringer.py demo # 3 real workers, verified end to end +./ringer demo # 3 real workers, verified end to end ``` The demo spawns three Codex workers in parallel, verifies each artifact by executing it, and prints a verdict table — and Ringside, the live dashboard, opens in your browser on its own. If all three say PASS, that's the whole setup. @@ -65,9 +65,11 @@ The demo spawns three Codex workers in parallel, verifies each artifact by execu Run your own batch: ```bash -./ringer.py run swarm.json --max-parallel 4 +./ringer run swarm.json --max-parallel 4 ``` +`./ringer` is the recommended entrypoint. It finds `python3.13`, `python3.12`, `python3.11`, or `python3` on `PATH`, verifies the interpreter is Python 3.11+, then runs the repository's `ringer.py`. Direct invocation with a Python 3.11+ interpreter, such as `python3.11 ringer.py ...`, remains supported for scripts and environments that pin an interpreter explicitly. + ```json { "run_name": "my-batch", diff --git a/ringer b/ringer new file mode 100755 index 0000000..b0c61bc --- /dev/null +++ b/ringer @@ -0,0 +1,25 @@ +#!/bin/sh + +launcher=$0 +case $launcher in + */*) launcher_dir=${launcher%/*} ;; + *) launcher_dir=. ;; +esac + +script_dir=$(CDPATH= cd "$launcher_dir" 2>/dev/null && pwd -P) +if [ -z "$script_dir" ]; then + printf '%s\n' "ringer: cannot resolve launcher directory" >&2 + exit 1 +fi + +ringer_py=$script_dir/ringer.py + +for candidate in python3.13 python3.12 python3.11 python3; do + command -v "$candidate" >/dev/null 2>&1 || continue + if "$candidate" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' >/dev/null 2>&1; then + exec "$candidate" "$ringer_py" "$@" + fi +done + +printf '%s\n' "ringer: Python 3.11+ is required; install Python 3.11 or newer and ensure python3.11, python3.12, python3.13, or python3 is on PATH." >&2 +exit 1 diff --git a/tests/test_launcher.py b/tests/test_launcher.py new file mode 100644 index 0000000..b846385 --- /dev/null +++ b/tests/test_launcher.py @@ -0,0 +1,160 @@ +from __future__ import annotations + +import os +import shutil +import stat +import subprocess +import tempfile +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +LAUNCHER_SOURCE = ROOT / "ringer" + + +class LauncherTests(unittest.TestCase): + def setUp(self) -> None: + self.tmp = tempfile.TemporaryDirectory(prefix="ringer-launcher-test-") + self.root = Path(self.tmp.name) + self.bin_dir = self.root / "bin" + self.bin_dir.mkdir() + self.log_dir = self.root / "logs" + self.log_dir.mkdir() + + def tearDown(self) -> None: + self.tmp.cleanup() + + def make_repo(self, name: str = "repo") -> Path: + repo = self.root / name + repo.mkdir() + shutil.copy2(LAUNCHER_SOURCE, repo / "ringer") + (repo / "ringer").chmod((repo / "ringer").stat().st_mode | stat.S_IXUSR) + (repo / "ringer.py").write_text("# launcher test stub\n", encoding="utf-8") + return repo + + def write_fake_python(self, name: str, *, probe_exit: int = 0, run_exit: int = 0) -> None: + path = self.bin_dir / name + path.write_text( + f"""#!/bin/sh +name=${{0##*/}} +if [ "$1" = "-c" ]; then + printf '%s\\n' "$name" >> "$RINGER_FAKE_LOG/probes" + exit {probe_exit} +fi +printf '%s\\n' "$name" > "$RINGER_FAKE_LOG/selected" +printf '%s\\0' "$@" > "$RINGER_FAKE_LOG/argv" +printf '%s\\n' "$PWD" > "$RINGER_FAKE_LOG/cwd" +exit {run_exit} +""", + encoding="utf-8", + ) + path.chmod(path.stat().st_mode | stat.S_IXUSR) + + def run_launcher( + self, repo: Path, *args: str, cwd: Path | None = None + ) -> subprocess.CompletedProcess[str]: + env = { + "PATH": str(self.bin_dir), + "RINGER_FAKE_LOG": str(self.log_dir), + } + return subprocess.run( + [str(repo / "ringer"), *args], + cwd=str(cwd or self.root), + env=env, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + + def probes(self) -> list[str]: + path = self.log_dir / "probes" + if not path.exists(): + return [] + return path.read_text(encoding="utf-8").splitlines() + + def selected(self) -> str: + return (self.log_dir / "selected").read_text(encoding="utf-8").strip() + + def forwarded_argv(self) -> list[str]: + raw = (self.log_dir / "argv").read_bytes() + return [part.decode("utf-8") for part in raw.split(b"\0")[:-1]] + + def test_prefers_highest_supported_python_command(self) -> None: + repo = self.make_repo() + self.write_fake_python("python3.13", run_exit=13) + self.write_fake_python("python3.12", run_exit=12) + self.write_fake_python("python3.11", run_exit=11) + self.write_fake_python("python3", run_exit=3) + + result = self.run_launcher(repo, "demo") + + self.assertEqual(result.returncode, 13, result.stderr) + self.assertEqual(self.probes(), ["python3.13"]) + self.assertEqual(self.selected(), "python3.13") + + def test_falls_back_from_broken_old_and_missing_candidates(self) -> None: + repo = self.make_repo() + self.write_fake_python("python3.13", probe_exit=2) + self.write_fake_python("python3.12", probe_exit=1) + self.write_fake_python("python3", run_exit=23) + + result = self.run_launcher(repo, "run", "swarm.json") + + self.assertEqual(result.returncode, 23, result.stderr) + self.assertEqual(self.probes(), ["python3.13", "python3.12", "python3"]) + self.assertEqual(self.selected(), "python3") + + def test_forwards_script_path_and_arguments_from_arbitrary_cwd(self) -> None: + repo = self.make_repo("repo with spaces") + caller_cwd = self.root / "caller cwd" + caller_cwd.mkdir() + self.write_fake_python("python3.11") + + result = self.run_launcher( + repo, + "run", + "arg with spaces", + "", + "--flag=value with spaces", + "semi;colon", + cwd=caller_cwd, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(self.selected(), "python3.11") + self.assertEqual( + self.forwarded_argv(), + [ + str((repo / "ringer.py").resolve()), + "run", + "arg with spaces", + "", + "--flag=value with spaces", + "semi;colon", + ], + ) + self.assertEqual( + (self.log_dir / "cwd").read_text(encoding="utf-8").strip(), + str(caller_cwd.resolve()), + ) + + def test_errors_when_no_compatible_interpreter_exists(self) -> None: + repo = self.make_repo() + self.write_fake_python("python3.13", probe_exit=1) + self.write_fake_python("python3.12", probe_exit=1) + self.write_fake_python("python3.11", probe_exit=1) + self.write_fake_python("python3", probe_exit=1) + + result = self.run_launcher(repo, "demo") + + self.assertNotEqual(result.returncode, 0) + self.assertEqual( + self.probes(), ["python3.13", "python3.12", "python3.11", "python3"] + ) + self.assertEqual(result.stdout, "") + self.assertEqual( + result.stderr.strip(), + "ringer: Python 3.11+ is required; install Python 3.11 or newer and ensure python3.11, python3.12, python3.13, or python3 is on PATH.", + ) From 47229e642594e5618d102016779d4015b2353897 Mon Sep 17 00:00:00 2001 From: Mark Lavercombe Date: Sat, 18 Jul 2026 01:18:01 +1000 Subject: [PATCH 2/2] Retry flaky macOS CI