Skip to content

fix(agent): trust the resolved livekit-agents version in the SDK check; add Windows test runner#894

Open
u9g wants to merge 6 commits into
mainfrom
fix/uv-lock-sdk-version-parsing
Open

fix(agent): trust the resolved livekit-agents version in the SDK check; add Windows test runner#894
u9g wants to merge 6 commits into
mainfrom
fix/uv-lock-sdk-version-parsing

Conversation

@u9g

@u9g u9g commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

The Python agent SDK-version check (lk agent start/dev/console, and the build-time check) could reject a project whose installed livekit-agents actually satisfies the minimum. This PR makes the check trust the resolved/installed version, and adds Windows test coverage for the package.

1. Parse the resolved version from uv.lock (pkg/agentfs)

checkUvLock matched a livekit-agents = "x" line that never appears in uv.lock — the file uses poetry.lock's TOML layout ([[package]] / name / version). So the resolved version was never found and the check fell back to the pyproject.toml constraint, misreading the lower bound (>=1.01.0) and failing it against the minimum (e.g. 1.6.0). Now it uses the same [[package]] regex as checkPoetryLock, so the resolved version is read and preferred.

2. Resolve the installed version for the run check (cmd/lk)

lk agent start/dev/console now resolves the installed livekit-agents via the project interpreter (importlib.metadata), the way the Node path already does — accurate regardless of installer (uv/pip/poetry) and not fooled by a loose version constraint. For uv it reads the existing environment with --no-sync, so the pre-flight check never syncs or downloads. When dependencies aren't installed (e.g. before a sync), it falls back to static project-file parsing (the uv.lock fix above, then constraints).

3. Windows CI (.github/workflows/windows.yaml)

test.yaml covers Linux/macOS Go tests and session-e2e.yaml's e2e job covers Linux/macOS session e2e, but Windows ran essentially no Go tests: mingw GCC can't compile the WebRTC APM C++ (MSVC SEH), and the native cgo link overflows the command-line limit. This adds a single windows.yaml that — like .goreleaser.yaml — cross-compiles on Linux with zig and runs natively on windows-latest:

  • One cross-build job produces lk.exe plus a test binary per package (discovered via go list, currently cmd/lk, pkg/agentfs, pkg/bootstrap, pkg/util).
  • windows-tests (needs cross-build) runs every package's tests from its directory so cwd-relative fixtures resolve; runs on PRs, no secrets.
  • windows-session-e2e (needs cross-build) drives the session lifecycle against the prebuilt lk.exe; skipped on PRs since it needs LiveKit secrets.

Both consumers depend on the one cross-build, so the expensive cgo compile happens once. The Windows jobs were moved out of session-e2e.yaml (now Linux/macOS only) into this workflow. Windows is amd64-only here, matching the prior session-e2e Windows arm; build.yaml still builds windows/arm64 for release.

Tests

  • pkg/agentfs: real uv.lock fixture (replacing a fake one that only matched the broken regex) + a regression test that a loose pyproject floor doesn't override a satisfying resolved version.
  • cmd/lk: resolvePythonAgentVersion / checkPythonSDKVersion exercised against a real uv project with a local stub livekit-agents package (known version, no network, no real SDK) — reads installed version, rejects too-old, installed beats a loose constraint, and falls back to file parsing when not installed. These run on Linux, macOS, and Windows.

Note

This is the first time the full Go suite runs on Windows, so the initial run may surface pre-existing Windows-specific issues in tests unrelated to this change.

@u9g u9g changed the title fix(agentfs): parse resolved livekit-agents version from uv.lock fix(agent): trust the resolved livekit-agents version in the SDK check; add Windows test runner Jun 30, 2026
@Bobronium

Bobronium commented Jul 1, 2026

Copy link
Copy Markdown
Member

Should we perhaps just check the actual installed version instead?

This would save us from parsing any requirements/lock files and guarantee that this is the version that will be used:

uv run python -c \
'from importlib.metadata import version; \
print(version("livekit-agents"))'
1.6.3

Comment thread cmd/lk/simulate_subprocess.go Outdated

// pythonResolveVersionScript prints the installed livekit-agents version, or
// exits non-zero if it isn't importable.
const pythonResolveVersionScript = `import importlib.metadata as m; print(m.version("livekit-agents"))`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's make sure this isn't executing any livekit-agents code. Otherwise, this could be a security vulnerability.

Let's make sure importlib is only reading metadata

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well, the interpreter starts, and the interpreter runs all .pth files of all dependencies which means all those files will be ran, but this is just a check before running uv run, so I don't see what the vulnerability could be here.

@u9g

u9g commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Should we perhaps just check the actual installed version instead?

This would save us from parsing any requirements/lock files and guarantee that this is the version that will be used:

uv run python -c \
'from importlib.metadata import version; \
print(version("livekit-agents"))'
1.6.3

The cloud build (for create and deploy subcommands on the lk cli) resolves dependencies from the project/lock files, so uv.lock is the source of truth for what will actually run in deployment; the locally installed version could differ (or not exist) and would be the wrong thing to check.

u9g added 3 commits July 2, 2026 12:23
checkUvLock matched a `livekit-agents = "x"` line that never appears in
uv.lock. uv.lock uses poetry.lock's TOML layout ([[package]] / name / version),
so the resolved version was never found, and the SDK version check fell back to
the pyproject.toml constraint floor — reading `>=1.0` as 1.0 and rejecting it
as older than the minimum (e.g. 1.6.0) even when the lock resolved a newer
version. Use the same [[package]] block regex as checkPoetryLock so the
resolved version is read and preferred, matching how the Node lockfile parsers
already read the installed version.

Also replaces the test's fake uv.lock fixture with the real format and adds a
regression test for the pyproject-floor-vs-resolved-version case.
…check

lk agent start/dev/console gated the Python SDK version by parsing project
files, which can't know the resolved version from a loose constraint (and
misread e.g. >=1.0 as 1.0). Resolve the installed version via the project
interpreter (importlib.metadata) like the Node path already does, so the check
reflects what will actually run regardless of installer. Falls back to static
file parsing when dependencies aren't installed (e.g. before a sync). uv reads
the existing env with --no-sync so the pre-flight check never syncs/downloads.
Combine the two Windows cross-builds into a single zig cross-build job in
windows.yaml that produces lk.exe plus a test binary per package. Both the
unit-test run (windows-tests) and the session e2e run (windows-session-e2e)
depend on it, so the expensive cgo compile happens once. Move the Windows jobs
out of session-e2e.yaml (now Linux/macOS only) and fold the standalone
test-windows.yaml in.
@u9g u9g force-pushed the fix/uv-lock-sdk-version-parsing branch from 02e3e10 to 1ecdb6e Compare July 2, 2026 16:26
@Bobronium

Bobronium commented Jul 2, 2026

Copy link
Copy Markdown
Member

The cloud build (for create and deploy subcommands on the lk cli) resolves dependencies from the project/lock files, so uv.lock is the source of truth for what will actually run in deployment; the locally installed version could differ (or not exist) and would be the wrong thing to check.

Totally fair, I've misinterpreted the scope initially.

For uv it reads the existing environment with --no-sync, so the pre-flight check never syncs or downloads.

I'd argue we should just add --no-sync to findPythonBinary to make it deterministic and consistent with the case when UV is not found and bare Python is used.

u9g added 3 commits July 2, 2026 15:11
The CLI proxies the project's local environment; `uv run` implicitly
re-locking and installing packages when launching an agent is a
surprising side effect (and can change dependency versions mid-debug).
Move --no-sync into findPythonBinary so both the version pre-flight and
the launch run against the environment as it exists on disk.

Since the launch no longer installs missing dependencies, the version
probe now distinguishes "interpreter ran but livekit-agents isn't
installed" and the pre-flight fails fast with a `uv sync` hint
instead of letting the agent die with ModuleNotFoundError.
The launch path now uses `uv run --no-sync`; deps come from the
explicit `uv sync` step in CI.
Stopping only signaled the spinner goroutine, which could sleep up to
80ms before clearing the line — so an error printed right after landed
on the leftover "Starting agent" text. Block the stop function until
the clear escape has been written.
@u9g

u9g commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

The cloud build (for create and deploy subcommands on the lk cli) resolves dependencies from the project/lock files, so uv.lock is the source of truth for what will actually run in deployment; the locally installed version could differ (or not exist) and would be the wrong thing to check.

Totally fair, I've misinterpreted the scope initially.

For uv it reads the existing environment with --no-sync, so the pre-flight check never syncs or downloads.

I'd argue we should just add --no-sync to findPythonBinary to make it deterministic and consistent with the case when UV is not found and bare Python is used.

This is a great point, I did it.

@Bobronium Bobronium left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

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.

3 participants