feat(lucebox): hub CLI + autotune/sweep/profile + harness adapters + shell wrapper#335
feat(lucebox): hub CLI + autotune/sweep/profile + harness adapters + shell wrapper#335easel wants to merge 14 commits into
Conversation
91276a7 to
62b21f0
Compare
## What Containerization stack for lucebox-hub. Dockerfile + docker-bake.hcl build the lucebox-hub image (build-env and runtime stages); scripts/build_image.sh drives local builds; server/scripts/entrypoint.sh emits IMAGE_INFO / HOST_INFO sidecars consumed by /props. GitHub Actions add .github/workflows/docker.yml (build & publish), update ci.yml, and add release-luce-bench.yml for tagging. Workspace-root files (pyproject.toml, uv.lock, Makefile, lefthook.yml, .gitignore, README) live here because the Dockerfile uv-syncs the workspace at build time. ## Why Provides the reproducible image and CI pipeline every other split PR deploys into. Centralizing build/publish here keeps Dockerfile, entrypoint, and workspace-root pinning in one reviewable change. ## Dependencies - Luce-Org#335 (lucebox-cli): Dockerfile COPYs lucebox/ into the image - Luce-Org#337 (lucebench-harness): Dockerfile COPYs luce-bench/ into the image
## What Containerization stack for lucebox-hub. Dockerfile + docker-bake.hcl build the lucebox-hub image (build-env and runtime stages); scripts/build_image.sh drives local builds; server/scripts/entrypoint.sh emits IMAGE_INFO / HOST_INFO sidecars consumed by /props. GitHub Actions add .github/workflows/docker.yml (build & publish), update ci.yml, and add release-luce-bench.yml for tagging. Workspace-root files (pyproject.toml, uv.lock, Makefile, lefthook.yml, .gitignore, README) live here because the Dockerfile uv-syncs the workspace at build time. ## Why Provides the reproducible image and CI pipeline every other split PR deploys into. Centralizing build/publish here keeps Dockerfile, entrypoint, and workspace-root pinning in one reviewable change. ## Dependencies - Luce-Org#335 (lucebox-cli): Dockerfile COPYs lucebox/ into the image - Luce-Org#337 (lucebench-harness): Dockerfile COPYs luce-bench/ into the image
6c9078b to
360d332
Compare
## What Containerization stack for lucebox-hub. Dockerfile + docker-bake.hcl build the lucebox-hub image (build-env and runtime stages); scripts/build_image.sh drives local builds; server/scripts/entrypoint.sh emits IMAGE_INFO / HOST_INFO sidecars consumed by /props. GitHub Actions add .github/workflows/docker.yml (build & publish), update ci.yml, and add release-luce-bench.yml for tagging. Workspace-root files (pyproject.toml, uv.lock, Makefile, lefthook.yml, .gitignore, README) live here because the Dockerfile uv-syncs the workspace at build time. ## Why Provides the reproducible image and CI pipeline every other split PR deploys into. Centralizing build/publish here keeps Dockerfile, entrypoint, and workspace-root pinning in one reviewable change. ## Dependencies - Luce-Org#335 (lucebox-cli): Dockerfile COPYs lucebox/ into the image - Luce-Org#337 (lucebench-harness): Dockerfile COPYs luce-bench/ into the image
1d588db to
0d8e1ff
Compare
There was a problem hiding this comment.
29 issues found across 52 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lucebox/src/lucebox/docker_run.py">
<violation number="1" location="lucebox/src/lucebox/docker_run.py:231">
P3: Several newly added helper functions are dead code (defined but never called anywhere in the codebase).</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| # ── subprocess helpers ───────────────────────────────────────────────────── | ||
|
|
||
|
|
||
| def run(argv: list[str], *, check: bool = True) -> subprocess.CompletedProcess[str]: |
There was a problem hiding this comment.
P3: Several newly added helper functions are dead code (defined but never called anywhere in the codebase).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox/src/lucebox/docker_run.py, line 231:
<comment>Several newly added helper functions are dead code (defined but never called anywhere in the codebase).</comment>
<file context>
@@ -0,0 +1,270 @@
+# ── subprocess helpers ─────────────────────────────────────────────────────
+
+
+def run(argv: list[str], *, check: bool = True) -> subprocess.CompletedProcess[str]:
+ """Run a command, streaming stdout/stderr to the user. `check=False` to
+ inspect exit codes manually."""
</file context>
There was a problem hiding this comment.
Fixed in 73f04f4: removed the unused run(), docker_inspect_running(), host_path_visible(), and stderr() helpers from docker_run.py, plus the now-unused sys import.
There was a problem hiding this comment.
Thanks for the update!
bb7bb11 to
a56b51b
Compare
Containerization stack for lucebox-hub. Dockerfile + docker-bake.hcl build the lucebox-hub image (build-env and runtime stages); scripts/build_image.sh drives local builds; server/scripts/entrypoint.sh emits IMAGE_INFO / HOST_INFO sidecars consumed by /props. GitHub Actions add .github/workflows/docker.yml (build & publish), update ci.yml, and add release-luce-bench.yml for tagging. Workspace-root files (pyproject.toml, uv.lock, Makefile, lefthook.yml, .gitignore, README) live here because the Dockerfile uv-syncs the workspace at build time. Provides the reproducible image and CI pipeline every other split PR deploys into. Centralizing build/publish here keeps Dockerfile, entrypoint, and workspace-root pinning in one reviewable change. - Luce-Org#335 (lucebox-cli): Dockerfile COPYs lucebox/ into the image - Luce-Org#337 (lucebench-harness): Dockerfile COPYs luce-bench/ into the image
…R + personal refs Strip forward-references to lucebox-cli (Luce-Org#335) and luce-bench (Luce-Org#337) plus the contributor's personal repos so the docker stack stands alone: - delete .github/workflows/release-luce-bench.yml (luce-bench PyPI publish; fires only on luce-bench-v* tags, needs a luce-bench/ dir not in this repo) - Makefile: drop test/smoke/bench/profile targets (invoke lucebench/lucebox modules absent here) and their now-unused vars - .gitignore: drop luce-bench/snapshots and external baseline-repo URLs - pyproject.toml / Dockerfile / docker.yml: de-reference Luce-Org#335/Luce-Org#337/luce-bench in comments No functional change: deps, workspace members, ruff config, and every build instruction are untouched, so CI stays green. The siblings re-add their own scaffolding when they land. Co-Authored-By: WOZCODE <contact@withwoz.com>
5a7e617 to
c46e358
Compare
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lucebox/src/lucebox/docker_run.py">
<violation number="1" location="lucebox/src/lucebox/docker_run.py:231">
P3: Several newly added helper functions are dead code (defined but never called anywhere in the codebase).</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
a731f1d to
71139a5
Compare
…oad core Defer second-order features to follow-up PRs so this PR is just the host wrapper and the CLI needed to install, launch/serve, configure, and download models. Cuts the PR from ~11.2k to ~6.2k added lines. Deferred out of this PR (land as stacked follow-ups): - Agent-client adapters + bench: the entire net-new harness/ package (bench.py, the six clients, run_lucebench.sh) and the client launcher verbs. harness/ reverts to main's loose-scripts state; the root pyproject no longer adds harness as a workspace member/dep. - Autotune sweep + profiles: candidate_configs, the Profile registry and per-arch brackets, sweep.py, and the `autotune` command. The host-derived DFLASH_* heuristic (runtime_from_host) STAYS — config.live_config needs it to bake serve defaults — slimmed into autotune.py. - profile + smoke commands and their modules. Coupling fixes: - recommend_preset moved autotune.py -> download.py (models sub-app uses it). - Wrapper: drop the autotune --sweep exec-routing special case and trim usage/completion/exec-set to the core verbs. Tests/CI follow the surface: deferred-feature tests removed; runtime_from_host heuristic tests kept; new guard asserts the deferred verbs are NOT registered. CI pytest step scoped to lucebox. lucebox 72 passed, wrapper 53 passed, ruff + mypy + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 37 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lucebox/src/lucebox/docker_run.py">
<violation number="1" location="lucebox/src/lucebox/docker_run.py:231">
P3: Several newly added helper functions are dead code (defined but never called anywhere in the codebase).</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Stacked on the core lucebox CLI (Luce-Org#335). Restores the second-order tuning and diagnostics surface deferred from that PR: - autotune.py: the empirical sweep machinery — candidate_configs, the Profile registry + per-arch coding-agent-loop brackets, get_profile. (recommend_preset stays in download.py from the core split.) - sweep.py: the per-cell config sweep driver (config set -> restart -> luce-bench snapshot -> winner pick). - profile.py / smoke.py + the `autotune`, `profile`, `smoke` CLI commands. - Wrapper: the `autotune --sweep` exec-routing special case (sweeps must stay on docker run, not exec into the container they'd restart) and the autotune/profile/smoke entries in usage + completion + the exec set. Tests: sweep / profile / smoke / autotune-cli / candidate-configs suites and the Profile/bracket tests restored; test_cli guards that the client launcher verbs are still deferred while autotune/profile/smoke register. lucebox 122 passed, wrapper 55 passed, ruff + mypy + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stacked on the tuning PR (which is stacked on core Luce-Org#335). Restores the last deferred cluster: the agent-client launch surface. - harness/ becomes an installable package again (bench.py + the six client adapters: claude_code, codex, hermes, openclaw, opencode, pi, plus _common and run_lucebench.sh) and rejoins the uv workspace. - cli.py: the claude/codex/opencode/hermes/pi/openclaw verbs, registered from one factory, plus _detect_server_url / _exec_client (which reuse profile._server_base_urls from the tuning PR — hence this stacks on it). - CI runs the harness pytest suite alongside lucebox again. recommend_preset stays in download.py (core's relocation), so the models sub-app is unaffected. lucebox + harness 156 passed, wrapper 55 passed, ruff + mypy + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stacked on the core lucebox CLI (Luce-Org#335). Restores the second-order tuning and diagnostics surface deferred from that PR: - autotune.py: the empirical sweep machinery — candidate_configs, the Profile registry + per-arch coding-agent-loop brackets, get_profile. (recommend_preset stays in download.py from the core split.) - sweep.py: the per-cell config sweep driver (config set -> restart -> luce-bench snapshot -> winner pick). - profile.py / smoke.py + the `autotune`, `profile`, `smoke` CLI commands. - Wrapper: the `autotune --sweep` exec-routing special case (sweeps must stay on docker run, not exec into the container they'd restart) and the autotune/profile/smoke entries in usage + completion + the exec set. Tests: sweep / profile / smoke / autotune-cli / candidate-configs suites and the Profile/bracket tests restored; test_cli guards that the client launcher verbs are still deferred while autotune/profile/smoke register. lucebox 122 passed, wrapper 55 passed, ruff + mypy + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stacked on the tuning PR (which is stacked on core Luce-Org#335). Restores the last deferred cluster: the agent-client launch surface. - harness/ becomes an installable package again (bench.py + the six client adapters: claude_code, codex, hermes, openclaw, opencode, pi, plus _common and run_lucebench.sh) and rejoins the uv workspace. - cli.py: the claude/codex/opencode/hermes/pi/openclaw verbs, registered from one factory, plus _detect_server_url / _exec_client (which reuse profile._server_base_urls from the tuning PR — hence this stacks on it). - CI runs the harness pytest suite alongside lucebox again. recommend_preset stays in download.py (core's relocation), so the models sub-app is unaffected. lucebox + harness 156 passed, wrapper 55 passed, ruff + mypy + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stacked on the tuning PR (which is stacked on core Luce-Org#335). Restores the last deferred cluster: the agent-client launch surface. - harness/ becomes an installable package again (bench.py + the six client adapters: claude_code, codex, hermes, openclaw, opencode, pi, plus _common and run_lucebench.sh) and rejoins the uv workspace. - cli.py: the claude/codex/opencode/hermes/pi/openclaw verbs, registered from one factory, plus _detect_server_url / _exec_client (which reuse profile._server_base_urls from the tuning PR — hence this stacks on it). - CI runs the harness pytest suite alongside lucebox again. recommend_preset stays in download.py (core's relocation), so the models sub-app is unaffected. lucebox + harness 156 passed, wrapper 55 passed, ruff + mypy + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oad core Defer second-order features to follow-up PRs so this PR is just the host wrapper and the CLI needed to install, launch/serve, configure, and download models. Cuts the PR from ~11.2k to ~6.2k added lines. Deferred out of this PR (land as stacked follow-ups): - Agent-client adapters + bench: the entire net-new harness/ package (bench.py, the six clients, run_lucebench.sh) and the client launcher verbs. harness/ reverts to main's loose-scripts state; the root pyproject no longer adds harness as a workspace member/dep. - Autotune sweep + profiles: candidate_configs, the Profile registry and per-arch brackets, sweep.py, and the `autotune` command. The host-derived DFLASH_* heuristic (runtime_from_host) STAYS — config.live_config needs it to bake serve defaults — slimmed into autotune.py. - profile + smoke commands and their modules. Coupling fixes: - recommend_preset moved autotune.py -> download.py (models sub-app uses it). - Wrapper: drop the autotune --sweep exec-routing special case and trim usage/completion/exec-set to the core verbs. Tests/CI follow the surface: deferred-feature tests removed; runtime_from_host heuristic tests kept; new guard asserts the deferred verbs are NOT registered. CI pytest step scoped to lucebox. lucebox 72 passed, wrapper 53 passed, ruff + mypy + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oad core Defer second-order features to follow-up PRs so this PR is just the host wrapper and the CLI needed to install, launch/serve, configure, and download models. Cuts the PR from ~11.2k to ~6.2k added lines. Deferred out of this PR (land as stacked follow-ups): - Agent-client adapters + bench: the entire net-new harness/ package (bench.py, the six clients, run_lucebench.sh) and the client launcher verbs. harness/ reverts to main's loose-scripts state; the root pyproject no longer adds harness as a workspace member/dep. - Autotune sweep + profiles: candidate_configs, the Profile registry and per-arch brackets, sweep.py, and the `autotune` command. The host-derived DFLASH_* heuristic (runtime_from_host) STAYS — config.live_config needs it to bake serve defaults — slimmed into autotune.py. - profile + smoke commands and their modules. Coupling fixes: - recommend_preset moved autotune.py -> download.py (models sub-app uses it). - Wrapper: drop the autotune --sweep exec-routing special case and trim usage/completion/exec-set to the core verbs. Tests/CI follow the surface: deferred-feature tests removed; runtime_from_host heuristic tests kept; new guard asserts the deferred verbs are NOT registered. CI pytest step scoped to lucebox. lucebox 72 passed, wrapper 53 passed, ruff + mypy + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lucebox/src/lucebox/config.py">
<violation number="1" location="lucebox/src/lucebox/config.py:355">
P2: The `seed_dflash_from_host` function can accidentally block legacy `.env` migration. Because it calls `load_doc()` directly—which returns `{}` for a missing file and never checks for a legacy `.env`—it may create a brand-new `config.toml` containing only the heuristic dflash keys. Once that `config.toml` exists, the standard `load()` path will skip migration forever, silently dropping any legacy overrides (port, image, models_dir, prior DFLASH values, etc.). The current CLI call site happens to call `_load_or_build()` first, but the function is a standalone public API and this is a latent data-loss hazard for any future caller or refactor. Consider running the legacy migration check inside `seed_dflash_from_host` before calling `load_doc()`, or calling `load(path)` upfront to ensure any legacy `.env` is promoted first.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| import lucebox.autotune as autotune_mod | ||
|
|
||
| path = path or default_config_path() | ||
| doc = load_doc(path) |
There was a problem hiding this comment.
P2: The seed_dflash_from_host function can accidentally block legacy .env migration. Because it calls load_doc() directly—which returns {} for a missing file and never checks for a legacy .env—it may create a brand-new config.toml containing only the heuristic dflash keys. Once that config.toml exists, the standard load() path will skip migration forever, silently dropping any legacy overrides (port, image, models_dir, prior DFLASH values, etc.). The current CLI call site happens to call _load_or_build() first, but the function is a standalone public API and this is a latent data-loss hazard for any future caller or refactor. Consider running the legacy migration check inside seed_dflash_from_host before calling load_doc(), or calling load(path) upfront to ensure any legacy .env is promoted first.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox/src/lucebox/config.py, line 355:
<comment>The `seed_dflash_from_host` function can accidentally block legacy `.env` migration. Because it calls `load_doc()` directly—which returns `{}` for a missing file and never checks for a legacy `.env`—it may create a brand-new `config.toml` containing only the heuristic dflash keys. Once that `config.toml` exists, the standard `load()` path will skip migration forever, silently dropping any legacy overrides (port, image, models_dir, prior DFLASH values, etc.). The current CLI call site happens to call `_load_or_build()` first, but the function is a standalone public API and this is a latent data-loss hazard for any future caller or refactor. Consider running the legacy migration check inside `seed_dflash_from_host` before calling `load_doc()`, or calling `load(path)` upfront to ensure any legacy `.env` is promoted first.</comment>
<file context>
@@ -334,6 +335,39 @@ def save(cfg: Config, path: Path | None = None, *, doc: dict[str, Any] | None =
+ import lucebox.autotune as autotune_mod
+
+ path = path or default_config_path()
+ doc = load_doc(path)
+ if "dflash" in doc:
+ return False
</file context>
… adapters New lucebox/ Python package exposing the hub CLI (autotune, sweep, profile, smoke, models, config, download, host-check, docker_run) plus the lucebox.sh launcher wrapper and install.sh. Adds the harness/ adapter package wrapping external coding agents (claude_code, codex, hermes, openclaw, opencode, pi) that autotune sweeps drive. Ships scripts/check_lucebox_wrapper_sandbox.sh and scripts/test_lucebox_sh.sh for wrapper validation, full pytest coverage under lucebox/tests/, and the bragi autotune profile-sweep protocol docs. This is the user-facing surface of lucebox-hub: one CLI to launch the image, tune layer-split / pflash settings against a host, run sweeps, and dispatch bench runs. Splitting it out keeps Python-side review independent of the C++ server and Docker stack reviews. - Luce-Org#334 (docker-stack): docker_run.py launches the lucebox-hub image - Luce-Org#337 (lucebench-harness): lucebox bench delegates to luce-bench (workspace dep) - Luce-Org#336 (server-layer-split): autotune presumes layer-split build artifacts
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…to-auto-pick entrypoint.sh:write_host_info() bailed loudly when /opt/lucebox-hub/ did not exist on the host (unit tests, plain docker run without bind mount), because bash refuses the > redirect before the command runs and 2>/dev/null does not suppress the redirect's own error. Guard with an upfront [ -d ] check. test_lucebox_sh.sh:test_entrypoint_multi_target was asserting against the pre-Luce-Org#334 multi-target semantics (auto-pick + warn + exec shim). PR Luce-Org#334 (merged) changed that to refuse-to-auto-pick + exit non-zero. Update the assertion: still drives the auto-detect block (so any DRAFT_FAMILY_GLOB set -u regression trips), but now requires the refuse warn to fire and the shim to NOT exec. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the missing rc != 0 assertion to test_entrypoint_multi_target. The previous fix updated the test to look for the "Refusing to auto-select" warn and a non-exec'd shim, but didn't check the exit code. A regression where the entrypoint logged the warn but still exited 0 (silently auto-picking under the covers) would have slipped through. The container MUST fail to start on multi-target ambiguity — that is the whole point of the policy added in Luce-Org#334. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e CI Behavior-preserving cleanup of the lucebox hub CLI surfaced by review: - cli.py: delete dead _pick_variant_from_driver; collapse the six near-identical Typer client subcommands into a factory loop. - harness adapters: rewire claude_code onto _common; add build_base_parser + run_main so the six main() arg-parsers stop duplicating (per-client differences preserved); hoist KNOWN_AREAS in bench.py. - Unify DFLASH_ALLOWLIST in types.py (was duplicated with a silent one-field divergence between cli.py and sweep.py). - config.py: extract _atomic_write_doc. - Shell: shared _append_host_env / _append_scalar_env / _set_tty_flags helpers in lucebox.sh, _trim in entrypoint.sh, sandbox factory in the test runner. - FIX a tty regression introduced while sharing the docker interactive flags: the helper ran behind `< <(...)`, so `[ -t 1 ]` tested the pipe and forced -i even on a real tty (breaking interactive client TUIs). Now runs in caller scope via nameref; added a PTY-based regression test. - Add unit tests for the previously-untested harness adapters + bench, and wire lucebox + harness pytest into CI (it previously never ran pytest). 116 -> 156 tests; bash wrapper 54 -> 55; ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oad core Defer second-order features to follow-up PRs so this PR is just the host wrapper and the CLI needed to install, launch/serve, configure, and download models. Cuts the PR from ~11.2k to ~6.2k added lines. Deferred out of this PR (land as stacked follow-ups): - Agent-client adapters + bench: the entire net-new harness/ package (bench.py, the six clients, run_lucebench.sh) and the client launcher verbs. harness/ reverts to main's loose-scripts state; the root pyproject no longer adds harness as a workspace member/dep. - Autotune sweep + profiles: candidate_configs, the Profile registry and per-arch brackets, sweep.py, and the `autotune` command. The host-derived DFLASH_* heuristic (runtime_from_host) STAYS — config.live_config needs it to bake serve defaults — slimmed into autotune.py. - profile + smoke commands and their modules. Coupling fixes: - recommend_preset moved autotune.py -> download.py (models sub-app uses it). - Wrapper: drop the autotune --sweep exec-routing special case and trim usage/completion/exec-set to the core verbs. Tests/CI follow the surface: deferred-feature tests removed; runtime_from_host heuristic tests kept; new guard asserts the deferred verbs are NOT registered. CI pytest step scoped to lucebox. lucebox 72 passed, wrapper 53 passed, ruff + mypy + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…utotune) The exhaustive VRAM-tier test for recommend_preset lived in the deferred test_autotune_cli.py; since the function moved to download.py in the split, its direct test belongs alongside it. The models auto-recommend path is also covered indirectly by test_models_cli. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eftovers The serve-argv builder (docker_run.py) — the core's whole job — was only 50% covered by one test. Add lucebox/tests/test_docker_run.py pinning the argv/printable contract, every DFLASH_* env (always-on + conditional), target/draft path resolution, host-env forwarding, volume mounts, and docker_pull. docker_run.py 50% -> 96%. Includes a regression guard for the preset-cap analysis: a large preset serves at the conservative DflashRuntime() floor (max_ctx=16384), not a high OOM-prone ctx — the VRAM-tier heuristic's higher caps only apply via `autotune --apply` (which threads cfg.model.preset; separate PR). No bug. Also: - Add lucebox/**/*.py to the ruff include set (was mypy-checked but never ruff-linted) and clear the 3 unused imports it surfaced in cli.py. - Drop an orphan `# autotune` section divider and refresh two docstrings that referenced the deferred autotune subcommand. lucebox 89 passed, wrapper 53 passed, ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Core carried three bits only the deferred follow-ups use. Evict them so the core CLI is just the basic launch/config/download surface: - types.BASE_DFLASH_ALLOWLIST — no core caller; used by the sweep's per-cell bracket. Moves to the tuning PR. - autotune.runtime_from_host preset-awareness (_preset_approx_gb + the ≥20 GB large-model 32K branch) — core always calls runtime_from_host(host) with no preset, so this was unreachable here. The preset-size cap moves to the tuning PR alongside autotune --apply (which threads cfg.model.preset). Behavior for the core's preset-less call is unchanged. - config.live_config(preset_name=...) — dead across the entire tree (every call site passes no arg). Deleted outright, not moved. Their tests move with them. lucebox 87 passed, wrapper 53, ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lucebox package is a uv workspace member (root pyproject + uv.lock), so
`uv sync --frozen` in the runtime stage fails ("Distribution not found at
file:///opt/lucebox-hub/lucebox") without its source present. The Dockerfile
carried a "copy later" TODO from the docker-stack PR; complete it here since
the CLI is meant to run in-container (the host wrapper docker-execs into it).
Adds COPY lucebox to the builder stage and COPY --from=builder to runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_serve_stop runs as the EXIT trap; if the server fast-fails, the trap can
fire after _serve_and_track has unwound, so its `local docker_pid` is out of
scope and `wait "$docker_pid"` trips `set -u` ("docker_pid: unbound
variable"), turning cleanup into a spurious exit-1 that fails the systemd
unit and masks the real startup error. Guard with ${docker_pid:-}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tStatus) `serve` traps SIGTERM, stops the container, and exits 143 (128+SIGTERM), so a normal `systemctl --user stop lucebox` left the unit in "failed" state instead of "inactive". Add SuccessExitStatus=143 SIGTERM to the generated unit so a clean stop is reported as success; genuine crashes exit with other codes and still trip Restart=on-failure. Guarded by the unit-directive test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A fresh install did not actually auto-tune: `models download --activate` writes only [model], and load() fills a config.toml with no [dflash] from DflashRuntime class defaults (max_ctx=16384), ignoring the host tier. The live_config heuristic only fires when there is NO config.toml at all — which stops being true the moment a model is activated. Net effect: a 24 GB card served at 16 K instead of the ~98 K it can handle. Add config.seed_dflash_from_host(host): on first activation it derives the VRAM-tier DflashRuntime and writes it (plus an [autotune] source=heuristic stamp) to config.toml. No-op when a [dflash] section already exists, so it never clobbers a prior tune or user edits. Wired into `models download --activate`. Verified: fresh [model]-only config now serves max_ctx=98304 (was 16384) on a 24 GB host. lucebox 89 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fd3d198 to
290e5c9
Compare
There was a problem hiding this comment.
21 issues found across 33 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="install.sh">
<violation number="1" location="install.sh:42">
P3: The EXIT trap cleans up `$tmp.bak`, but the intermediate file actually produced is `$tmp.baked` — a naming mismatch that leaves a stray temp file behind if the script exits between the sed step and the `mv` into `$tmp`.</violation>
<violation number="2" location="install.sh:110">
P3: `escaped_url` is sed-replacement-escaped (so `|` becomes `\|`) and then reused as a grep BRE pattern for the post-bake verification; GNU grep interprets `\|` as alternation, so the check could silently mismatch if the channel URL ever contained a literal pipe character.</violation>
</file>
<file name="lucebox.sh">
<violation number="1" location="lucebox.sh:504">
P2: The docker-socket group lookup uses GNU-only `stat -c '%g'`, which will error out on macOS/BSD hosts even though the script otherwise adds BSD/macOS fallbacks (e.g. RAM detection via `sysctl`). This will break `lucebox serve`/`check`-adjacent docker-run construction on non-GNU stat systems when the socket path exists.</violation>
<violation number="2" location="lucebox.sh:878">
P2: `lucebox update` pipes a remote script straight into `bash -c` with no integrity check and no timeout on the `curl` fetch, so a stalled connection can hang the update indefinitely and a compromised/MITM'd channel can execute arbitrary code. Consider adding a curl timeout (e.g. `--max-time`) and, longer-term, a checksum/signature check before executing the fetched installer.</violation>
</file>
<file name="scripts/test_lucebox_sh.sh">
<violation number="1" location="scripts/test_lucebox_sh.sh:121">
P3: The self-exclusion `[ "$f" = "${BASH_SOURCE[0]}" ]` compares an absolute find-derived path to a possibly-relative `BASH_SOURCE[0]`, so it silently fails to dedupe and the test script ends up in the shellcheck target list twice; normalize both to absolute paths (e.g. `readlink -f`) before comparing.</violation>
<violation number="2" location="scripts/test_lucebox_sh.sh:164">
P1: The generic dispatch loop runs `lucebox.sh install` and `lucebox.sh uninstall` without sandboxing HOME/XDG_CONFIG_HOME, so on a machine with a real user systemd session and docker present this could write to or delete the developer's actual `~/.config/systemd/user/lucebox.service`. Skip `install`/`uninstall` from this generic loop (they're already covered by the sandboxed `test_install_writes_robust_unit`) or sandbox HOME here too.</violation>
</file>
<file name="lucebox/src/lucebox/types.py">
<violation number="1" location="lucebox/src/lucebox/types.py:87">
P3: The 'Q:' in the fa_window comment looks like a leftover draft annotation rather than intentional prose — worth cleaning up before merge so the comment reads clearly.</violation>
<violation number="2" location="lucebox/src/lucebox/types.py:98">
P3: think_soft_close_min_ratio (and similarly prefill_keep_ratio) is documented with a valid range but nothing enforces it — an out-of-range value written via `config set` or config.toml will be silently forwarded to the server as `--think-soft-close-min-ratio`. Consider adding range validation (e.g. in a `__post_init__` or in the config caster) to fail loudly like the enum fields do.</violation>
</file>
<file name="Dockerfile">
<violation number="1" location="Dockerfile:128">
P3: Copying the whole `lucebox/` tree (including `lucebox/tests`) into both the builder and runtime stages bakes test files into the final image and causes the uv-sync layer to be invalidated on test-only changes, even though only `src/lucebox` + `pyproject.toml`/`README.md` are needed to build the wheel. Consider copying only the source/manifest subpaths needed for the build.</violation>
</file>
<file name="lucebox/src/lucebox/docker_run.py">
<violation number="1" location="lucebox/src/lucebox/docker_run.py:83">
P2: Mounting the full $HOME directory read-write into the container (to resolve absolute symlink targets) exposes credentials/secrets under $HOME to the containerized server unnecessarily. Consider mounting it read-only since only symlink resolution is needed.</violation>
</file>
<file name=".github/workflows/ci.yml">
<violation number="1" location=".github/workflows/ci.yml:55">
P2: This PR adds a full harness package (bench.py + per-agent adapters) with its own standalone test suite, but the new CI job only wires up lucebox's mypy/pytest — there's no equivalent `uv run pytest` (or similar) step for `harness`. Consider adding a harness unit-test step alongside the lucebox one so harness regressions are caught automatically instead of relying on manual `cd harness && uv run pytest` runs.</violation>
</file>
<file name="scripts/check_lucebox_wrapper_sandbox.sh">
<violation number="1" location="scripts/check_lucebox_wrapper_sandbox.sh:92">
P3: On a failing command inside `run_logged_capture`, the `===== exit=%s =====` trailer is never written to the transcript/log because `set -e` short-circuits the subshell before that line runs, making failure transcripts less informative than intended (exit code still propagates correctly via `pipefail`, but the diagnostic marker is lost).</violation>
<violation number="2" location="scripts/check_lucebox_wrapper_sandbox.sh:167">
P2: The `docker manifest inspect` call always hits the real registry over the network regardless of `--no-pull`/`LUCEBOX_TEST_RUN_PULL=0`, defeating the purpose of that flag and making the sandbox test still require network/registry access even when the caller explicitly opted out of it.</violation>
</file>
<file name="lucebox/pyproject.toml">
<violation number="1" location="lucebox/pyproject.toml:10">
P2: requires-python here (>=3.11) is looser than the workspace root's >=3.12,<3.13. Since lucebox is also meant to be installed standalone in the container image per the README, this allows installs on Python versions (3.11, 3.13+) that aren't exercised by the rest of the workspace's CI/testing.</violation>
<violation number="2" location="lucebox/pyproject.toml:45">
P3: raw-options.tag_regex passes a deprecated setuptools_scm parameter (superseded by `tag.regex` under `[tool.setuptools_scm.tag]`); it still works today but will emit a DeprecationWarning and may need updating later.</violation>
</file>
<file name="lucebox/src/lucebox/config.py">
<violation number="1" location="lucebox/src/lucebox/config.py:244">
P2: `_from_dict` assigns `prefill_mode` straight from the TOML doc without running it through `_cast_prefill_mode`, so an invalid value in a hand-edited config.toml bypasses validation and is passed through to `DFLASH_PREFILL_MODE` at container launch instead of failing loudly at load time.</violation>
<violation number="2" location="lucebox/src/lucebox/config.py:259">
P3: `gpu_vendor` and `ctk` are typed as `Literal` in `HostFacts` but loaded from TOML without any allowed-value check, so a malformed `[host]` section can produce a `HostFacts` instance holding a value outside its declared literal type.</violation>
</file>
<file name="lucebox/src/lucebox/download.py">
<violation number="1" location="lucebox/src/lucebox/download.py:38">
P3: Because `cli.py` imports `lucebox.download` unconditionally, this module-level `os.environ.setdefault("HF_HUB_DISABLE_XET", "1")` runs on every `lucebox` invocation, not just `models download`. Consider moving this side-effecting env mutation into `download_preset`/`_fetch` (or a lazy import) so it only fires when actually downloading.</violation>
<violation number="2" location="lucebox/src/lucebox/download.py:440">
P3: Docstring mislabels the unit: `1e9` is decimal SI GB, not 'binary' — the wording is self-contradictory and may confuse anyone cross-checking sizes against `du`/binary-unit tools.</violation>
</file>
<file name="lucebox/tests/test_models_cli.py">
<violation number="1" location="lucebox/tests/test_models_cli.py:47">
P3: The assertion `"No presets installed" in result.stdout or "Models dir" in result.stdout` is always true for any successful invocation because `_print_installed_presets` always prints `"Models dir:"`. This makes the first condition dead — the test never actually verifies the empty-state message. Tighten to assert the specific expected output for this scenario so a regression in the default-view empty case doesn't go undetected.</violation>
<violation number="2" location="lucebox/tests/test_models_cli.py:140">
P2: `b"x" * (5 * 10**9)` allocates a 5 GB bytes object in memory, then writes 5 GB to disk via `write_bytes`. For a unit test this is extremely heavy — on CI runners with limited RAM (4–8 GB) this will OOM, and on tmpfs or constrained /tmp it will exhaust space. Replace with a much smaller file size (e.g., `5 * 10**6` = 5 MB) and verify the size-per-byte conversion proportionally — the file size arithmetic doesn't need real GBs to validate.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| # On the bare runner there is no user systemd, no installed unit, and no | ||
| # docker — so every command is expected to exit non-zero with a CLEAN error | ||
| # message. What we forbid is a raw bash "unbound variable" leak. | ||
| for sub in start stop restart enable disable status install uninstall; do |
There was a problem hiding this comment.
P1: The generic dispatch loop runs lucebox.sh install and lucebox.sh uninstall without sandboxing HOME/XDG_CONFIG_HOME, so on a machine with a real user systemd session and docker present this could write to or delete the developer's actual ~/.config/systemd/user/lucebox.service. Skip install/uninstall from this generic loop (they're already covered by the sandboxed test_install_writes_robust_unit) or sandbox HOME here too.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/test_lucebox_sh.sh, line 164:
<comment>The generic dispatch loop runs `lucebox.sh install` and `lucebox.sh uninstall` without sandboxing HOME/XDG_CONFIG_HOME, so on a machine with a real user systemd session and docker present this could write to or delete the developer's actual `~/.config/systemd/user/lucebox.service`. Skip `install`/`uninstall` from this generic loop (they're already covered by the sandboxed `test_install_writes_robust_unit`) or sandbox HOME here too.</comment>
<file context>
@@ -0,0 +1,1130 @@
+# On the bare runner there is no user systemd, no installed unit, and no
+# docker — so every command is expected to exit non-zero with a CLEAN error
+# message. What we forbid is a raw bash "unbound variable" leak.
+for sub in start stop restart enable disable status install uninstall; do
+ assert_no_set_u_leak "$sub dispatch (no set -u leak)" "$SCRIPT" "$sub"
+done
</file context>
| # when actually needed; pulling that mount when the host talks to | ||
| # docker over TCP/SSH is fine. | ||
| if [ -S "$DOCKER_SOCK_PATH" ]; then | ||
| argv+=(--group-add "$(stat -c '%g' "$DOCKER_SOCK_PATH")") |
There was a problem hiding this comment.
P2: The docker-socket group lookup uses GNU-only stat -c '%g', which will error out on macOS/BSD hosts even though the script otherwise adds BSD/macOS fallbacks (e.g. RAM detection via sysctl). This will break lucebox serve/check-adjacent docker-run construction on non-GNU stat systems when the socket path exists.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox.sh, line 504:
<comment>The docker-socket group lookup uses GNU-only `stat -c '%g'`, which will error out on macOS/BSD hosts even though the script otherwise adds BSD/macOS fallbacks (e.g. RAM detection via `sysctl`). This will break `lucebox serve`/`check`-adjacent docker-run construction on non-GNU stat systems when the socket path exists.</comment>
<file context>
@@ -0,0 +1,1285 @@
+ # when actually needed; pulling that mount when the host talks to
+ # docker over TCP/SSH is fine.
+ if [ -S "$DOCKER_SOCK_PATH" ]; then
+ argv+=(--group-add "$(stat -c '%g' "$DOCKER_SOCK_PATH")")
+ argv+=(-v "$DOCKER_SOCK_PATH:/var/run/docker.sock")
+ fi
</file context>
| # $LUCEBOX_INSTALL_URL (which we set to source_url) and | ||
| # $LUCEBOX_INSTALL_DEST (the realpath of *this* file, so a symlinked | ||
| # install replaces the actual file behind the link). | ||
| LUCEBOX_INSTALL_URL="$source_url" \ |
There was a problem hiding this comment.
P2: lucebox update pipes a remote script straight into bash -c with no integrity check and no timeout on the curl fetch, so a stalled connection can hang the update indefinitely and a compromised/MITM'd channel can execute arbitrary code. Consider adding a curl timeout (e.g. --max-time) and, longer-term, a checksum/signature check before executing the fetched installer.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox.sh, line 878:
<comment>`lucebox update` pipes a remote script straight into `bash -c` with no integrity check and no timeout on the `curl` fetch, so a stalled connection can hang the update indefinitely and a compromised/MITM'd channel can execute arbitrary code. Consider adding a curl timeout (e.g. `--max-time`) and, longer-term, a checksum/signature check before executing the fetched installer.</comment>
<file context>
@@ -0,0 +1,1285 @@
+ # $LUCEBOX_INSTALL_URL (which we set to source_url) and
+ # $LUCEBOX_INSTALL_DEST (the realpath of *this* file, so a symlinked
+ # install replaces the actual file behind the link).
+ LUCEBOX_INSTALL_URL="$source_url" \
+ LUCEBOX_INSTALL_DEST="$target" \
+ bash -c "$(curl -fsSL "$installer_url")" \
</file context>
| models = str(cfg.models_dir) | ||
| volumes = [(models, "/opt/lucebox-hub/server/models")] | ||
| if home != models: | ||
| volumes.append((home, home)) |
There was a problem hiding this comment.
P2: Mounting the full $HOME directory read-write into the container (to resolve absolute symlink targets) exposes credentials/secrets under $HOME to the containerized server unnecessarily. Consider mounting it read-only since only symlink resolution is needed.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox/src/lucebox/docker_run.py, line 83:
<comment>Mounting the full $HOME directory read-write into the container (to resolve absolute symlink targets) exposes credentials/secrets under $HOME to the containerized server unnecessarily. Consider mounting it read-only since only symlink resolution is needed.</comment>
<file context>
@@ -0,0 +1,232 @@
+ models = str(cfg.models_dir)
+ volumes = [(models, "/opt/lucebox-hub/server/models")]
+ if home != models:
+ volumes.append((home, home))
+ return tuple(volumes)
+
</file context>
| # The fast workspace sync above is enough: the suite mocks the | ||
| # docker / HTTP surfaces, so no torch wheel or GPU is needed. | ||
| # Keeps the lucebox Python honest on every push. | ||
| run: uv run --frozen --extra dev pytest lucebox -q |
There was a problem hiding this comment.
P2: This PR adds a full harness package (bench.py + per-agent adapters) with its own standalone test suite, but the new CI job only wires up lucebox's mypy/pytest — there's no equivalent uv run pytest (or similar) step for harness. Consider adding a harness unit-test step alongside the lucebox one so harness regressions are caught automatically instead of relying on manual cd harness && uv run pytest runs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/ci.yml, line 55:
<comment>This PR adds a full harness package (bench.py + per-agent adapters) with its own standalone test suite, but the new CI job only wires up lucebox's mypy/pytest — there's no equivalent `uv run pytest` (or similar) step for `harness`. Consider adding a harness unit-test step alongside the lucebox one so harness regressions are caught automatically instead of relying on manual `cd harness && uv run pytest` runs.</comment>
<file context>
@@ -34,6 +34,35 @@ jobs:
+ # The fast workspace sync above is enough: the suite mocks the
+ # docker / HTTP surfaces, so no torch wheel or GPU is needed.
+ # Keeps the lucebox Python honest on every push.
+ run: uv run --frozen --extra dev pytest lucebox -q
+
+ - name: Smoke-test lucebox.sh wrapper
</file context>
| # Untagged checkouts (e.g. fresh clone before tagging lucebox-v0.2.1) | ||
| # resolve to this rather than 0.0.0.dev0. | ||
| fallback-version = "0.2.1.dev0" | ||
| raw-options.tag_regex = '''^lucebox-v(?P<version>\d+\.\d+\.\d+)$''' |
There was a problem hiding this comment.
P3: raw-options.tag_regex passes a deprecated setuptools_scm parameter (superseded by tag.regex under [tool.setuptools_scm.tag]); it still works today but will emit a DeprecationWarning and may need updating later.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox/pyproject.toml, line 45:
<comment>raw-options.tag_regex passes a deprecated setuptools_scm parameter (superseded by `tag.regex` under `[tool.setuptools_scm.tag]`); it still works today but will emit a DeprecationWarning and may need updating later.</comment>
<file context>
@@ -0,0 +1,54 @@
+# Untagged checkouts (e.g. fresh clone before tagging lucebox-v0.2.1)
+# resolve to this rather than 0.0.0.dev0.
+fallback-version = "0.2.1.dev0"
+raw-options.tag_regex = '''^lucebox-v(?P<version>\d+\.\d+\.\d+)$'''
+
+[tool.hatch.build.hooks.vcs]
</file context>
| host = HostFacts( | ||
| nproc=int(host_raw.get("nproc", 0)), | ||
| ram_gb=int(host_raw.get("ram_gb", 0)), | ||
| gpu_vendor=host_raw.get("gpu_vendor", "none"), |
There was a problem hiding this comment.
P3: gpu_vendor and ctk are typed as Literal in HostFacts but loaded from TOML without any allowed-value check, so a malformed [host] section can produce a HostFacts instance holding a value outside its declared literal type.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox/src/lucebox/config.py, line 259:
<comment>`gpu_vendor` and `ctk` are typed as `Literal` in `HostFacts` but loaded from TOML without any allowed-value check, so a malformed `[host]` section can produce a `HostFacts` instance holding a value outside its declared literal type.</comment>
<file context>
@@ -0,0 +1,482 @@
+ host = HostFacts(
+ nproc=int(host_raw.get("nproc", 0)),
+ ram_gb=int(host_raw.get("ram_gb", 0)),
+ gpu_vendor=host_raw.get("gpu_vendor", "none"),
+ gpu_name=str(host_raw.get("gpu_name", "")),
+ gpu_count=int(host_raw.get("gpu_count", 0)),
</file context>
|
|
||
|
|
||
| def installed_size_gb(cfg: Config, preset: ModelPreset) -> float: | ||
| """Sum of on-disk byte sizes for the preset's files, in GB (binary 1e9).""" |
There was a problem hiding this comment.
P3: Docstring mislabels the unit: 1e9 is decimal SI GB, not 'binary' — the wording is self-contradictory and may confuse anyone cross-checking sizes against du/binary-unit tools.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox/src/lucebox/download.py, line 440:
<comment>Docstring mislabels the unit: `1e9` is decimal SI GB, not 'binary' — the wording is self-contradictory and may confuse anyone cross-checking sizes against `du`/binary-unit tools.</comment>
<file context>
@@ -0,0 +1,515 @@
+
+
+def installed_size_gb(cfg: Config, preset: ModelPreset) -> float:
+ """Sum of on-disk byte sizes for the preset's files, in GB (binary 1e9)."""
+ total = 0
+ target = _local_target_path(cfg, preset)
</file context>
| # downloader instead so bytes grow continuously and the Rich bar tracks | ||
| # reality. Set before importing hf_hub_download so the import picks | ||
| # the env up. `setdefault` lets a user override on the command line. | ||
| os.environ.setdefault("HF_HUB_DISABLE_XET", "1") |
There was a problem hiding this comment.
P3: Because cli.py imports lucebox.download unconditionally, this module-level os.environ.setdefault("HF_HUB_DISABLE_XET", "1") runs on every lucebox invocation, not just models download. Consider moving this side-effecting env mutation into download_preset/_fetch (or a lazy import) so it only fires when actually downloading.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox/src/lucebox/download.py, line 38:
<comment>Because `cli.py` imports `lucebox.download` unconditionally, this module-level `os.environ.setdefault("HF_HUB_DISABLE_XET", "1")` runs on every `lucebox` invocation, not just `models download`. Consider moving this side-effecting env mutation into `download_preset`/`_fetch` (or a lazy import) so it only fires when actually downloading.</comment>
<file context>
@@ -0,0 +1,515 @@
+# downloader instead so bytes grow continuously and the Rich bar tracks
+# reality. Set before importing hf_hub_download so the import picks
+# the env up. `setdefault` lets a user override on the command line.
+os.environ.setdefault("HF_HUB_DISABLE_XET", "1")
+
+from huggingface_hub import HfApi, hf_hub_download # noqa: E402
</file context>
| # No models on disk → default view says "no presets installed". | ||
| result = CliRunner().invoke(app, ["models"]) | ||
| assert result.exit_code == 0 | ||
| assert "No presets installed" in result.stdout or "Models dir" in result.stdout |
There was a problem hiding this comment.
P3: The assertion "No presets installed" in result.stdout or "Models dir" in result.stdout is always true for any successful invocation because _print_installed_presets always prints "Models dir:". This makes the first condition dead — the test never actually verifies the empty-state message. Tighten to assert the specific expected output for this scenario so a regression in the default-view empty case doesn't go undetected.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lucebox/tests/test_models_cli.py, line 47:
<comment>The assertion `"No presets installed" in result.stdout or "Models dir" in result.stdout` is always true for any successful invocation because `_print_installed_presets` always prints `"Models dir:"`. This makes the first condition dead — the test never actually verifies the empty-state message. Tighten to assert the specific expected output for this scenario so a regression in the default-view empty case doesn't go undetected.</comment>
<file context>
@@ -0,0 +1,142 @@
+ # No models on disk → default view says "no presets installed".
+ result = CliRunner().invoke(app, ["models"])
+ assert result.exit_code == 0
+ assert "No presets installed" in result.stdout or "Models dir" in result.stdout
+
+
</file context>
| assert "No presets installed" in result.stdout or "Models dir" in result.stdout | |
| assert "No presets installed" in result.stdout |
Summary
Adds the
luceboxhub CLI (autotune, sweep, profile, smoke, models, config, download, host-check, docker_run) plus thelucebox.shwrapper andinstall.shbootstrap. Also lands theharness/adapter package (claude_code, codex, hermes, openclaw, opencode, pi) and therun_lucebench.shshim thatlucebox bench-style flows shell out to. Additive only — no existing source files modified outside CI workflow edits, README touch-ups, and alefthook.ymlfor the new packages.Files
lucebox/— new Python package: CLI entrypoint, autotune/sweep/profile/smoke/config/download/host-check/docker_run modules, full pytest suite underlucebox/tests/.harness/— new Python package:harness.bench+ per-agent client adapters (claude_code.py,codex.py,hermes.py,openclaw.py,opencode.py,pi.py) and therun_lucebench.shoperator shim.lucebox.sh+install.sh— top-level shell wrapper and bootstrap installer.scripts/check_lucebox_wrapper_sandbox.sh,scripts/test_lucebox_sh.sh— wrapper-shape integration tests..github/workflows/ci.yml— wires the new packages into CI.harness/clients/README.md,lefthook.yml— supporting docs/config.Single commit, ~10.5k LOC added across 49 files.
Dependencies
lucebox.docker_runshells out todocker runagainst thelucebox-hubimage and reads theIMAGE_INFOproduced by the docker build. The CLI cannot exercise a real run path until docker-stack lands.harness/bench.pyinvokespython -m lucebench.cli ...as a subprocess;harness/clients/run_lucebench.shis the operator shim around the same package. Both require the lucebench package to be importable.Unit tests in
lucebox/tests/andharness/mock the external surfaces and pass standalone, but end-to-end validation requires the three siblings above.Test plan
cd lucebox && uv run pytest(standalone — mocks docker/lucebench surfaces)cd harness && uv run pytest(standalone)./lucebox.sh --helpresolves once build(docker): lucebox-hub container image + CI release pipeline #334 lands and the image is availablelucebox host-checkreports GPU + driver facts on a fresh hostlucebox autotune --dry-runenumerates candidate configs without launching a sweeplucebox bench-style flow once feat(luce-bench): in-tree bench harness + multi-turn agent_recorded + LLM judge #337 (luce-bench) is mergeable and refactor(server): shared layer-split backend + GGUF inspection + c2-gate plumbing #336 (server layer-split) is exposing the expected build flagsNote: this PR's CLI/adapter code is self-contained and unit-tests pass standalone, but full integration validation requires the docker, luce-bench, and server-layer-split siblings to land together.
Generated with Claude Code