feat: support manual add and edit for memory facts (#1538)

* feat: support manual add and edit for memory facts

* fix: restore memory updater save helper

* fix: address memory fact review feedback

* fix: remove duplicate memory fact edit action

* docs: simplify memory fact review setup

* docs: relax memory review startup instructions

* fix: clear rebase marker in memory settings page

* fix: address memory fact review and format issues

* fix: address memory fact review feedback

* refactor: make memory fact updates explicit patch semantics

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
Admire
2026-03-29 23:53:23 +08:00
committed by GitHub
parent cdb2a3a017
commit fc7de7fffe
15 changed files with 977 additions and 52 deletions
+154
View File
@@ -5,7 +5,9 @@ from deerflow.agents.memory.updater import (
MemoryUpdater,
_extract_text,
clear_memory_data,
create_memory_fact,
delete_memory_fact,
update_memory_fact,
)
from deerflow.config.memory_config import MemoryConfig
@@ -184,6 +186,43 @@ def test_delete_memory_fact_removes_only_matching_fact() -> None:
assert [fact["id"] for fact in result["facts"]] == ["fact_keep"]
def test_create_memory_fact_appends_manual_fact() -> None:
with (
patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()),
patch("deerflow.agents.memory.updater._save_memory_to_file", return_value=True),
):
result = create_memory_fact(
content=" User prefers concise code reviews. ",
category="preference",
confidence=0.88,
)
assert len(result["facts"]) == 1
assert result["facts"][0]["content"] == "User prefers concise code reviews."
assert result["facts"][0]["category"] == "preference"
assert result["facts"][0]["confidence"] == 0.88
assert result["facts"][0]["source"] == "manual"
def test_create_memory_fact_rejects_empty_content() -> None:
try:
create_memory_fact(content=" ")
except ValueError as exc:
assert exc.args == ("content",)
else:
raise AssertionError("Expected ValueError for empty fact content")
def test_create_memory_fact_rejects_invalid_confidence() -> None:
for confidence in (-0.1, 1.1, float("nan"), float("inf"), float("-inf")):
try:
create_memory_fact(content="User likes tests", confidence=confidence)
except ValueError as exc:
assert exc.args == ("confidence",)
else:
raise AssertionError("Expected ValueError for invalid fact confidence")
def test_delete_memory_fact_raises_for_unknown_id() -> None:
with patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()):
try:
@@ -194,6 +233,121 @@ def test_delete_memory_fact_raises_for_unknown_id() -> None:
raise AssertionError("Expected KeyError for missing fact id")
def test_update_memory_fact_updates_only_matching_fact() -> None:
current_memory = _make_memory(
facts=[
{
"id": "fact_keep",
"content": "User likes Python",
"category": "preference",
"confidence": 0.9,
"createdAt": "2026-03-18T00:00:00Z",
"source": "thread-a",
},
{
"id": "fact_edit",
"content": "User prefers tabs",
"category": "preference",
"confidence": 0.8,
"createdAt": "2026-03-18T00:00:00Z",
"source": "manual",
},
]
)
with (
patch("deerflow.agents.memory.updater.get_memory_data", return_value=current_memory),
patch("deerflow.agents.memory.updater._save_memory_to_file", return_value=True),
):
result = update_memory_fact(
fact_id="fact_edit",
content="User prefers spaces",
category="workflow",
confidence=0.91,
)
assert result["facts"][0]["content"] == "User likes Python"
assert result["facts"][1]["content"] == "User prefers spaces"
assert result["facts"][1]["category"] == "workflow"
assert result["facts"][1]["confidence"] == 0.91
assert result["facts"][1]["createdAt"] == "2026-03-18T00:00:00Z"
assert result["facts"][1]["source"] == "manual"
def test_update_memory_fact_preserves_omitted_fields() -> None:
current_memory = _make_memory(
facts=[
{
"id": "fact_edit",
"content": "User prefers tabs",
"category": "preference",
"confidence": 0.8,
"createdAt": "2026-03-18T00:00:00Z",
"source": "manual",
},
]
)
with (
patch("deerflow.agents.memory.updater.get_memory_data", return_value=current_memory),
patch("deerflow.agents.memory.updater._save_memory_to_file", return_value=True),
):
result = update_memory_fact(
fact_id="fact_edit",
content="User prefers spaces",
)
assert result["facts"][0]["content"] == "User prefers spaces"
assert result["facts"][0]["category"] == "preference"
assert result["facts"][0]["confidence"] == 0.8
def test_update_memory_fact_raises_for_unknown_id() -> None:
with patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()):
try:
update_memory_fact(
fact_id="fact_missing",
content="User prefers concise code reviews.",
category="preference",
confidence=0.88,
)
except KeyError as exc:
assert exc.args == ("fact_missing",)
else:
raise AssertionError("Expected KeyError for missing fact id")
def test_update_memory_fact_rejects_invalid_confidence() -> None:
current_memory = _make_memory(
facts=[
{
"id": "fact_edit",
"content": "User prefers tabs",
"category": "preference",
"confidence": 0.8,
"createdAt": "2026-03-18T00:00:00Z",
"source": "manual",
},
]
)
for confidence in (-0.1, 1.1, float("nan"), float("inf"), float("-inf")):
with patch(
"deerflow.agents.memory.updater.get_memory_data",
return_value=current_memory,
):
try:
update_memory_fact(
fact_id="fact_edit",
content="User prefers spaces",
confidence=confidence,
)
except ValueError as exc:
assert exc.args == ("confidence",)
else:
raise AssertionError("Expected ValueError for invalid fact confidence")
# ---------------------------------------------------------------------------
# _extract_text — LLM response content normalization
# ---------------------------------------------------------------------------