Skip to content

feat: add long-term memory#754

Open
tyaroshko wants to merge 42 commits into
mainfrom
feat/long-term-memory
Open

feat: add long-term memory#754
tyaroshko wants to merge 42 commits into
mainfrom
feat/long-term-memory

Conversation

@tyaroshko
Copy link
Copy Markdown
Collaborator

@tyaroshko tyaroshko commented May 27, 2026

Note

Medium Risk
Stores per-user facts in external DBs and binds tools per request; cross-user delete is blocked in code, but mis-scoped user_id or backend config could leak or mix data—mitigated by tests for isolation and concurrent runs.

Overview
Adds user-scoped long-term memory for agents: durable facts with embedding-backed remember, recall, and programmatic forget, separate from short-term conversation memory.

A shared LongTermMemoryBackend implements dedup (normalized content hash per user), optional semantic upsert when cosine similarity exceeds upsert_threshold, and user isolation on search/delete. Storage ships as in-memory, Postgres + pgvector, and Qdrant backends with scoped CRUD and vector search.

Agents gain optional LongTermMemoryConfig (enabled, backend, which tools to expose). When user_id is present on a run, remember_fact / recall_facts tools are built with user_id baked in and exposed only for that execution via a ContextVar overlay—so concurrent execute() calls do not mutate shared tools or leak another user’s tools. Tool lists, prompts, and function-calling schemas read _runtime_tools; the LTM embedder is initialized in init_components.

BaseConnection.to_dict now strips forwarded include_secure_params so serialization through backends/tools does not break model_dump.

Unit and integration tests cover backend behavior, tools, and agent concurrency/isolation.

Reviewed by Cursor Bugbot for commit 611ea39. Bugbot is set up for automated code reviews on this repo. Configure here.

tyaroshko added 25 commits May 25, 2026 21:37
@tyaroshko tyaroshko requested a review from a team as a code owner May 27, 2026 11:15
Comment thread dynamiq/nodes/tools/long_term_memory.py
Comment thread dynamiq/memory/long_term/backends/qdrant.py Outdated
Comment thread dynamiq/nodes/agents/base.py
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 27, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
dynamiq/connections
   connections.py76414281%18–23, 64, 90, 114, 202–205, 327, 329–337, 364, 366–368, 409, 556, 558–560, 611, 613–616, 681–682, 684–685, 692–693, 695–696, 711–712, 714, 740, 742, 754, 756–758, 878, 893–895, 897, 902–903, 1003, 1056, 1113, 1212–1213, 1246–1247, 1283–1284, 1332–1333, 1335, 1338–1339, 1344, 1349, 1356–1357, 1360–1364, 1399–1400, 1402–1403, 1405–1406, 1408, 1416, 1424–1425, 1427–1430, 1432–1433, 1435, 1438, 1445, 1447, 1456–1457, 1459, 1548, 1550, 1581–1582, 1584, 1615–1616, 1732, 1736, 1738, 1789–1799, 1802, 1850, 1852–1853, 1855–1856, 1864, 1866, 1871–1872, 1874–1875, 1877, 1879
dynamiq/memory/long_term
   base.py1211587%153–155, 171–173, 194–196, 202–204, 212–214
   long_term_memory.py25580%39, 43–45, 48
dynamiq/memory/long_term/backends
   pgvector.py955344%43–46, 50, 78, 82–83, 86, 89–93, 98, 102–105, 111, 120–122, 126–127, 130–131, 148–149, 156–157, 160–161, 167–168, 171–172, 187–188, 203–205, 213–214, 217–219, 225–226, 229–231, 238
   qdrant.py875141%30, 34–36, 45, 57, 82, 86–87, 90, 93, 97–98, 102–103, 111–113, 117–118, 121, 133, 139–141, 144, 155–157, 160, 175–178, 181, 199, 207, 210, 216, 225–229, 237–242, 246
dynamiq/nodes/agents
   agent.py88714184%83–84, 337–340, 345, 361–362, 380–381, 403, 410–411, 418–419, 425–426, 428, 487–489, 699–701, 716, 725, 751, 767, 786, 798–801, 803, 825–826, 828, 843, 855, 860, 877–878, 888, 891, 901, 941–943, 949–951, 1064, 1069, 1120, 1154, 1156, 1171–1172, 1179, 1182, 1209–1210, 1226, 1265, 1292–1296, 1363, 1369, 1373, 1376, 1410, 1438, 1441, 1446, 1462, 1525–1526, 1561, 1566, 1590, 1646, 1668–1670, 1673–1674, 1684, 1687, 1697, 1704, 1754–1757, 1777–1778, 1782, 1784–1786, 1788, 1790, 1823, 1831–1832, 1834, 1837, 1840, 1891–1894, 1910, 1919, 1938–1939, 2014, 2020–2026, 2032, 2034–2043, 2045–2046
   base.py108630671%72, 78, 170–172, 187, 195, 198, 349, 367, 377, 380, 425, 427, 464–466, 468–473, 478–480, 502–508, 512–517, 520–524, 536–537, 539–544, 546–547, 555, 575, 577, 579, 600–601, 616, 708–709, 713–716, 725–726, 736–739, 753–760, 768–770, 808, 819–820, 966, 969, 973–974, 998–1014, 1034, 1038, 1079–1080, 1084–1085, 1098, 1139, 1171, 1182, 1206–1209, 1211, 1213, 1215–1217, 1219–1220, 1227, 1229–1230, 1234–1236, 1242, 1247, 1250–1252, 1273, 1275, 1293–1295, 1299–1301, 1304, 1316–1317, 1338–1343, 1360–1361, 1363–1364, 1368–1369, 1371–1378, 1381–1384, 1386–1387, 1389–1391, 1411, 1418–1419, 1424, 1441–1442, 1447, 1458–1460, 1471–1472, 1477, 1485, 1487, 1504, 1510, 1517, 1541, 1564–1565, 1581, 1588–1591, 1594, 1599, 1607, 1631, 1635–1636, 1663–1665, 1667–1672, 1674–1675, 1677–1685, 1692, 1694–1697, 1699–1707, 1714, 1716, 1718–1720, 1734, 1753–1754, 1759, 1765, 1822, 1836, 1842, 1845, 1852, 1918, 1927, 1930, 1952–1954, 1959–1960, 1976–1977, 1981, 1994, 1998, 2004, 2006–2007, 2009–2010, 2012–2015, 2017, 2019, 2021–2024, 2026, 2029, 2031, 2035, 2038, 2040, 2044, 2047, 2049, 2053, 2056–2058, 2065, 2072, 2075–2076
TOTAL33094959871% 

Tests Skipped Failures Errors Time
2461 2 💤 0 ❌ 0 🔥 2m 52s ⏱️

Comment thread dynamiq/memory/long_term/backends/qdrant.py Outdated
Comment thread dynamiq/nodes/agents/base.py Outdated
Comment thread dynamiq/memory/long_term/backends/qdrant.py Outdated
Comment thread dynamiq/memory/long_term/backends/pgvector.py Outdated
Comment thread dynamiq/nodes/agents/base.py
Comment thread dynamiq/nodes/agents/base.py Outdated
Comment thread dynamiq/nodes/agents/base.py
Comment thread dynamiq/nodes/agents/base.py
Comment thread dynamiq/nodes/tools/long_term_memory.py Outdated
Comment thread dynamiq/nodes/agents/base.py Outdated
Comment thread dynamiq/nodes/agents/base.py Outdated
Comment thread dynamiq/nodes/agents/base.py
Comment thread dynamiq/memory/long_term/long_term_memory.py Outdated
@maksymbuleshnyi
Copy link
Copy Markdown
Contributor

looks like long term memory tools are invisible in FUNCTION_CALLING / STRUCTURED_OUTPUT modes.

Returns:
dict: A dictionary representation of the connection instance.
"""
# Swallow `include_secure_params` if a caller forwards it down — the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets shorten the large comments

query_norm = np.linalg.norm(query) or 1.0

scored: list[tuple[Fact, float]] = []
for fact_id, fact in self._facts.items():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to do more efiicienly? I think it is better to vectorize it.

- {"query": "food preferences"}
- {"query": "what does the user do for work?", "limit": 3}
- {"query": "timezone or schedule constraints", "limit": 10}
"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to ask model to produce mutltiple queiries. Since it is semantic search it depends on phrasing and can improve accuracy.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 611ea39. Configure here.

if not _run_extra_tools.get():
return self._tools, self._response_format
return self._build_inference_schemas(self._runtime_tools)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prompt template excludes tool blocks when only LTM tools exist

Medium Severity

The has_tools flag in _init_prompt_blocks only checks self.tools and skills, but doesn't account for self.long_term_memory. When an agent has no static tools but has LTM configured, has_tools is False at init time, so build_react_prompt builds a template without tool-related blocks. At runtime, even though _setup_prompt_and_stop_sequences passes tool_description as a kwarg, the template has no placeholder for it — making LTM tools invisible in XML/ReAct inference modes.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 611ea39. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants