Skip to content

Relative agent spec in langstage.toml is resolved against CWD, not the toml's directory — an init-scaffolded project runs at its root but breaks from any subdirectory (Agent file not found) #116

Description

@dkedar7

Summary

langstage-cli's config discovery walks up ancestor directories to find langstage.toml (a project-root concept — this is what lets you run the CLI from anywhere in your project). But a relative agent.spec declared inside that langstage.toml is resolved against the process CWD, not against the directory that contains the langstage.toml. So the moment you run the CLI from a subdirectory of your project, it looks for my_agent.py under the subdirectory and crashes with Agent file not found — even though --show-config (run from the same subdir) correctly finds the langstage.toml and reports the spec as resolved.

This hits the documented, init-scaffolded happy path: langstage-cli init writes spec = "my_agent.py:graph" (relative) at the project root, and the very next command the init "Next:" hint prints (langstage-cli "Hello!", langstage-cli --verify) works at the root but fails from a subdir.

Severity: major — a real adopter faceplant. Config-file discovery walks up precisely so you can run from anywhere in the project, but a relative spec in that config silently defeats it. The error (Agent file not found: .../src/my_agent.py) is misleading because the file exists and --show-config says the spec resolved fine.

Distinct from the closed #30. #30 was about a spec from -a / LANGSTAGE_AGENT_SPEC and deliberately chose cwd-relative ("the file is where the user typed the command"). This issue is about a spec that comes from a langstage.toml discovered by walking up. Its natural base is the config file's own directory (the project root), not the invocation cwd — the same way a path in pyproject.toml / pytest.ini / tsconfig.json / .eslintrc resolves relative to the config file, never to wherever you happened to cd. Applying #30's cwd-relative rule uniformly to toml-sourced specs is what produces this bug.

Repro (verbatim, using the documented init scaffold)

pip install langstage-cli            # 0.6.29
mkdir initrepro && cd initrepro
langstage-cli init                   # writes my_agent.py + langstage.toml (spec = "my_agent.py:graph")

# 1) From the project root -> works
langstage-cli "hello from root"      # -> You said: hello from root

# 2) From a normal subdir of the SAME project -> crashes
mkdir src && cd src
langstage-cli --show-config | grep -E "agent_spec|TOML read"
langstage-cli "hello from src"

Actual

# from src/ — --show-config finds everything:
  agent_spec       = my_agent.py:graph          [toml (langstage.toml)]   (env: LANGSTAGE_AGENT_SPEC ..., toml: agent.spec)
  TOML read from: /abs/initrepro/langstage.toml

# ...yet the run crashes:
Error: FileNotFoundError: Agent file not found: /abs/initrepro/src/my_agent.py     # exit 1

The file actually exists at /abs/initrepro/my_agent.py (next to the langstage.toml), but the relative spec was looked up under src/ (cwd). Both langstage-cli "..." and langstage-cli --verify — the two commands the init "Next:" hint suggests — fail identically from the subdir.

Control: rewriting the toml spec to an absolute path (spec = "/abs/initrepro/my_agent.py:graph") makes it work from src/ — confirming the base directory (cwd vs toml dir) is the sole variable.

Expected

A relative agent.spec that comes from a langstage.toml should resolve relative to the directory containing that langstage.toml (the discovered project root), so the project runs identically from its root and from any subdirectory — consistent with the walk-up config discovery and with --show-config's own report. (Specs from -a / LANGSTAGE_AGENT_SPEC keep #30's cwd-relative behavior; only toml-sourced relative specs change base.)

Root cause

langstage_core/host/loader.py resolves the file path against cwd with no notion of the config file's directory:

# langstage_core/host/loader.py  (_import_module)
file_path = Path(module_path)
if file_path.suffix == ".py" or any(sep in module_path for sep in ("/", "\\")):
    file_path = file_path.resolve()          # <-- Path.resolve() is cwd-relative
    if not file_path.exists():
        raise FileNotFoundError(f"Agent file not found: {file_path}")

The spec string "my_agent.py:graph" reaches load_agent_spec() as-is; the CLI never rebases the relative path onto the directory of the langstage.toml it read the spec from (cfg._toml_paths[-1].parent), even though that path is already known (it's what --show-config prints as "TOML read from").

Suggested fix

When the resolved agent.spec originated from a TOML file (source is toml (...)), rebase a relative file-path spec onto that toml's parent directory before calling load_agent_spec — e.g. join Path(toml_path).parent / file_part when file_part is relative, then resolve. Module-path specs (pkg.mod:attr) and -a/env specs are unaffected.

Related secondary symptom (same root cause, lower priority)

The session store is keyed the same cwd-relative way: with the default workspace_root = ".", the store hash is derived from the absolute CWD, so langstage-cli -c from a subdirectory silently starts a fresh empty session instead of resuming the one started at the project root (README advertises a "durable, per-workspace store"). Pinning LANGSTAGE_WORKSPACE_ROOT to the same absolute path from both locations yields the same store hash and fixes it — confirming the store, like the spec, keys off cwd rather than the discovered project root. Mentioned for context; happy to split into its own issue if you'd prefer.

Environment

  • langstage-cli 0.6.29 (PyPI), langstage-core 1.0.33
  • langgraph 1.2.10, langchain-core 1.5.3
  • Python 3.11.15, Linux (fresh venv, clean-room install)

Filed by the daily LangStage dogfooding routine.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingroutineFiled by the daily power-user dogfooding routine

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions