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.
Summary
langstage-cli's config discovery walks up ancestor directories to findlangstage.toml(a project-root concept — this is what lets you run the CLI from anywhere in your project). But a relativeagent.specdeclared inside thatlangstage.tomlis resolved against the process CWD, not against the directory that contains thelangstage.toml. So the moment you run the CLI from a subdirectory of your project, it looks formy_agent.pyunder the subdirectory and crashes withAgent file not found— even though--show-config(run from the same subdir) correctly finds thelangstage.tomland reports the spec as resolved.This hits the documented,
init-scaffolded happy path:langstage-cli initwritesspec = "my_agent.py:graph"(relative) at the project root, and the very next command theinit"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-configsays the spec resolved fine.Repro (verbatim, using the documented
initscaffold)Actual
The file actually exists at
/abs/initrepro/my_agent.py(next to thelangstage.toml), but the relative spec was looked up undersrc/(cwd). Bothlangstage-cli "..."andlangstage-cli --verify— the two commands theinit"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 fromsrc/— confirming the base directory (cwd vs toml dir) is the sole variable.Expected
A relative
agent.specthat comes from alangstage.tomlshould resolve relative to the directory containing thatlangstage.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_SPECkeep #30's cwd-relative behavior; only toml-sourced relative specs change base.)Root cause
langstage_core/host/loader.pyresolves the file path against cwd with no notion of the config file's directory:The spec string
"my_agent.py:graph"reachesload_agent_spec()as-is; the CLI never rebases the relative path onto the directory of thelangstage.tomlit read the spec from (cfg._toml_paths[-1].parent), even though that path is already known (it's what--show-configprints as "TOML read from").Suggested fix
When the resolved
agent.specoriginated from a TOML file (source istoml (...)), rebase a relative file-path spec onto that toml's parent directory before callingload_agent_spec— e.g. joinPath(toml_path).parent / file_partwhenfile_partis 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, solangstage-cli -cfrom 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"). PinningLANGSTAGE_WORKSPACE_ROOTto 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
Filed by the daily LangStage dogfooding routine.