diff --git a/backend/packages/harness/deerflow/config/app_config.py b/backend/packages/harness/deerflow/config/app_config.py index c190d419c..5e31c3e37 100644 --- a/backend/packages/harness/deerflow/config/app_config.py +++ b/backend/packages/harness/deerflow/config/app_config.py @@ -33,6 +33,12 @@ load_dotenv() logger = logging.getLogger(__name__) +CONFIG_FILE_DATABASE_DEFAULTS = { + "backend": "sqlite", + "sqlite_dir": ".deer-flow/data", +} + + class CircuitBreakerConfig(BaseModel): """Configuration for the LLM Circuit Breaker.""" @@ -118,6 +124,7 @@ class AppConfig(BaseModel): cls._check_config_version(config_data, resolved_path) config_data = cls.resolve_env_variables(config_data) + cls._apply_database_defaults(config_data) # Load title config if present if "title" in config_data: @@ -169,6 +176,18 @@ class AppConfig(BaseModel): result = cls.model_validate(config_data) return result + @classmethod + def _apply_database_defaults(cls, config_data: dict[str, Any]) -> None: + """Apply config.yaml defaults for persistence when the section is absent.""" + database_config = config_data.get("database") + if database_config is None: + database_config = {} + config_data["database"] = database_config + if not isinstance(database_config, dict): + return + for key, value in CONFIG_FILE_DATABASE_DEFAULTS.items(): + database_config.setdefault(key, value) + @classmethod def _check_config_version(cls, config_data: dict, config_path: Path) -> None: """Check if the user's config.yaml is outdated compared to config.example.yaml. diff --git a/backend/tests/test_app_config_reload.py b/backend/tests/test_app_config_reload.py index 9e865f142..31e571afe 100644 --- a/backend/tests/test_app_config_reload.py +++ b/backend/tests/test_app_config_reload.py @@ -7,7 +7,7 @@ from pathlib import Path import yaml from deerflow.config.agents_api_config import get_agents_api_config -from deerflow.config.app_config import get_app_config, reset_app_config +from deerflow.config.app_config import AppConfig, get_app_config, reset_app_config def _write_config(path: Path, *, model_name: str, supports_thinking: bool) -> None: @@ -57,6 +57,42 @@ def _write_extensions_config(path: Path) -> None: path.write_text(json.dumps({"mcpServers": {}, "skills": {}}), encoding="utf-8") +def test_app_config_defaults_missing_database_to_sqlite(tmp_path, monkeypatch): + config_path = tmp_path / "config.yaml" + extensions_path = tmp_path / "extensions_config.json" + _write_extensions_config(extensions_path) + _write_config(config_path, model_name="first-model", supports_thinking=False) + + monkeypatch.setenv("DEER_FLOW_EXTENSIONS_CONFIG_PATH", str(extensions_path)) + + config = AppConfig.from_file(str(config_path)) + + assert config.database.backend == "sqlite" + assert config.database.sqlite_dir == ".deer-flow/data" + + +def test_app_config_defaults_empty_database_to_sqlite(tmp_path, monkeypatch): + config_path = tmp_path / "config.yaml" + extensions_path = tmp_path / "extensions_config.json" + _write_extensions_config(extensions_path) + config_path.write_text( + yaml.safe_dump( + { + "database": {}, + "sandbox": {"use": "deerflow.sandbox.local:LocalSandboxProvider"}, + } + ), + encoding="utf-8", + ) + + monkeypatch.setenv("DEER_FLOW_EXTENSIONS_CONFIG_PATH", str(extensions_path)) + + config = AppConfig.from_file(str(config_path)) + + assert config.database.backend == "sqlite" + assert config.database.sqlite_dir == ".deer-flow/data" + + def test_get_app_config_reloads_when_file_changes(tmp_path, monkeypatch): config_path = tmp_path / "config.yaml" extensions_path = tmp_path / "extensions_config.json" diff --git a/config.example.yaml b/config.example.yaml index 366e755ed..0b72ab80f 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -816,10 +816,14 @@ skill_evolution: # Unified storage backend for LangGraph checkpointer and DeerFlow # application data (runs, threads metadata, feedback, etc.). # -# backend: memory -- No persistence, data lost on restart (default) +# backend: memory -- No persistence, data lost on restart # backend: sqlite -- Single-node deployment, files in sqlite_dir # backend: postgres -- Production multi-node deployment # +# If this section is omitted or empty in config.yaml, DeerFlow uses: +# backend: sqlite +# sqlite_dir: .deer-flow/data +# # SQLite mode uses a single deerflow.db file with WAL journal mode # for both checkpointer and application data. #