@@ -58,7 +58,9 @@ func findPythonBinary(dir string, projectType agentfs.ProjectType) (string, []st
5858 if projectType == agentfs .ProjectTypePythonUV {
5959 uvPath , err := exec .LookPath ("uv" )
6060 if err == nil {
61- return uvPath , []string {"run" , "python" }, nil
61+ // --no-sync: the CLI proxies the environment as it exists on disk
62+ // and must never install or upgrade packages as a side effect.
63+ return uvPath , []string {"run" , "--no-sync" , "python" }, nil
6264 }
6365 }
6466
@@ -180,22 +182,27 @@ func checkNodeSDKVersion(cfg AgentStartConfig) error {
180182 return nil
181183}
182184
183- // pythonResolveVersionScript prints the installed livekit-agents version, or
184- // exits non-zero if it isn't importable.
185- const pythonResolveVersionScript = `import importlib.metadata as m; print(m.version("livekit-agents"))`
185+ // pythonResolveVersionScript prints the installed livekit-agents version, or a
186+ // sentinel when the interpreter runs fine but the package isn't installed —
187+ // distinguishing "dependencies not synced" from probe failures (no
188+ // interpreter, timeout), which exit non-zero.
189+ const pythonResolveVersionScript = `import importlib.metadata as m
190+ try:
191+ print(m.version("livekit-agents"))
192+ except m.PackageNotFoundError:
193+ print("` + pythonAgentNotInstalled + `")`
194+
195+ const pythonAgentNotInstalled = "__NOT_INSTALLED__"
186196
187197// resolvePythonAgentVersion returns the installed livekit-agents version read
188198// via the project's interpreter, so any installer (uv, pip, poetry) reports the
189- // version that will actually run. Returns "" when it can't be determined (no
190- // interpreter, dependencies not installed, etc.). For uv it reads the existing
191- // environment without syncing, so a pre-flight check never mutates or downloads .
192- func resolvePythonAgentVersion (dir string , projectType agentfs.ProjectType ) string {
199+ // version that will actually run. notInstalled reports that the interpreter ran
200+ // but the package is missing from its environment; version is "" when it can't
201+ // be determined at all (no interpreter, probe failure, etc.) .
202+ func resolvePythonAgentVersion (dir string , projectType agentfs.ProjectType ) ( version string , notInstalled bool ) {
193203 pythonBin , prefixArgs , err := findPythonBinary (dir , projectType )
194204 if err != nil {
195- return ""
196- }
197- if projectType == agentfs .ProjectTypePythonUV && len (prefixArgs ) > 0 && prefixArgs [0 ] == "run" {
198- prefixArgs = append ([]string {"run" , "--no-sync" }, prefixArgs [1 :]... )
205+ return "" , false
199206 }
200207 ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
201208 defer cancel ()
@@ -204,17 +211,27 @@ func resolvePythonAgentVersion(dir string, projectType agentfs.ProjectType) stri
204211 cmd .Dir = dir
205212 out , err := cmd .Output ()
206213 if err != nil {
207- return ""
214+ return "" , false
208215 }
209- return strings .TrimSpace (string (out ))
216+ v := strings .TrimSpace (string (out ))
217+ if v == pythonAgentNotInstalled {
218+ return "" , true
219+ }
220+ return v , false
210221}
211222
212223// checkPythonSDKVersion gates a Python agent on thinCLIMinVersion. It prefers
213224// the installed version (resolved via the interpreter, accurate regardless of
214225// the package manager and not fooled by a loose version constraint); when
215226// dependencies aren't installed it falls back to static project-file parsing.
216227func checkPythonSDKVersion (cfg AgentStartConfig ) error {
217- if version := resolvePythonAgentVersion (cfg .Dir , cfg .ProjectType ); version != "" {
228+ version , notInstalled := resolvePythonAgentVersion (cfg .Dir , cfg .ProjectType )
229+ if notInstalled && cfg .ProjectType == agentfs .ProjectTypePythonUV {
230+ // The launch runs `uv run --no-sync`, so a missing package won't be
231+ // installed on the way up — fail fast with the fix instead.
232+ return fmt .Errorf ("livekit-agents is not installed in the project environment; run `uv sync` and try again" )
233+ }
234+ if version != "" {
218235 // An unparseable version (e.g. a local "0.0.0.dev" tag) shouldn't block a run.
219236 if ok , err := agentfs .IsVersionSatisfied (version , thinCLIMinVersion ); err == nil && ! ok {
220237 return fmt .Errorf ("livekit-agents version %s is too old, please upgrade to %s or newer" , version , thinCLIMinVersion )
@@ -324,7 +341,7 @@ const thinCLIMinVersion = "1.6.0"
324341// buildAgentCommand resolves the interpreter and argv for an agent subprocess,
325342// branching on project type. Python uses the thin CLI:
326343// `<python> <runtime-args> -m livekit.agents SUBCOMMAND ENTRYPOINT FLAGS`
327- // (uv prefixes `run python`). Node runs the entrypoint directly:
344+ // (uv prefixes `run --no-sync python`). Node runs the entrypoint directly:
328345// `node [--experimental-strip-types] <runtime-args> ENTRYPOINT SUBCOMMAND FLAGS`,
329346// where the type-stripping flag lets a `.ts` entrypoint run without a build.
330347func buildAgentCommand (cfg AgentStartConfig ) (string , []string , error ) {
0 commit comments