Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion src/agentic_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@
set_settings(my_settings)
settings = get_settings()

2. Context-based (isolated contexts, multi-tenant):
2. Context-based (isolated settings lookup, e.g. tests or per-request
overrides):
with SettingsContext(my_settings):
# Code here sees my_settings via get_settings()
settings = get_settings() # Returns my_settings

Note: isolation covers settings *lookup* only. API credentials are
exported to process-global env vars at manager initialization
(provider SDKs read them from the environment), so a SettingsContext
does not isolate credentials between contexts in one process.

Settings Loading Priority (highest to lowest):
1. Environment variables (AGENTIC_* prefix)
2. Project config (./.{app_name}/settings.json)
Expand Down
20 changes: 17 additions & 3 deletions src/agentic_cli/workflow/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,11 +652,25 @@ def set_thinking_effort(self, effort: str) -> None:
object.__setattr__(self, "thinking_effort", effort)

def export_api_keys_to_env(self) -> None:
"""Export API keys to environment variables."""
"""Export configured API keys to provider environment variables.

Provider SDKs used by the orchestrators (ADK's AnthropicLlm/Gemini,
LangChain clients) read credentials from process env vars. The export
OVERWRITES the env from this settings instance: the key fields bind
only via their env alias (real env vars are the highest-priority
source), so settings and env diverge only when the process env
changed after this instance loaded — e.g. an earlier manager's
export, or a key loaded from a class-specific env_file — and then
this instance's configured value must win. (The previous
set-if-absent guard let the first exporting manager pin credentials
for every later one.) A key unset in settings leaves the environment
untouched. Credentials are process-global; SettingsContext does not
isolate them.
"""
import os

if self.google_api_key and not os.environ.get("GOOGLE_API_KEY"):
if self.google_api_key:
os.environ["GOOGLE_API_KEY"] = self.google_api_key

if self.anthropic_api_key and not os.environ.get("ANTHROPIC_API_KEY"):
if self.anthropic_api_key:
os.environ["ANTHROPIC_API_KEY"] = self.anthropic_api_key
35 changes: 35 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,41 @@ def test_export_api_keys_to_env(self, temp_workspace: Path):
else:
os.environ.pop("GOOGLE_API_KEY", None)

def test_export_overwrites_stale_env_key(self, temp_workspace: Path, monkeypatch):
"""Export re-asserts this instance's key over a mutated env var.

The key fields bind only via their env alias, so settings and env
diverge when the process env changed after this instance loaded —
exactly what an earlier manager's export does in a two-settings
process. The previous set-if-absent guard then silently kept the
first manager's credentials for every later one.
"""
monkeypatch.setenv("ANTHROPIC_API_KEY", "current-key")
settings = BaseSettings(workspace_dir=temp_workspace)
assert settings.anthropic_api_key == "current-key"

# Another settings instance exported its key in the meantime
monkeypatch.setenv("ANTHROPIC_API_KEY", "stale-key")

settings.export_api_keys_to_env()

assert os.environ["ANTHROPIC_API_KEY"] == "current-key"

def test_export_leaves_env_when_setting_unset(
self, temp_workspace: Path, monkeypatch, tmp_path: Path
):
"""A key unset in settings never clobbers an externally-set env var."""
monkeypatch.setenv("HOME", str(tmp_path)) # no user config leakage
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
settings = BaseSettings(workspace_dir=temp_workspace)
assert settings.google_api_key is None

monkeypatch.setenv("GOOGLE_API_KEY", "external-key")

settings.export_api_keys_to_env()

assert os.environ["GOOGLE_API_KEY"] == "external-key"


class TestSettingsContext:
"""Tests for context-based settings management."""
Expand Down
Loading