memlint 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- memlint-0.1.0/.env.example +3 -0
- memlint-0.1.0/.gitignore +19 -0
- memlint-0.1.0/LICENSE +21 -0
- memlint-0.1.0/PKG-INFO +152 -0
- memlint-0.1.0/README.md +129 -0
- memlint-0.1.0/examples/basic_usage.py +17 -0
- memlint-0.1.0/examples/langchain_integration.py +41 -0
- memlint-0.1.0/examples/sample_memories.json +59 -0
- memlint-0.1.0/memlint/__init__.py +19 -0
- memlint-0.1.0/memlint/adapters/__init__.py +0 -0
- memlint-0.1.0/memlint/adapters/_utils.py +8 -0
- memlint-0.1.0/memlint/adapters/json_adapter.py +28 -0
- memlint-0.1.0/memlint/adapters/langchain_tool.py +25 -0
- memlint-0.1.0/memlint/adapters/mem0_adapter.py +28 -0
- memlint-0.1.0/memlint/classifier.py +155 -0
- memlint-0.1.0/memlint/cli.py +79 -0
- memlint-0.1.0/memlint/core.py +165 -0
- memlint-0.1.0/memlint/models.py +73 -0
- memlint-0.1.0/memlint/scorer.py +103 -0
- memlint-0.1.0/pyproject.toml +38 -0
- memlint-0.1.0/tests/__init__.py +0 -0
- memlint-0.1.0/tests/fixtures/sample.json +17 -0
- memlint-0.1.0/tests/fixtures/sample_mem0.json +9 -0
- memlint-0.1.0/tests/test_adapters.py +67 -0
- memlint-0.1.0/tests/test_classifier.py +115 -0
- memlint-0.1.0/tests/test_core.py +82 -0
- memlint-0.1.0/tests/test_models.py +58 -0
- memlint-0.1.0/tests/test_scorer.py +93 -0
memlint-0.1.0/.gitignore
ADDED
memlint-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MatrixEscaper
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
memlint-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memlint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Detect stale facts in LLM agent memory stores
|
|
5
|
+
Project-URL: Homepage, https://github.com/Bhavye2003Developer/memlint
|
|
6
|
+
Project-URL: Issues, https://github.com/Bhavye2003Developer/memlint/issues
|
|
7
|
+
Author-email: Bhavye <bhavyedevelopment2003@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: click>=8.0
|
|
12
|
+
Requires-Dist: pydantic>=2.0
|
|
13
|
+
Requires-Dist: python-dateutil>=2.8
|
|
14
|
+
Requires-Dist: python-dotenv>=1.0
|
|
15
|
+
Requires-Dist: rich>=13.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
19
|
+
Provides-Extra: llm
|
|
20
|
+
Requires-Dist: langchain-core>=0.2; extra == 'llm'
|
|
21
|
+
Requires-Dist: langchain-openai>=0.1; extra == 'llm'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# memlint
|
|
25
|
+
|
|
26
|
+
**Lint your LLM agent's memory before it lies to you.**
|
|
27
|
+
|
|
28
|
+
`memlint` detects stale facts in an LLM agent's memory store before they are injected into the context window. It scores each fact by age, confirmation history, and contradiction signals, then tells you which ones to flag, refresh, or discard.
|
|
29
|
+
|
|
30
|
+
## The problem
|
|
31
|
+
|
|
32
|
+
LLM agents that work across sessions store facts about the user and world - where they live, where they work, what they're building. These facts go stale when the real world changes but the memory doesn't. A fact like `"User works at xyz"` stays in memory after a job change. The agent retrieves it, injects it, and answers confidently with wrong information.
|
|
33
|
+
|
|
34
|
+
`memlint` catches this before it happens.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install memlint
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
With optional LLM-assisted classification:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install memlint[llm]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from memlint import StaleDetector
|
|
52
|
+
from memlint.adapters.json_adapter import load_from_json
|
|
53
|
+
|
|
54
|
+
facts = load_from_json("sample_memories.json")
|
|
55
|
+
detector = StaleDetector()
|
|
56
|
+
report = detector.check(facts)
|
|
57
|
+
|
|
58
|
+
print(f"Total: {report.total_facts} | Flagged: {len(report.flagged)}")
|
|
59
|
+
for result in report.flagged:
|
|
60
|
+
print(f" [{result.staleness_level.value.upper()}] {result.content}")
|
|
61
|
+
print(f" Reason: {result.reason}")
|
|
62
|
+
print(f" Action: {result.recommendation}")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## CLI Usage
|
|
66
|
+
|
|
67
|
+
Check all facts:
|
|
68
|
+
```bash
|
|
69
|
+
memlint check memories.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Show only stale and expired:
|
|
73
|
+
```bash
|
|
74
|
+
memlint check memories.json --only-flagged
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Output raw JSON:
|
|
78
|
+
```bash
|
|
79
|
+
memlint check memories.json --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Parse Mem0 format:
|
|
83
|
+
```bash
|
|
84
|
+
memlint check memories.json --format mem0
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Sample output:
|
|
88
|
+
```
|
|
89
|
+
╭──────────┬────────────────────────────────────────┬────────────┬─────┬───────┬─────────┬─────────╮
|
|
90
|
+
│ ID │ Content │ Category │ Age │ Score │ Level │ Action │
|
|
91
|
+
├──────────┼────────────────────────────────────────┼────────────┼─────┼───────┼─────────┼─────────┤
|
|
92
|
+
│ mem_004 │ User works at XYZ as a senior cons... │ employment │ 279 │ 0.70 │ STALE │ flag │
|
|
93
|
+
│ mem_006 │ User debugged a LangGraph memory is... │ episodic │ 29 │ 1.00 │ EXPIRED │ discard │
|
|
94
|
+
╰──────────┴────────────────────────────────────────┴────────────┴─────┴───────┴─────────┴─────────╯
|
|
95
|
+
|
|
96
|
+
Checked 8 facts: 1 fresh, 2 aging, 3 stale, 2 expired
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Staleness Score Explained
|
|
100
|
+
|
|
101
|
+
Each fact is assigned a category with a natural lifespan:
|
|
102
|
+
|
|
103
|
+
| Category | Examples | Typical Valid Window |
|
|
104
|
+
|--------------|---------------------------------------|----------------------|
|
|
105
|
+
| `location` | "lives in Delhi", "office in Sector 5"| 6–24 months |
|
|
106
|
+
| `employment` | "works at xyz", "role is consultant" | 6–18 months |
|
|
107
|
+
| `project` | "building pract-agents", "using Pinecone" | 1–6 months |
|
|
108
|
+
| `preference` | "prefers Python", "uses dark mode" | 3–12 months |
|
|
109
|
+
| `relationship`| "manager is X", "team has 5 people" | 3–12 months |
|
|
110
|
+
| `identity` | "name is X", "speaks Hindi" | Very long/permanent |
|
|
111
|
+
| `episodic` | "debugged a LangGraph issue today" | Days to weeks |
|
|
112
|
+
| `system_fact`| "Python version is 3.10", "npm v9" | 1–3 months |
|
|
113
|
+
|
|
114
|
+
Score thresholds:
|
|
115
|
+
- `0.0 – 0.29` → **FRESH** (safe to use)
|
|
116
|
+
- `0.30 – 0.59` → **AGING** (use with caution)
|
|
117
|
+
- `0.60 – 0.79` → **STALE** (flag before injecting)
|
|
118
|
+
- `0.80 – 1.0` → **EXPIRED** (do not inject without reconfirmation)
|
|
119
|
+
|
|
120
|
+
## Adapters
|
|
121
|
+
|
|
122
|
+
**JSON**: default format:
|
|
123
|
+
```python
|
|
124
|
+
from memlint.adapters.json_adapter import load_from_json
|
|
125
|
+
facts = load_from_json("memories.json")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Mem0**: maps `memory` to `content`, `updated_at` to `last_confirmed_at`:
|
|
129
|
+
```python
|
|
130
|
+
from memlint.adapters.mem0_adapter import load_from_mem0
|
|
131
|
+
facts = load_from_mem0("mem0_export.json")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**LangChain**: two tools: `check_memory_staleness` and `filter_stale_memories` (see below).
|
|
135
|
+
|
|
136
|
+
## LangChain / LangGraph Integration
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from memlint.adapters.langchain_tool import (
|
|
140
|
+
check_memory_staleness,
|
|
141
|
+
filter_stale_memories,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# In a LangGraph node: filter before injecting memories into the LLM
|
|
145
|
+
safe_facts_json = filter_stale_memories.invoke({"facts_json": memories_json_string})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Requires `pip install memlint[llm]`.
|
|
149
|
+
|
|
150
|
+
## Contributing
|
|
151
|
+
|
|
152
|
+
Open an issue or pull request at the project repository.
|
memlint-0.1.0/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# memlint
|
|
2
|
+
|
|
3
|
+
**Lint your LLM agent's memory before it lies to you.**
|
|
4
|
+
|
|
5
|
+
`memlint` detects stale facts in an LLM agent's memory store before they are injected into the context window. It scores each fact by age, confirmation history, and contradiction signals, then tells you which ones to flag, refresh, or discard.
|
|
6
|
+
|
|
7
|
+
## The problem
|
|
8
|
+
|
|
9
|
+
LLM agents that work across sessions store facts about the user and world - where they live, where they work, what they're building. These facts go stale when the real world changes but the memory doesn't. A fact like `"User works at xyz"` stays in memory after a job change. The agent retrieves it, injects it, and answers confidently with wrong information.
|
|
10
|
+
|
|
11
|
+
`memlint` catches this before it happens.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install memlint
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
With optional LLM-assisted classification:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install memlint[llm]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from memlint import StaleDetector
|
|
29
|
+
from memlint.adapters.json_adapter import load_from_json
|
|
30
|
+
|
|
31
|
+
facts = load_from_json("sample_memories.json")
|
|
32
|
+
detector = StaleDetector()
|
|
33
|
+
report = detector.check(facts)
|
|
34
|
+
|
|
35
|
+
print(f"Total: {report.total_facts} | Flagged: {len(report.flagged)}")
|
|
36
|
+
for result in report.flagged:
|
|
37
|
+
print(f" [{result.staleness_level.value.upper()}] {result.content}")
|
|
38
|
+
print(f" Reason: {result.reason}")
|
|
39
|
+
print(f" Action: {result.recommendation}")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## CLI Usage
|
|
43
|
+
|
|
44
|
+
Check all facts:
|
|
45
|
+
```bash
|
|
46
|
+
memlint check memories.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Show only stale and expired:
|
|
50
|
+
```bash
|
|
51
|
+
memlint check memories.json --only-flagged
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Output raw JSON:
|
|
55
|
+
```bash
|
|
56
|
+
memlint check memories.json --json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Parse Mem0 format:
|
|
60
|
+
```bash
|
|
61
|
+
memlint check memories.json --format mem0
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Sample output:
|
|
65
|
+
```
|
|
66
|
+
╭──────────┬────────────────────────────────────────┬────────────┬─────┬───────┬─────────┬─────────╮
|
|
67
|
+
│ ID │ Content │ Category │ Age │ Score │ Level │ Action │
|
|
68
|
+
├──────────┼────────────────────────────────────────┼────────────┼─────┼───────┼─────────┼─────────┤
|
|
69
|
+
│ mem_004 │ User works at XYZ as a senior cons... │ employment │ 279 │ 0.70 │ STALE │ flag │
|
|
70
|
+
│ mem_006 │ User debugged a LangGraph memory is... │ episodic │ 29 │ 1.00 │ EXPIRED │ discard │
|
|
71
|
+
╰──────────┴────────────────────────────────────────┴────────────┴─────┴───────┴─────────┴─────────╯
|
|
72
|
+
|
|
73
|
+
Checked 8 facts: 1 fresh, 2 aging, 3 stale, 2 expired
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Staleness Score Explained
|
|
77
|
+
|
|
78
|
+
Each fact is assigned a category with a natural lifespan:
|
|
79
|
+
|
|
80
|
+
| Category | Examples | Typical Valid Window |
|
|
81
|
+
|--------------|---------------------------------------|----------------------|
|
|
82
|
+
| `location` | "lives in Delhi", "office in Sector 5"| 6–24 months |
|
|
83
|
+
| `employment` | "works at xyz", "role is consultant" | 6–18 months |
|
|
84
|
+
| `project` | "building pract-agents", "using Pinecone" | 1–6 months |
|
|
85
|
+
| `preference` | "prefers Python", "uses dark mode" | 3–12 months |
|
|
86
|
+
| `relationship`| "manager is X", "team has 5 people" | 3–12 months |
|
|
87
|
+
| `identity` | "name is X", "speaks Hindi" | Very long/permanent |
|
|
88
|
+
| `episodic` | "debugged a LangGraph issue today" | Days to weeks |
|
|
89
|
+
| `system_fact`| "Python version is 3.10", "npm v9" | 1–3 months |
|
|
90
|
+
|
|
91
|
+
Score thresholds:
|
|
92
|
+
- `0.0 – 0.29` → **FRESH** (safe to use)
|
|
93
|
+
- `0.30 – 0.59` → **AGING** (use with caution)
|
|
94
|
+
- `0.60 – 0.79` → **STALE** (flag before injecting)
|
|
95
|
+
- `0.80 – 1.0` → **EXPIRED** (do not inject without reconfirmation)
|
|
96
|
+
|
|
97
|
+
## Adapters
|
|
98
|
+
|
|
99
|
+
**JSON**: default format:
|
|
100
|
+
```python
|
|
101
|
+
from memlint.adapters.json_adapter import load_from_json
|
|
102
|
+
facts = load_from_json("memories.json")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Mem0**: maps `memory` to `content`, `updated_at` to `last_confirmed_at`:
|
|
106
|
+
```python
|
|
107
|
+
from memlint.adapters.mem0_adapter import load_from_mem0
|
|
108
|
+
facts = load_from_mem0("mem0_export.json")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**LangChain**: two tools: `check_memory_staleness` and `filter_stale_memories` (see below).
|
|
112
|
+
|
|
113
|
+
## LangChain / LangGraph Integration
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from memlint.adapters.langchain_tool import (
|
|
117
|
+
check_memory_staleness,
|
|
118
|
+
filter_stale_memories,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# In a LangGraph node: filter before injecting memories into the LLM
|
|
122
|
+
safe_facts_json = filter_stale_memories.invoke({"facts_json": memories_json_string})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Requires `pip install memlint[llm]`.
|
|
126
|
+
|
|
127
|
+
## Contributing
|
|
128
|
+
|
|
129
|
+
Open an issue or pull request at the project repository.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
|
5
|
+
|
|
6
|
+
from memlint import StaleDetector
|
|
7
|
+
from memlint.adapters.json_adapter import load_from_json
|
|
8
|
+
|
|
9
|
+
facts = load_from_json(os.path.join(os.path.dirname(__file__), "sample_memories.json"))
|
|
10
|
+
detector = StaleDetector()
|
|
11
|
+
report = detector.check(facts)
|
|
12
|
+
|
|
13
|
+
print(f"Total: {report.total_facts} | Flagged: {len(report.flagged)}")
|
|
14
|
+
for result in report.flagged:
|
|
15
|
+
print(f" [{result.staleness_level.value.upper()}] {result.content}")
|
|
16
|
+
print(f" Reason: {result.reason}")
|
|
17
|
+
print(f" Action: {result.recommendation}")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangChain/LangGraph integration example.
|
|
3
|
+
|
|
4
|
+
Replace the mock invocation with a real LangGraph node in production.
|
|
5
|
+
Requires: pip install memlint[llm]
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from memlint.adapters.langchain_tool import (
|
|
15
|
+
check_memory_staleness,
|
|
16
|
+
filter_stale_memories,
|
|
17
|
+
LANGCHAIN_AVAILABLE,
|
|
18
|
+
)
|
|
19
|
+
except ImportError:
|
|
20
|
+
LANGCHAIN_AVAILABLE = False
|
|
21
|
+
|
|
22
|
+
if not LANGCHAIN_AVAILABLE:
|
|
23
|
+
print("langchain-core not installed. Run: pip install memlint[llm]")
|
|
24
|
+
sys.exit(0)
|
|
25
|
+
|
|
26
|
+
# --- Replace with real LangGraph node invocation in production ---
|
|
27
|
+
sample_fact = {
|
|
28
|
+
"id": "mem_001",
|
|
29
|
+
"content": "User works at PwC",
|
|
30
|
+
"created_at": "2024-09-01T00:00:00",
|
|
31
|
+
"confirmation_count": 0,
|
|
32
|
+
"source": "user",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Tool 1: check a single fact
|
|
36
|
+
result_json = check_memory_staleness.invoke({"fact_json": json.dumps(sample_fact)})
|
|
37
|
+
print("Single fact result:", result_json)
|
|
38
|
+
|
|
39
|
+
# Tool 2: filter a list — returns only FRESH and AGING facts
|
|
40
|
+
safe_json = filter_stale_memories.invoke({"facts_json": json.dumps([sample_fact])})
|
|
41
|
+
print("Safe facts:", safe_json)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "mem_001",
|
|
4
|
+
"content": "User prefers Python over JavaScript",
|
|
5
|
+
"created_at": "2026-05-20T09:00:00",
|
|
6
|
+
"last_confirmed_at": "2026-06-01T09:00:00",
|
|
7
|
+
"confirmation_count": 3,
|
|
8
|
+
"source": "user"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "mem_002",
|
|
12
|
+
"content": "User prefers dark mode in all editors",
|
|
13
|
+
"created_at": "2026-01-28T10:00:00",
|
|
14
|
+
"confirmation_count": 0,
|
|
15
|
+
"source": "user"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "mem_003",
|
|
19
|
+
"content": "User is based in New Delhi for work",
|
|
20
|
+
"created_at": "2025-11-19T08:00:00",
|
|
21
|
+
"confirmation_count": 0,
|
|
22
|
+
"source": "user"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "mem_004",
|
|
26
|
+
"content": "User works at PwC as a senior consultant",
|
|
27
|
+
"created_at": "2025-08-31T09:00:00",
|
|
28
|
+
"confirmation_count": 0,
|
|
29
|
+
"source": "user"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "mem_005",
|
|
33
|
+
"content": "User is building pract-agents using LangGraph framework",
|
|
34
|
+
"created_at": "2026-02-07T11:00:00",
|
|
35
|
+
"confirmation_count": 0,
|
|
36
|
+
"source": "user"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "mem_006",
|
|
40
|
+
"content": "User debugged a LangGraph memory issue this morning",
|
|
41
|
+
"created_at": "2026-05-08T07:00:00",
|
|
42
|
+
"confirmation_count": 0,
|
|
43
|
+
"source": "user"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "mem_007",
|
|
47
|
+
"content": "User lives in Delhi",
|
|
48
|
+
"created_at": "2025-05-03T10:00:00",
|
|
49
|
+
"confirmation_count": 0,
|
|
50
|
+
"source": "user"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "mem_008",
|
|
54
|
+
"content": "User lives in Mumbai now",
|
|
55
|
+
"created_at": "2026-04-18T10:00:00",
|
|
56
|
+
"confirmation_count": 0,
|
|
57
|
+
"source": "user"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from memlint.core import StaleDetector
|
|
2
|
+
from memlint.models import (
|
|
3
|
+
MemoryFact,
|
|
4
|
+
StalenessResult,
|
|
5
|
+
DetectionReport,
|
|
6
|
+
FactCategory,
|
|
7
|
+
StalenessLevel,
|
|
8
|
+
)
|
|
9
|
+
from memlint.classifier import classify_fact_async
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"StaleDetector",
|
|
13
|
+
"MemoryFact",
|
|
14
|
+
"StalenessResult",
|
|
15
|
+
"DetectionReport",
|
|
16
|
+
"FactCategory",
|
|
17
|
+
"StalenessLevel",
|
|
18
|
+
"classify_fact_async",
|
|
19
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from memlint.adapters._utils import parse_dt
|
|
3
|
+
from memlint.models import MemoryFact
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load_from_json(filepath: str) -> list[MemoryFact]:
|
|
7
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
8
|
+
data = json.load(f)
|
|
9
|
+
|
|
10
|
+
if not isinstance(data, list):
|
|
11
|
+
raise ValueError(f"Expected a JSON array at root, got {type(data).__name__}")
|
|
12
|
+
|
|
13
|
+
facts = []
|
|
14
|
+
for i, entry in enumerate(data):
|
|
15
|
+
for required in ("id", "content", "created_at"):
|
|
16
|
+
if required not in entry:
|
|
17
|
+
raise ValueError(f"Entry {i} missing required field '{required}'")
|
|
18
|
+
|
|
19
|
+
facts.append(MemoryFact(
|
|
20
|
+
id=entry["id"],
|
|
21
|
+
content=entry["content"],
|
|
22
|
+
created_at=parse_dt(entry["created_at"]),
|
|
23
|
+
last_confirmed_at=parse_dt(entry.get("last_confirmed_at")),
|
|
24
|
+
confirmation_count=entry.get("confirmation_count", 0),
|
|
25
|
+
source=entry.get("source", "user"),
|
|
26
|
+
metadata=entry.get("metadata", {}),
|
|
27
|
+
))
|
|
28
|
+
return facts
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from memlint.models import MemoryFact
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from langchain_core.tools import tool
|
|
6
|
+
LANGCHAIN_AVAILABLE = True
|
|
7
|
+
except ImportError:
|
|
8
|
+
LANGCHAIN_AVAILABLE = False
|
|
9
|
+
|
|
10
|
+
if LANGCHAIN_AVAILABLE:
|
|
11
|
+
from memlint.core import StaleDetector
|
|
12
|
+
|
|
13
|
+
@tool
|
|
14
|
+
def check_memory_staleness(fact_json: str) -> str:
|
|
15
|
+
"""Check if a single memory fact is stale before injecting it into context."""
|
|
16
|
+
fact = MemoryFact.model_validate(json.loads(fact_json))
|
|
17
|
+
result = StaleDetector().check_one(fact)
|
|
18
|
+
return result.model_dump_json()
|
|
19
|
+
|
|
20
|
+
@tool
|
|
21
|
+
def filter_stale_memories(facts_json: str) -> str:
|
|
22
|
+
"""Filter out stale and expired memory facts from a list, returning only safe-to-use facts."""
|
|
23
|
+
facts = [MemoryFact.model_validate(d) for d in json.loads(facts_json)]
|
|
24
|
+
safe = StaleDetector().filter_safe(facts)
|
|
25
|
+
return json.dumps([f.model_dump() for f in safe], default=str)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from memlint.adapters._utils import parse_dt
|
|
3
|
+
from memlint.models import MemoryFact
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load_from_mem0(filepath: str) -> list[MemoryFact]:
|
|
7
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
8
|
+
data = json.load(f)
|
|
9
|
+
|
|
10
|
+
if not isinstance(data, list):
|
|
11
|
+
raise ValueError(f"Expected a JSON array at root, got {type(data).__name__}")
|
|
12
|
+
|
|
13
|
+
facts = []
|
|
14
|
+
for i, entry in enumerate(data):
|
|
15
|
+
for required in ("id", "memory", "created_at"):
|
|
16
|
+
if required not in entry:
|
|
17
|
+
raise ValueError(f"Entry {i} missing required field '{required}'")
|
|
18
|
+
|
|
19
|
+
facts.append(MemoryFact(
|
|
20
|
+
id=entry["id"],
|
|
21
|
+
content=entry["memory"],
|
|
22
|
+
created_at=parse_dt(entry["created_at"]),
|
|
23
|
+
last_confirmed_at=parse_dt(entry.get("updated_at")),
|
|
24
|
+
confirmation_count=entry.get("confirmation_count", 0),
|
|
25
|
+
source=entry.get("source", "user"),
|
|
26
|
+
metadata=entry.get("metadata", {}),
|
|
27
|
+
))
|
|
28
|
+
return facts
|