Skip to content
Draft
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Reference target: **RTX 3090 (Ampere sm_86)** — all headline numbers. Other NV

[`harness/`](harness/) contains RTX 3090 client launchers and regression tests
for Lucebox server compatibility. Run Lucebox inside Claude Code, Codex,
OpenCode, Hermes, Pi, OpenClaw, or Open WebUI, or check if a server change
OpenCode, Hermes, OMP, Pi, OpenClaw, or Open WebUI, or check if a server change
still works with those clients.

<table>
Expand All @@ -123,6 +123,7 @@ still works with those clients.
| Codex | [`run_codex.sh`](harness/clients/run_codex.sh) |
| OpenCode | [`run_opencode.sh`](harness/clients/run_opencode.sh) |
| Hermes | [`run_hermes.sh`](harness/clients/run_hermes.sh) |
| [OMP](https://omp.sh/) | [`run_omp.sh`](harness/clients/run_omp.sh) |
| Pi | [`run_pi.sh`](harness/clients/run_pi.sh) |
| OpenClaw | [`run_openclaw.sh`](harness/clients/run_openclaw.sh) |
| Open WebUI | [`run_openwebui.sh`](harness/clients/run_openwebui.sh) |
Expand All @@ -149,7 +150,7 @@ Launcher scripts install missing real-client CLIs automatically under
`.harness-work/`. To preinstall them yourself:

```bash
python3 harness/client_test_runner.py install --clients codex,hermes,openwebui
python3 harness/client_test_runner.py install --clients codex,hermes,omp,openwebui
```

For direct TPS/TTFT numbers against a running server:
Expand Down
7 changes: 4 additions & 3 deletions harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<p align="center">
<strong>Client launchers and regression tests for Lucebox server compatibility.</strong><br/>
Run Lucebox from Claude Code, Codex, OpenCode, Hermes, Pi, OpenClaw, or Open WebUI.<br/>
Run Lucebox from Claude Code, Codex, OpenCode, Hermes, OMP, Pi, OpenClaw, or Open WebUI.<br/>
RTX 3090 / 24 GB defaults are included for each client.
</p>

Expand All @@ -31,6 +31,7 @@ cd lucebox-hub
harness/clients/run_codex.sh
harness/clients/run_claude_code.sh
harness/clients/run_opencode.sh
harness/clients/run_omp.sh
```

The launchers default to the current repo, install/use client packages under
Expand Down Expand Up @@ -93,7 +94,7 @@ If you already have `dflash_server` running, use `probe`:
```bash
python3 harness/client_test_runner.py probe \
--url http://127.0.0.1:8000 \
--clients claude_code,codex,opencode,openwebui,pi \
--clients claude_code,codex,opencode,omp,openwebui,pi \
--json-out /tmp/lucebox_harness_probe.json
```

Expand All @@ -103,7 +104,7 @@ client packages. Without it, the HTTP protocol probes still run.
To preinstall real-client CLIs yourself:

```bash
python3 harness/client_test_runner.py install --clients codex,hermes,openwebui
python3 harness/client_test_runner.py install --clients codex,hermes,omp,openwebui
```

For a GPU sweep, let the runner start Lucebox for each profile:
Expand Down
2 changes: 1 addition & 1 deletion harness/benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ deterministic prompts.

Use this when you want to know whether a server change affects output quality or
decode speed. Use `harness/clients/` when you want to know whether Codex,
OpenCode, Open WebUI, Pi, and the other clients still work.
OpenCode, OMP, Open WebUI, Pi, and the other clients still work.

## Bench suites (HumanEval, GSM8K, Math500, Agent)

Expand Down
28 changes: 28 additions & 0 deletions harness/client_test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ class ClientSpec:
binary="opencode",
protocol="openai_chat",
),
"omp": ClientSpec(
name="omp",
install="omp",
package="https://raw.githubusercontent.com/can1357/oh-my-pi/main/scripts/install.sh",
binary="omp",
protocol="responses",
notes="OMP uses a custom openai-responses provider pointed at Lucebox.",
),
"pi": ClientSpec(
name="pi",
install="npm",
Expand Down Expand Up @@ -281,6 +289,8 @@ def client_bin(work_dir: Path, spec: ClientSpec) -> Path:
return pip_venv(work_dir, spec.name) / "bin" / spec.binary
if spec.install == "hermes":
return hermes_home(work_dir) / ".local" / "bin" / spec.binary
if spec.install == "omp":
return work_dir / "clients" / spec.name / "bin" / spec.binary
raise HarnessError(f"unknown installer {spec.install}")


Expand Down Expand Up @@ -339,6 +349,24 @@ def install_client(work_dir: Path, spec: ClientSpec) -> dict[str, Any]:
timeout=1800,
stream=True,
)
elif spec.install == "omp":
# Install the latest release, matching the repo's unpinned npm/pip
# client policy. The --version smoke below records the exact binary
# in the install report for reproducibility.
root = work_dir / "clients" / spec.name
bin_dir = root / "bin"
root.mkdir(parents=True, exist_ok=True)
bin_dir.mkdir(parents=True, exist_ok=True)
script_path = root / "install.sh"
urllib.request.urlretrieve(spec.package, script_path)
env = os.environ.copy()
env["PI_INSTALL_DIR"] = str(bin_dir)
result = run_cmd(
["sh", str(script_path), "--binary"],
env=env,
timeout=900,
stream=True,
)
else:
raise HarnessError(f"unknown installer {spec.install}")

Expand Down
27 changes: 20 additions & 7 deletions harness/clients/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ If a client CLI is missing, the launcher installs it automatically. Set
To preinstall real-client CLIs yourself:

```bash
python3 harness/client_test_runner.py install --clients codex,hermes,openwebui
python3 harness/client_test_runner.py install --clients codex,hermes,omp,openwebui
```

The launcher will start `server/build/dflash_server` by default, or the path in
Expand Down Expand Up @@ -52,7 +52,8 @@ harness/clients/run_codex.sh

The C++ server is expected to handle the same client protocol shapes covered by
these launchers and probes: OpenAI Chat Completions, streaming chunks, tool
metadata, OpenAI Responses for Codex, Anthropic Messages for Claude Code, and
metadata, OpenAI Responses for Codex and OMP, Anthropic Messages for Claude
Code, and
Open WebUI model metadata.

## Defaults
Expand All @@ -66,6 +67,7 @@ The defaults below are the current RTX 3090 starting points for
| Codex | `run_codex.sh` | `MAX_CTX=32768 BUDGET=22 VERIFY_MODE=ddtree EXTRA_SERVER_ARGS=--lazy-draft` |
| OpenCode | `run_opencode.sh` | `MAX_CTX=86016 BUDGET=22 VERIFY_MODE=ddtree EXTRA_SERVER_ARGS=--lazy-draft` |
| Hermes Agent | `run_hermes.sh` | `MAX_CTX=98304 BUDGET=22 VERIFY_MODE=ddtree EXTRA_SERVER_ARGS=--lazy-draft` |
| OMP | `run_omp.sh` | `MAX_CTX=65536 BUDGET=22 VERIFY_MODE=ddtree EXTRA_SERVER_ARGS=--lazy-draft OMP_TIMEOUT=3600` |
| Pi | `run_pi.sh` | `MAX_CTX=65536 BUDGET=22 VERIFY_MODE=ddtree EXTRA_SERVER_ARGS=--lazy-draft PI_TIMEOUT=3600` |
| OpenClaw | `run_openclaw.sh` | `MAX_CTX=204800 BUDGET=22 VERIFY_MODE=ddtree EXTRA_SERVER_ARGS=--lazy-draft` |
| Open WebUI chat | `run_openwebui.sh` | `MAX_CTX=262144 BUDGET=22 VERIFY_MODE=ddtree EXTRA_SERVER_ARGS=--lazy-draft` |
Expand All @@ -79,6 +81,17 @@ PROMPT='Explain the repo and end with lucebox-client-ok' harness/clients/run_ope
PROMPT_FILE=harness/clients/prompts/repo_inspection.txt harness/clients/run_hermes.sh
```

OMP uses a generated `models.yml` with Lucebox as a keyless custom
`openai-responses` provider. `OMP_TIMEOUT` controls the launcher's wall-clock
deadline, while `OMP_STREAM_IDLE_TIMEOUT_MS` defaults to one hour so long
prefills are not interrupted by OMP's stream watchdog; set it to `0` to
disable that watchdog. Override either when needed:

```bash
OMP_TIMEOUT=0 OMP_STREAM_IDLE_TIMEOUT_MS=7200000 \
harness/clients/run_omp.sh
```

`PI_TIMEOUT` is Pi's total wall-clock limit in seconds. Its one-hour default
allows long-context prefill and long generations to finish; set
`PI_TIMEOUT=0` to run without a launcher deadline. The launcher also disables
Expand All @@ -92,8 +105,8 @@ put the same setting in `~/.pi/agent/settings.json`:

The other real-client launchers also allow one hour by default. Override their
deadlines with `CLAUDE_TIMEOUT`, `CODEX_TIMEOUT`, `OPENCODE_TIMEOUT`,
`HERMES_TIMEOUT`, `OPENCLAW_TIMEOUT`, or (for Open WebUI's curl probe)
`CURL_MAX_TIME`. The CLI launcher timeouts accept `0` to disable the outer
`OMP_TIMEOUT`, `HERMES_TIMEOUT`, `OPENCLAW_TIMEOUT`, or (for Open WebUI's
curl probe) `CURL_MAX_TIME`. The CLI launcher timeouts accept `0` to disable the outer
deadline. OpenCode's provider-level request and chunk deadlines default to one
hour too and can be changed with `OPENCODE_REQUEST_TIMEOUT_MS` and
`OPENCODE_CHUNK_TIMEOUT_MS`. The server independently sends SSE heartbeat
Expand All @@ -119,9 +132,9 @@ CLIENT=opencode PROMPT_FILE=harness/clients/prompts/repo_inspection.txt \
harness/clients/run_backend_pair.sh
```

OpenAI Chat Completions clients can call llama.cpp directly. Claude Code and
Codex use `llamacpp_compat_proxy.py` so their real Anthropic Messages and
Responses requests can be compared too.
OpenAI Chat Completions clients and OMP's Responses client can call llama.cpp
directly. Claude Code and Codex use `llamacpp_compat_proxy.py` so their real
Anthropic Messages and Responses requests can be compared too.

## Notes

Expand Down
1 change: 1 addition & 0 deletions harness/clients/run_backend_pair.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ case "$CLIENT" in
openclaw) CLIENT_SCRIPT="$SCRIPT_DIR/run_openclaw.sh" ;;
openwebui) CLIENT_SCRIPT="$SCRIPT_DIR/run_openwebui.sh" ;;
openwebui_tools) CLIENT_SCRIPT="$SCRIPT_DIR/run_openwebui_tools.sh" ;;
omp) CLIENT_SCRIPT="$SCRIPT_DIR/run_omp.sh" ;;
pi) CLIENT_SCRIPT="$SCRIPT_DIR/run_pi.sh" ;;
*)
echo "unknown CLIENT=$CLIENT" >&2
Expand Down
89 changes: 89 additions & 0 deletions harness/clients/run_omp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
: "${MAX_CTX:=65536}"
: "${BUDGET:=22}"
: "${VERIFY_MODE:=ddtree}"
: "${EXTRA_SERVER_ARGS:=--lazy-draft}"
: "${OMP_TOOLS:=read,grep,glob}"
: "${OMP_TIMEOUT:=3600}"
: "${OMP_STREAM_IDLE_TIMEOUT_MS:=3600000}"
if [[ ! "$OMP_TIMEOUT" =~ ^[0-9]+$ ]]; then
echo "OMP_TIMEOUT must be a non-negative integer (seconds; 0 disables it)" >&2
exit 2
fi
if [[ ! "$OMP_STREAM_IDLE_TIMEOUT_MS" =~ ^[0-9]+$ ]]; then
echo "OMP_STREAM_IDLE_TIMEOUT_MS must be a non-negative integer (0 disables OMP's stream watchdog)" >&2
exit 2
fi
source "$SCRIPT_DIR/common.sh"

CLIENT_OUT="$LOG_DIR/omp.out"
OMP_BIN="${OMP_BIN:-$CLIENT_WORK_DIR/clients/omp/bin/omp}"
require_client_binary "OMP" "$OMP_BIN" "omp" "OMP_BIN"
HOME_DIR="$LOG_DIR/omp-home"
AGENT_DIR="$HOME_DIR/agent"
mkdir -p "$AGENT_DIR" "$HOME_DIR/sessions"

cat > "$AGENT_DIR/models.yml" <<YAML
providers:
lucebox:
baseUrl: "$BASE_URL/v1"
auth: none
api: openai-responses
compat:
supportsDeveloperRole: false
supportsReasoningEffort: false
supportsUsageInStreaming: true
maxTokensField: max_tokens
streamIdleTimeoutMs: $OMP_STREAM_IDLE_TIMEOUT_MS
models:
- id: "$MODEL_ID"
name: "Lucebox DFlash"
reasoning: false
input: [text]
contextWindow: $MAX_CTX
maxTokens: $MAX_TOKENS
cost:
input: 0
output: 0
cacheRead: 0
cacheWrite: 0
YAML

start_lucebox_server
trap stop_lucebox_server EXIT
wait_lucebox_server

omp_env=(
"HOME=$HOME_DIR"
"PI_CODING_AGENT_DIR=$AGENT_DIR"
"PI_CODING_AGENT_SESSION_DIR=$HOME_DIR/sessions"
# An inherited OMP_PROFILE/PI_PROFILE would redirect OMP to a profile
# agent dir and ignore the generated models.yml above.
"OMP_PROFILE="
"PI_PROFILE="
)
omp_cmd=(
"$OMP_BIN"
--model "lucebox/$MODEL_ID"
--print
--mode json
--tools "$OMP_TOOLS"
--no-session
--no-extensions
--no-skills
--no-rules
--no-title
"$PROMPT"
)

set +e
run_with_timeout "$OMP_TIMEOUT" env "${omp_env[@]}" "${omp_cmd[@]}" \
< /dev/null > "$CLIENT_OUT" 2>&1
RC=$?
set -e

finish_report "$CLIENT_OUT" "$RC"
exit "$RC"
44 changes: 43 additions & 1 deletion harness/clients/summarize_backend_pair.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"openclaw.out",
"openwebui.out",
"openwebui-tools.out",
"omp.out",
"pi.out",
}
MARKERS = ("OK_DONE", "lucebox-client-ok", "OPENWEBUI_TOOL_OK")
Expand Down Expand Up @@ -129,6 +130,36 @@ def first_json_value(text: str):
return value


def extract_omp_json_text(text: str) -> str:
"""Pull assistant text out of OMP's `--mode json` event stream.

OMP echoes the user prompt and tool results as message events too, so only
`role == "assistant"` message content counts as generated output.
"""
parts: list[str] = []
for line in text.splitlines():
line = line.strip()
if not line.startswith("{"):
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if not isinstance(event, dict) or event.get("type") != "message_end":
continue
message = event.get("message")
if not isinstance(message, dict) or message.get("role") != "assistant":
continue
content = message.get("content")
if isinstance(content, str):
parts.append(content)
elif isinstance(content, list):
for item in content:
if isinstance(item, dict) and isinstance(item.get("text"), str):
parts.append(item["text"])
return "\n".join(part for part in parts if part)


def extract_generated_text(text: str) -> str:
parts: list[str] = []
value = first_json_value(text)
Expand Down Expand Up @@ -164,6 +195,9 @@ def extract_generated_text(text: str) -> str:
return ""

if not parts:
omp_text = extract_omp_json_text(text)
if omp_text:
return omp_text
for line in text.splitlines():
line = line.strip()
if not line:
Expand Down Expand Up @@ -205,6 +239,10 @@ def tool_call_ok(text: str) -> bool:
return "tool_call:" in text or '"tool_calls"' in text


def omp_tool_call_ok(text: str) -> bool:
return '"toolCall"' in text or '"stopReason":"toolUse"' in text or '"tool_execution_start"' in text


def preview(text: str, limit: int = 180) -> str:
compact = re.sub(r"\s+", " ", text).strip()
return compact[:limit]
Expand All @@ -230,7 +268,11 @@ def summarize_backend(pair_dir: Path, backend: str) -> dict:
m = re.search(r"^rc=(\d+)$", backend_out, flags=re.M)
if m:
rc = m.group(1)
observed_tool_call = tool_call_ok(generated_text) or any(call.get("finish") == "tool_calls" for call in calls)
observed_tool_call = (
tool_call_ok(generated_text)
or omp_tool_call_ok(client_text)
or any(call.get("finish") == "tool_calls" for call in calls)
)
return {
"backend": backend,
"run_dir": str(run_dir),
Expand Down
3 changes: 3 additions & 0 deletions harness/tests/test_client_launcher_timeouts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ launcher_cases=(
'run_hermes.sh|HERMES_BIN|HERMES_TIMEOUT|1'
'run_openclaw.sh|OPENCLAW_BIN|OPENCLAW_TIMEOUT|2'
'run_opencode.sh|OPENCODE_BIN|OPENCODE_TIMEOUT|1'
'run_omp.sh|OMP_BIN|OMP_TIMEOUT|1'
)
for launcher_case in "${launcher_cases[@]}"; do
IFS='|' read -r script client_var timeout_var expected_calls <<<"$launcher_case"
Expand Down Expand Up @@ -159,6 +160,8 @@ grep -Fq ': "${HERMES_TIMEOUT:=3600}"' "$CLIENTS/run_hermes.sh"
grep -Fq 'run_with_timeout "$HERMES_TIMEOUT"' "$CLIENTS/run_hermes.sh"
grep -Fq ': "${CLAUDE_TIMEOUT:=3600}"' "$CLIENTS/run_claude_code.sh"
grep -Fq 'run_with_timeout "$CLAUDE_TIMEOUT"' "$CLIENTS/run_claude_code.sh"
grep -Fq ': "${OMP_TIMEOUT:=3600}"' "$CLIENTS/run_omp.sh"
grep -Fq 'run_with_timeout "$OMP_TIMEOUT"' "$CLIENTS/run_omp.sh"
grep -Fq ': "${OPENCLAW_TIMEOUT:=3600}"' "$CLIENTS/run_openclaw.sh"
grep -Fq 'run_with_timeout "$OPENCLAW_TIMEOUT"' "$CLIENTS/run_openclaw.sh"
grep -Fq 'openclaw_cmd+=(--timeout "$OPENCLAW_TIMEOUT")' "$CLIENTS/run_openclaw.sh"
Expand Down
Loading