diff --git a/backend/app/gateway/app.py b/backend/app/gateway/app.py index 80de83bcc..6b1638a73 100644 --- a/backend/app/gateway/app.py +++ b/backend/app/gateway/app.py @@ -145,12 +145,10 @@ async def _migrate_orphaned_threads(store, admin_user_id: str) -> int: async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: """Application lifespan handler.""" - # Load config and check necessary environment variables at startup. - # Phase 2: explicit-passing primitive. app.state.config is the single source - # of truth for FastAPI request handlers via Depends(get_config). AppConfig.init() - # is still invoked for backward compatibility with legacy AppConfig.current() - # callers that haven't been migrated yet. try: + # app.state.config is the source of truth for Depends(get_config). + # AppConfig.init() mirrors it to the process-global for not-yet-migrated + # callers; both go away in P2-10 once AppConfig.current() is removed. app.state.config = AppConfig.from_file() AppConfig.init(app.state.config) logger.info("Configuration loaded successfully") diff --git a/backend/packages/harness/deerflow/agents/lead_agent/agent.py b/backend/packages/harness/deerflow/agents/lead_agent/agent.py index f7cd89a7e..e85a6aad6 100644 --- a/backend/packages/harness/deerflow/agents/lead_agent/agent.py +++ b/backend/packages/harness/deerflow/agents/lead_agent/agent.py @@ -210,6 +210,7 @@ Being proactive with task management demonstrates thoroughness and ensures all r def _build_middlewares( app_config: AppConfig, config: RunnableConfig, + *, model_name: str | None, agent_name: str | None = None, custom_middlewares: list[AgentMiddleware] | None = None, @@ -297,6 +298,11 @@ def make_lead_agent( from deerflow.tools.builtins import setup_agent if app_config is None: + # LangGraph Server registers make_lead_agent via langgraph.json; its + # invocation path only hands us RunnableConfig. Until that registration + # layer owns its own AppConfig, we tolerate the process-global fallback + # here. All other entry points (DeerFlowClient, Gateway Worker) pass + # app_config explicitly. Remove alongside AppConfig.current() in P2-10. app_config = AppConfig.current() cfg = config.get("configurable", {}) diff --git a/backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py b/backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py index 1f19899fe..5ddbb4bbd 100644 --- a/backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py +++ b/backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py @@ -72,7 +72,7 @@ class ToolErrorHandlingMiddleware(AgentMiddleware[AgentState]): def _build_runtime_middlewares( *, - app_config: "AppConfig | None" = None, + app_config: "AppConfig", include_uploads: bool, include_dangling_tool_call_patch: bool, lazy_init: bool = True, @@ -81,10 +81,6 @@ def _build_runtime_middlewares( from deerflow.agents.middlewares.llm_error_handling_middleware import LLMErrorHandlingMiddleware from deerflow.agents.middlewares.thread_data_middleware import ThreadDataMiddleware from deerflow.sandbox.middleware import SandboxMiddleware - from deerflow.config.app_config import AppConfig - - if app_config is None: - app_config = AppConfig.current() middlewares: list[AgentMiddleware] = [ ThreadDataMiddleware(lazy_init=lazy_init), @@ -133,7 +129,7 @@ def _build_runtime_middlewares( return middlewares -def build_lead_runtime_middlewares(*, app_config: "AppConfig | None" = None, lazy_init: bool = True) -> list[AgentMiddleware]: +def build_lead_runtime_middlewares(*, app_config: "AppConfig", lazy_init: bool = True) -> list[AgentMiddleware]: """Middlewares shared by lead agent runtime before lead-only middlewares.""" return _build_runtime_middlewares( app_config=app_config, diff --git a/backend/packages/harness/deerflow/models/factory.py b/backend/packages/harness/deerflow/models/factory.py index aa8ab833b..f7babf669 100644 --- a/backend/packages/harness/deerflow/models/factory.py +++ b/backend/packages/harness/deerflow/models/factory.py @@ -47,7 +47,12 @@ def create_chat_model( Returns: A chat model instance. """ - config = app_config if app_config is not None else AppConfig.current() + if app_config is None: + # TODO(P2-10): fold into a required parameter once all callers + # (memory updater, summarization middleware's implicit model) thread + # config explicitly. + app_config = AppConfig.current() + config = app_config if name is None: name = config.models[0].name model_config = config.get_model_config(name) diff --git a/backend/packages/harness/deerflow/runtime/runs/worker.py b/backend/packages/harness/deerflow/runtime/runs/worker.py index 8cdebd5f4..8931f70bc 100644 --- a/backend/packages/harness/deerflow/runtime/runs/worker.py +++ b/backend/packages/harness/deerflow/runtime/runs/worker.py @@ -54,9 +54,7 @@ class RunContext: run_events_config: Any | None = field(default=None) thread_store: Any | None = field(default=None) follow_up_to_run_id: str | None = field(default=None) - # Phase 2: app-level config flows through RunContext so Worker can build - # DeerFlowContext without consulting the process-global. - app_config: Any | None = field(default=None) + app_config: AppConfig | None = field(default=None) async def run_agent( diff --git a/backend/packages/harness/deerflow/tools/tools.py b/backend/packages/harness/deerflow/tools/tools.py index 5294c07fa..463f81f9b 100644 --- a/backend/packages/harness/deerflow/tools/tools.py +++ b/backend/packages/harness/deerflow/tools/tools.py @@ -56,7 +56,11 @@ def get_available_tools( Returns: List of available tools. """ - config = app_config if app_config is not None else AppConfig.current() + if app_config is None: + # TODO(P2-10): fold into a required parameter once all callers thread + # config explicitly (community tool factories, subagent registry, etc.). + app_config = AppConfig.current() + config = app_config tool_configs = [tool for tool in config.tools if groups is None or tool.group in groups] # Do not expose host bash by default when LocalSandboxProvider is active. diff --git a/backend/tests/test_client.py b/backend/tests/test_client.py index b01502817..36ea1644d 100644 --- a/backend/tests/test_client.py +++ b/backend/tests/test_client.py @@ -85,7 +85,6 @@ class TestClientInit: DeerFlowClient(agent_name="../path/traversal") def test_custom_config_path(self, mock_app_config): - # Phase 2: DeerFlowClient stores config locally in self._app_config # rather than touching AppConfig.init() / process-global state. with patch.object(AppConfig, "from_file", return_value=mock_app_config) as mock_from_file: client = DeerFlowClient(config_path="/tmp/custom.yaml") @@ -1090,7 +1089,6 @@ class TestMcpConfig: ext_config = MagicMock() ext_config.mcp_servers = {"github": server} - # Phase 2: client reads from self._app_config, not AppConfig.current() client._app_config = MagicMock(extensions=ext_config) result = client.get_mcp_config() @@ -1116,7 +1114,6 @@ class TestMcpConfig: # Pre-set agent to verify it gets invalidated client._agent = MagicMock() - # Phase 2: initial config is stored on the client, not process-global client._app_config = MagicMock(extensions=current_config) with ( @@ -1330,7 +1327,6 @@ class TestMemoryManagement: app_cfg = MagicMock() app_cfg.memory = mem_config - # Phase 2: client reads from self._app_config client._app_config = app_cfg result = client.get_memory_config() @@ -1351,7 +1347,6 @@ class TestMemoryManagement: app_cfg.memory = mem_config data = {"version": "1.0", "facts": []} - # Phase 2: client reads from self._app_config client._app_config = app_cfg with patch("deerflow.agents.memory.updater.get_memory_data", return_value=data): result = client.get_memory_status() @@ -2030,7 +2025,6 @@ class TestScenarioMemoryWorkflow: app_cfg = MagicMock() app_cfg.memory = config - # Phase 2: client reads from self._app_config client._app_config = app_cfg with patch("deerflow.agents.memory.updater.get_memory_data", return_value=updated_data): status = client.get_memory_status() @@ -2315,7 +2309,6 @@ class TestGatewayConformance: ext_config = MagicMock() ext_config.mcp_servers = {"test": server} - # Phase 2: client reads from self._app_config client._app_config = MagicMock(extensions=ext_config) result = client.get_mcp_config() @@ -2341,7 +2334,6 @@ class TestGatewayConformance: config_file = tmp_path / "extensions_config.json" config_file.write_text("{}") - # Phase 2: client reads from self._app_config client._app_config = MagicMock(extensions=ext_config) with ( patch("deerflow.client.ExtensionsConfig.resolve_config_path", return_value=config_file), @@ -2379,7 +2371,6 @@ class TestGatewayConformance: app_cfg = MagicMock() app_cfg.memory = mem_cfg - # Phase 2: client reads from self._app_config client._app_config = app_cfg result = client.get_memory_config() @@ -2415,7 +2406,6 @@ class TestGatewayConformance: "facts": [], } - # Phase 2: client reads from self._app_config client._app_config = app_cfg with patch("deerflow.agents.memory.updater.get_memory_data", return_value=memory_data): result = client.get_memory_status()