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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dependencies = [
"jsonref>=1.1.0,<2",
"temporalio>=1.26.0,<2",
"aiohttp>=3.10.10,<4",
"redis>=5.2.0,<6",
"redis>=5.2.0,<8",
"litellm>=1.83.7,<2",
"kubernetes>=25.0.0,<36.0.0",
"jinja2>=3.1.3,<4",
Expand Down
21 changes: 19 additions & 2 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,13 @@ frozenlist==1.8.0
# via aiosignal
fsspec==2026.3.0
# via huggingface-hub
genai-prices==0.0.61
# via pydantic-ai-slim
google-auth==2.49.1
# via kubernetes
griffelib==2.0.2
# via openai-agents
# via pydantic-ai-slim
h11==0.16.0
# via httpcore
# via uvicorn
Expand All @@ -126,12 +129,15 @@ httpcore==1.0.9
httpx==0.28.1
# via agentex-sdk
# via anthropic
# via genai-prices
# via httpx-aiohttp
# via huggingface-hub
# via langsmith
# via litellm
# via mcp
# via openai
# via pydantic-ai-slim
# via pydantic-graph
# via respx
# via scale-gp
# via scale-gp-beta
Expand Down Expand Up @@ -196,6 +202,8 @@ langsmith==0.7.22
# via langchain-core
litellm==1.83.7
# via agentex-sdk
logfire-api==4.33.0
# via pydantic-graph
markdown-it-py==3.0.0
# via rich
markupsafe==3.0.3
Expand Down Expand Up @@ -236,6 +244,7 @@ opentelemetry-api==1.40.0
# via ddtrace
# via opentelemetry-sdk
# via opentelemetry-semantic-conventions
# via pydantic-ai-slim
opentelemetry-sdk==1.40.0
# via agentex-sdk
opentelemetry-semantic-conventions==0.61b0
Expand Down Expand Up @@ -287,18 +296,25 @@ pydantic==2.12.5
# via agentex-sdk
# via anthropic
# via fastapi
# via genai-prices
# via langchain-core
# via langsmith
# via litellm
# via mcp
# via openai
# via openai-agents
# via pydantic-ai-slim
# via pydantic-graph
# via pydantic-settings
# via python-on-whales
# via scale-gp
# via scale-gp-beta
pydantic-ai-slim==1.101.0
# via agentex-sdk
pydantic-core==2.41.5
# via pydantic
pydantic-graph==1.101.0
# via pydantic-ai-slim
pydantic-settings==2.13.1
# via mcp
pygments==2.19.2
Expand All @@ -308,7 +324,6 @@ pygments==2.19.2
# via rich
pyjwt==2.12.1
# via mcp
# via redis
pyright==1.1.399
pytest==8.4.2
# via agentex-sdk
Expand Down Expand Up @@ -339,7 +354,7 @@ pyzmq==27.1.0
# via jupyter-client
questionary==2.1.1
# via agentex-sdk
redis==5.3.1
redis==7.4.0
# via agentex-sdk
referencing==0.37.0
# via jsonschema
Expand Down Expand Up @@ -459,6 +474,8 @@ typing-inspection==0.4.2
# via fastapi
# via mcp
# via pydantic
# via pydantic-ai-slim
# via pydantic-graph
# via pydantic-settings
tzdata==2025.3
# via agentex-sdk
Expand Down
21 changes: 19 additions & 2 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,13 @@ frozenlist==1.8.0
# via aiosignal
fsspec==2026.3.0
# via huggingface-hub
genai-prices==0.0.61
# via pydantic-ai-slim
google-auth==2.49.1
# via kubernetes
griffelib==2.0.2
# via openai-agents
# via pydantic-ai-slim
h11==0.16.0
# via httpcore
# via uvicorn
Expand All @@ -113,12 +116,15 @@ httpcore==1.0.9
httpx==0.28.1
# via agentex-sdk
# via anthropic
# via genai-prices
# via httpx-aiohttp
# via huggingface-hub
# via langsmith
# via litellm
# via mcp
# via openai
# via pydantic-ai-slim
# via pydantic-graph
# via scale-gp
# via scale-gp-beta
httpx-aiohttp==0.1.12
Expand Down Expand Up @@ -180,6 +186,8 @@ langsmith==0.7.22
# via langchain-core
litellm==1.83.7
# via agentex-sdk
logfire-api==4.33.0
# via pydantic-graph
markdown-it-py==4.0.0
# via rich
markupsafe==3.0.3
Expand Down Expand Up @@ -214,6 +222,7 @@ opentelemetry-api==1.40.0
# via ddtrace
# via opentelemetry-sdk
# via opentelemetry-semantic-conventions
# via pydantic-ai-slim
opentelemetry-sdk==1.40.0
# via agentex-sdk
opentelemetry-semantic-conventions==0.61b0
Expand Down Expand Up @@ -260,18 +269,25 @@ pydantic==2.12.5
# via agentex-sdk
# via anthropic
# via fastapi
# via genai-prices
# via langchain-core
# via langsmith
# via litellm
# via mcp
# via openai
# via openai-agents
# via pydantic-ai-slim
# via pydantic-graph
# via pydantic-settings
# via python-on-whales
# via scale-gp
# via scale-gp-beta
pydantic-ai-slim==1.101.0
# via agentex-sdk
pydantic-core==2.41.5
# via pydantic
pydantic-graph==1.101.0
# via pydantic-ai-slim
pydantic-settings==2.13.1
# via mcp
pygments==2.20.0
Expand All @@ -281,7 +297,6 @@ pygments==2.20.0
# via rich
pyjwt==2.12.1
# via mcp
# via redis
pytest==9.0.2
# via agentex-sdk
# via pytest-asyncio
Expand All @@ -308,7 +323,7 @@ pyzmq==27.1.0
# via jupyter-client
questionary==2.1.1
# via agentex-sdk
redis==5.3.1
redis==7.4.0
# via agentex-sdk
referencing==0.37.0
# via jsonschema
Expand Down Expand Up @@ -424,6 +439,8 @@ typing-inspection==0.4.2
# via fastapi
# via mcp
# via pydantic
# via pydantic-ai-slim
# via pydantic-graph
# via pydantic-settings
tzdata==2025.3
# via agentex-sdk
Expand Down
11 changes: 10 additions & 1 deletion src/agentex/lib/cli/handlers/run_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,13 +365,22 @@ def create_agent_environment(manifest: AgentManifest) -> dict[str, str]:
env_vars = {
"ENVIRONMENT": "development",
"TEMPORAL_ADDRESS": "localhost:7233",
"REDIS_URL": "redis://localhost:6379",
"AGENT_NAME": manifest.agent.name,
"ACP_TYPE": manifest.agent.acp_type,
"ACP_URL": f"http://{manifest.local_development.agent.host_address}", # type: ignore[union-attr]
"ACP_PORT": str(manifest.local_development.agent.port), # type: ignore[union-attr]
}

# Gate the default REDIS_URL on an explicit manifest flag. Agents that don't use
# adk.messages/adk.streaming can set local_development.redis_enabled: false to avoid
# the localhost:6379 default, which otherwise causes silent request hangs when no
# local Redis is reachable (MAY-1086). When opting out, also drop any REDIS_URL the
# parent shell may have exported, so the opt-out actually guarantees a clean env.
if manifest.local_development.redis_enabled: # type: ignore[union-attr]
env_vars["REDIS_URL"] = "redis://localhost:6379"
else:
env.pop("REDIS_URL", None)

if manifest.agent.agent_input_type:
env_vars["AGENT_INPUT_TYPE"] = manifest.agent.agent_input_type

Expand Down
9 changes: 9 additions & 0 deletions src/agentex/lib/sdk/config/local_development_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@ class LocalDevelopmentConfig(BaseModel):
paths: LocalPathsConfig | None = Field(
default=None, description="File paths for local development"
)
redis_enabled: bool = Field(
default=True,
description=(
"Whether the local CLI should set REDIS_URL=redis://localhost:6379 for the "
"agent process. Set to false for agents that don't use adk.messages/adk.streaming "
"when no local Redis is available, to avoid silent request hangs from the lazy "
"Redis client."
),
)
71 changes: 71 additions & 0 deletions tests/lib/cli/test_run_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Tests for run_handlers.create_agent_environment — REDIS_URL gating (MAY-1086)."""

from __future__ import annotations

from pathlib import Path

import pytest

from agentex.lib.cli.handlers.run_handlers import create_agent_environment
from agentex.lib.sdk.config.agent_manifest import AgentManifest

_MANIFEST_TEMPLATE = """\
build:
context:
root: .
dockerfile: Dockerfile

local_development:
agent:
port: 8000
host_address: host.docker.internal
{redis_line}

agent:
acp_type: async
name: test-agent
description: Fixture manifest for run_handlers tests.
"""


def _write_manifest(tmp_path: Path, redis_enabled: bool | None) -> AgentManifest:
"""Write a minimal manifest with the requested redis_enabled value (or omit for default)."""
redis_line = "" if redis_enabled is None else f" redis_enabled: {str(redis_enabled).lower()}"
manifest_path = tmp_path / "manifest.yaml"
manifest_path.write_text(_MANIFEST_TEMPLATE.format(redis_line=redis_line))
return AgentManifest.from_yaml(file_path=str(manifest_path))


class TestCreateAgentEnvironmentRedisGating:
def test_default_seeds_redis_url(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
"""With redis_enabled unset (default true), CLI seeds the localhost REDIS_URL."""
monkeypatch.delenv("REDIS_URL", raising=False)
manifest = _write_manifest(tmp_path, redis_enabled=None)
assert manifest.local_development is not None
assert manifest.local_development.redis_enabled is True

env = create_agent_environment(manifest)

assert env.get("REDIS_URL") == "redis://localhost:6379"

def test_opt_out_clears_redis_url_when_parent_env_clean(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
"""With redis_enabled=false and no parent REDIS_URL, REDIS_URL is absent."""
monkeypatch.delenv("REDIS_URL", raising=False)
manifest = _write_manifest(tmp_path, redis_enabled=False)

env = create_agent_environment(manifest)

assert "REDIS_URL" not in env

def test_opt_out_clears_redis_url_when_parent_env_has_one(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
"""With redis_enabled=false, a stale parent-shell REDIS_URL must not leak through."""
monkeypatch.setenv("REDIS_URL", "redis://leftover.from.parent.shell:6379")
manifest = _write_manifest(tmp_path, redis_enabled=False)

env = create_agent_environment(manifest)

assert "REDIS_URL" not in env
Loading