forgetted 0.2.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.
- forgetted-0.2.0/PKG-INFO +219 -0
- forgetted-0.2.0/README.md +190 -0
- forgetted-0.2.0/forgetted/__init__.py +41 -0
- forgetted-0.2.0/forgetted/adapters/__init__.py +25 -0
- forgetted-0.2.0/forgetted/adapters/base.py +72 -0
- forgetted-0.2.0/forgetted/adapters/file_write.py +53 -0
- forgetted-0.2.0/forgetted/adapters/mem0.py +127 -0
- forgetted-0.2.0/forgetted/checkpoint.py +82 -0
- forgetted-0.2.0/forgetted/cleaner.py +88 -0
- forgetted-0.2.0/forgetted/guard.py +161 -0
- forgetted-0.2.0/forgetted/session.py +171 -0
- forgetted-0.2.0/forgetted/trigger.py +35 -0
- forgetted-0.2.0/forgetted.egg-info/PKG-INFO +219 -0
- forgetted-0.2.0/forgetted.egg-info/SOURCES.txt +21 -0
- forgetted-0.2.0/forgetted.egg-info/dependency_links.txt +1 -0
- forgetted-0.2.0/forgetted.egg-info/requires.txt +8 -0
- forgetted-0.2.0/forgetted.egg-info/top_level.txt +1 -0
- forgetted-0.2.0/pyproject.toml +56 -0
- forgetted-0.2.0/setup.cfg +4 -0
- forgetted-0.2.0/tests/test_adapters.py +154 -0
- forgetted-0.2.0/tests/test_adversarial.py +294 -0
- forgetted-0.2.0/tests/test_forgetted.py +296 -0
- forgetted-0.2.0/tests/test_session.py +267 -0
forgetted-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: forgetted
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Selective memory governance for AI agents โ branch the timeline, never merge back
|
|
5
|
+
Author-email: Hermes Labs <lpcisystems@gmail.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/roli-lpci/forgetted
|
|
8
|
+
Project-URL: Repository, https://github.com/roli-lpci/forgetted
|
|
9
|
+
Project-URL: Issues, https://github.com/roli-lpci/forgetted/issues
|
|
10
|
+
Keywords: llm,agent,memory,privacy,ai-safety,fork,selective-forget
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Security
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Provides-Extra: trash
|
|
24
|
+
Requires-Dist: send2trash>=1.8.0; extra == "trash"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<h1 align="center">๐ซฅ forgetted</h1>
|
|
32
|
+
<p align="center"><strong>Your AI agent remembers everything. Now it doesn't have to.</strong></p>
|
|
33
|
+
<p align="center">
|
|
34
|
+
<a href="https://pypi.org/project/forgetted/"><img src="https://img.shields.io/pypi/v/forgetted?color=blue" alt="PyPI"></a>
|
|
35
|
+
<a href="https://github.com/roli-lpci/forgetted/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="License"></a>
|
|
36
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.9+-blue" alt="Python"></a>
|
|
37
|
+
</p>
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
**forgetted** gives AI agents selective memory governance. One line of code, and your agent keeps full context but writes nothing to memory.
|
|
43
|
+
|
|
44
|
+
> Traditional incognito is dumb: no past, no future, fully isolated.
|
|
45
|
+
> **forgetted** gives you: full continuity + selective non-persistence.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from forgetted import ForgetSession
|
|
49
|
+
|
|
50
|
+
with ForgetSession("/path/to/workspace"):
|
|
51
|
+
agent.chat("this conversation never happened")
|
|
52
|
+
# โ No trace in memory, logs, or vector DB. Agent resumes normally.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Why?
|
|
56
|
+
|
|
57
|
+
AI agents write everything: memory files, session logs, vector embeddings, deliverables. Sometimes you need context without consequences:
|
|
58
|
+
|
|
59
|
+
- ๐ฌ **Sensitive conversations** that shouldn't persist in agent memory
|
|
60
|
+
- ๐งช **Experiments** you don't want polluting your agent's knowledge base
|
|
61
|
+
- ๐ **Client data** discussed but not stored
|
|
62
|
+
- ๐ค **Brainstorming** that shouldn't bias future responses
|
|
63
|
+
|
|
64
|
+
**forgetted** is not a prompt. It's software that wraps the agent's persistence layer โ writes silently vanish, reads still work, and the agent resumes normally after.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install forgetted
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
### Simple (file-level protection)
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from forgetted import ForgetSession
|
|
78
|
+
|
|
79
|
+
# Everything inside is forgetted โ writes to memory/, logs, deliverables vanish
|
|
80
|
+
with ForgetSession("/path/to/agent/workspace"):
|
|
81
|
+
agent.chat("tell me about the secret project")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### With vector DB protection
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from forgetted import ForgetSession
|
|
88
|
+
from forgetted.adapters.mem0 import Mem0Adapter
|
|
89
|
+
|
|
90
|
+
session = ForgetSession(
|
|
91
|
+
workspace="/path/to/workspace",
|
|
92
|
+
adapters=[Mem0Adapter(memory_instance, user_id="roli")],
|
|
93
|
+
)
|
|
94
|
+
session.start(checkpoint_summary="Discussing API design")
|
|
95
|
+
# ... conversation happens with full context, zero persistence ...
|
|
96
|
+
session.stop() # re-enables all layers, cleans up
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Trigger detection (for chat agents)
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from forgetted import is_forget_trigger, ForgetSession
|
|
103
|
+
|
|
104
|
+
if is_forget_trigger(user_message): # "/forget", "off the record", etc.
|
|
105
|
+
with ForgetSession(workspace):
|
|
106
|
+
handle_conversation()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## What Gets Blocked
|
|
110
|
+
|
|
111
|
+
| Layer | How | Status |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| Memory files (`memory/*.md`) | `builtins.open` patch | โ
Blocked |
|
|
114
|
+
| Deliverables / audit logs | `builtins.open` patch | โ
Blocked |
|
|
115
|
+
| Session logs (`*.jsonl`) | Blocked + deleted on exit | โ
Blocked |
|
|
116
|
+
| mem0 / semantic memory | Method patch on `add`/`update` | โ
Blocked |
|
|
117
|
+
| Any custom persistence | Write your own adapter | ๐ Extensible |
|
|
118
|
+
|
|
119
|
+
## How It Works
|
|
120
|
+
|
|
121
|
+
**forgetted** uses a layered defense:
|
|
122
|
+
|
|
123
|
+
1. **`FileWriteAdapter`** (always on) โ patches `builtins.open` to intercept writes to protected paths. Returns no-op file handles instead of raising โ agent code doesn't crash, writes just vanish.
|
|
124
|
+
|
|
125
|
+
2. **`Mem0Adapter`** (opt-in) โ patches `memory.add()` and `memory.update()` during the window. Post-window cleanup deletes any memories that leaked through.
|
|
126
|
+
|
|
127
|
+
3. **`ForgetSession`** orchestrates everything: checkpoint โ disable adapters โ run conversation โ enable adapters โ cleanup โ delete session log.
|
|
128
|
+
|
|
129
|
+
Reads are **never** blocked. The agent has full context โ it just can't write new context.
|
|
130
|
+
|
|
131
|
+
## Write Your Own Adapter
|
|
132
|
+
|
|
133
|
+
Any persistence layer can be controlled:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from forgetted.adapters.base import PersistenceAdapter
|
|
137
|
+
|
|
138
|
+
class RedisAdapter(PersistenceAdapter):
|
|
139
|
+
name = "redis"
|
|
140
|
+
|
|
141
|
+
def disable(self):
|
|
142
|
+
self._client.config_set("save", "")
|
|
143
|
+
self._active = True
|
|
144
|
+
|
|
145
|
+
def enable(self):
|
|
146
|
+
self._client.config_set("save", "3600 1")
|
|
147
|
+
self._active = False
|
|
148
|
+
|
|
149
|
+
def cleanup(self):
|
|
150
|
+
for key in self._window_keys:
|
|
151
|
+
self._client.delete(key)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def is_active(self): return self._active
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Register it: `ForgetSession(workspace, adapters=[RedisAdapter(client)])`
|
|
158
|
+
|
|
159
|
+
## Trigger Phrases
|
|
160
|
+
|
|
161
|
+
Built-in detection for natural-language triggers:
|
|
162
|
+
|
|
163
|
+
| Trigger | Example |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `/forgetted` | "/forgetted" |
|
|
166
|
+
| `/forget` | "/forget" |
|
|
167
|
+
| `forget this` | "hey, forget this conversation" |
|
|
168
|
+
| `off the record` | "let's go off the record" |
|
|
169
|
+
| `forgetted mode` | "enable forgetted mode" |
|
|
170
|
+
| `don't remember this` | "don't remember this" |
|
|
171
|
+
|
|
172
|
+
## Architecture
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
176
|
+
โ ForgetSession โ
|
|
177
|
+
โ (orchestrator โ context manager) โ
|
|
178
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
179
|
+
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
|
|
180
|
+
โ โ FileWrite โ โ Mem0 โ โ
|
|
181
|
+
โ โ Adapter โ โ Adapter โ ... โ
|
|
182
|
+
โ โ (safety net) โ โ (opt-in) โ โ
|
|
183
|
+
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
|
|
184
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
185
|
+
โ checkpoint โ disable โ conversation โ
|
|
186
|
+
โ โ enable โ cleanup โ delete log โ
|
|
187
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## What This Really Is
|
|
191
|
+
|
|
192
|
+
This is not a UX toggle. It's a **memory governance primitive**.
|
|
193
|
+
|
|
194
|
+
Like git: you branch, but you never merge back. The conversation exists in context but is never written to the agent's persistent state. After the window closes, it's as if it never happened.
|
|
195
|
+
|
|
196
|
+
> *"I want contextโฆ but I don't want consequences."*
|
|
197
|
+
|
|
198
|
+
## Tested
|
|
199
|
+
|
|
200
|
+
97 tests including an adversarial suite:
|
|
201
|
+
- โ
Write blocking (open w/a/x/wb/r+, symlinks, binary)
|
|
202
|
+
- โ
Trigger detection (zero false positives on "forgot password", "forgetful", etc.)
|
|
203
|
+
- โ
Adapter error isolation (one failing adapter doesn't break others)
|
|
204
|
+
- โ
Exception safety (cleanup runs even if conversation crashes)
|
|
205
|
+
- โ
Idempotency (double-start, stop-before-start, double-stop all safe)
|
|
206
|
+
|
|
207
|
+
Known limitations are [documented as xfail tests](tests/test_adversarial.py) โ not hidden.
|
|
208
|
+
|
|
209
|
+
## Threat Model
|
|
210
|
+
|
|
211
|
+
**What forgetted blocks:** Everything the agent controls โ memory files, vector DB writes, session logs, deliverables.
|
|
212
|
+
|
|
213
|
+
**What forgetted does NOT block:** LLM API provider logs, network telemetry, OS-level forensics. That's not the point.
|
|
214
|
+
|
|
215
|
+
**The guarantee:** *"If someone looks through the agent's memory and logs, they won't find what you forgetted."*
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
[Apache-2.0](LICENSE) โ Hermes Labs
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">๐ซฅ forgetted</h1>
|
|
3
|
+
<p align="center"><strong>Your AI agent remembers everything. Now it doesn't have to.</strong></p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://pypi.org/project/forgetted/"><img src="https://img.shields.io/pypi/v/forgetted?color=blue" alt="PyPI"></a>
|
|
6
|
+
<a href="https://github.com/roli-lpci/forgetted/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="License"></a>
|
|
7
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.9+-blue" alt="Python"></a>
|
|
8
|
+
</p>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
**forgetted** gives AI agents selective memory governance. One line of code, and your agent keeps full context but writes nothing to memory.
|
|
14
|
+
|
|
15
|
+
> Traditional incognito is dumb: no past, no future, fully isolated.
|
|
16
|
+
> **forgetted** gives you: full continuity + selective non-persistence.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from forgetted import ForgetSession
|
|
20
|
+
|
|
21
|
+
with ForgetSession("/path/to/workspace"):
|
|
22
|
+
agent.chat("this conversation never happened")
|
|
23
|
+
# โ No trace in memory, logs, or vector DB. Agent resumes normally.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Why?
|
|
27
|
+
|
|
28
|
+
AI agents write everything: memory files, session logs, vector embeddings, deliverables. Sometimes you need context without consequences:
|
|
29
|
+
|
|
30
|
+
- ๐ฌ **Sensitive conversations** that shouldn't persist in agent memory
|
|
31
|
+
- ๐งช **Experiments** you don't want polluting your agent's knowledge base
|
|
32
|
+
- ๐ **Client data** discussed but not stored
|
|
33
|
+
- ๐ค **Brainstorming** that shouldn't bias future responses
|
|
34
|
+
|
|
35
|
+
**forgetted** is not a prompt. It's software that wraps the agent's persistence layer โ writes silently vanish, reads still work, and the agent resumes normally after.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install forgetted
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### Simple (file-level protection)
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from forgetted import ForgetSession
|
|
49
|
+
|
|
50
|
+
# Everything inside is forgetted โ writes to memory/, logs, deliverables vanish
|
|
51
|
+
with ForgetSession("/path/to/agent/workspace"):
|
|
52
|
+
agent.chat("tell me about the secret project")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### With vector DB protection
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from forgetted import ForgetSession
|
|
59
|
+
from forgetted.adapters.mem0 import Mem0Adapter
|
|
60
|
+
|
|
61
|
+
session = ForgetSession(
|
|
62
|
+
workspace="/path/to/workspace",
|
|
63
|
+
adapters=[Mem0Adapter(memory_instance, user_id="roli")],
|
|
64
|
+
)
|
|
65
|
+
session.start(checkpoint_summary="Discussing API design")
|
|
66
|
+
# ... conversation happens with full context, zero persistence ...
|
|
67
|
+
session.stop() # re-enables all layers, cleans up
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Trigger detection (for chat agents)
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from forgetted import is_forget_trigger, ForgetSession
|
|
74
|
+
|
|
75
|
+
if is_forget_trigger(user_message): # "/forget", "off the record", etc.
|
|
76
|
+
with ForgetSession(workspace):
|
|
77
|
+
handle_conversation()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## What Gets Blocked
|
|
81
|
+
|
|
82
|
+
| Layer | How | Status |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| Memory files (`memory/*.md`) | `builtins.open` patch | โ
Blocked |
|
|
85
|
+
| Deliverables / audit logs | `builtins.open` patch | โ
Blocked |
|
|
86
|
+
| Session logs (`*.jsonl`) | Blocked + deleted on exit | โ
Blocked |
|
|
87
|
+
| mem0 / semantic memory | Method patch on `add`/`update` | โ
Blocked |
|
|
88
|
+
| Any custom persistence | Write your own adapter | ๐ Extensible |
|
|
89
|
+
|
|
90
|
+
## How It Works
|
|
91
|
+
|
|
92
|
+
**forgetted** uses a layered defense:
|
|
93
|
+
|
|
94
|
+
1. **`FileWriteAdapter`** (always on) โ patches `builtins.open` to intercept writes to protected paths. Returns no-op file handles instead of raising โ agent code doesn't crash, writes just vanish.
|
|
95
|
+
|
|
96
|
+
2. **`Mem0Adapter`** (opt-in) โ patches `memory.add()` and `memory.update()` during the window. Post-window cleanup deletes any memories that leaked through.
|
|
97
|
+
|
|
98
|
+
3. **`ForgetSession`** orchestrates everything: checkpoint โ disable adapters โ run conversation โ enable adapters โ cleanup โ delete session log.
|
|
99
|
+
|
|
100
|
+
Reads are **never** blocked. The agent has full context โ it just can't write new context.
|
|
101
|
+
|
|
102
|
+
## Write Your Own Adapter
|
|
103
|
+
|
|
104
|
+
Any persistence layer can be controlled:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from forgetted.adapters.base import PersistenceAdapter
|
|
108
|
+
|
|
109
|
+
class RedisAdapter(PersistenceAdapter):
|
|
110
|
+
name = "redis"
|
|
111
|
+
|
|
112
|
+
def disable(self):
|
|
113
|
+
self._client.config_set("save", "")
|
|
114
|
+
self._active = True
|
|
115
|
+
|
|
116
|
+
def enable(self):
|
|
117
|
+
self._client.config_set("save", "3600 1")
|
|
118
|
+
self._active = False
|
|
119
|
+
|
|
120
|
+
def cleanup(self):
|
|
121
|
+
for key in self._window_keys:
|
|
122
|
+
self._client.delete(key)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def is_active(self): return self._active
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Register it: `ForgetSession(workspace, adapters=[RedisAdapter(client)])`
|
|
129
|
+
|
|
130
|
+
## Trigger Phrases
|
|
131
|
+
|
|
132
|
+
Built-in detection for natural-language triggers:
|
|
133
|
+
|
|
134
|
+
| Trigger | Example |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `/forgetted` | "/forgetted" |
|
|
137
|
+
| `/forget` | "/forget" |
|
|
138
|
+
| `forget this` | "hey, forget this conversation" |
|
|
139
|
+
| `off the record` | "let's go off the record" |
|
|
140
|
+
| `forgetted mode` | "enable forgetted mode" |
|
|
141
|
+
| `don't remember this` | "don't remember this" |
|
|
142
|
+
|
|
143
|
+
## Architecture
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
147
|
+
โ ForgetSession โ
|
|
148
|
+
โ (orchestrator โ context manager) โ
|
|
149
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
150
|
+
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
|
|
151
|
+
โ โ FileWrite โ โ Mem0 โ โ
|
|
152
|
+
โ โ Adapter โ โ Adapter โ ... โ
|
|
153
|
+
โ โ (safety net) โ โ (opt-in) โ โ
|
|
154
|
+
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
|
|
155
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
156
|
+
โ checkpoint โ disable โ conversation โ
|
|
157
|
+
โ โ enable โ cleanup โ delete log โ
|
|
158
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## What This Really Is
|
|
162
|
+
|
|
163
|
+
This is not a UX toggle. It's a **memory governance primitive**.
|
|
164
|
+
|
|
165
|
+
Like git: you branch, but you never merge back. The conversation exists in context but is never written to the agent's persistent state. After the window closes, it's as if it never happened.
|
|
166
|
+
|
|
167
|
+
> *"I want contextโฆ but I don't want consequences."*
|
|
168
|
+
|
|
169
|
+
## Tested
|
|
170
|
+
|
|
171
|
+
97 tests including an adversarial suite:
|
|
172
|
+
- โ
Write blocking (open w/a/x/wb/r+, symlinks, binary)
|
|
173
|
+
- โ
Trigger detection (zero false positives on "forgot password", "forgetful", etc.)
|
|
174
|
+
- โ
Adapter error isolation (one failing adapter doesn't break others)
|
|
175
|
+
- โ
Exception safety (cleanup runs even if conversation crashes)
|
|
176
|
+
- โ
Idempotency (double-start, stop-before-start, double-stop all safe)
|
|
177
|
+
|
|
178
|
+
Known limitations are [documented as xfail tests](tests/test_adversarial.py) โ not hidden.
|
|
179
|
+
|
|
180
|
+
## Threat Model
|
|
181
|
+
|
|
182
|
+
**What forgetted blocks:** Everything the agent controls โ memory files, vector DB writes, session logs, deliverables.
|
|
183
|
+
|
|
184
|
+
**What forgetted does NOT block:** LLM API provider logs, network telemetry, OS-level forensics. That's not the point.
|
|
185
|
+
|
|
186
|
+
**The guarantee:** *"If someone looks through the agent's memory and logs, they won't find what you forgetted."*
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
[Apache-2.0](LICENSE) โ Hermes Labs
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
forgetted โ Selective memory governance for AI agents.
|
|
3
|
+
|
|
4
|
+
Branch the timeline, but never merge back.
|
|
5
|
+
|
|
6
|
+
When triggered, forgetted:
|
|
7
|
+
1. Checkpoints the current session context into a resumption file
|
|
8
|
+
2. Blocks all persistence layers via registered adapters
|
|
9
|
+
3. Self-cleans on exit (session logs, leaked memories)
|
|
10
|
+
4. Enables seamless resume from the checkpoint in the next session
|
|
11
|
+
|
|
12
|
+
This is not incognito mode. This is a fork without consequence โ
|
|
13
|
+
a memory architecture primitive that gives users control over
|
|
14
|
+
what becomes part of their agent's memory.
|
|
15
|
+
|
|
16
|
+
Author: Hermes Labs
|
|
17
|
+
License: Apache-2.0
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .adapters.base import PersistenceAdapter
|
|
21
|
+
from .adapters.file_write import FileWriteAdapter
|
|
22
|
+
from .checkpoint import create_checkpoint, load_checkpoint
|
|
23
|
+
from .cleaner import delete_session_log, find_session_log
|
|
24
|
+
from .guard import ForgetGuard
|
|
25
|
+
from .session import ForgetSession
|
|
26
|
+
from .trigger import TRIGGERS, is_forget_trigger
|
|
27
|
+
|
|
28
|
+
__version__ = "0.2.0"
|
|
29
|
+
__author__ = "Hermes Labs"
|
|
30
|
+
__all__ = [
|
|
31
|
+
"create_checkpoint",
|
|
32
|
+
"delete_session_log",
|
|
33
|
+
"FileWriteAdapter",
|
|
34
|
+
"find_session_log",
|
|
35
|
+
"ForgetGuard",
|
|
36
|
+
"ForgetSession",
|
|
37
|
+
"is_forget_trigger",
|
|
38
|
+
"load_checkpoint",
|
|
39
|
+
"PersistenceAdapter",
|
|
40
|
+
"TRIGGERS",
|
|
41
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
forgetted.adapters โ Persistence layer adapters.
|
|
3
|
+
|
|
4
|
+
Built-in adapters:
|
|
5
|
+
- FileWriteAdapter: blocks file writes via builtins.open patch
|
|
6
|
+
- Mem0Adapter: blocks mem0 add/update (requires mem0ai)
|
|
7
|
+
|
|
8
|
+
Custom adapters: subclass ``PersistenceAdapter`` from ``forgetted.adapters.base``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .base import PersistenceAdapter
|
|
12
|
+
from .file_write import FileWriteAdapter
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"PersistenceAdapter",
|
|
16
|
+
"FileWriteAdapter",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
# Optional adapters โ import only if deps are available.
|
|
20
|
+
try:
|
|
21
|
+
from .mem0 import Mem0Adapter # noqa: F401
|
|
22
|
+
|
|
23
|
+
__all__.append("Mem0Adapter")
|
|
24
|
+
except ImportError:
|
|
25
|
+
pass
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
forgetted.adapters.base โ Abstract base for persistence adapters.
|
|
3
|
+
|
|
4
|
+
Every persistence layer that forgetted can control implements this interface.
|
|
5
|
+
Adapters are registered with a ForgetSession, which calls disable/enable/cleanup
|
|
6
|
+
at the appropriate times.
|
|
7
|
+
|
|
8
|
+
To write a custom adapter::
|
|
9
|
+
|
|
10
|
+
from forgetted.adapters.base import PersistenceAdapter
|
|
11
|
+
|
|
12
|
+
class MyVectorDBAdapter(PersistenceAdapter):
|
|
13
|
+
name = "my-vector-db"
|
|
14
|
+
|
|
15
|
+
def disable(self):
|
|
16
|
+
self._client.pause_writes()
|
|
17
|
+
self._active = False
|
|
18
|
+
|
|
19
|
+
def enable(self):
|
|
20
|
+
self._client.resume_writes()
|
|
21
|
+
self._active = True
|
|
22
|
+
|
|
23
|
+
def cleanup(self):
|
|
24
|
+
self._client.delete_since(self._window_start)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from abc import ABC, abstractmethod
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PersistenceAdapter(ABC):
|
|
31
|
+
"""Interface for a persistence layer that forgetted can control.
|
|
32
|
+
|
|
33
|
+
Subclasses must implement ``disable``, ``enable``, ``cleanup``,
|
|
34
|
+
and the ``name`` and ``is_active`` properties.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def name(self) -> str:
|
|
40
|
+
"""Human-readable identifier for this adapter (e.g., 'mem0', 'file-write')."""
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def is_active(self) -> bool:
|
|
45
|
+
"""True when writes are being blocked (adapter is in disabled/forgetted state)."""
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def disable(self) -> None:
|
|
49
|
+
"""Block writes through this persistence layer.
|
|
50
|
+
|
|
51
|
+
Called when a forgetted window starts. Must be idempotent โ
|
|
52
|
+
calling disable() twice should not error.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def enable(self) -> None:
|
|
57
|
+
"""Restore normal write behavior.
|
|
58
|
+
|
|
59
|
+
Called when a forgetted window ends. Must be idempotent โ
|
|
60
|
+
calling enable() twice should not error.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def cleanup(self) -> None:
|
|
65
|
+
"""Remove any data that leaked through during the forgetted window.
|
|
66
|
+
|
|
67
|
+
Called after enable(). This is the post-window sweep โ delete
|
|
68
|
+
any memories, embeddings, or logs that were written despite
|
|
69
|
+
the disable() call (e.g., by framework-level code).
|
|
70
|
+
|
|
71
|
+
Must be safe to call even if nothing leaked (no-op in that case).
|
|
72
|
+
"""
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
forgetted.adapters.file_write โ File write blocking adapter.
|
|
3
|
+
|
|
4
|
+
Wraps the existing ForgetGuard (builtins.open monkey-patch) as a
|
|
5
|
+
PersistenceAdapter. This is the safety-net layer โ it catches writes
|
|
6
|
+
that slip past higher-level adapters.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from ..guard import ForgetGuard
|
|
13
|
+
from .base import PersistenceAdapter
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FileWriteAdapter(PersistenceAdapter):
|
|
19
|
+
"""Adapter that blocks file writes to protected workspace paths.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
workspace_path : str
|
|
24
|
+
Absolute path to the agent workspace root.
|
|
25
|
+
extra_protected : set[str], optional
|
|
26
|
+
Additional relative paths or directory names to protect.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, workspace_path: str, extra_protected: Optional[set[str]] = None):
|
|
30
|
+
self._guard = ForgetGuard(workspace_path, extra_protected)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def name(self) -> str:
|
|
34
|
+
return "file-write"
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def is_active(self) -> bool:
|
|
38
|
+
return self._guard.active
|
|
39
|
+
|
|
40
|
+
def disable(self) -> None:
|
|
41
|
+
self._guard.start()
|
|
42
|
+
|
|
43
|
+
def enable(self) -> None:
|
|
44
|
+
self._guard.stop()
|
|
45
|
+
|
|
46
|
+
def cleanup(self) -> None:
|
|
47
|
+
# No cleanup needed โ writes were blocked, not captured.
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def blocked_count(self) -> int:
|
|
52
|
+
"""Number of write attempts blocked during the current/last window."""
|
|
53
|
+
return self._guard.blocked_count
|