Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0285033
fix: preserve shell precedence for runtime config
kic635 Aug 11, 2026
ebc80c7
fix: isolate container dotenv expansion and CLI tests
kic635 Aug 12, 2026
e7dcbf8
test: verify minimum deps and container env boundary
kic635 Aug 12, 2026
ceab7e7
test: cover runtime dotenv compatibility boundaries
kic635 Aug 12, 2026
ee24e5d
docs: add runtime migration handoff
kic635 Aug 12, 2026
3237c32
chore: remove local handoff document
kic635 Aug 12, 2026
63fcf6d
fix: safely interpolate container dotenv values
kic635 Aug 12, 2026
2b491b1
fix: preserve dotenv default expansion semantics
kic635 Aug 12, 2026
cb84614
fix: align dotenv interpolation contracts
kic635 Aug 13, 2026
8bc8bce
test: harden dotenv conformance coverage
kic635 Aug 13, 2026
8465c47
fix: preserve layered valueless dotenv semantics
kic635 Aug 13, 2026
f41f7d7
refactor: narrow environment fix to host runtimes
webup Aug 16, 2026
635951c
refactor: isolate CLI constants from runtime settings
webup Aug 16, 2026
dc2d3a5
feat: add strict dotenv adapter
webup Aug 16, 2026
6d8aa8b
fix: make inherited runtime environment authoritative
webup Aug 16, 2026
ca9591f
fix: launch runtime roles in fresh processes
webup Aug 16, 2026
9857fc5
test: harden runtime child process regressions
webup Aug 16, 2026
2e9d79b
fix: clean up interrupted child processes
webup Aug 16, 2026
e8985ea
fix: close process supervision races
webup Aug 16, 2026
a6c39ec
test: verify dotenv dependency floor
webup Aug 16, 2026
00b6236
test: add pytest asyncio floor coverage
webup Aug 16, 2026
49846e4
docs: document host environment ownership
webup Aug 16, 2026
d6c5893
test: cover process supervision boundaries
webup Aug 16, 2026
5c2867d
style: format new runtime tests
webup Aug 16, 2026
28faa2f
ci: isolate host runtime process tests
webup Aug 16, 2026
6f4d5ab
test: make runtime process proofs platform-specific
webup Aug 16, 2026
56e06f9
fix: render CLI banner on legacy consoles
webup Aug 16, 2026
e57d400
fix: normalize legacy console banner fallback
webup Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ jobs:
uv run python -c
"from pathlib import Path; import subprocess; out = Path('.tmp/agentseek.Dockerfile'); out.parent.mkdir(exist_ok=True); subprocess.run(['uv', 'run', 'agentseek-api', 'dockerfile', '--config', 'examples/external_graph/manifest.json', str(out)], check=True); text = out.read_text(encoding='utf-8'); assert 'ENV PYTHONPATH=/deps/agent' in text; assert 'ENV AGENTSEEK_GRAPHS=/deps/agent/examples/external_graph/manifest.json' in text"

- name: CLI config, host environment, and process tests
run: >-
uv run pytest
tests/unit/test_cli.py
tests/unit/test_graph_manifest.py
tests/unit/test_dotenv_adapter.py
tests/unit/test_runtime_environment.py
tests/unit/test_runtime_entrypoint.py
tests/unit/test_process_supervisor.py
tests/integration/test_cli_runtime_processes.py
-q

- name: Sync embedded SeekDB extra for serve smoke
if: runner.os == 'Linux' || (runner.os == 'macOS' && runner.arch == 'ARM64')
run: uv sync --dev --extra embedded
Expand All @@ -90,8 +102,9 @@ jobs:
if: runner.os == 'Linux' || (runner.os == 'macOS' && runner.arch == 'ARM64')
run: uv run python scripts/test_cli_serve_smoke.py

- name: CLI config and Docker planning tests
run: uv run pytest tests/unit/test_cli.py tests/unit/test_graph_manifest.py -q
- name: Minimum direct dependency compatibility
if: runner.os == 'Linux'
run: uv run python scripts/test_minimum_cli_dependencies.py

embedded-seekdb-smoke:
name: Embedded SeekDB Smoke
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

Notable changes to AgentSeek API are documented in this file.

## Unreleased

### Fixed

- Made inherited host environment keys, including explicit empty strings,
authoritative over config and CLI dotenv sources.
- Parse each dotenv source independently with strict malformed-file handling,
and distinguish valueless `KEY` from explicit empty `KEY=`.
- Start worker and scheduler roles in fresh child processes so their settings
are constructed after the resolved environment is installed.
- Kept version, help, and Dockerfile rendering independent of runtime settings
validation.

### Upgrade notes

- Dotenv values no longer interpolate from config mappings or other dotenv
files. Put dependent bindings in one physical file or pass the final literal
value.
- Malformed dotenv syntax now exits with status 2 instead of warning and
continuing with a partial runtime configuration.

## 0.2.1 - 2026-07-14

### Fixed
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,35 @@ When running from this repository, use `uv run agentseek-api ...`.

- `-c, --config PATH`: explicit `agentseek.json`, `langgraph.json`, or manifest
path
- `--env-file PATH`: dotenv-style file loaded into the runtime environment
- `--env-file PATH`: host-runtime dotenv source

### Host runtime environment

For `dev`, `serve`, `worker`, and `scheduler`, direct assignments are applied in
this order:

1. the dotenv path named by config `env`;
2. the literal config `env` mapping and `auth.path`;
3. the CLI `--env-file`;
4. the environment inherited by `agentseek-api`.

The inherited environment is authoritative by key presence. This includes an
explicit empty value. A lower source can fill an absent key, but cannot replace
an inherited `KEY=`.

Each dotenv file is evaluated independently. It can reference the inherited
environment and earlier bindings in the same physical file; it cannot reference
a config mapping or another dotenv file. Later assignment does not recompute an
earlier interpolated value.

In dotenv syntax, a bare `KEY` is valid but contributes no assignment, while
`KEY=` contributes an explicit empty string. Missing files, invalid UTF-8, and
malformed syntax stop the command before a runtime child starts.

The CLI applies command-owned values after this merge: the selected config path
becomes `AGENTSEEK_GRAPHS`, and `dev` forces `STUDIO_AUTH_LOCAL_DEV=true`.
Host and port options are passed as child argv and do not rewrite environment
keys with similar names.

### Common usage

Expand Down
9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@ dependencies = [
"uvicorn>=0.30.0",
"pydantic>=2.8.0",
"pydantic-settings>=2.4.0",
"sqlalchemy>=2.0.0",
"sqlalchemy>=2.0.12",
"greenlet>=3.1.0",
"asyncpg>=0.29.0",
"aiomysql>=0.2.0",
"aiosqlite>=0.20.0",
"redis>=5.0.0",
"langgraph>=1.0.3",
"langgraph>=1.0.6",
"langgraph-sdk>=0.3.5",
"langchain-core>=1.0.0",
"langchain-core>=1.2.5",
"langchain-openai>=1.0.0",
"langchain-anthropic>=1.0.0",
"langchain-oceanbase==0.6.0",
"cryptography>=45.0.0",
"pymysql>=1.1.0",
"langchain>=0.3.9",
"mcp>=1.27.1,<2",
"python-dotenv>=1.0,<1.3",
"scalar-fastapi>=1.0.3",
]

Expand All @@ -39,7 +40,7 @@ agentseek-api = "agentseek_api.cli:main"
[dependency-groups]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"pytest-asyncio>=0.23.5",
"pytest-cov>=5.0.0",
"httpx>=0.27.0",
"ruff>=0.6.0",
Expand Down
109 changes: 109 additions & 0 deletions scripts/test_minimum_cli_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Verify host environment resolution with all direct dependency floors."""

from __future__ import annotations

import os
import subprocess
import sys
import tempfile
import tomllib
from pathlib import Path


def main() -> None:
repository = Path(__file__).resolve().parents[1]
project = tomllib.loads((repository / "pyproject.toml").read_text(encoding="utf-8"))
requirements = list(project["project"]["dependencies"])
requirements.append("pytest>=8.0.0")
requirements.append("pytest-asyncio>=0.23.5")

with tempfile.TemporaryDirectory(prefix="agentseek-minimum-") as directory:
environment = Path(directory) / ".venv"
subprocess.run(
[
"uv",
"venv",
"--python",
"3.12",
str(environment),
],
check=True,
)
python = environment / (
"Scripts/python.exe" if sys.platform == "win32" else "bin/python"
)
subprocess.run(
[
"uv",
"pip",
"install",
"--python",
str(python),
"--resolution",
"lowest-direct",
*requirements,
],
check=True,
)
subprocess.run(
[
"uv",
"pip",
"install",
"--python",
str(python),
"--no-deps",
"-e",
str(repository),
],
check=True,
)

version = subprocess.run(
[
str(python),
"-c",
(
"import importlib.metadata; "
"print(importlib.metadata.version('python-dotenv'))"
),
],
check=True,
capture_output=True,
text=True,
)
assert version.stdout.strip() == "1.0.0"

cli_env = dict(os.environ)
cli_env.pop("PYTHONPATH", None)
cli_env["PORT"] = "not-an-integer"
version_result = subprocess.run(
[str(python), "-m", "agentseek_api.cli", "version"],
cwd=repository,
env=cli_env,
check=True,
capture_output=True,
text=True,
)
expected_version = project["project"]["version"]
assert version_result.stdout.strip() == f"agentseek-api {expected_version}"

test_env = dict(os.environ)
test_env.pop("PYTHONPATH", None)
subprocess.run(
[
str(python),
"-m",
"pytest",
"tests/unit/test_dotenv_adapter.py",
"tests/unit/test_runtime_environment.py",
"-q",
],
cwd=repository,
env=test_env,
check=True,
)


if __name__ == "__main__":
main()
Loading
Loading