Summary
The README documents two interchangeable-looking TOML config locations with the same keys ("Configuration Files": global ~/.langstage/config.toml and project langstage.toml), and its one example uses a relative spec = "my_agent.py:graph". But a relative [agent] spec resolves against a different base directory depending on which file holds it:
Where the same value spec = "my_agent.py:graph" is set |
Resolves against |
Result |
LANGSTAGE_AGENT_SPEC env var |
invocation cwd |
✅ runs |
project langstage.toml |
project/toml dir (= cwd here) |
✅ runs |
global ~/.langstage/config.toml |
~/.langstage/ (config-home dir) |
❌ FileNotFoundError |
So a user who sets a global default agent using the documented relative-spec format gets, from every project, an error naming a path they never wrote (~/.langstage/my_agent.py) — because no one keeps agent code in ~/.langstage/.
This is the global-config sibling of the #116 (project-spec) / #132 ([workspace] root) relative-resolution family. The #116 fix ("resolve a relative path against the dir containing the toml, like pyproject.toml") is sound for a project config, where the toml dir ≈ where your code lives — but for the global config, ~/.langstage/ is a config dir, never a code dir, so that rule yields a guaranteed-useless path.
Environment
langstage-cli 0.6.30, langstage-core 1.0.34
- Python 3.11, Linux
- Clean-room
pip install langstage-cli from PyPI
Minimal repro
export LANGSTAGE_CONFIG_HOME=/tmp/clean_home # relocates the global config dir (documented)
rm -rf /tmp/clean_home /tmp/clean_proj && mkdir -p /tmp/clean_home /tmp/clean_proj
# The agent lives where you WORK:
cat > /tmp/clean_proj/my_agent.py <<'PY'
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import MessagesState
from langchain_core.messages import AIMessage
def respond(state): return {"messages":[AIMessage(content="agent ran OK")]}
g=StateGraph(MessagesState); g.add_node("respond",respond)
g.add_edge(START,"respond"); g.add_edge("respond",END)
graph=g.compile()
PY
# GLOBAL config points at it with the documented relative-spec format:
printf '[agent]\nspec = "my_agent.py:graph"\n' > /tmp/clean_home/config.toml
cd /tmp/clean_proj
langstage-cli --no-interactive "hi" # <-- A) global config
cp /tmp/clean_home/config.toml ./langstage.toml
langstage-cli --no-interactive "hi" # <-- B) same value, project config
Actual
# A) global config
Error: FileNotFoundError: Agent file not found: /tmp/clean_home/my_agent.py
# exit=1
# B) same value in project langstage.toml
agent ran OK
# exit=0
The agent file is present in the cwd the whole time; only the config location changed.
Expected
One of:
- A relative
[agent] spec in the global config resolves against the invocation cwd (the only sensible base for a cross-project default), matching the env-var behavior; or
- It is rejected up front with a clear message ("a relative agent spec in the global config is ambiguous — use an absolute path or a
module:attr spec"), instead of silently resolving to ~/.langstage/… and 404-ing.
Either way, the current silent resolution against ~/.langstage/ — a directory the user never referenced — is the surprising option.
Aggravating factor: --show-config gives no hint
agent_spec = my_agent.py:graph [toml (config.toml)] (env: LANGSTAGE_AGENT_SPEC …, toml: agent.spec)
It shows the raw relative value with no indication of the base directory it will resolve against, so a user debugging the 404 can't tell from --show-config that a global-vs-project distinction is in play. (Surfacing the resolved absolute agent path in --show-config would make this class of resolution surprise self-diagnosing.)
Severity
Minor — the workaround (use an absolute path or a module:attr spec in the global config) is easy once you know the rule; the cost is the silent, undocumented divergence from the project/env behavior that the docs present as equivalent.
Filed by the nightly dogfood routine.
Summary
The README documents two interchangeable-looking TOML config locations with the same keys ("Configuration Files": global
~/.langstage/config.tomland projectlangstage.toml), and its one example uses a relativespec = "my_agent.py:graph". But a relative[agent] specresolves against a different base directory depending on which file holds it:spec = "my_agent.py:graph"is setLANGSTAGE_AGENT_SPECenv varlangstage.toml~/.langstage/config.toml~/.langstage/(config-home dir)FileNotFoundErrorSo a user who sets a global default agent using the documented relative-spec format gets, from every project, an error naming a path they never wrote (
~/.langstage/my_agent.py) — because no one keeps agent code in~/.langstage/.This is the global-config sibling of the #116 (project-spec) / #132 (
[workspace] root) relative-resolution family. The #116 fix ("resolve a relative path against the dir containing the toml, likepyproject.toml") is sound for a project config, where the toml dir ≈ where your code lives — but for the global config,~/.langstage/is a config dir, never a code dir, so that rule yields a guaranteed-useless path.Environment
langstage-cli0.6.30,langstage-core1.0.34pip install langstage-clifrom PyPIMinimal repro
Actual
The agent file is present in the cwd the whole time; only the config location changed.
Expected
One of:
[agent] specin the global config resolves against the invocation cwd (the only sensible base for a cross-project default), matching the env-var behavior; ormodule:attrspec"), instead of silently resolving to~/.langstage/…and 404-ing.Either way, the current silent resolution against
~/.langstage/— a directory the user never referenced — is the surprising option.Aggravating factor:
--show-configgives no hintIt shows the raw relative value with no indication of the base directory it will resolve against, so a user debugging the 404 can't tell from
--show-configthat a global-vs-project distinction is in play. (Surfacing the resolved absolute agent path in--show-configwould make this class of resolution surprise self-diagnosing.)Severity
Minor — the workaround (use an absolute path or a
module:attrspec in the global config) is easy once you know the rule; the cost is the silent, undocumented divergence from the project/env behavior that the docs present as equivalent.Filed by the nightly dogfood routine.