Skip to content

feat(lucebox): hub CLI + autotune/sweep/profile + harness adapters + shell wrapper#335

Open
easel wants to merge 14 commits into
Luce-Org:mainfrom
easel:feat/lucebox-cli
Open

feat(lucebox): hub CLI + autotune/sweep/profile + harness adapters + shell wrapper#335
easel wants to merge 14 commits into
Luce-Org:mainfrom
easel:feat/lucebox-cli

Conversation

@easel

@easel easel commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the lucebox hub CLI (autotune, sweep, profile, smoke, models, config, download, host-check, docker_run) plus the lucebox.sh wrapper and install.sh bootstrap. Also lands the harness/ adapter package (claude_code, codex, hermes, openclaw, opencode, pi) and the run_lucebench.sh shim that lucebox bench-style flows shell out to. Additive only — no existing source files modified outside CI workflow edits, README touch-ups, and a lefthook.yml for the new packages.

Files

  • lucebox/ — new Python package: CLI entrypoint, autotune/sweep/profile/smoke/config/download/host-check/docker_run modules, full pytest suite under lucebox/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 the run_lucebench.sh operator 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

Unit tests in lucebox/tests/ and harness/ mock the external surfaces and pass standalone, but end-to-end validation requires the three siblings above.

Test plan

Note: 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

@easel
easel force-pushed the feat/lucebox-cli branch 2 times, most recently from 91276a7 to 62b21f0 Compare June 4, 2026 02:50
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 4, 2026
## 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
@easel
easel force-pushed the feat/lucebox-cli branch from 62b21f0 to 8732b35 Compare June 4, 2026 05:02
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 4, 2026
## 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
@easel
easel force-pushed the feat/lucebox-cli branch 2 times, most recently from 6c9078b to 360d332 Compare June 4, 2026 17:17
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 4, 2026
## 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
@easel
easel force-pushed the feat/lucebox-cli branch 2 times, most recently from 1d588db to 0d8e1ff Compare June 4, 2026 18:38
@easel
easel marked this pull request as ready for review June 4, 2026 19:18

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread harness/src/harness/clients/opencode.py Outdated
Comment thread lucebox/src/lucebox/autotune.py Outdated
Comment thread harness/src/harness/bench.py Outdated
Comment thread lucebox.sh Outdated
Comment thread lucebox/src/lucebox/sweep.py Outdated
Comment thread lucebox/src/lucebox/sweep.py Outdated
Comment thread harness/clients/run_lucebench.sh Outdated
Comment thread lucebox.sh Outdated
Comment thread lefthook.yml Outdated
Comment thread lucebox/src/lucebox/docker_run.py Outdated
# ── subprocess helpers ─────────────────────────────────────────────────────


def run(argv: list[str], *, check: bool = True) -> subprocess.CompletedProcess[str]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update!

@easel
easel force-pushed the feat/lucebox-cli branch from 0d8e1ff to 73f04f4 Compare June 4, 2026 23:30
easel pushed a commit to easel/lucebox-hub that referenced this pull request Jun 5, 2026
@easel
easel force-pushed the feat/lucebox-cli branch 2 times, most recently from bb7bb11 to a56b51b Compare June 8, 2026 22:08
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 8, 2026
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
davide221 added a commit to easel/lucebox-hub that referenced this pull request Jun 9, 2026
…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>
@easel
easel force-pushed the feat/lucebox-cli branch 2 times, most recently from 5a7e617 to c46e358 Compare June 11, 2026 15:15

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread scripts/test_lucebox_sh.sh
@easel
easel force-pushed the feat/lucebox-cli branch 3 times, most recently from a731f1d to 71139a5 Compare June 16, 2026 17:22
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 16, 2026
…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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread lucebox.sh
@easel
easel force-pushed the feat/lucebox-cli branch from f3db094 to d9ca9f8 Compare June 16, 2026 23:09
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 16, 2026
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>
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 16, 2026
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>
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 17, 2026
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>
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 17, 2026
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>
easel added a commit to easel/lucebox-hub that referenced this pull request Jun 17, 2026
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>
@davide221
davide221 requested a review from dusterbloom June 24, 2026 09:47
@easel
easel force-pushed the feat/lucebox-cli branch from aeae314 to 0ae47c5 Compare July 6, 2026 01:32
easel added a commit to easel/lucebox-hub that referenced this pull request Jul 6, 2026
…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>
easel added a commit to easel/lucebox-hub that referenced this pull request Jul 6, 2026
…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>
@easel
easel force-pushed the feat/lucebox-cli branch from 0ae47c5 to 573570a Compare July 6, 2026 19:05

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread lucebox.sh

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

easel and others added 13 commits July 20, 2026 22:34
… 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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread lucebox.sh
# 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")")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread lucebox.sh
# $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" \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread .github/workflows/ci.yml
# 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread lucebox/pyproject.toml
# 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+)$'''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
assert "No presets installed" in result.stdout or "Models dir" in result.stdout
assert "No presets installed" in result.stdout

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants