pjpjq commited on
Commit
82dbc68
·
1 Parent(s): 9c0e837

Persist state across restarts and avoid readability js crash loop

Browse files
README.md CHANGED
@@ -192,6 +192,19 @@ For dynamic websites, DeerFlow also supports the `agent_browser` tool (based on
192
  # agent-browser install --with-deps
193
  ```
194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  ## From Deep Research to Super Agent Harness
196
 
197
  DeerFlow started as a Deep Research framework — and the community ran with it. Since launch, developers have pushed it far beyond research: building data pipelines, generating slide decks, spinning up dashboards, automating content workflows. Things we never anticipated.
 
192
  # agent-browser install --with-deps
193
  ```
194
 
195
+ #### Persistent Storage (Space / External DB)
196
+
197
+ By default, local `langgraph dev` state is in-memory and will be lost after restart.
198
+
199
+ This Space startup now supports persistent state with:
200
+ - `DEER_FLOW_HOME` (defaults to `/data/deer-flow`): stores thread files, artifacts, and memory
201
+ - `DEER_FLOW_EXTENSIONS_CONFIG_PATH`: stores MCP/skills enable-state JSON
202
+ - LangGraph checkpointer:
203
+ - `LANGGRAPH_CHECKPOINT_POSTGRES_URI` (recommended external DB, PostgreSQL)
204
+ - fallback: `LANGGRAPH_CHECKPOINT_SQLITE_PATH` (SQLite file, defaults to `${DEER_FLOW_HOME}/checkpoints.sqlite`)
205
+
206
+ If you enable HF persistent storage, `/data` survives restarts, so memory/skills/thread artifacts and SQLite checkpoints persist.
207
+
208
  ## From Deep Research to Super Agent Harness
209
 
210
  DeerFlow started as a Deep Research framework — and the community ran with it. Since launch, developers have pushed it far beyond research: building data pipelines, generating slide decks, spinning up dashboards, automating content workflows. Things we never anticipated.
backend/checkpointer.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LangGraph checkpointer bootstrap for persistent thread state.
2
+
3
+ Resolution order:
4
+ 1. `LANGGRAPH_CHECKPOINT_POSTGRES_URI` -> Postgres checkpointer
5
+ 2. `LANGGRAPH_CHECKPOINT_SQLITE_PATH` -> SQLite checkpointer
6
+ 3. Fallback to `${DEER_FLOW_HOME}/checkpoints.sqlite`
7
+ """
8
+
9
+ import atexit
10
+ import os
11
+ from pathlib import Path
12
+
13
+ from langgraph.checkpoint.postgres import PostgresSaver
14
+ from langgraph.checkpoint.sqlite import SqliteSaver
15
+
16
+ _ctx = None
17
+
18
+
19
+ def _default_sqlite_path() -> str:
20
+ deer_flow_home = os.getenv("DEER_FLOW_HOME")
21
+ if deer_flow_home:
22
+ base = Path(deer_flow_home)
23
+ else:
24
+ base = Path.cwd() / ".deer-flow"
25
+ base.mkdir(parents=True, exist_ok=True)
26
+ return str((base / "checkpoints.sqlite").resolve())
27
+
28
+
29
+ def _init_checkpointer():
30
+ global _ctx
31
+
32
+ postgres_uri = os.getenv("LANGGRAPH_CHECKPOINT_POSTGRES_URI", "").strip()
33
+ if postgres_uri:
34
+ _ctx = PostgresSaver.from_conn_string(postgres_uri)
35
+ saver = _ctx.__enter__()
36
+ saver.setup()
37
+ return saver
38
+
39
+ sqlite_path = os.getenv("LANGGRAPH_CHECKPOINT_SQLITE_PATH", "").strip() or _default_sqlite_path()
40
+ sqlite_file = Path(sqlite_path).expanduser().resolve()
41
+ sqlite_file.parent.mkdir(parents=True, exist_ok=True)
42
+ _ctx = SqliteSaver.from_conn_string(str(sqlite_file))
43
+ saver = _ctx.__enter__()
44
+ saver.setup()
45
+ return saver
46
+
47
+
48
+ def _close_context() -> None:
49
+ global _ctx
50
+ if _ctx is not None:
51
+ try:
52
+ _ctx.__exit__(None, None, None)
53
+ finally:
54
+ _ctx = None
55
+
56
+
57
+ checkpointer = _init_checkpointer()
58
+ atexit.register(_close_context)
backend/docs/CONFIGURATION.md CHANGED
@@ -198,6 +198,10 @@ models:
198
  - `NOVITA_API_KEY` - Novita API key (OpenAI-compatible endpoint)
199
  - `TAVILY_API_KEY` - Tavily search API key
200
  - `DEER_FLOW_CONFIG_PATH` - Custom config file path
 
 
 
 
201
 
202
  ## Configuration Location
203
 
 
198
  - `NOVITA_API_KEY` - Novita API key (OpenAI-compatible endpoint)
199
  - `TAVILY_API_KEY` - Tavily search API key
200
  - `DEER_FLOW_CONFIG_PATH` - Custom config file path
201
+ - `DEER_FLOW_HOME` - Base directory for memory/thread files (default path root for persistence)
202
+ - `DEER_FLOW_EXTENSIONS_CONFIG_PATH` - Path to `extensions_config.json` (MCP + skills state)
203
+ - `LANGGRAPH_CHECKPOINT_POSTGRES_URI` - PostgreSQL URI for LangGraph checkpointer
204
+ - `LANGGRAPH_CHECKPOINT_SQLITE_PATH` - SQLite file path for LangGraph checkpointer
205
 
206
  ## Configuration Location
207
 
backend/langgraph.json CHANGED
@@ -4,7 +4,8 @@
4
  "."
5
  ],
6
  "env": ".env",
 
7
  "graphs": {
8
  "lead_agent": "src.agents:make_lead_agent"
9
  }
10
- }
 
4
  "."
5
  ],
6
  "env": ".env",
7
+ "checkpointer": "checkpointer:checkpointer",
8
  "graphs": {
9
  "lead_agent": "src.agents:make_lead_agent"
10
  }
11
+ }
backend/pyproject.toml CHANGED
@@ -15,6 +15,8 @@ dependencies = [
15
  "langchain-mcp-adapters>=0.1.0",
16
  "langchain-openai>=1.1.7",
17
  "langgraph>=1.0.6",
 
 
18
  "langgraph-cli[inmem]>=0.4.11",
19
  "markdownify>=1.2.2",
20
  "markitdown[all,xlsx]>=0.0.1a2",
 
15
  "langchain-mcp-adapters>=0.1.0",
16
  "langchain-openai>=1.1.7",
17
  "langgraph>=1.0.6",
18
+ "langgraph-checkpoint-postgres>=3.0.4",
19
+ "langgraph-checkpoint-sqlite>=3.0.3",
20
  "langgraph-cli[inmem]>=0.4.11",
21
  "markdownify>=1.2.2",
22
  "markitdown[all,xlsx]>=0.0.1a2",
backend/src/utils/readability.py CHANGED
@@ -1,6 +1,7 @@
1
  import logging
2
  import re
3
  import subprocess
 
4
  from urllib.parse import urljoin
5
 
6
  from markdownify import markdownify as md
@@ -9,6 +10,36 @@ from readabilipy import simple_json_from_html_string
9
  logger = logging.getLogger(__name__)
10
 
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  class Article:
13
  url: str
14
 
@@ -57,8 +88,9 @@ class Article:
57
 
58
  class ReadabilityExtractor:
59
  def extract_article(self, html: str) -> Article:
 
60
  try:
61
- article = simple_json_from_html_string(html, use_readability=True)
62
  except (subprocess.CalledProcessError, FileNotFoundError) as exc:
63
  stderr = getattr(exc, "stderr", None)
64
  if isinstance(stderr, bytes):
 
1
  import logging
2
  import re
3
  import subprocess
4
+ from functools import lru_cache
5
  from urllib.parse import urljoin
6
 
7
  from markdownify import markdownify as md
 
10
  logger = logging.getLogger(__name__)
11
 
12
 
13
+ @lru_cache(maxsize=1)
14
+ def _is_readability_js_available() -> bool:
15
+ """Check whether Node + @mozilla/readability is available in runtime."""
16
+ try:
17
+ subprocess.run(
18
+ ["node", "-e", "require.resolve('@mozilla/readability')"],
19
+ capture_output=True,
20
+ text=True,
21
+ check=True,
22
+ timeout=5,
23
+ )
24
+ return True
25
+ except FileNotFoundError:
26
+ logger.warning("Node.js is not available; readability extraction will use pure-Python mode")
27
+ return False
28
+ except subprocess.TimeoutExpired:
29
+ logger.warning("Node.js readability check timed out; readability extraction will use pure-Python mode")
30
+ return False
31
+ except subprocess.CalledProcessError as exc:
32
+ stderr_info = (exc.stderr or "").strip()
33
+ if stderr_info:
34
+ logger.warning(
35
+ "Node readability module is unavailable (%s); readability extraction will use pure-Python mode",
36
+ stderr_info,
37
+ )
38
+ else:
39
+ logger.warning("Node readability module is unavailable; readability extraction will use pure-Python mode")
40
+ return False
41
+
42
+
43
  class Article:
44
  url: str
45
 
 
88
 
89
  class ReadabilityExtractor:
90
  def extract_article(self, html: str) -> Article:
91
+ use_readability_js = _is_readability_js_available()
92
  try:
93
+ article = simple_json_from_html_string(html, use_readability=use_readability_js)
94
  except (subprocess.CalledProcessError, FileNotFoundError) as exc:
95
  stderr = getattr(exc, "stderr", None)
96
  if isinstance(stderr, bytes):
backend/tests/test_readability.py CHANGED
@@ -11,6 +11,7 @@ def test_extract_article_falls_back_when_readability_js_fails(monkeypatch):
11
  """When Node-based readability fails, extraction should fall back to Python mode."""
12
 
13
  calls: list[bool] = []
 
14
 
15
  def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
16
  calls.append(use_readability)
@@ -38,6 +39,7 @@ def test_extract_article_re_raises_unexpected_exception(monkeypatch):
38
  """Unexpected errors should be surfaced instead of silently falling back."""
39
 
40
  calls: list[bool] = []
 
41
 
42
  def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
43
  calls.append(use_readability)
@@ -53,3 +55,23 @@ def test_extract_article_re_raises_unexpected_exception(monkeypatch):
53
  with pytest.raises(RuntimeError, match="unexpected parser failure"):
54
  ReadabilityExtractor().extract_article("<html><body>test</body></html>")
55
  assert calls == [True]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  """When Node-based readability fails, extraction should fall back to Python mode."""
12
 
13
  calls: list[bool] = []
14
+ monkeypatch.setattr("src.utils.readability._is_readability_js_available", lambda: True)
15
 
16
  def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
17
  calls.append(use_readability)
 
39
  """Unexpected errors should be surfaced instead of silently falling back."""
40
 
41
  calls: list[bool] = []
42
+ monkeypatch.setattr("src.utils.readability._is_readability_js_available", lambda: True)
43
 
44
  def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
45
  calls.append(use_readability)
 
55
  with pytest.raises(RuntimeError, match="unexpected parser failure"):
56
  ReadabilityExtractor().extract_article("<html><body>test</body></html>")
57
  assert calls == [True]
58
+
59
+
60
+ def test_extract_article_uses_python_path_when_js_unavailable(monkeypatch):
61
+ """If JS readability is unavailable, extractor should directly use Python mode."""
62
+
63
+ calls: list[bool] = []
64
+ monkeypatch.setattr("src.utils.readability._is_readability_js_available", lambda: False)
65
+
66
+ def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
67
+ calls.append(use_readability)
68
+ return {"title": "Python Mode", "content": "<p>OK</p>"}
69
+
70
+ monkeypatch.setattr(
71
+ "src.utils.readability.simple_json_from_html_string",
72
+ _fake_simple_json_from_html_string,
73
+ )
74
+
75
+ article = ReadabilityExtractor().extract_article("<html><body>test</body></html>")
76
+ assert calls == [False]
77
+ assert article.title == "Python Mode"
backend/uv.lock CHANGED
@@ -131,6 +131,15 @@ wheels = [
131
  { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
132
  ]
133
 
 
 
 
 
 
 
 
 
 
134
  [[package]]
135
  name = "annotated-doc"
136
  version = "0.0.4"
@@ -610,6 +619,8 @@ dependencies = [
610
  { name = "langchain-mcp-adapters" },
611
  { name = "langchain-openai" },
612
  { name = "langgraph" },
 
 
613
  { name = "langgraph-cli", extra = ["inmem"] },
614
  { name = "markdownify" },
615
  { name = "markitdown", extra = ["all", "xlsx"] },
@@ -644,6 +655,8 @@ requires-dist = [
644
  { name = "langchain-mcp-adapters", specifier = ">=0.1.0" },
645
  { name = "langchain-openai", specifier = ">=1.1.7" },
646
  { name = "langgraph", specifier = ">=1.0.6" },
 
 
647
  { name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.11" },
648
  { name = "markdownify", specifier = ">=1.2.2" },
649
  { name = "markitdown", extras = ["all", "xlsx"], specifier = ">=0.0.1a2" },
@@ -1475,6 +1488,35 @@ wheels = [
1475
  { url = "https://files.pythonhosted.org/packages/4a/de/ddd53b7032e623f3c7bcdab2b44e8bf635e468f62e10e5ff1946f62c9356/langgraph_checkpoint-4.0.0-py3-none-any.whl", hash = "sha256:3fa9b2635a7c5ac28b338f631abf6a030c3b508b7b9ce17c22611513b589c784", size = 46329, upload-time = "2026-01-12T20:30:25.2Z" },
1476
  ]
1477
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1478
  [[package]]
1479
  name = "langgraph-cli"
1480
  version = "0.4.11"
@@ -2485,6 +2527,31 @@ wheels = [
2485
  { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" },
2486
  ]
2487
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2488
  [[package]]
2489
  name = "pycparser"
2490
  version = "2.23"
@@ -3100,6 +3167,18 @@ wheels = [
3100
  { url = "https://files.pythonhosted.org/packages/b8/a7/903429719d39ac2c42aa37086c90e816d883560f13c87d51f09a2962e021/speechrecognition-3.14.5-py3-none-any.whl", hash = "sha256:0c496d74e9f29b1daadb0d96f5660f47563e42bf09316dacdd57094c5095977e", size = 32856308, upload-time = "2025-12-31T11:25:41.161Z" },
3101
  ]
3102
 
 
 
 
 
 
 
 
 
 
 
 
 
3103
  [[package]]
3104
  name = "sse-starlette"
3105
  version = "2.1.3"
 
131
  { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
132
  ]
133
 
134
+ [[package]]
135
+ name = "aiosqlite"
136
+ version = "0.22.1"
137
+ source = { registry = "https://pypi.org/simple" }
138
+ sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" }
139
+ wheels = [
140
+ { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" },
141
+ ]
142
+
143
  [[package]]
144
  name = "annotated-doc"
145
  version = "0.0.4"
 
619
  { name = "langchain-mcp-adapters" },
620
  { name = "langchain-openai" },
621
  { name = "langgraph" },
622
+ { name = "langgraph-checkpoint-postgres" },
623
+ { name = "langgraph-checkpoint-sqlite" },
624
  { name = "langgraph-cli", extra = ["inmem"] },
625
  { name = "markdownify" },
626
  { name = "markitdown", extra = ["all", "xlsx"] },
 
655
  { name = "langchain-mcp-adapters", specifier = ">=0.1.0" },
656
  { name = "langchain-openai", specifier = ">=1.1.7" },
657
  { name = "langgraph", specifier = ">=1.0.6" },
658
+ { name = "langgraph-checkpoint-postgres", specifier = ">=3.0.4" },
659
+ { name = "langgraph-checkpoint-sqlite", specifier = ">=3.0.3" },
660
  { name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.11" },
661
  { name = "markdownify", specifier = ">=1.2.2" },
662
  { name = "markitdown", extras = ["all", "xlsx"], specifier = ">=0.0.1a2" },
 
1488
  { url = "https://files.pythonhosted.org/packages/4a/de/ddd53b7032e623f3c7bcdab2b44e8bf635e468f62e10e5ff1946f62c9356/langgraph_checkpoint-4.0.0-py3-none-any.whl", hash = "sha256:3fa9b2635a7c5ac28b338f631abf6a030c3b508b7b9ce17c22611513b589c784", size = 46329, upload-time = "2026-01-12T20:30:25.2Z" },
1489
  ]
1490
 
1491
+ [[package]]
1492
+ name = "langgraph-checkpoint-postgres"
1493
+ version = "3.0.4"
1494
+ source = { registry = "https://pypi.org/simple" }
1495
+ dependencies = [
1496
+ { name = "langgraph-checkpoint" },
1497
+ { name = "orjson" },
1498
+ { name = "psycopg" },
1499
+ { name = "psycopg-pool" },
1500
+ ]
1501
+ sdist = { url = "https://files.pythonhosted.org/packages/f5/39/6a409958bd1e4e0804bbe4f9351e620f6087d5346e452c59824298a2a330/langgraph_checkpoint_postgres-3.0.4.tar.gz", hash = "sha256:83e6a1097563369173442de2a66e6d712d60a1a6de07c98c5130d476bb2b76ae", size = 127627, upload-time = "2026-01-31T00:44:16.478Z" }
1502
+ wheels = [
1503
+ { url = "https://files.pythonhosted.org/packages/14/56/7466f596add278798ab42697a56e992adde6866664afff6a5e4432540f29/langgraph_checkpoint_postgres-3.0.4-py3-none-any.whl", hash = "sha256:12cd5661da2a374882770deb9008a4eb16641c3fd38d7595e312030080390c6e", size = 42834, upload-time = "2026-01-31T00:44:15.118Z" },
1504
+ ]
1505
+
1506
+ [[package]]
1507
+ name = "langgraph-checkpoint-sqlite"
1508
+ version = "3.0.3"
1509
+ source = { registry = "https://pypi.org/simple" }
1510
+ dependencies = [
1511
+ { name = "aiosqlite" },
1512
+ { name = "langgraph-checkpoint" },
1513
+ { name = "sqlite-vec" },
1514
+ ]
1515
+ sdist = { url = "https://files.pythonhosted.org/packages/04/61/40b7f8f29d6de92406e668c35265f409f57064907e31eae84ab3f2a3e3e1/langgraph_checkpoint_sqlite-3.0.3.tar.gz", hash = "sha256:438c234d37dabda979218954c9c6eb1db73bee6492c2f1d3a00552fe23fa34ed", size = 123876, upload-time = "2026-01-19T00:38:44.473Z" }
1516
+ wheels = [
1517
+ { url = "https://files.pythonhosted.org/packages/a3/d8/84ef22ee1cc485c4910df450108fd5e246497379522b3c6cfba896f71bf6/langgraph_checkpoint_sqlite-3.0.3-py3-none-any.whl", hash = "sha256:02eb683a79aa6fcda7cd4de43861062a5d160dbbb990ef8a9fd76c979998a952", size = 33593, upload-time = "2026-01-19T00:38:43.288Z" },
1518
+ ]
1519
+
1520
  [[package]]
1521
  name = "langgraph-cli"
1522
  version = "0.4.11"
 
2527
  { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" },
2528
  ]
2529
 
2530
+ [[package]]
2531
+ name = "psycopg"
2532
+ version = "3.3.3"
2533
+ source = { registry = "https://pypi.org/simple" }
2534
+ dependencies = [
2535
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
2536
+ { name = "tzdata", marker = "sys_platform == 'win32'" },
2537
+ ]
2538
+ sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624, upload-time = "2026-02-18T16:52:16.546Z" }
2539
+ wheels = [
2540
+ { url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" },
2541
+ ]
2542
+
2543
+ [[package]]
2544
+ name = "psycopg-pool"
2545
+ version = "3.3.0"
2546
+ source = { registry = "https://pypi.org/simple" }
2547
+ dependencies = [
2548
+ { name = "typing-extensions" },
2549
+ ]
2550
+ sdist = { url = "https://files.pythonhosted.org/packages/56/9a/9470d013d0d50af0da9c4251614aeb3c1823635cab3edc211e3839db0bcf/psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5", size = 31606, upload-time = "2025-12-01T11:34:33.11Z" }
2551
+ wheels = [
2552
+ { url = "https://files.pythonhosted.org/packages/e7/c3/26b8a0908a9db249de3b4169692e1c7c19048a9bc41a4d3209cee7dbb758/psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063", size = 39995, upload-time = "2025-12-01T11:34:29.761Z" },
2553
+ ]
2554
+
2555
  [[package]]
2556
  name = "pycparser"
2557
  version = "2.23"
 
3167
  { url = "https://files.pythonhosted.org/packages/b8/a7/903429719d39ac2c42aa37086c90e816d883560f13c87d51f09a2962e021/speechrecognition-3.14.5-py3-none-any.whl", hash = "sha256:0c496d74e9f29b1daadb0d96f5660f47563e42bf09316dacdd57094c5095977e", size = 32856308, upload-time = "2025-12-31T11:25:41.161Z" },
3168
  ]
3169
 
3170
+ [[package]]
3171
+ name = "sqlite-vec"
3172
+ version = "0.1.6"
3173
+ source = { registry = "https://pypi.org/simple" }
3174
+ wheels = [
3175
+ { url = "https://files.pythonhosted.org/packages/88/ed/aabc328f29ee6814033d008ec43e44f2c595447d9cccd5f2aabe60df2933/sqlite_vec-0.1.6-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:77491bcaa6d496f2acb5cc0d0ff0b8964434f141523c121e313f9a7d8088dee3", size = 164075, upload-time = "2024-11-20T16:40:29.847Z" },
3176
+ { url = "https://files.pythonhosted.org/packages/a7/57/05604e509a129b22e303758bfa062c19afb020557d5e19b008c64016704e/sqlite_vec-0.1.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fdca35f7ee3243668a055255d4dee4dea7eed5a06da8cad409f89facf4595361", size = 165242, upload-time = "2024-11-20T16:40:31.206Z" },
3177
+ { url = "https://files.pythonhosted.org/packages/f2/48/dbb2cc4e5bad88c89c7bb296e2d0a8df58aab9edc75853728c361eefc24f/sqlite_vec-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b0519d9cd96164cd2e08e8eed225197f9cd2f0be82cb04567692a0a4be02da3", size = 103704, upload-time = "2024-11-20T16:40:33.729Z" },
3178
+ { url = "https://files.pythonhosted.org/packages/80/76/97f33b1a2446f6ae55e59b33869bed4eafaf59b7f4c662c8d9491b6a714a/sqlite_vec-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:823b0493add80d7fe82ab0fe25df7c0703f4752941aee1c7b2b02cec9656cb24", size = 151556, upload-time = "2024-11-20T16:40:35.387Z" },
3179
+ { url = "https://files.pythonhosted.org/packages/6a/98/e8bc58b178266eae2fcf4c9c7a8303a8d41164d781b32d71097924a6bebe/sqlite_vec-0.1.6-py3-none-win_amd64.whl", hash = "sha256:c65bcfd90fa2f41f9000052bcb8bb75d38240b2dae49225389eca6c3136d3f0c", size = 281540, upload-time = "2024-11-20T16:40:37.296Z" },
3180
+ ]
3181
+
3182
  [[package]]
3183
  name = "sse-starlette"
3184
  version = "2.1.3"
start-hf.sh CHANGED
@@ -14,7 +14,26 @@ fi
14
 
15
  export OPENAI_API_KEY
16
 
17
- mkdir -p /app/logs /app/backend/.deer-flow/threads /app/backend/.deer-flow/artifacts
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  touch /app/.env /app/backend/.env /app/frontend/.env
19
 
20
  if [ -n "${SPACE_PASSWORD}" ]; then
@@ -86,6 +105,14 @@ tools:
86
  group: bash
87
  use: src.sandbox.tools:bash_tool
88
 
 
 
 
 
 
 
 
 
89
  sandbox:
90
  use: src.sandbox.local:LocalSandboxProvider
91
  EOF
 
14
 
15
  export OPENAI_API_KEY
16
 
17
+ PERSIST_ROOT="${DEER_FLOW_HOME:-/data/deer-flow}"
18
+ if ! mkdir -p "${PERSIST_ROOT}" 2>/dev/null; then
19
+ PERSIST_ROOT="/app/backend/.deer-flow"
20
+ mkdir -p "${PERSIST_ROOT}"
21
+ fi
22
+
23
+ export DEER_FLOW_HOME="${PERSIST_ROOT}"
24
+ export DEER_FLOW_EXTENSIONS_CONFIG_PATH="${DEER_FLOW_EXTENSIONS_CONFIG_PATH:-${DEER_FLOW_HOME}/extensions_config.json}"
25
+ export LANGGRAPH_CHECKPOINT_SQLITE_PATH="${LANGGRAPH_CHECKPOINT_SQLITE_PATH:-${DEER_FLOW_HOME}/checkpoints.sqlite}"
26
+ export DEER_FLOW_MEMORY_PATH="${DEER_FLOW_MEMORY_PATH:-${DEER_FLOW_HOME}/memory.json}"
27
+ export DEER_FLOW_SKILLS_PATH="${DEER_FLOW_SKILLS_PATH:-${DEER_FLOW_HOME}/skills}"
28
+
29
+ mkdir -p /app/logs "${DEER_FLOW_HOME}"
30
+ if [ ! -f "${DEER_FLOW_EXTENSIONS_CONFIG_PATH}" ]; then
31
+ cp /app/extensions_config.example.json "${DEER_FLOW_EXTENSIONS_CONFIG_PATH}"
32
+ fi
33
+ if [ ! -d "${DEER_FLOW_SKILLS_PATH}" ]; then
34
+ cp -R /app/skills "${DEER_FLOW_SKILLS_PATH}"
35
+ fi
36
+
37
  touch /app/.env /app/backend/.env /app/frontend/.env
38
 
39
  if [ -n "${SPACE_PASSWORD}" ]; then
 
105
  group: bash
106
  use: src.sandbox.tools:bash_tool
107
 
108
+ skills:
109
+ path: ${DEER_FLOW_SKILLS_PATH}
110
+ container_path: /mnt/skills
111
+
112
+ memory:
113
+ enabled: true
114
+ storage_path: ${DEER_FLOW_MEMORY_PATH}
115
+
116
  sandbox:
117
  use: src.sandbox.local:LocalSandboxProvider
118
  EOF