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
82 changes: 81 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ jobs:
# Depends on the fast 3.11 install gate; `gate` is also a direct need so this
# job can read run_ci (the matrix and main-only jobs run independently).
needs: [ gate, install ]
# Safety net: without this, a hang falls back to GitHub's 360-minute
# default. pytest's own --timeout=600 below should already catch a stuck
# test, but this bounds the whole job even if some hang manages to dodge
# that (e.g. a shared resource -- see WEIGHTSLAB_NOTEBOOK_EXEC_TIMEOUT).
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -221,8 +226,83 @@ jobs:
- name: Run unit tests - General
run: |
export WEIGHTSLAB_LOG_LEVEL="DEBUG"
# Bounds EmbeddedKernelBridge's per-cell wait (default: unbounded --
# see notebook_service.py) so a dead/unresponsive embedded kernel in
# TestNotebookKernelEmbedded fails that one test fast instead of
# holding the kernel's lock forever and wedging every later test in
# the same contract-test class behind it.
export WEIGHTSLAB_NOTEBOOK_EXEC_TIMEOUT="60"
# A per-test timeout guards against any regression that hangs a test.
python -m pytest ./tests -v --timeout=600
# -m "not scale": tests/backend/test_logger_scale.py deliberately
# builds a multi-million-row DuckDB fixture to stress-test large-scale
# queries (see its module docstring) -- real, by-design heavy work,
# not a hang, but easily 600s+ on a shared/throttled runner. Its own
# marker registration (pyproject.toml) already says "deselect with
# -m 'not scale'"; this job just never actually did. Each `scale`
# test hitting the per-test timeout instead of being deselected burns
# 10 minutes AND leaves the job stuck at the same progress % across
# unrelated pushes, which read as a hang.
python -m pytest ./tests -v --timeout=600 -m "not scale"

# ── Agent smoke test on a pip-installed package ───────────────────────────
# Proves the Option-2 promise end-to-end: install weightslab into a CLEAN
# virtualenv (from the built wheel, not editable) and confirm the OpenCode
# agent works with NO manual `npm i -g opencode` / `npx` step --
# 1. the managed OpenCode binary provisions and runs (`--version`),
# 2. `weightslab start` (UI) brings the agent server up even with no
# credential configured (the user can configure it afterwards), and
# 3. `weightslab start example` boots without ever hitting the
# "no opencode/npx" path.
agent-smoke:
needs: [ gate, install ]
if: ${{ needs.gate.outputs.run_ci == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 30
name: agent smoke (pip install)
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v6
with:
python-version: '3.11'

- name: Create clean virtual environment and install from wheel
run: |
python -m venv .venv-smoke
. .venv-smoke/bin/activate
python -m pip install --upgrade pip build
# Build a wheel and install THAT (a real "pip install the package",
# not an editable checkout) so package-data / entry points are exercised
# exactly as an end user would get them.
python -m build --wheel
python -m pip install dist/*.whl --extra-index-url https://download.pytorch.org/whl/cpu

- name: Agent smoke — provision opencode + weightslab start
env:
WEIGHTSLAB_LOG_LEVEL: INFO
# Force the managed provisioning path (do not depend on the runner's
# preinstalled Node): the standalone binary must run on its own.
WEIGHTSLAB_OPENCODE_AUTODOWNLOAD: '1'
run: |
. .venv-smoke/bin/activate
python scripts/ci/agent_smoke.py start

- name: Agent smoke — weightslab agent init (CLI, headless)
env:
WEIGHTSLAB_LOG_LEVEL: INFO
WEIGHTSLAB_OPENCODE_AUTODOWNLOAD: '1'
run: |
. .venv-smoke/bin/activate
python scripts/ci/agent_smoke.py cli-init

- name: Agent smoke — weightslab start example (agent optional, no error)
env:
WEIGHTSLAB_LOG_LEVEL: INFO
run: |
. .venv-smoke/bin/activate
python scripts/ci/agent_smoke.py example

build-and-publish-dev:
# Only publish to TestPyPI when pushing to main (not on PRs or dev branch pushes).
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# Changelog - 2026-08-21 v2.0.0
# Changelog - 2026-08-26 v2.0.1
32 changes: 23 additions & 9 deletions docs/agent_quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,39 @@ server. This page is the fastest path from "just installed WeightsLab" to
What you need
--------------

- WeightsLab installed (``pip install weightslab``) — this brings the
``opencode-ai`` bundled binary with it, so there is nothing extra to
install for the agent itself.
- WeightsLab installed (``pip install weightslab``). That's the only install
step: WeightsLab provisions the OpenCode binary itself, on first use, into a
per-user cache — **no Node.js and no manual ``npm``/``opencode`` install
required**.
- One set of credentials for a model provider: an OpenRouter API key, an
Anthropic key, or a local Ollama install. Pick whichever you already have.

Step 1 — authenticate OpenCode once
Step 1 — initialize the agent once
------------------------------------

The agent's provider and credentials live entirely inside OpenCode, never in
WeightsLab itself. Do this once per machine:
WeightsLab itself. The one-liner below provisions the OpenCode binary (if it
isn't already) and then signs you in — do this once per machine:

.. code-block:: bash

opencode auth login
weightslab agent init

Follow the prompts to sign in to OpenRouter, Anthropic, or point it at a
local Ollama endpoint. You can also do this later from the browser, using the
login modal on the Weights Studio landing page — no terminal required.
Follow the prompts to sign in to OpenRouter, Anthropic, or point it at a local
Ollama endpoint. Equivalent alternatives:

- ``opencode auth login`` — if you prefer to drive OpenCode directly (WeightsLab
installs the binary either way).
- The login modal on the Weights Studio landing page — no terminal required.
- ``weightslab agent init --provision-only`` — headless/CI: just install the
binary, skip the interactive sign-in.

.. note::

You can skip this step and start straight away — if no credential is found,
WeightsLab logs an *info* line ("OpenCode is installed, but the agent is not
initialized yet — run ``weightslab agent init``") and keeps running. The
assistant is optional; nothing else is blocked.

Step 2 — start an experiment
------------------------------
Expand Down
63 changes: 63 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,22 @@ server and the backend SDK agent share. These control where it lives.
one. Takes precedence over everything else, and configures **both** the
UI server and the SDK agent — set it once and the two converge on a
single process.
* - ``WEIGHTSLAB_OPENCODE_HOST``
- ``127.0.0.1``
- Host the spawned agent server **binds** to. Loopback by default (the
server has filesystem access and must not be reachable off-machine on a
normal local run). Set to ``0.0.0.0`` when running in a container reached
over an SSH tunnel / published port, so the published port can reach it —
the URL handed to the browser stays ``127.0.0.1`` either way. See
:ref:`studio-bridging`.
* - ``WEIGHTSLAB_UI_TRUSTED_HOSTS``
- *(unset)*
- Comma-separated extra source IPs/CIDRs allowed to call the UI server's
local-only control routes (start agent, notebook, loops). Loopback is
always trusted; behind a tunnel + published port the browser's request
arrives from the container gateway, so set e.g.
``172.16.0.0/12,192.168.0.0/16`` there (the real trust boundary being the
tunnel + host publishing to ``127.0.0.1``).

.. note::

Expand All @@ -823,6 +839,53 @@ server and the backend SDK agent share. These control where it lives.
browser, this port has to be reachable from the browser's side — see
:ref:`studio-bridging`.

Agent installation (OpenCode binary)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

WeightsLab provisions the OpenCode standalone binary itself — no Node.js
required — the first time it is needed (on ``import weightslab``,
``weightslab start``, ``weightslab start example``, or the first agent use).
It is fetched once into a per-user cache and reused. These control that.

.. list-table::
:header-rows: 1
:widths: 35 15 50

* - Variable
- Default
- Description
* - ``WEIGHTSLAB_OPENCODE_AUTOINSTALL``
- ``1``
- Auto-install OpenCode in the background (logged) on ``import weightslab``
/ ``weightslab start`` when it isn't already present. Set to ``0`` to
disable the on-import/start install (e.g. air-gapped or CI hosts).
* - ``WEIGHTSLAB_OPENCODE_AUTODOWNLOAD``
- ``1``
- Master switch for the network fetch. ``0`` forbids all on-demand
downloads — an already-provisioned binary is still used, but nothing new
is fetched (stricter than ``AUTOINSTALL``, which only gates the
import/start pre-warm).
* - ``WEIGHTSLAB_OPENCODE_VERSION``
- *(pinned)*
- Override the OpenCode version WeightsLab provisions. Each release pins a
known-good version; set this only to track a different one.
* - ``WEIGHTSLAB_OPENCODE_HOME``
- *(per-user cache)*
- Directory the managed binary is installed under. Defaults to the
platform cache (``~/.cache/weightslab/opencode`` on Linux,
``%LOCALAPPDATA%\\weightslab\\opencode`` on Windows,
``~/Library/Caches/weightslab/opencode`` on macOS).

.. tip::

Sign in once with ``weightslab agent init`` (provisions the binary, then runs
``opencode auth login``); ``weightslab agent init --provision-only`` just
installs the binary without the interactive sign-in, for headless/CI use.
To uninstall, delete ``$WEIGHTSLAB_OPENCODE_HOME`` (default per-user cache
above) and set ``WEIGHTSLAB_OPENCODE_AUTOINSTALL=0`` (and, to also block the
on-demand fetch, ``WEIGHTSLAB_OPENCODE_AUTODOWNLOAD=0``) so it isn't
re-installed.

Agent Provider Setup
~~~~~~~~~~~~~~~~~~~~

Expand Down
Loading
Loading