Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/nightly-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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_*).
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 7 additions & 2 deletions docs/setup-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
```

Expand Down
9 changes: 8 additions & 1 deletion packages/studyloop/src/studyloop/installers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
21 changes: 21 additions & 0 deletions packages/studyloop/tests/test_ci_workflow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
25 changes: 24 additions & 1 deletion packages/studyloop/tests/test_installers_workspace_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
Expand All @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]"'
Expand Down
7 changes: 6 additions & 1 deletion scripts/smoke-uv-tool-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading