Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
1b5fe6c
Ringside: make Open folder work on WSL, Linux, and native Windows
RadioactiveCheese Jul 8, 2026
9e270ab
Windows-native portability: check shell, process kills, pid probes, e…
RadioactiveCheese Jul 9, 2026
9af253f
Docs and templates: native Windows guidance, drop macOS interpreter h…
RadioactiveCheese Jul 9, 2026
a5fcfda
Tests: cross-platform on native Windows and Linux
RadioactiveCheese Jul 9, 2026
ea6a785
MODEL-NOTES: Windows-native portability job evidence (codex + process…
RadioactiveCheese Jul 9, 2026
66d65aa
Merge branch 'fix/open-folder-cross-platform' into feat/win-linux-compat
RadioactiveCheese Jul 9, 2026
e8cff49
Review: require STILL_ACTIVE in win32 pid_is_alive, not just OpenProcess
RadioactiveCheese Jul 9, 2026
2db0795
Review: run fix-swarm verify commands through the POSIX check shell o…
RadioactiveCheese Jul 9, 2026
83272b2
Review: route all template-validator user commands through the POSIX …
RadioactiveCheese Jul 9, 2026
380dc78
Review: escalate to forced tree kill when graceful taskkill /T is ref…
RadioactiveCheese Jul 9, 2026
9308df1
Ringside overhaul: running-now switcher, agent cards, work gating, ex…
justfinethanku Jul 9, 2026
31d492c
Merge remote-tracking branch 'origin/feat/ringside-overhaul' into mer…
RadioactiveCheese Jul 9, 2026
383e7ba
Merge fix: update design-reference test for Ringside work gating
RadioactiveCheese Jul 9, 2026
9ef6e82
OpenCode sandbox for Linux and Windows-via-WSL; pin .sh files to LF
RadioactiveCheese Jul 9, 2026
474f359
MODEL-NOTES: grok-4.5 audition evidence (PASS attempt 1 via WSL sandb…
RadioactiveCheese Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Shell scripts must stay LF: Windows checkouts with core.autocrlf=true would
# otherwise smudge them to CRLF, and bash in WSL/Linux rejects CRLF scripts
# (engine wrappers are executed directly inside WSL from this working tree).
*.sh text eol=lf
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ manifest.json ──▶ ringer.py ──▶ N parallel workers (codex exec, each

## Quickstart

Ringer runs on macOS and Linux (Windows via WSL) and needs Python 3.11+.
Ringer runs on macOS, Linux, WSL, and native Windows, and needs Python 3.11+. On native Windows, manifest `check` commands require Git Bash from Git for Windows; if Git Bash is not on the default path, set `RINGER_CHECK_SHELL` to a POSIX `sh`.

1. Install a worker CLI and sign in (Codex is the built-in default engine):

Expand All @@ -45,6 +45,19 @@ git clone https://github.com/NateBJones-Projects/ringer && cd ringer
mkdir -p ~/.config/ringer && cp config.sample.toml ~/.config/ringer/config.toml # optional — sane defaults without it
```

On native Windows, use PowerShell-friendly commands:

```powershell
git clone https://github.com/NateBJones-Projects/ringer
cd ringer
New-Item -ItemType Directory -Force "$env:USERPROFILE\.config\ringer" | Out-Null
Copy-Item config.sample.toml "$env:USERPROFILE\.config\ringer\config.toml" # optional — sane defaults without it
python ringer.py demo
python ringer.py hud
```

Windows manifest paths can use forward slashes, e.g. `"workdir": "C:/Users/<user>/ringer-runs/my-batch"`. Native Windows check execution still requires Git Bash; set `RINGER_CHECK_SHELL` when you need to point Ringer at a specific POSIX shell.

3. Teach your agent to route work through Ringer:

```bash
Expand Down Expand Up @@ -156,7 +169,7 @@ Per-task `"engine": "mymodel"` routes work to it — the invariants (stdin close

Unless a model ships its own first-class harness (Codex does), OpenCode is the harness that runs it — one engine block covers every OpenRouter-served model. `config.sample.toml` includes a ready-to-uncomment engine whose `{model}` placeholder is filled per task from the manifest's `"model"` field, with `model_default` as the fallback. The shipped default is OpenRouter's `z-ai/glm-5.2` — roughly $0.74/M input and $2.33/M output (2026-07), about 20-30x cheaper output than frontier coding models; a complete write-code-and-pass-the-check task lands around a penny.

OpenCode ships no OS sandbox, so the engine's `bin` points at an absolute path to `engines/opencode-sandboxed.sh` (ringer does not resolve engine bins relative to the repo): a macOS Seatbelt wrapper that leaves network and reads open but confines writes to the task dir, a per-run scratch dir (wired as the agent's `TMPDIR`/`XDG_CACHE_HOME`), and OpenCode's own state/config dirs. Its `--dangerously-skip-permissions` flag only silences OpenCode's interactive prompts; Seatbelt is the actual containment. Task paths reach the profile as `sandbox-exec -D` parameters rather than string interpolation, so a task dir with quotes or parens can't inject sandbox rules. `--no-sandbox` is wired as the engine's `full_access_args`, so ringer's `allow_full_access` gate still governs escapes. Non-macOS installs need their own sandbox (or full-access mode).
OpenCode ships no OS sandbox, so the engine's `bin` points at an absolute path to `engines/opencode-sandboxed.sh` (ringer does not resolve engine bins relative to the repo): a macOS Seatbelt wrapper that leaves network and reads open but confines writes to the task dir, a per-run scratch dir (wired as the agent's `TMPDIR`/`XDG_CACHE_HOME`), and OpenCode's own state/config dirs. Its `--dangerously-skip-permissions` flag only silences OpenCode's interactive prompts; Seatbelt is the actual containment. Task paths reach the profile as `sandbox-exec -D` parameters rather than string interpolation, so a task dir with quotes or parens can't inject sandbox rules. `--no-sandbox` is wired as the engine's `full_access_args`, so ringer's `allow_full_access` gate still governs escapes. `engines/opencode-sandboxed.sh` is macOS-only; its siblings cover the other platforms with the same contract: `engines/opencode-sandboxed-linux.sh` (Linux/WSL, bubblewrap — install `bubblewrap`) and `engines/opencode-sandboxed-wsl.sh` (Windows: set `bin = "wsl.exe"` and point `args_template` at the script's `/mnt` path; it translates `C:\` task dirs and hands off to the Linux wrapper inside WSL).

Setting it up takes about five minutes:

Expand All @@ -170,9 +183,10 @@ curl -fsSL https://opencode.ai/install | bash
opencode auth login # select OpenRouter, paste the key

# 3) In ~/.config/ringer/config.toml, uncomment [engines.opencode] and set
# bin to the ABSOLUTE path of engines/opencode-sandboxed.sh in this clone.
# (Linux/WSL: the wrapper is macOS-only — set bin to the opencode binary
# itself; there is no OS write-confinement then, so keep manifests scoped.)
# bin to the ABSOLUTE path of the wrapper for your OS in this clone:
# opencode-sandboxed.sh (macOS Seatbelt), opencode-sandboxed-linux.sh
# (Linux/WSL, needs bubblewrap), or on Windows bin = "wsl.exe" with
# args_template pointing at opencode-sandboxed-wsl.sh's /mnt path.
```

Route with per-task `"engine": "opencode"`, pick the model with per-task `"model": "openrouter/<any-model>"`, and set reasoning effort via `engine_args`: `["--variant", "low|high|max"]`. A sensible split: mechanical or tightly-specced tasks on the cheap lane, gnarly ones on your frontier engine — the executed check catches shortfalls either way, and `swarm_runs` rows tell you whether the cheap lane's pass rate holds.
Expand Down Expand Up @@ -209,6 +223,8 @@ The top of the page is the run's live results document: what the job is, a progr

Multiple swarms at once is the designed-for case: run three batches under three identities and Ringside shows all three, live. `--browser` opens a simpler per-run fallback dashboard, and `--no-dashboard` runs headless.

On native Windows, per-task child-process counts currently read zero in the dashboard; run status, logs, and verdicts still work.

A native desktop build (Tauri, under `hud/`) exists as a v0.1.1 prototype; the web dashboard is currently ahead of it — start there.

## The eval loop
Expand Down
32 changes: 27 additions & 5 deletions config.sample.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Sample config for ringer.py.
# Copy to ~/.config/ringer/config.toml or pass with --config /path/to/config.toml.
# Copy to ~/.config/ringer/config.toml (Windows: $env:USERPROFILE\.config\ringer\config.toml)
# or pass with --config /path/to/config.toml.

# Default identity stamped into state JSON and eval rows. Resolution order:
# --identity flag > FLEET_IDENTITY / RINGER_IDENTITY env > a .fleet-agent file
Expand All @@ -15,7 +16,7 @@ state_dir = "~/.ringer"
dashboard_port_base = 8787

# Optional native HUD app path. Public installs should omit this and let the
# dashboard open in the browser.
# dashboard open in the browser. This /Applications example is macOS-only.
# hud_app_path = "/Applications/Ringside.app"

# Belt-and-suspenders full-access gate. A task with "full_access": true will
Expand Down Expand Up @@ -43,13 +44,19 @@ bin = "codex"
# - {taskdir}: task working directory
# - {spec}: task prompt/spec
# - {access_args}: expands to sandbox_args or full_access_args
# - {model_args}: expands to "-m <model>" only when the manifest task sets
# "model" or this engine sets model_default; otherwise it expands to nothing,
# preserving Codex's configured default. Setting model_default = "gpt-5.6-sol"
# pins the model instead of inheriting ~/.codex/config.toml drift and makes
# scoreboard attribution exact.
# - {engine_args}: expands to the task's optional "engine_args" list — the orchestrator
# sets per-task flags here, e.g. ["-c", "model_reasoning_effort=medium"] to match
# reasoning depth to task difficulty instead of inheriting the CLI-wide default
args_template = [
"exec",
"--skip-git-repo-check",
"{access_args}",
"{model_args}",
"{engine_args}",
"-C",
"{taskdir}",
Expand Down Expand Up @@ -112,16 +119,31 @@ token_regex = "tokens\\s+used\\s*:?\\s*([0-9][0-9,]*)"
# the manifest's "model" field, falling back to model_default below. Example
# default: GLM-5.2 (z-ai/glm-5.2, roughly $0.74/M input, $2.33/M output as of
# 2026-07) — the cheap-intelligence lane.
# OpenCode has no OS sandbox, so `bin` points at engines/opencode-sandboxed.sh
# (macOS Seatbelt: network + reads open, writes confined to the task dir, a
# per-run scratch dir, and OpenCode's state/config dirs). Auth: put your
# OpenCode has no OS sandbox, so `bin` points at a wrapper (all three share one
# contract: network + reads open, writes confined to the task dir, a per-run
# scratch dir, and OpenCode's state/config dirs):
# macOS engines/opencode-sandboxed.sh (Seatbelt)
# Linux/WSL engines/opencode-sandboxed-linux.sh (bubblewrap; install bwrap)
# Windows bin = "wsl.exe" bridging into engines/opencode-sandboxed-wsl.sh
# (see the Windows example below; opencode + bwrap live inside WSL)
# Auth: put your
# OpenRouter key where your OpenCode version expects it — commonly
# ~/.local/share/opencode/auth.json ({"openrouter": {"type": "api", "key": "..."}});
# confirm the path with your installed CLI.
# Per-task engine_args can set reasoning effort ("--variant", "low|high|max").
# Uncomment and set an absolute path for `bin` to enable.
# [engines.opencode]
# macOS wrapper example (Linux: swap in opencode-sandboxed-linux.sh):
# bin = "/absolute/path/to/ringer/engines/opencode-sandboxed.sh"
# Windows example (verified 2026-07-09: ringer.py on Windows, sandbox in WSL):
# bin = "C:/Windows/System32/wsl.exe"
# args_template = [
# "-d", "Ubuntu", "-e",
# "/mnt/<drive>/path/to/ringer/engines/opencode-sandboxed-wsl.sh",
# "{taskdir}", "{access_args}", "run", "-m", "{model}",
# "--dangerously-skip-permissions", "--format", "json",
# "{engine_args}", "--dir", "{taskdir}", "{spec}",
# ]
# model_default = "openrouter/z-ai/glm-5.2"
# args_template = [
# "{taskdir}",
Expand Down
12 changes: 8 additions & 4 deletions dashboard/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -1260,8 +1260,12 @@ <h1 id="headline">Ringside mission control</h1>
function fileHref(path) {
const text = String(path || "").trim();
if (!text) return "";
if (/^(?:file|https?):/i.test(text)) return text;
return `file://${encodeURI(text).replace(/#/g, "%23")}`;
const normalized = text.replace(/\\/g, "/");
if (/^(?:file|https?):/i.test(normalized)) return normalized;
if (/^[A-Za-z]:\//.test(normalized)) {
return `file:///${encodeURI(normalized).replace(/#/g, "%23")}`;
}
return `file://${encodeURI(normalized).replace(/#/g, "%23")}`;
}

function artifactWrapperHref(run, task, sourcePath) {
Expand All @@ -1285,7 +1289,7 @@ <h1 id="headline">Ringside mission control</h1>
const links = [];
if (task.taskdir) links.push(linkHtml(fileHref(task.taskdir), "taskdir"));

const logPath = task.log_path || (task.taskdir ? `${task.taskdir}/worker.log` : "");
const logPath = task.log_path || (task.taskdir ? `${String(task.taskdir).replace(/\\/g, "/")}/worker.log` : "");
if (logPath) {
links.push(linkHtml(artifactWrapperHref(run, task, logPath) || fileHref(logPath), "worker.log"));
}
Expand All @@ -1301,7 +1305,7 @@ <h1 id="headline">Ringside mission control</h1>
}

function workerLogPath(task) {
return String(task?.log_path || (task?.taskdir ? `${task.taskdir}/worker.log` : "") || "");
return String(task?.log_path || (task?.taskdir ? `${String(task.taskdir).replace(/\\/g, "/")}/worker.log` : "") || "");
}

function workerLogKey(task) {
Expand Down
Loading