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.
@@ -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