Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
80 changes: 62 additions & 18 deletions libs/code/deepagents_code/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from langgraph.store.base import BaseStore
from langgraph.types import Command

from deepagents_code.config import CredentialsSnapshot, ModelResult
from deepagents_code.extensions.registry import ExtensionRegistry
from deepagents_code.mcp_tools import MCPServerInfo
from deepagents_code.output import OutputFormat
Expand Down Expand Up @@ -1524,6 +1525,8 @@ def get_system_prompt(
interactive: bool = True,
cwd: str | Path | None = None,
fs_tools: list[FsToolName] | None = None,
has_tavily: bool | None = None,
model_result: ModelResult | None = None,
) -> str:
"""Get the base system prompt for the agent.

Expand All @@ -1542,6 +1545,8 @@ def get_system_prompt(
cwd: Override the working directory shown in the prompt.
fs_tools: Filesystem tool allowlist. Restricted prompts omit guidance
for unavailable tools; `None` retains all guidance.
has_tavily: Workspace credential availability override.
model_result: Workspace model metadata override.

Returns:
The system prompt string
Expand Down Expand Up @@ -1596,15 +1601,28 @@ def get_system_prompt(
)

model_identity_section = build_model_identity_section(
runtime_state.model_name,
provider=runtime_state.model_provider,
context_limit=runtime_state.model_context_limit,
unsupported_modalities=runtime_state.model_unsupported_modalities,
model_result.model_name
if model_result is not None
else runtime_state.model_name,
provider=(
model_result.provider
if model_result is not None
else runtime_state.model_provider
),
context_limit=(
model_result.context_limit
if model_result is not None
else runtime_state.model_context_limit
),
unsupported_modalities=(
model_result.unsupported_modalities
if model_result is not None
else runtime_state.model_unsupported_modalities
),
)
filesystem_tool_guidance = _build_fs_tool_prompt_guidance(fs_tools)
web_search_tool_guidance = (
_WEB_SEARCH_TOOL_GUIDANCE if credentials.has_tavily else ""
)
tavily_available = credentials.has_tavily if has_tavily is None else has_tavily
web_search_tool_guidance = _WEB_SEARCH_TOOL_GUIDANCE if tavily_available else ""

# Build working directory section (local vs sandbox)
if sandbox_type:
Expand Down Expand Up @@ -2404,6 +2422,9 @@ def create_cli_agent(
summarization_model: str | None = None,
enforce_model_policy: bool = True,
extension_registry: ExtensionRegistry | None = None,
environ: Mapping[str, str] | None = None,
credentials_snapshot: CredentialsSnapshot | None = None,
model_result: ModelResult | None = None,
) -> tuple[Pregel[Any, Any, Any, Any], CompositeBackend]:
"""Create a CLI-configured agent with flexible options.

Expand Down Expand Up @@ -2574,6 +2595,9 @@ def create_cli_agent(
listing rather than raising. Any caller that can run the graph must
leave this `True`.
extension_registry: Server-owned Python extension registrations.
environ: Environment snapshot frozen into local shell execution.
credentials_snapshot: Credentials resolved from `environ` for this runtime.
model_result: Workspace model metadata used in the generated prompt.

Returns:
2-tuple of `(agent_graph, backend)`
Expand All @@ -2594,10 +2618,15 @@ def create_cli_agent(
a prebuilt `BaseChatModel` came from a path that already checked.
""" # noqa: DOC502 - propagates from `ModelConfig.require_model_allowed`
tools = list(tools or [])
environment = os.environ if environ is None else environ
runtime_credentials = (
credentials if credentials_snapshot is None else credentials_snapshot
)
if extension_registry is not None:
from deepagents_code._env_vars import EXPERIMENTAL, is_env_truthy
from deepagents_code._env_vars import EXPERIMENTAL, classify_env_bool

if not is_env_truthy(EXPERIMENTAL):
experimental = classify_env_bool(environment.get(EXPERIMENTAL, "")) is True
if not experimental:
extension_registry = None
mcp_tools = tuple(mcp_tools or ())
if auto_mode_enabled and sandbox is not None:
Expand Down Expand Up @@ -2655,7 +2684,7 @@ def create_cli_agent(
project_agents_dir = (
project_context.project_agents_dir()
if project_context is not None
else get_project_agents_dir(credentials.project_root)
else get_project_agents_dir(runtime_credentials.project_root)
)

def _subagent_cli_middleware(
Expand All @@ -2672,6 +2701,7 @@ def _subagent_cli_middleware(
ConfigurableModelMiddleware(
persist_model_state=False,
cli_max_retries=cli_max_retries,
environ=environment,
)
)
# Checkpoint nested spend before HITL can pause the subgraph, then hand
Expand Down Expand Up @@ -2821,7 +2851,11 @@ def _subagent_cli_middleware(

# Build middleware stack based on enabled features
agent_middleware: list[AgentMiddleware[Any, Any]] = [
ConfigurableModelMiddleware(cli_max_retries=cli_max_retries),
ConfigurableModelMiddleware(
cli_max_retries=cli_max_retries,
environ=environment,
model_result=model_result,
),
]
if not interactive:
agent_middleware.append(_GlmTerminalStallRecovery())
Expand Down Expand Up @@ -2873,7 +2907,7 @@ def _subagent_cli_middleware(
project_agent_md_paths = (
project_context.project_agent_md_paths()
if project_context is not None
else get_project_agent_md_path(credentials.project_root)
else get_project_agent_md_path(runtime_credentials.project_root)
)
memory_sources.extend(str(p) for p in project_agent_md_paths)

Expand Down Expand Up @@ -2928,14 +2962,17 @@ def _subagent_cli_middleware(
# separately. When they had none, drop the agent's override (the
# `deepagents-code` default applied at bootstrap) entirely so shell
# commands don't inherit it.
shell_env = os.environ.copy()
shell_env = dict(environment)
shell_env["GIT_TERMINAL_PROMPT"] = "0"
if credentials.user_langchain_project is not None:
shell_env["LANGSMITH_PROJECT"] = credentials.user_langchain_project
if runtime_credentials.user_langchain_project is not None:
shell_env["LANGSMITH_PROJECT"] = (
runtime_credentials.user_langchain_project
)
else:
shell_env.pop("LANGSMITH_PROJECT", None)
restore_user_tracing_env(shell_env)
restore_user_tracing_api_keys(shell_env)
if environ is None:
restore_user_tracing_env(shell_env)
restore_user_tracing_api_keys(shell_env)
# Re-apply a launch-time PYTHONPATH that was stripped from the server
# interpreter but relayed for approval-gated `execute` commands.
_apply_inherited_pythonpath(shell_env)
Expand Down Expand Up @@ -3010,7 +3047,7 @@ def _subagent_cli_middleware(
backend=backend,
mcp_server_info=mcp_server_info,
tracing_project=get_langsmith_project_name(),
user_tracing_project=credentials.user_langchain_project,
user_tracing_project=runtime_credentials.user_langchain_project,
)
)

Expand All @@ -3026,6 +3063,8 @@ def _subagent_cli_middleware(
interactive=interactive,
cwd=effective_cwd,
fs_tools=fs_tools,
has_tavily=runtime_credentials.has_tavily,
model_result=model_result,
)

interrupt_on: dict[str, bool | InterruptOnConfig] = {}
Expand Down Expand Up @@ -3117,6 +3156,7 @@ def _subagent_cli_middleware(
composite_backend,
cli_max_retries=cli_max_retries,
summarization_model_spec=summarization_model,
environ=environment,
)
if auto_mode_config is not None and resolved_interrupt_on is not None:
from deepagents_code.auto_mode import AutoModeHITLMiddleware
Expand All @@ -3139,6 +3179,7 @@ def _subagent_cli_middleware(
shell_allow_list=narrow_allow_list,
classifier_model=classifier_model,
cli_max_retries=cli_max_retries,
environ=environment,
classifier_timeout_seconds=resolve_auto_classifier_timeout(),
trusted_ask_user_tool=trusted_ask_user_tool,
trusted_compaction_tool=compaction_middleware.tools[0],
Expand Down Expand Up @@ -3234,11 +3275,13 @@ def _subagent_cli_middleware(
fs_tools=fs_tools,
model_retries=model_retries,
cli_max_retries=cli_max_retries,
environ=environment,
)
criteria_fallback_agent = create_goal_criteria_fallback_agent(
model=model,
model_retries=model_retries,
cli_max_retries=cli_max_retries,
environ=environment,
)
agent_middleware.append(
GoalCriteriaMiddleware(criteria_agent, criteria_fallback_agent)
Expand Down Expand Up @@ -3304,6 +3347,7 @@ def _subagent_cli_middleware(
persist_model_state=False,
cli_max_retries=cli_max_retries,
strict_model_resolution=True,
environ=environment,
),
# Both clients filter this nested message stream. A transient fault can
# safely retry the failed model node without replaying grader tools.
Expand Down
24 changes: 16 additions & 8 deletions libs/code/deepagents_code/auto_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,9 +610,11 @@ def _redact_remote(value: str) -> str:
return _CONTROL_RE.sub("", value)[:2000]


def _known_credential_values() -> tuple[str, ...]:
def _known_credential_values(
environ: Mapping[str, str] | None = None,
) -> tuple[str, ...]:
values: set[str] = set()
for name, value in os.environ.items():
for name, value in (os.environ if environ is None else environ).items():
if _SECRET_KEY_RE.search(name) and len(value) >= _MIN_SECRET_LENGTH:
values.add(value)
try:
Expand Down Expand Up @@ -2048,6 +2050,7 @@ def __init__(
),
classifier_model: str | BaseChatModel | None = None,
cli_max_retries: int | None = None,
environ: Mapping[str, str] | None = None,
trusted_ask_user_tool: BaseTool | None = None,
trusted_compaction_tool: BaseTool | None = None,
) -> None:
Expand All @@ -2069,6 +2072,7 @@ def __init__(
`classifier_model` on the runtime context wins over this value.
cli_max_retries: Explicit `--max-retries` value to retain when a
distinct classifier model is constructed.
environ: Workspace environment retained for lazy model construction.
trusted_ask_user_tool: Built-in tool allowed to create consent receipts.
trusted_compaction_tool: Built-in tool that performs conversation
compaction.
Expand Down Expand Up @@ -2129,12 +2133,13 @@ def __init__(
)
self._configured_classifier_model = classifier_model
self._cli_max_retries = cli_max_retries
self._environ = environ
self._classifier_model_cache: OrderedDict[str, BaseChatModel] = OrderedDict()
self._classifier_model_lock = asyncio.Lock()
self._classifier_model_constructions: dict[
str, asyncio.Task[BaseChatModel]
] = {}
self._known_secrets = _known_credential_values()
self._known_secrets = _known_credential_values(environ)
self._trusted_ask_user_tool = trusted_ask_user_tool
self._trusted_compaction_tool = trusted_compaction_tool
self._emitted_events: OrderedDict[str, set[tuple[str, ...]]] = OrderedDict()
Expand Down Expand Up @@ -2503,11 +2508,14 @@ async def _construct_classifier_model(self, selected: str) -> BaseChatModel:
if self._cli_max_retries is not None
else {}
)
result = await asyncio.to_thread(
create_model,
selected,
**retry_kwargs,
)
from deepagents_code.config import use_environment

with use_environment(self._environ):
result = await asyncio.to_thread(
create_model,
selected,
**retry_kwargs,
)
except asyncio.CancelledError:
raise
except Exception as exc:
Expand Down
5 changes: 4 additions & 1 deletion libs/code/deepagents_code/client/launch/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
PATHS,
export_profile_env,
)
from deepagents_code.config import _INHERITED_PYTHONPATH_ENV
from deepagents_code.config import _INHERITED_PYTHONPATH_ENV, _dotenv_loaded_values

if TYPE_CHECKING:
from collections.abc import Callable, Iterator, Mapping
Expand Down Expand Up @@ -401,6 +401,9 @@ def _build_server_env() -> dict[str, str]:
Environment dict for `subprocess.Popen`.
"""
env = os.environ.copy()
for key, value in _dotenv_loaded_values.items():
if env.get(key) == value:
env.pop(key)
export_profile_env(env)
env["PYTHONDONTWRITEBYTECODE"] = "1"
env["LANGGRAPH_AUTH_TYPE"] = "noop"
Expand Down
Loading
Loading