feat: update memory extraction and management protocols to enforce structured bullet formats, utilize user first names, and enhance validation for team and user memory entries

This commit is contained in:
Anish Sarkar 2026-04-10 04:36:51 +05:30
parent ad2a981a77
commit b8e1c9801b
8 changed files with 215 additions and 101 deletions

View file

@ -1,8 +1,12 @@
"""Unit tests for memory scope validation."""
"""Unit tests for memory scope validation and bullet format validation."""
import pytest
from app.agents.new_chat.tools.update_memory import _save_memory, _validate_memory_scope
from app.agents.new_chat.tools.update_memory import (
_save_memory,
_validate_bullet_format,
_validate_memory_scope,
)
pytestmark = pytest.mark.unit
@ -23,34 +27,132 @@ class _Recorder:
self.rollback_calls += 1
def test_validate_memory_scope_rejects_user_sections_in_team_scope() -> None:
content = "## About the user\n- (2026-04-10) Student studying DSA\n"
# ---------------------------------------------------------------------------
# _validate_memory_scope — marker-based
# ---------------------------------------------------------------------------
def test_validate_memory_scope_rejects_pref_marker_in_team_scope() -> None:
content = "- (2026-04-10) [pref] Prefers dark mode\n"
result = _validate_memory_scope(content, "team")
assert result is not None
assert result["status"] == "error"
assert "personal sections" in result["message"]
assert "[pref]" in result["message"]
def test_validate_memory_scope_rejects_team_sections_in_user_scope() -> None:
content = "## Team decisions\n- (2026-04-10) Python-first backend policy\n"
def test_validate_memory_scope_rejects_instr_marker_in_team_scope() -> None:
content = "- (2026-04-10) [instr] Always respond in Spanish\n"
result = _validate_memory_scope(content, "team")
assert result is not None
assert result["status"] == "error"
assert "[instr]" in result["message"]
def test_validate_memory_scope_rejects_both_personal_markers_in_team() -> None:
content = (
"- (2026-04-10) [pref] Prefers dark mode\n"
"- (2026-04-10) [instr] Always respond in Spanish\n"
)
result = _validate_memory_scope(content, "team")
assert result is not None
assert result["status"] == "error"
assert "[instr]" in result["message"]
assert "[pref]" in result["message"]
def test_validate_memory_scope_allows_fact_in_team_scope() -> None:
content = "- (2026-04-10) [fact] Office is in downtown Seattle\n"
result = _validate_memory_scope(content, "team")
assert result is None
def test_validate_memory_scope_allows_all_markers_in_user_scope() -> None:
content = (
"- (2026-04-10) [fact] Python developer\n"
"- (2026-04-10) [pref] Prefers concise answers\n"
"- (2026-04-10) [instr] Always use bullet points\n"
)
result = _validate_memory_scope(content, "user")
assert result is not None
assert result["status"] == "error"
assert "team sections" in result["message"]
assert result is None
def test_validate_memory_scope_normalizes_heading_case_and_spacing() -> None:
content = "## About The User \n- (2026-04-10) Student\n"
def test_validate_memory_scope_allows_any_heading_in_team() -> None:
content = (
"## Architecture\n"
"- (2026-04-10) [fact] Uses PostgreSQL for persistence\n"
)
result = _validate_memory_scope(content, "team")
assert result is not None
assert result["status"] == "error"
assert result is None
def test_validate_memory_scope_allows_any_heading_in_user() -> None:
content = (
"## My Projects\n"
"- (2026-04-10) [fact] Working on SurfSense\n"
)
result = _validate_memory_scope(content, "user")
assert result is None
# ---------------------------------------------------------------------------
# _validate_bullet_format
# ---------------------------------------------------------------------------
def test_validate_bullet_format_passes_valid_bullets() -> None:
content = (
"## Work\n"
"- (2026-04-10) [fact] Senior Python developer\n"
"- (2026-04-10) [pref] Prefers dark mode\n"
"- (2026-04-10) [instr] Always respond in bullet points\n"
)
warnings = _validate_bullet_format(content)
assert warnings == []
def test_validate_bullet_format_warns_on_missing_marker() -> None:
content = "- (2026-04-10) Senior Python developer\n"
warnings = _validate_bullet_format(content)
assert len(warnings) == 1
assert "Malformed bullet" in warnings[0]
def test_validate_bullet_format_warns_on_missing_date() -> None:
content = "- [fact] Senior Python developer\n"
warnings = _validate_bullet_format(content)
assert len(warnings) == 1
assert "Malformed bullet" in warnings[0]
def test_validate_bullet_format_warns_on_unknown_marker() -> None:
content = "- (2026-04-10) [context] Working on project X\n"
warnings = _validate_bullet_format(content)
assert len(warnings) == 1
assert "Malformed bullet" in warnings[0]
def test_validate_bullet_format_ignores_non_bullet_lines() -> None:
content = "## Some Heading\nSome paragraph text\n"
warnings = _validate_bullet_format(content)
assert warnings == []
def test_validate_bullet_format_warns_on_old_format_without_marker() -> None:
content = "## About the user\n- (2026-04-10) Likes cats\n"
warnings = _validate_bullet_format(content)
assert len(warnings) == 1
# ---------------------------------------------------------------------------
# _save_memory — end-to-end with marker scope check
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_save_memory_blocks_cross_scope_write_before_commit() -> None:
async def test_save_memory_blocks_pref_in_team_before_commit() -> None:
recorder = _Recorder()
result = await _save_memory(
updated_memory="## About the user\n- (2026-04-10) Student\n",
updated_memory="- (2026-04-10) [pref] Prefers dark mode\n",
old_memory=None,
llm=None,
apply_fn=recorder.apply,
@ -65,9 +167,9 @@ async def test_save_memory_blocks_cross_scope_write_before_commit() -> None:
@pytest.mark.asyncio
async def test_save_memory_allows_valid_scope_and_commits() -> None:
async def test_save_memory_allows_fact_in_team_and_commits() -> None:
recorder = _Recorder()
content = "## Team decisions\n- (2026-04-10) Python-first backend policy\n"
content = "- (2026-04-10) [fact] Weekly standup on Mondays\n"
result = await _save_memory(
updated_memory=content,
old_memory=None,
@ -81,3 +183,22 @@ async def test_save_memory_allows_valid_scope_and_commits() -> None:
assert result["status"] == "saved"
assert recorder.commit_calls == 1
assert recorder.applied_content == content
@pytest.mark.asyncio
async def test_save_memory_includes_format_warnings() -> None:
recorder = _Recorder()
content = "- (2026-04-10) Missing marker text\n"
result = await _save_memory(
updated_memory=content,
old_memory=None,
llm=None,
apply_fn=recorder.apply,
commit_fn=recorder.commit,
rollback_fn=recorder.rollback,
label="memory",
scope="user",
)
assert result["status"] == "saved"
assert "format_warnings" in result
assert len(result["format_warnings"]) == 1