Skip to content

Commit 05bd963

Browse files
committed
Skip malformed memory entries when loading from JSONL
- Extract memory_entry_from_payload with field-level validation for memory_id, content, created_at, and tags types - Return None for invalid entries instead of crashing on load
1 parent 54a7b72 commit 05bd963

2 files changed

Lines changed: 36 additions & 8 deletions

File tree

teaagent/memory.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,30 @@ def _read_entries(self) -> List[MemoryEntry]:
6868
payload = json.loads(line)
6969
except json.JSONDecodeError:
7070
continue
71-
entries.append(
72-
MemoryEntry(
73-
memory_id=payload["memory_id"],
74-
content=payload["content"],
75-
tags=tuple(payload.get("tags", [])),
76-
created_at=payload.get("created_at", utc_now()),
77-
)
78-
)
71+
entry = memory_entry_from_payload(payload)
72+
if entry is not None:
73+
entries.append(entry)
7974
return entries
8075

8176

77+
def memory_entry_from_payload(payload: Any) -> MemoryEntry | None:
78+
if not isinstance(payload, dict):
79+
return None
80+
memory_id = payload.get("memory_id")
81+
content = payload.get("content")
82+
tags = payload.get("tags", [])
83+
created_at = payload.get("created_at", utc_now())
84+
if not isinstance(memory_id, str) or not memory_id:
85+
return None
86+
if not isinstance(content, str) or not content:
87+
return None
88+
if not isinstance(created_at, str) or not created_at:
89+
return None
90+
if not isinstance(tags, list) or not all(isinstance(tag, str) for tag in tags):
91+
return None
92+
return MemoryEntry(memory_id=memory_id, content=content, tags=tuple(tags), created_at=created_at)
93+
94+
8295
def normalize_tags(tags: tuple[str, ...]) -> tuple[str, ...]:
8396
return tuple(sorted({tag.strip().lower() for tag in tags if tag.strip()}))
8497

tests/test_memory.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import tempfile
66
import unittest
77
from contextlib import redirect_stdout
8+
from pathlib import Path
89

910
from teaagent import MemoryCatalog
1011
from teaagent.cli import main
@@ -22,6 +23,20 @@ def test_memory_catalog_add_list_search_show(self) -> None:
2223
self.assertEqual(catalog.search("graph")[0].tags, ("graph", "sqlite"))
2324
self.assertEqual(catalog.show(entry.memory_id).content, "GraphQLite uses a SQLite extension")
2425

26+
def test_memory_catalog_skips_malformed_entries(self) -> None:
27+
with tempfile.TemporaryDirectory() as tmp:
28+
catalog = MemoryCatalog(tmp)
29+
good = catalog.add("Keep this memory", tags=("valid",))
30+
path = Path(tmp) / ".teaagent" / "memory.jsonl"
31+
with path.open("a", encoding="utf-8") as handle:
32+
handle.write("not json\n")
33+
handle.write(json.dumps({"content": "missing id"}) + "\n")
34+
handle.write(json.dumps({"memory_id": "bad-tags", "content": "x", "tags": [1]}) + "\n")
35+
36+
entries = catalog.list()
37+
38+
self.assertEqual([entry.memory_id for entry in entries], [good.memory_id])
39+
2540
def test_cli_memory_commands(self) -> None:
2641
with tempfile.TemporaryDirectory() as tmp:
2742
add_output = io.StringIO()

0 commit comments

Comments
 (0)