From a652d3dadb0b70a46c63d1f5e37be7dd8a262cfd Mon Sep 17 00:00:00 2001 From: NetDevAutomate Date: Sat, 26 Sep 2026 15:07:30 +0100 Subject: [PATCH 1/2] test(install): RED -- the studyloop tool venv must carry the semantic runtime `studyloop web` warms the query encoder at boot inside the STUDYLOOP tool venv; the warm imports tokenizers, onnxruntime, huggingface_hub, numpy and sqlite_vec there. install_workspace_tools() co-installs agent-session-tools into that venv with `--with-editable ` and no extras, so none of them arrive (the standalone agent-session-tools tool gets [all], but it is a different venv). Observed on the maintainer's install: the warm fails in 9 ms with "RuntimeError: tokenizers is not installed", the header chip reads "semantic: failed (0.0s)", and `studyloop doctor` in the same venv reports the encoder check as "does not apply". The same warm in the repo .venv reaches `warm` in 0.28 s. Nothing could see it: the nightly installer job checks `--version` and `--help`, which pass without the runtime. Four RED, each for the stated reason: - the installer's studyloop command co-installs the bare path (2 tests); - scripts/smoke-uv-tool-install.sh neither co-installs [all] nor imports the runtime in the studyloop venv; - the nightly installer job has no step importing it after install.sh. --- .../tests/test_ci_workflow_contract.py | 21 ++++++++++++++++ .../tests/test_installers_workspace_tools.py | 25 ++++++++++++++++++- .../test_smoke_installed_cli_contract.py | 9 ++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/studyloop/tests/test_ci_workflow_contract.py b/packages/studyloop/tests/test_ci_workflow_contract.py index be7101fcd..c56b73ba9 100644 --- a/packages/studyloop/tests/test_ci_workflow_contract.py +++ b/packages/studyloop/tests/test_ci_workflow_contract.py @@ -290,3 +290,24 @@ def test_nightly_installer_job_plants_a_harness_before_running_install_sh() -> N assert verify["env"]["HOME"] == "${{ runner.temp }}/home" for planted_marker in planted: assert planted_marker in verify["run"], f"verify step does not look inside {planted_marker}" + + +def test_nightly_installer_job_proves_the_studyloop_tool_env_carries_the_semantic_runtime() -> None: + """`install.sh` gave the studyloop tool venv no semantic runtime, and the + job's only checks were `--version` and `--help`, which pass without it -- + so every install's `studyloop web` failed its encoder warm at import and + nothing in CI could see it. The job must import what the warm imports, in + the venv that serves `studyloop web`, after the script has run. + """ + steps = _nightly_workflow()["jobs"]["installer"]["steps"] + names = [step.get("name") for step in steps] + wanted = "Verify the studyloop tool env carries the semantic runtime" + assert wanted in names, "the installer job must check the studyloop venv's semantic runtime" + assert names.index(wanted) > names.index("Run scripts/install.sh") + + verify = steps[names.index(wanted)] + assert verify["env"]["UV_TOOL_DIR"] == "${{ runner.temp }}/tools" + assert ( + '"$UV_TOOL_DIR/studyloop/bin/python" -c ' + '"import huggingface_hub, numpy, onnxruntime, sqlite_vec, tokenizers"' + ) in verify["run"] diff --git a/packages/studyloop/tests/test_installers_workspace_tools.py b/packages/studyloop/tests/test_installers_workspace_tools.py index 476ebf1cd..c97e0707e 100644 --- a/packages/studyloop/tests/test_installers_workspace_tools.py +++ b/packages/studyloop/tests/test_installers_workspace_tools.py @@ -59,7 +59,7 @@ def test_install_workspace_tools_installs_expected_tool_commands(tmp_path: Path) _PY_VER, f"{studyloop_pkg}[all]", "--with-editable", - str(agent_pkg), + f"{agent_pkg}[all]", "--editable", "--force", ], @@ -68,6 +68,29 @@ def test_install_workspace_tools_installs_expected_tool_commands(tmp_path: Path) ] +def test_the_studyloop_tool_env_gets_agent_session_tools_with_its_runtime_extras( + tmp_path: Path, +) -> None: + """`studyloop web` imports agent_session_tools in-process, in the STUDYLOOP + tool venv: the server's boot-time encoder warm and every hybrid search run + tokenizers, onnxruntime, huggingface_hub, numpy and sqlite_vec there. The + standalone agent-session-tools tool gets ``[all]``, but that is a different + venv. Co-installed bare, the studyloop venv had none of them, so the warm + failed at import in ~10 ms and the header chip read ``semantic: failed + (0.0s)`` -- while `doctor`, running in the same venv, called the encoder + check "does not apply". + """ + repo_root = _workspace(tmp_path) + + with patch("studyloop.installers._run") as run: + install_workspace_tools(repo_root, sync_workspace=False) + + studyloop_spec = f"{repo_root / 'packages' / 'studyloop'}[all]" + command = next(c.args[0] for c in run.call_args_list if studyloop_spec in c.args[0]) + co_installed = command[command.index("--with-editable") + 1] + assert co_installed == f"{repo_root / 'packages' / 'agent-session-tools'}[all]" + + def test_install_workspace_tools_can_skip_sync_and_force(tmp_path: Path) -> None: repo_root = _workspace(tmp_path) diff --git a/packages/studyloop/tests/test_smoke_installed_cli_contract.py b/packages/studyloop/tests/test_smoke_installed_cli_contract.py index 3e6373cd8..da3865655 100644 --- a/packages/studyloop/tests/test_smoke_installed_cli_contract.py +++ b/packages/studyloop/tests/test_smoke_installed_cli_contract.py @@ -67,7 +67,14 @@ def test_smoke_uv_tool_install_uses_isolated_tool_home_and_runs_cli_smoke() -> N 'uv tool install --force --editable --python "$PY_REQUEST" ' '"$ROOT_DIR/packages/studyloop[all]"' ) in script_text - assert '--with-editable "$ROOT_DIR/packages/agent-session-tools"' in script_text + assert '--with-editable "$ROOT_DIR/packages/agent-session-tools[all]"' in script_text + # The studyloop venv serves `studyloop web`, whose encoder warm imports + # the semantic runtime in-process. Import level only: the isolated HOME + # has no Hugging Face cache, and the ONNX artefact is `doctor --fix`'s job. + assert ( + '"$(uv tool dir)/studyloop/bin/python" -c ' + '"import huggingface_hub, numpy, onnxruntime, sqlite_vec, tokenizers"' + ) in script_text assert ( 'uv tool install --force --editable --python "$PY_REQUEST" ' '"$ROOT_DIR/packages/agent-session-tools[all]"' From 8b3e3a7c3e5e566d4619454fc1534720c5cca8e4 Mon Sep 17 00:00:00 2001 From: NetDevAutomate Date: Sat, 26 Sep 2026 15:11:11 +0100 Subject: [PATCH 2/2] fix(install): co-install agent-session-tools[all] into the studyloop tool venv `studyloop web` warms the query encoder in-process at boot, inside the studyloop tool venv, and every hybrid search runs there too. The installer co-installed agent-session-tools into that venv bare, so tokenizers, onnxruntime, huggingface-hub, numpy and sqlite-vec never arrived: the warm failed at import, the header chip read "semantic: failed (0.0s)", search silently stayed lexical-only, and `doctor` in the same venv reported the encoder check as "does not apply". The standalone agent-session-tools tool always had [all]; it is a different venv. - installers.py: `--with-editable [all]`. - scripts/smoke-uv-tool-install.sh: the same co-install, plus an import of the runtime in the studyloop venv (import level only; the isolated HOME has no Hugging Face cache and the artefact is `doctor --fix`'s job). - nightly-install.yml: a step importing the runtime from the studyloop venv after install.sh -- the job's --version/--help checks pass without it, which is why nothing in CI ever saw this. - docs/setup-guide.md: both manual commands carry [all], with one sentence on why the co-install needs it. - CHANGELOG: Unreleased/Fixed entry. Verified on the maintainer's machine after rebuilding the studyloop tool venv with the fixed command: the web-surface warm reaches `warm` (bge-small-en-v1.5), and the real app started from that venv on 127.0.0.1:8599 answers /api/retrieval/health with state "warm". --- .github/workflows/nightly-install.yml | 10 ++++++++++ CHANGELOG.md | 10 ++++++++++ docs/setup-guide.md | 9 +++++++-- packages/studyloop/src/studyloop/installers.py | 9 ++++++++- scripts/smoke-uv-tool-install.sh | 7 ++++++- 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nightly-install.yml b/.github/workflows/nightly-install.yml index 9fedbc088..c897c9261 100644 --- a/.github/workflows/nightly-install.yml +++ b/.github/workflows/nightly-install.yml @@ -96,6 +96,16 @@ jobs: "$UV_TOOL_BIN_DIR/studyloop" --version "$UV_TOOL_BIN_DIR/session-export" --help + - name: Verify the studyloop tool env carries the semantic runtime + # `studyloop web` warms the query encoder in-process at boot, inside + # the studyloop tool venv (not agent-session-tools' own). Without + # these the warm fails at import and the header chip reads + # "semantic: failed" -- invisible to the --version/--help checks. + env: + UV_TOOL_DIR: ${{ runner.temp }}/tools + run: | + "$UV_TOOL_DIR/studyloop/bin/python" -c "import huggingface_hub, numpy, onnxruntime, sqlite_vec, tokenizers" + - name: Verify installed agent definitions # One artefact per planted harness, each a path `install agents` # writes for that tool (installers.py _TOOL_LINKS / _configure_*). diff --git a/CHANGELOG.md b/CHANGELOG.md index 15d8e152a..9f290884b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,16 @@ experience may change before `1.0.0`. drained future was already complete, the pump exited without reading that future's `StopAsyncIteration`. The pump now retrieves the result of a done pull future on every exit path. +- `studyloop web` started from an installed tool no longer shows + **semantic: failed (0.0s)**. The installer co-installed `agent-session-tools` + into the `studyloop` tool venv without its extras, so the semantic runtime + (tokenizers, onnxruntime, huggingface-hub, numpy, sqlite-vec) never reached + the venv that serves the web UI: the boot-time encoder warm failed at import + and search silently stayed lexical-only, while `studyloop doctor` in the same + venv called the encoder check "does not apply". The co-install now carries + `[all]`, and the nightly install job and `scripts/smoke-uv-tool-install.sh` + import the runtime in that venv. Existing installs pick it up with + `./scripts/install.sh --tools-only`. ## [0.5.0] - 2026-09-21 diff --git a/docs/setup-guide.md b/docs/setup-guide.md index 7e7f71723..56df818c4 100644 --- a/docs/setup-guide.md +++ b/docs/setup-guide.md @@ -136,10 +136,15 @@ such as `session-export`, `session-query`, and `session-sync` may not appear on ```bash uv sync --all-packages uv tool install --editable './packages/studyloop[all]' \ - --with-editable ./packages/agent-session-tools + --with-editable './packages/agent-session-tools[all]' uv tool install --editable './packages/agent-session-tools[all]' ``` +The `[all]` on `--with-editable` matters: `studyloop web` runs the semantic +search layer inside the `studyloop` tool venv, so co-installing +`agent-session-tools` without its extras leaves the header chip reading +`semantic: failed`. + Prefer `./scripts/install.sh` or `studyloop install tools` for normal source checkout installs because they keep the two tool venvs wired together. @@ -943,7 +948,7 @@ command: ```bash uv sync --all-packages uv tool install --editable './packages/studyloop[all]' \ - --with-editable ./packages/agent-session-tools \ + --with-editable './packages/agent-session-tools[all]' \ --force ``` diff --git a/packages/studyloop/src/studyloop/installers.py b/packages/studyloop/src/studyloop/installers.py index 2ee679d6f..6e9613d76 100644 --- a/packages/studyloop/src/studyloop/installers.py +++ b/packages/studyloop/src/studyloop/installers.py @@ -1310,7 +1310,14 @@ def install_workspace_tools( cmd.append(f"{pkg_dir}[all]") elif package_name == "studyloop": cmd.append(f"{pkg_dir}[all]") - cmd.extend(["--with-editable", str(repo_root / "packages" / "agent-session-tools")]) + # [all] on the co-installed package too. `studyloop web` imports + # agent_session_tools IN THIS venv: the server's boot-time encoder + # warm and every hybrid search run tokenizers, onnxruntime, + # huggingface_hub, numpy and sqlite_vec here, not in + # agent-session-tools' own tool venv. Co-installed bare, the warm + # failed at import and the header chip read "semantic: failed". + ast_pkg = repo_root / "packages" / "agent-session-tools" + cmd.extend(["--with-editable", f"{ast_pkg}[all]"]) else: cmd.append(str(pkg_dir)) cmd.append("--editable") diff --git a/scripts/smoke-uv-tool-install.sh b/scripts/smoke-uv-tool-install.sh index a53ae4ee5..fa43a413f 100755 --- a/scripts/smoke-uv-tool-install.sh +++ b/scripts/smoke-uv-tool-install.sh @@ -28,9 +28,14 @@ TOOL_BIN="$UV_TOOL_BIN_DIR" PY_REQUEST="$(tr -d '[:space:]' < "$ROOT_DIR/.python-version")" uv tool install --force --editable --python "$PY_REQUEST" "$ROOT_DIR/packages/studyloop[all]" \ - --with-editable "$ROOT_DIR/packages/agent-session-tools" + --with-editable "$ROOT_DIR/packages/agent-session-tools[all]" uv tool install --force --editable --python "$PY_REQUEST" "$ROOT_DIR/packages/agent-session-tools[all]" test -x "$TOOL_BIN/studyloop" test -x "$TOOL_BIN/session-export" +# The studyloop venv serves `studyloop web`, whose boot-time encoder warm +# imports the semantic runtime in-process; without it the header chip reads +# "semantic: failed". Import level only: this isolated HOME has no Hugging +# Face cache, and fetching the ONNX artefact is `doctor --fix`'s job. +"$(uv tool dir)/studyloop/bin/python" -c "import huggingface_hub, numpy, onnxruntime, sqlite_vec, tokenizers" STUDYLOOP_EXPECT_BIN_DIR="$TOOL_BIN" PATH="$TOOL_BIN:$PATH" "$ROOT_DIR/scripts/smoke-installed-cli.sh"