Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 31 additions & 9 deletions .claude/skills/ringer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ description: >-

# Ringer orchestrator playbook

## Codex Sol orchestrator seat

When the user chooses ChatGPT Codex Sol as the orchestrator, keep Sol in the
planning and review lane. Use Ringer's external CLI workers for implementation;
do not substitute invisible in-process subagents for the Ringer run. Stamp runs
with `--identity codex-sol` unless the user supplies a different name.

On Windows, run the Ringer runtime and its POSIX checks inside WSL. The native
Codex app can still orchestrate by invoking the WSL command line, while Ringside
remains reachable on `127.0.0.1`. Do not claim a verified swarm until the
manifest check commands have executed successfully inside that supported
runtime.

## Read this first — the four rules that actually get broken

1. **You review; workers type.** Your lane: specs, checks, pattern choice,
Expand Down Expand Up @@ -48,8 +61,8 @@ description: >-
`--no-dashboard` except in automated tests or when the user explicitly
asks.

Ringer runs manifest tasks in parallel across cheap CLI workers (Codex,
OpenCode/GLM, others via config) and verifies every task by **executing a
Ringer runs manifest tasks in parallel across CLI workers (Codex, Cursor,
Claude Code, OpenCode/OpenRouter, and custom engines) and verifies every task by **executing a
check command** — exit 0 is the only PASS. Failed tasks are retried once
with the check's actual failure output injected into the retry prompt. You —
the orchestrating model — pay tokens only for specs, orchestration, and
Expand Down Expand Up @@ -211,10 +224,12 @@ audition one rung up in adjacent types; repeated first-attempt failures end
the audition — record the demotion in MODEL-NOTES so the next orchestrator
doesn't re-run the experiment.

**OpenCode is the harness; the model is a manifest field.** Unless a model
ships its own first-class harness (Codex does), it runs through the
`opencode` engine with the task's `"model"` field set to the OpenRouter
slug — e.g. `"engine": "opencode", "model": "openrouter/moonshotai/kimi-k2.7-code"`.
**The harness and model are separate manifest fields.** Use first-class
harnesses when the account provides them: `codex` for OpenAI/Codex access,
`cursor` for Cursor-enabled models such as Composer and Grok, and `claude`
for Claude subscription access. Use `opencode` for OpenRouter models with
the task's `"model"` field set to the full OpenRouter slug — e.g.
`"engine": "opencode", "model": "openrouter/moonshotai/kimi-k2.7-code"`.
This holds even when someone — including the user, in the heat of a run —
says to "call kimi directly" or reach for the model's own CLI: the harness
is what provides the sandbox, raw logs, token counts, and executed
Expand All @@ -224,13 +239,20 @@ what the `model` field is for, and a bakeoff is only real when the MANIFEST
names each competitor (2026-07-06 lesson: an engine block with a hard-coded
model ran one model under three competitors' names).

Engines are config blocks (`[engines.<name>]` in config.toml), selectable
per task via the manifest `engine` field. Defaults are deliberate:
Built-in engines can be overridden with `[engines.<name>]` in config.toml
and are selectable per task via the manifest `engine` field. Defaults are deliberate:

- **codex** (default): strongest general worker. Use per-task `engine_args`
to set reasoning effort — spend it on hard tasks, not boilerplate.
- **cursor**: the Cursor subscription lane. Pick an exact slug from
`cursor-agent models`; use Composer and Grok here rather than inventing a
direct Grok engine.
- **claude**: the Claude subscription lane. Use an explicit model and keep
Claude's OS sandbox prerequisites installed (`bubblewrap` + `socat` on
Linux/WSL2).
- **opencode**: the universal lane — any OpenRouter model via the `model`
field (engine `model_default` is GLM-5.2, the cheap-intelligence pick).
field. There is no built-in model default; name the full slug so routing
cannot drift. Linux/WSL2 runs behind Ringer's bubblewrap wrapper.
Validate a model new to you with a trivial one-task manifest before
trusting it with a batch.
- Small/flash-class models are the first to choke on long conversational or
Expand Down
4 changes: 4 additions & 0 deletions .claude/skills/ringer/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Ringer Orchestrator"
short_description: "Plan and review verified multi-model swarms"
default_prompt: "Use $ringer to route this work through a verified Ringer swarm."
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.py text eol=lf
*.sh text eol=lf
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ hud/dist/
hud/gen/
hud/icons/icon.iconset/
*.log
.skill_validate_deps/
.tmp/
.test-tmp/
.sandbox-test-root/
88 changes: 55 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,26 @@ 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
```

3. Teach your agent to route work through Ringer:
3. Teach your orchestrating agent to route work through Ringer:

```bash
# optional but recommended: teach your agent to route work through ringer
# Claude Code (backward-compatible default)
./ringer.py install-agent

# ChatGPT Codex / Codex CLI / IDE
./ringer.py install-agent --agent codex
```

On Windows, install the Codex integration from PowerShell against the Windows
checkout so it reaches the native Codex profile:

```powershell
py -3 .\ringer.py install-agent --agent codex
```

Run Ringer itself inside WSL. Running `install-agent` from WSL would configure
the Linux user's profile, not the native Windows Codex app.

4. Run the demo:

```bash
Expand Down Expand Up @@ -129,20 +142,31 @@ Between swarms, agents drift back to invisible inline work. Reminders decay, so
Run one command:

```bash
./ringer.py install-agent
./ringer.py install-agent # Claude Code
./ringer.py install-agent --agent codex # ChatGPT Codex / Codex CLI / IDE
```

It installs the ringer skill — the orchestrator playbook — user-level for Claude Code, and registers two gentle hooks: a Bash hook that notices model-calling or harness commands running outside a live Ringer run, and an edit-loop hook that notices batch editing without a run. Each hook nudges ONCE per session, pointing the agent at the skill.
It installs the ringer skill — the orchestrator playbook — user-level for Claude Code by default, or for ChatGPT Codex with `--agent codex`. It also registers two gentle hooks: a Bash hook that notices model-calling or harness commands running outside a live Ringer run, and an edit-loop hook that notices batch editing without a run. Each hook nudges ONCE per session, pointing the agent at the skill.

Codex installs the skill under `~/.agents/skills/ringer` and its hooks under
`~/.codex/hooks.json`, matching Codex's documented user-level discovery paths.
On Windows, hook entries include a native `commandWindows` override; Ringer
itself still runs in WSL because manifests and process control are POSIX-based.
Codex treats these as non-managed hooks: review and trust them in the Hooks
settings (or with `/hooks` in the CLI) before they can run. A changed hook
definition is skipped until it is trusted again.

The hooks never block anything. A user who says "just do it inline" is obeyed; uninstall with `./ringer.py uninstall-agent`.
The hooks never block anything. A user who says "just do it inline" is obeyed;
uninstall Claude with `./ringer.py uninstall-agent`, or Codex with
`./ringer.py uninstall-agent --agent codex`.

For CI and evals, `config.sample.toml` includes `[engines.mock]` so the enforcement stack can be tested without an API bill.

## Engines are pluggable

![Identical workers, each under its own light](docs/engines.png)

Ringer ships with three worker lanes: **Codex CLI** is the built-in default, and `config.sample.toml` carries verified engine blocks for **Grok Build CLI** (works as-is once you `grok login`) and **OpenCode + OpenRouter** (one edit: point `bin` at the sandbox wrapper in your clone). Anything else with a headless CLI is a config block away:
Ringer ships with four built-in worker harnesses: **Codex CLI** is the default; **Cursor Agent** routes account-enabled models such as Composer and Grok; **Claude Code** uses a Claude subscription; and **OpenCode** routes explicit OpenRouter models. Anything else with a headless CLI is a config block away:

```toml
[engines.mymodel]
Expand All @@ -152,47 +176,45 @@ args_template = ["run", "{spec}", "--dir", "{taskdir}"]

Per-task `"engine": "mymodel"` routes work to it — the invariants (stdin closed, process-group kill, executed verification, raw logs) apply to every engine identically.

### The universal harness: OpenCode + OpenRouter
### Cursor account lane: Composer and Grok

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.
Install the native CLI in the same Linux or WSL environment that runs Ringer, then authenticate it:

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).
```bash
curl https://cursor.com/install -fsS | bash
cursor-agent login
cursor-agent models
```

Setting it up takes about five minutes:
Route a task with `"engine": "cursor"` and an explicit model slug from `cursor-agent models`. For example, `"model": "composer-2.5-fast"` selects Cursor's fast Composer lane and `"model": "grok-4.5-fast-high"` names its Grok 4.5 medium-effort fast lane. A listed slug is not proof of account entitlement: Cursor Free accounts can list named models that the server still rejects, so run a one-task probe before assigning a batch. Ringer invokes Cursor headlessly with structured output, `--force`, an explicit workspace, and `--sandbox enabled`; full access switches to `--sandbox disabled` only when both the task and Ringer config allow it.

```bash
# 1) Install the OpenCode CLI (pick one)
curl -fsSL https://opencode.ai/install | bash
# or: npm install -g opencode-ai
# or: brew install anomalyco/tap/opencode
### Claude subscription lane

# 2) Connect OpenRouter — create a key at https://openrouter.ai/settings/keys
opencode auth login # select OpenRouter, paste the key
Claude Code can use a Claude Pro or Max subscription without an Anthropic API 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.)
```
```bash
curl -fsSL https://claude.ai/install.sh | bash
claude auth login --claudeai

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.
# Linux/WSL2 prerequisites for Claude's OS sandbox
sudo apt-get install bubblewrap socat
```

### The plan lane: Grok Build CLI
Route a task with `"engine": "claude"` and an explicit model, such as the full model reported by a prior run. Ringer enables Claude's OS sandbox, sets `failIfUnavailable=true`, disables its unsandboxed-command escape hatch, uses structured output, and records the model from Claude's init event. Full access uses Claude's explicit permission bypass only when both Ringer gates allow it.

If you already pay for SuperGrok or X Premium Plus, Grok Build is a second flat-rate worker lane — no per-token bill:
### The universal harness: OpenCode + OpenRouter

```bash
# 1) Install (pick one)
curl -fsSL https://x.ai/cli/install.sh | bash
# or: npm install -g @xai-official/grok
OpenCode covers OpenRouter-served models through one engine. The task's `"model"` field is passed directly to the harness, so model selection remains explicit rather than drifting with a CLI default.

# 2) Sign in — OAuth on a SuperGrok or X Premium Plus plan
grok login
OpenCode does not supply its own OS boundary, so Ringer does. On Linux and WSL2, `engines/opencode-sandboxed-linux.sh` uses bubblewrap: only the task directory is mapped read/write, the system runtime is read-only, `HOME` is ephemeral, inherited environment variables are cleared, and the OpenCode credential is copied into that temporary home with owner-only permissions. On macOS, Ringer selects the Seatbelt wrapper. `--no-sandbox` remains behind Ringer's two-part full-access gate.

# 3) In ~/.config/ringer/config.toml, uncomment [engines.grok]
```bash
curl -fsSL https://opencode.ai/install | bash
opencode auth login # select OpenRouter
opencode models openrouter
```

Route with per-task `"engine": "grok"` and pick the model with `"model": "grok-build"` or `"model": "grok-composer-2.5-fast"` (the shipped default — the speed pick). Grok brings its own OS sandbox on macOS (profile `workspace`: read everywhere, writes confined to the task dir, temp, and `~/.grok`), and its JSON output exposes no token counts — plan-billed workers report cost as included in plan.
Route with `"engine": "opencode"`, choose `"model": "openrouter/<provider>/<model>"`, and optionally pass a provider-supported reasoning variant through `engine_args`, for example `["--variant", "high"]`. The wrapper prioritizes the native Linux install at `~/.opencode/bin/opencode`, avoiding an accidental Windows npm shim in WSL.

## Ringside — mission control

Expand Down
108 changes: 43 additions & 65 deletions config.sample.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,40 +81,41 @@ token_regex = "tokens\\s+used\\s*:?\\s*([0-9][0-9,]*)"
# default reads the `model:` header; override it here only if that format changes.
model_report_regex = "(?m)^model:[ \\t]*([^ \\t\\r\\n]+)[ \\t]*\\r?$"

# Grok Build CLI (xAI). Install: curl -fsSL https://x.ai/cli/install.sh | bash
# (or: npm install -g @xai-official/grok), then `grok login` — OAuth on a
# SuperGrok or X Premium Plus plan. Headless mode (-p) runs a full agentic
# loop with tools and exits. Grok brings its own OS sandbox (Seatbelt on
# macOS): profile "workspace" = read everywhere, write CWD + temp + ~/.grok,
# network allowed. Models: grok-build, grok-composer-2.5-fast (route with the
# per-task "model" field). Verified against Grok Build v0.2.81 (2026-07-06).
# Note: --no-auto-update is accepted but absent from --help in v0.2.81 (grok
# rejects unknown flags, so this is a hidden flag, not a typo) — it stops the
# CLI self-updating mid-swarm.
# Uncomment to enable.
# [engines.grok]
# bin = "grok"
# model_default = "grok-composer-2.5-fast"
# args_template = [
# "--cwd",
# "{taskdir}",
# "{access_args}",
# "-m",
# "{model}",
# "--always-approve",
# "--no-auto-update",
# "--output-format",
# "json",
# "{engine_args}",
# "-p",
# "{spec}",
# ]
# sandbox_args = ["--sandbox", "workspace"]
# full_access_args = ["--sandbox", "off", "--permission-mode", "bypassPermissions"]
# Grok's JSON output carries no usage/token fields (verified v0.2.81) — plan
# CLIs report cost as "included in plan"; regex is a harmless no-match. Its JSON
# also does not self-report a model, so no model_report_regex is configured.
# token_regex = "\"total_tokens\"\\s*:\\s*([0-9]+)"
# Cursor Agent is also built in. Install: curl https://cursor.com/install -fsS | bash
# then `cursor-agent login`. Cursor exposes Composer, Grok, Claude, OpenAI, and
# other account-enabled models. Always set a task's "model" field explicitly;
# `cursor-agent models` prints the exact slugs available to the signed-in account.
# Free accounts may list named models that the server still rejects; probe first.
# Headless edits require --force. Sandbox mode remains explicit on every run.
[engines.cursor]
bin = "cursor-agent"
args_template = [
"--print",
"--output-format",
"stream-json",
"--trust",
"--workspace",
"{taskdir}",
"--force",
"{access_args}",
"--model",
"{model}",
"{engine_args}",
"{spec}",
]
sandbox_args = ["--sandbox", "enabled"]
full_access_args = ["--sandbox", "disabled"]
model_report_regex = '"model"\s*:\s*"([^"]+)"'
model_report_aliases = { "Composer 2.5 Fast" = "composer-2.5-fast", "Cursor Grok 4.5 Medium Fast" = "grok-4.5-fast-high" }

# Claude Code is built in. Install: curl -fsSL https://claude.ai/install.sh | bash
# then run `claude auth login --claudeai` to use a Pro or Max subscription.
# Ringer enables Claude's OS sandbox with failIfUnavailable=true and disables
# the unsandboxed-command escape hatch. Always set an explicit task model.
# Linux/WSL2 sandbox prerequisites: bubblewrap and socat.
# Override only machine-specific fields when needed:
# [engines.claude]
# bin = "claude"

# For CI, acceptance evals, and trying Ringer without an API bill.
# [engines.mock]
Expand All @@ -126,39 +127,16 @@ model_report_regex = "(?m)^model:[ \\t]*([^ \\t\\r\\n]+)[ \\t]*\\r?$"
# sandbox_args = []
# full_access_args = []

# OpenCode + OpenRouter — the universal model harness. One engine block runs
# ANY OpenRouter-served model: the {model} placeholder is filled per task from
# 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
# 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.
# OpenCode + OpenRouter is built in as the universal model harness. Ringer picks
# engines/opencode-sandboxed-linux.sh on Linux/WSL2 (bubblewrap) and the Seatbelt
# wrapper on macOS. The Linux wrapper exposes only the task directory read/write,
# uses an ephemeral HOME, and clears inherited environment variables. Install
# with `curl -fsSL https://opencode.ai/install | bash`, then `opencode auth login`
# and choose OpenRouter. Always set an explicit task model such as
# "openrouter/z-ai/glm-5.2". Per-task engine_args can set `--variant`.
# To pin a local default instead of requiring explicit task models:
# [engines.opencode]
# bin = "/absolute/path/to/ringer/engines/opencode-sandboxed.sh"
# model_default = "openrouter/z-ai/glm-5.2"
# args_template = [
# "{taskdir}",
# "{access_args}",
# "run",
# "-m",
# "{model}",
# "--dangerously-skip-permissions",
# "--format",
# "json",
# "{engine_args}",
# "--dir",
# "{taskdir}",
# "{spec}",
# ]
# sandbox_args = []
# full_access_args = ["--no-sandbox"]
# token_regex = '"tokens":\{"total":([0-9]+)'

[artifact]
# Zero-LLM, self-contained HTML artifacts rendered directly from state — no model calls, no
Expand Down
Loading