refactor(persistence): unify SQLite to single deerflow.db and move checkpointer to runtime

Merge checkpoints.db and app.db into a single deerflow.db file (WAL mode
handles concurrent access safely). Move checkpointer module from
agents/checkpointer to runtime/checkpointer to better reflect its role
as a runtime infrastructure concern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
rayhpeng
2026-04-10 10:08:23 +08:00
parent 9197000690
commit 03952eca53
17 changed files with 54 additions and 43 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import deerflow.config.app_config as app_config_module
from deerflow.agents.checkpointer import get_checkpointer, reset_checkpointer
from deerflow.runtime.checkpointer import get_checkpointer, reset_checkpointer
from deerflow.config.checkpointer_config import (
CheckpointerConfig,
get_checkpointer_config,
@@ -78,7 +78,7 @@ class TestGetCheckpointer:
"""get_checkpointer should return InMemorySaver when not configured."""
from langgraph.checkpoint.memory import InMemorySaver
with patch("deerflow.agents.checkpointer.provider.get_app_config", side_effect=FileNotFoundError):
with patch("deerflow.runtime.checkpointer.provider.get_app_config", side_effect=FileNotFoundError):
cp = get_checkpointer()
assert cp is not None
assert isinstance(cp, InMemorySaver)
+4 -4
View File
@@ -12,14 +12,14 @@ class TestCheckpointerNoneFix:
@pytest.mark.anyio
async def test_async_make_checkpointer_returns_in_memory_saver_when_not_configured(self):
"""make_checkpointer should return InMemorySaver when config.checkpointer is None."""
from deerflow.agents.checkpointer.async_provider import make_checkpointer
from deerflow.runtime.checkpointer.async_provider import make_checkpointer
# Mock get_app_config to return a config with checkpointer=None and database=None
mock_config = MagicMock()
mock_config.checkpointer = None
mock_config.database = None
with patch("deerflow.agents.checkpointer.async_provider.get_app_config", return_value=mock_config):
with patch("deerflow.runtime.checkpointer.async_provider.get_app_config", return_value=mock_config):
async with make_checkpointer() as checkpointer:
# Should return InMemorySaver, not None
assert checkpointer is not None
@@ -36,13 +36,13 @@ class TestCheckpointerNoneFix:
def test_sync_checkpointer_context_returns_in_memory_saver_when_not_configured(self):
"""checkpointer_context should return InMemorySaver when config.checkpointer is None."""
from deerflow.agents.checkpointer.provider import checkpointer_context
from deerflow.runtime.checkpointer.provider import checkpointer_context
# Mock get_app_config to return a config with checkpointer=None
mock_config = MagicMock()
mock_config.checkpointer = None
with patch("deerflow.agents.checkpointer.provider.get_app_config", return_value=mock_config):
with patch("deerflow.runtime.checkpointer.provider.get_app_config", return_value=mock_config):
with checkpointer_context() as checkpointer:
# Should return InMemorySaver, not None
assert checkpointer is not None
+7 -7
View File
@@ -817,7 +817,7 @@ class TestEnsureAgent:
patch("deerflow.client._build_middlewares", return_value=[]) as mock_build_middlewares,
patch("deerflow.client.apply_prompt_template", return_value="prompt") as mock_apply_prompt,
patch.object(client, "_get_tools", return_value=[]),
patch("deerflow.agents.checkpointer.get_checkpointer", return_value=MagicMock()),
patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()),
):
client._agent_name = "custom-agent"
client._available_skills = {"test_skill"}
@@ -842,7 +842,7 @@ class TestEnsureAgent:
patch("deerflow.client._build_middlewares", return_value=[]),
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
patch.object(client, "_get_tools", return_value=[]),
patch("deerflow.agents.checkpointer.get_checkpointer", return_value=mock_checkpointer),
patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=mock_checkpointer),
):
client._ensure_agent(config)
@@ -867,7 +867,7 @@ class TestEnsureAgent:
patch("deerflow.client._build_middlewares", side_effect=fake_build_middlewares),
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
patch.object(client, "_get_tools", return_value=[]),
patch("deerflow.agents.checkpointer.get_checkpointer", return_value=MagicMock()),
patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()),
):
client._ensure_agent(config)
@@ -886,7 +886,7 @@ class TestEnsureAgent:
patch("deerflow.client._build_middlewares", return_value=[]),
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
patch.object(client, "_get_tools", return_value=[]),
patch("deerflow.agents.checkpointer.get_checkpointer", return_value=None),
patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=None),
):
client._ensure_agent(config)
@@ -1844,7 +1844,7 @@ class TestScenarioAgentRecreation:
patch("deerflow.client._build_middlewares", return_value=[]),
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
patch.object(client, "_get_tools", return_value=[]),
patch("deerflow.agents.checkpointer.get_checkpointer", return_value=MagicMock()),
patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()),
):
client._ensure_agent(config_a)
first_agent = client._agent
@@ -1872,7 +1872,7 @@ class TestScenarioAgentRecreation:
patch("deerflow.client._build_middlewares", return_value=[]),
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
patch.object(client, "_get_tools", return_value=[]),
patch("deerflow.agents.checkpointer.get_checkpointer", return_value=MagicMock()),
patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()),
):
client._ensure_agent(config)
client._ensure_agent(config)
@@ -1897,7 +1897,7 @@ class TestScenarioAgentRecreation:
patch("deerflow.client._build_middlewares", return_value=[]),
patch("deerflow.client.apply_prompt_template", return_value="prompt"),
patch.object(client, "_get_tools", return_value=[]),
patch("deerflow.agents.checkpointer.get_checkpointer", return_value=MagicMock()),
patch("deerflow.runtime.checkpointer.get_checkpointer", return_value=MagicMock()),
):
client._ensure_agent(config)
client.reset_agent()
+7 -6
View File
@@ -24,18 +24,19 @@ class TestDatabaseConfig:
assert c.backend == "memory"
assert c.pool_size == 5
def test_sqlite_paths_are_different(self):
def test_sqlite_paths_unified(self):
c = DatabaseConfig(backend="sqlite", sqlite_dir="./mydata")
assert c.checkpointer_sqlite_path.endswith("checkpoints.db")
assert c.app_sqlite_path.endswith("app.db")
assert "mydata" in c.checkpointer_sqlite_path
assert c.checkpointer_sqlite_path != c.app_sqlite_path
assert c.sqlite_path.endswith("deerflow.db")
assert "mydata" in c.sqlite_path
# Backward-compatible aliases point to the same file
assert c.checkpointer_sqlite_path == c.sqlite_path
assert c.app_sqlite_path == c.sqlite_path
def test_app_sqlalchemy_url_sqlite(self):
c = DatabaseConfig(backend="sqlite", sqlite_dir="./data")
url = c.app_sqlalchemy_url
assert url.startswith("sqlite+aiosqlite:///")
assert "app.db" in url
assert "deerflow.db" in url
def test_app_sqlalchemy_url_postgres(self):
c = DatabaseConfig(