0
# Memory Tool Classes
1
2
Memory tools enable Claude to store and retrieve information across conversations by providing a file-system-like interface for persistent memory. Implement custom memory backends by subclassing the abstract memory tool classes.
3
4
## Capabilities
5
6
### Synchronous Memory Tool
7
8
Abstract base class for implementing synchronous memory backends that Claude can use to create, view, edit, and delete memory files.
9
10
```python { .api }
11
class BetaAbstractMemoryTool(BetaBuiltinFunctionTool):
12
"""
13
Abstract base class for synchronous memory tool implementations.
14
15
Subclass this to create custom memory storage solutions such as:
16
- File system storage
17
- Database-backed memory
18
- Cloud storage (S3, GCS, etc.)
19
- Encrypted memory stores
20
- In-memory data structures
21
22
The memory tool provides a file-system-like interface with commands
23
for viewing, creating, editing, deleting, and renaming memory files.
24
"""
25
26
def __init__(self, *, cache_control: BetaCacheControlEphemeralParam | None = None) -> None:
27
"""
28
Initialize the memory tool.
29
30
Args:
31
cache_control: Optional cache control for prompt caching
32
"""
33
34
def execute(self, command: BetaMemoryTool20250818Command) -> str | Iterable[BetaContent]:
35
"""
36
Execute a memory command and return the result.
37
38
This method dispatches to the appropriate handler method based on
39
the command type. You typically don't need to override this method.
40
41
Args:
42
command: Memory command to execute (view, create, str_replace, insert, delete, rename)
43
44
Returns:
45
Command result as string or content blocks
46
47
Raises:
48
NotImplementedError: If command type is unknown
49
"""
50
51
@abstractmethod
52
def view(self, command: BetaMemoryTool20250818ViewCommand) -> str | Iterable[BetaContent]:
53
"""
54
View the contents of a memory path.
55
56
Args:
57
command: View command with path to view
58
59
Returns:
60
File contents or directory listing
61
"""
62
63
@abstractmethod
64
def create(self, command: BetaMemoryTool20250818CreateCommand) -> str | Iterable[BetaContent]:
65
"""
66
Create a new memory file with the specified content.
67
68
Args:
69
command: Create command with path and content
70
71
Returns:
72
Success message or error
73
"""
74
75
@abstractmethod
76
def str_replace(self, command: BetaMemoryTool20250818StrReplaceCommand) -> str | Iterable[BetaContent]:
77
"""
78
Replace text in a memory file.
79
80
Args:
81
command: Replace command with path, old_str, and new_str
82
83
Returns:
84
Success message or error
85
"""
86
87
@abstractmethod
88
def insert(self, command: BetaMemoryTool20250818InsertCommand) -> str | Iterable[BetaContent]:
89
"""
90
Insert text at a specific line number in a memory file.
91
92
Args:
93
command: Insert command with path, line number, and content
94
95
Returns:
96
Success message or error
97
"""
98
99
@abstractmethod
100
def delete(self, command: BetaMemoryTool20250818DeleteCommand) -> str | Iterable[BetaContent]:
101
"""
102
Delete a memory file or directory.
103
104
Args:
105
command: Delete command with path to delete
106
107
Returns:
108
Success message or error
109
"""
110
111
@abstractmethod
112
def rename(self, command: BetaMemoryTool20250818RenameCommand) -> str | Iterable[BetaContent]:
113
"""
114
Rename or move a memory file or directory.
115
116
Args:
117
command: Rename command with old_path and new_path
118
119
Returns:
120
Success message or error
121
"""
122
123
def clear_all_memory(self) -> str | Iterable[BetaContent]:
124
"""
125
Clear all memory data.
126
127
Optional method for clearing entire memory store.
128
129
Returns:
130
Success message
131
132
Raises:
133
NotImplementedError: If not implemented by subclass
134
"""
135
```
136
137
#### Usage Examples
138
139
**Simple in-memory storage:**
140
141
```python
142
from anthropic import Anthropic
143
from anthropic.lib.tools import BetaAbstractMemoryTool
144
from anthropic.types.beta import (
145
BetaMemoryTool20250818ViewCommand,
146
BetaMemoryTool20250818CreateCommand,
147
BetaMemoryTool20250818StrReplaceCommand,
148
BetaMemoryTool20250818InsertCommand,
149
BetaMemoryTool20250818DeleteCommand,
150
BetaMemoryTool20250818RenameCommand,
151
)
152
153
class InMemoryTool(BetaAbstractMemoryTool):
154
"""Store memory in Python dictionaries."""
155
156
def __init__(self):
157
super().__init__()
158
self.files: dict[str, str] = {}
159
160
def view(self, command: BetaMemoryTool20250818ViewCommand) -> str:
161
path = command["path"]
162
if path not in self.files:
163
return f"Error: File not found: {path}"
164
return self.files[path]
165
166
def create(self, command: BetaMemoryTool20250818CreateCommand) -> str:
167
path = command["path"]
168
content = command["file_text"]
169
170
if path in self.files:
171
return f"Error: File already exists: {path}"
172
173
self.files[path] = content
174
return f"Created file: {path}"
175
176
def str_replace(self, command: BetaMemoryTool20250818StrReplaceCommand) -> str:
177
path = command["path"]
178
old_str = command["old_str"]
179
new_str = command["new_str"]
180
181
if path not in self.files:
182
return f"Error: File not found: {path}"
183
184
content = self.files[path]
185
if old_str not in content:
186
return f"Error: String not found: {old_str}"
187
188
self.files[path] = content.replace(old_str, new_str, 1)
189
return f"Replaced text in: {path}"
190
191
def insert(self, command: BetaMemoryTool20250818InsertCommand) -> str:
192
path = command["path"]
193
insert_line = command["insert_line"]
194
new_str = command["new_str"]
195
196
if path not in self.files:
197
return f"Error: File not found: {path}"
198
199
lines = self.files[path].split("\n")
200
if insert_line < 0 or insert_line > len(lines):
201
return f"Error: Invalid line number: {insert_line}"
202
203
lines.insert(insert_line, new_str)
204
self.files[path] = "\n".join(lines)
205
return f"Inserted text at line {insert_line} in: {path}"
206
207
def delete(self, command: BetaMemoryTool20250818DeleteCommand) -> str:
208
path = command["path"]
209
if path not in self.files:
210
return f"Error: File not found: {path}"
211
212
del self.files[path]
213
return f"Deleted: {path}"
214
215
def rename(self, command: BetaMemoryTool20250818RenameCommand) -> str:
216
old_path = command["old_path"]
217
new_path = command["new_path"]
218
219
if old_path not in self.files:
220
return f"Error: File not found: {old_path}"
221
if new_path in self.files:
222
return f"Error: Destination already exists: {new_path}"
223
224
self.files[new_path] = self.files[old_path]
225
del self.files[old_path]
226
return f"Renamed {old_path} to {new_path}"
227
228
# Use with Claude
229
client = Anthropic()
230
memory = InMemoryTool()
231
232
runner = client.beta.messages.tool_runner(
233
model="claude-sonnet-4-5",
234
max_tokens=1024,
235
tools=[memory],
236
messages=[{"role": "user", "content": "Remember that my favorite color is blue"}],
237
betas=["memory-2025-08-18"]
238
)
239
240
final_message = runner.until_done()
241
print(final_message.content[0].text)
242
243
# Later conversation
244
runner2 = client.beta.messages.tool_runner(
245
model="claude-sonnet-4-5",
246
max_tokens=1024,
247
tools=[memory],
248
messages=[{"role": "user", "content": "What's my favorite color?"}],
249
betas=["memory-2025-08-18"]
250
)
251
252
response = runner2.until_done()
253
print(response.content[0].text) # "Your favorite color is blue."
254
```
255
256
**File system backed memory:**
257
258
```python
259
import os
260
import json
261
from pathlib import Path
262
263
class FileSystemMemoryTool(BetaAbstractMemoryTool):
264
"""Store memory in the file system."""
265
266
def __init__(self, base_dir: str = "./memory"):
267
super().__init__()
268
self.base_dir = Path(base_dir)
269
self.base_dir.mkdir(exist_ok=True)
270
271
def _resolve_path(self, path: str) -> Path:
272
"""Resolve and validate memory path."""
273
full_path = (self.base_dir / path).resolve()
274
# Ensure path is within base_dir
275
if not str(full_path).startswith(str(self.base_dir)):
276
raise ValueError(f"Invalid path: {path}")
277
return full_path
278
279
def view(self, command: BetaMemoryTool20250818ViewCommand) -> str:
280
path = self._resolve_path(command["path"])
281
282
if not path.exists():
283
return f"Error: Path not found: {command['path']}"
284
285
if path.is_dir():
286
# List directory contents
287
entries = [p.name for p in path.iterdir()]
288
return "\n".join(sorted(entries))
289
else:
290
# Read file content
291
return path.read_text()
292
293
def create(self, command: BetaMemoryTool20250818CreateCommand) -> str:
294
path = self._resolve_path(command["path"])
295
296
if path.exists():
297
return f"Error: Path already exists: {command['path']}"
298
299
# Create parent directories
300
path.parent.mkdir(parents=True, exist_ok=True)
301
302
# Write content
303
path.write_text(command["file_text"])
304
return f"Created: {command['path']}"
305
306
def str_replace(self, command: BetaMemoryTool20250818StrReplaceCommand) -> str:
307
path = self._resolve_path(command["path"])
308
309
if not path.exists():
310
return f"Error: File not found: {command['path']}"
311
312
content = path.read_text()
313
old_str = command["old_str"]
314
new_str = command["new_str"]
315
316
if old_str not in content:
317
return f"Error: String not found: {old_str}"
318
319
new_content = content.replace(old_str, new_str, 1)
320
path.write_text(new_content)
321
return f"Replaced text in: {command['path']}"
322
323
def insert(self, command: BetaMemoryTool20250818InsertCommand) -> str:
324
path = self._resolve_path(command["path"])
325
326
if not path.exists():
327
return f"Error: File not found: {command['path']}"
328
329
lines = path.read_text().split("\n")
330
insert_line = command["insert_line"]
331
332
if insert_line < 0 or insert_line > len(lines):
333
return f"Error: Invalid line number: {insert_line}"
334
335
lines.insert(insert_line, command["new_str"])
336
path.write_text("\n".join(lines))
337
return f"Inserted text at line {insert_line}"
338
339
def delete(self, command: BetaMemoryTool20250818DeleteCommand) -> str:
340
path = self._resolve_path(command["path"])
341
342
if not path.exists():
343
return f"Error: Path not found: {command['path']}"
344
345
if path.is_dir():
346
import shutil
347
shutil.rmtree(path)
348
else:
349
path.unlink()
350
351
return f"Deleted: {command['path']}"
352
353
def rename(self, command: BetaMemoryTool20250818RenameCommand) -> str:
354
old_path = self._resolve_path(command["old_path"])
355
new_path = self._resolve_path(command["new_path"])
356
357
if not old_path.exists():
358
return f"Error: Source not found: {command['old_path']}"
359
if new_path.exists():
360
return f"Error: Destination exists: {command['new_path']}"
361
362
new_path.parent.mkdir(parents=True, exist_ok=True)
363
old_path.rename(new_path)
364
return f"Renamed {command['old_path']} to {command['new_path']}"
365
366
# Use file-based memory
367
memory = FileSystemMemoryTool("./claude_memory")
368
```
369
370
**Database-backed memory:**
371
372
```python
373
import sqlite3
374
375
class DatabaseMemoryTool(BetaAbstractMemoryTool):
376
"""Store memory in SQLite database."""
377
378
def __init__(self, db_path: str = "memory.db"):
379
super().__init__()
380
self.db_path = db_path
381
self._init_db()
382
383
def _init_db(self):
384
"""Initialize database schema."""
385
with sqlite3.connect(self.db_path) as conn:
386
conn.execute("""
387
CREATE TABLE IF NOT EXISTS memory_files (
388
path TEXT PRIMARY KEY,
389
content TEXT NOT NULL,
390
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
391
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
392
)
393
""")
394
395
def view(self, command: BetaMemoryTool20250818ViewCommand) -> str:
396
with sqlite3.connect(self.db_path) as conn:
397
cursor = conn.execute(
398
"SELECT content FROM memory_files WHERE path = ?",
399
(command["path"],)
400
)
401
row = cursor.fetchone()
402
403
if row is None:
404
return f"Error: File not found: {command['path']}"
405
406
return row[0]
407
408
def create(self, command: BetaMemoryTool20250818CreateCommand) -> str:
409
try:
410
with sqlite3.connect(self.db_path) as conn:
411
conn.execute(
412
"INSERT INTO memory_files (path, content) VALUES (?, ?)",
413
(command["path"], command["file_text"])
414
)
415
return f"Created: {command['path']}"
416
except sqlite3.IntegrityError:
417
return f"Error: File already exists: {command['path']}"
418
419
def str_replace(self, command: BetaMemoryTool20250818StrReplaceCommand) -> str:
420
with sqlite3.connect(self.db_path) as conn:
421
cursor = conn.execute(
422
"SELECT content FROM memory_files WHERE path = ?",
423
(command["path"],)
424
)
425
row = cursor.fetchone()
426
427
if row is None:
428
return f"Error: File not found: {command['path']}"
429
430
content = row[0]
431
old_str = command["old_str"]
432
433
if old_str not in content:
434
return f"Error: String not found: {old_str}"
435
436
new_content = content.replace(old_str, command["new_str"], 1)
437
conn.execute(
438
"UPDATE memory_files SET content = ?, updated_at = CURRENT_TIMESTAMP WHERE path = ?",
439
(new_content, command["path"])
440
)
441
442
return f"Replaced text in: {command['path']}"
443
444
def insert(self, command: BetaMemoryTool20250818InsertCommand) -> str:
445
with sqlite3.connect(self.db_path) as conn:
446
cursor = conn.execute(
447
"SELECT content FROM memory_files WHERE path = ?",
448
(command["path"],)
449
)
450
row = cursor.fetchone()
451
452
if row is None:
453
return f"Error: File not found: {command['path']}"
454
455
lines = row[0].split("\n")
456
insert_line = command["insert_line"]
457
458
if insert_line < 0 or insert_line > len(lines):
459
return f"Error: Invalid line number: {insert_line}"
460
461
lines.insert(insert_line, command["new_str"])
462
new_content = "\n".join(lines)
463
464
conn.execute(
465
"UPDATE memory_files SET content = ?, updated_at = CURRENT_TIMESTAMP WHERE path = ?",
466
(new_content, command["path"])
467
)
468
469
return f"Inserted text at line {insert_line}"
470
471
def delete(self, command: BetaMemoryTool20250818DeleteCommand) -> str:
472
with sqlite3.connect(self.db_path) as conn:
473
cursor = conn.execute(
474
"DELETE FROM memory_files WHERE path = ?",
475
(command["path"],)
476
)
477
478
if cursor.rowcount == 0:
479
return f"Error: File not found: {command['path']}"
480
481
return f"Deleted: {command['path']}"
482
483
def rename(self, command: BetaMemoryTool20250818RenameCommand) -> str:
484
with sqlite3.connect(self.db_path) as conn:
485
# Check old path exists
486
cursor = conn.execute(
487
"SELECT 1 FROM memory_files WHERE path = ?",
488
(command["old_path"],)
489
)
490
if cursor.fetchone() is None:
491
return f"Error: Source not found: {command['old_path']}"
492
493
# Check new path doesn't exist
494
cursor = conn.execute(
495
"SELECT 1 FROM memory_files WHERE path = ?",
496
(command["new_path"],)
497
)
498
if cursor.fetchone() is not None:
499
return f"Error: Destination exists: {command['new_path']}"
500
501
# Rename
502
conn.execute(
503
"UPDATE memory_files SET path = ?, updated_at = CURRENT_TIMESTAMP WHERE path = ?",
504
(command["new_path"], command["old_path"])
505
)
506
507
return f"Renamed {command['old_path']} to {command['new_path']}"
508
509
def clear_all_memory(self) -> str:
510
"""Clear all memory entries."""
511
with sqlite3.connect(self.db_path) as conn:
512
conn.execute("DELETE FROM memory_files")
513
return "All memory cleared"
514
515
# Use database memory
516
memory = DatabaseMemoryTool()
517
```
518
519
### Asynchronous Memory Tool
520
521
Abstract base class for implementing asynchronous memory backends with async/await support.
522
523
```python { .api }
524
class BetaAsyncAbstractMemoryTool(BetaAsyncBuiltinFunctionTool):
525
"""
526
Abstract base class for asynchronous memory tool implementations.
527
528
Identical to BetaAbstractMemoryTool but with async methods for
529
non-blocking I/O operations.
530
"""
531
532
def __init__(self, *, cache_control: BetaCacheControlEphemeralParam | None = None) -> None:
533
"""
534
Initialize the async memory tool.
535
536
Args:
537
cache_control: Optional cache control for prompt caching
538
"""
539
540
async def execute(self, command: BetaMemoryTool20250818Command) -> str | Iterable[BetaContent]:
541
"""
542
Execute a memory command asynchronously.
543
544
Args:
545
command: Memory command to execute
546
547
Returns:
548
Command result as string or content blocks
549
"""
550
551
@abstractmethod
552
async def view(self, command: BetaMemoryTool20250818ViewCommand) -> str | Iterable[BetaContent]:
553
"""View the contents of a memory path."""
554
555
@abstractmethod
556
async def create(self, command: BetaMemoryTool20250818CreateCommand) -> str | Iterable[BetaContent]:
557
"""Create a new memory file."""
558
559
@abstractmethod
560
async def str_replace(self, command: BetaMemoryTool20250818StrReplaceCommand) -> str | Iterable[BetaContent]:
561
"""Replace text in a memory file."""
562
563
@abstractmethod
564
async def insert(self, command: BetaMemoryTool20250818InsertCommand) -> str | Iterable[BetaContent]:
565
"""Insert text at a specific line number."""
566
567
@abstractmethod
568
async def delete(self, command: BetaMemoryTool20250818DeleteCommand) -> str | Iterable[BetaContent]:
569
"""Delete a memory file or directory."""
570
571
@abstractmethod
572
async def rename(self, command: BetaMemoryTool20250818RenameCommand) -> str | Iterable[BetaContent]:
573
"""Rename or move a memory file or directory."""
574
575
async def clear_all_memory(self) -> str | Iterable[BetaContent]:
576
"""Clear all memory data."""
577
```
578
579
#### Usage Examples
580
581
**Async file system memory:**
582
583
```python
584
import aiofiles
585
from pathlib import Path
586
from anthropic import AsyncAnthropic
587
from anthropic.lib.tools import BetaAsyncAbstractMemoryTool
588
589
class AsyncFileSystemMemoryTool(BetaAsyncAbstractMemoryTool):
590
"""Async file system memory storage."""
591
592
def __init__(self, base_dir: str = "./memory"):
593
super().__init__()
594
self.base_dir = Path(base_dir)
595
self.base_dir.mkdir(exist_ok=True)
596
597
def _resolve_path(self, path: str) -> Path:
598
full_path = (self.base_dir / path).resolve()
599
if not str(full_path).startswith(str(self.base_dir)):
600
raise ValueError(f"Invalid path: {path}")
601
return full_path
602
603
async def view(self, command: BetaMemoryTool20250818ViewCommand) -> str:
604
path = self._resolve_path(command["path"])
605
606
if not path.exists():
607
return f"Error: File not found: {command['path']}"
608
609
async with aiofiles.open(path, 'r') as f:
610
content = await f.read()
611
return content
612
613
async def create(self, command: BetaMemoryTool20250818CreateCommand) -> str:
614
path = self._resolve_path(command["path"])
615
616
if path.exists():
617
return f"Error: File already exists: {command['path']}"
618
619
path.parent.mkdir(parents=True, exist_ok=True)
620
621
async with aiofiles.open(path, 'w') as f:
622
await f.write(command["file_text"])
623
624
return f"Created: {command['path']}"
625
626
async def str_replace(self, command: BetaMemoryTool20250818StrReplaceCommand) -> str:
627
path = self._resolve_path(command["path"])
628
629
if not path.exists():
630
return f"Error: File not found: {command['path']}"
631
632
async with aiofiles.open(path, 'r') as f:
633
content = await f.read()
634
635
old_str = command["old_str"]
636
if old_str not in content:
637
return f"Error: String not found: {old_str}"
638
639
new_content = content.replace(old_str, command["new_str"], 1)
640
641
async with aiofiles.open(path, 'w') as f:
642
await f.write(new_content)
643
644
return f"Replaced text in: {command['path']}"
645
646
async def insert(self, command: BetaMemoryTool20250818InsertCommand) -> str:
647
path = self._resolve_path(command["path"])
648
649
if not path.exists():
650
return f"Error: File not found: {command['path']}"
651
652
async with aiofiles.open(path, 'r') as f:
653
content = await f.read()
654
655
lines = content.split("\n")
656
insert_line = command["insert_line"]
657
658
if insert_line < 0 or insert_line > len(lines):
659
return f"Error: Invalid line number: {insert_line}"
660
661
lines.insert(insert_line, command["new_str"])
662
663
async with aiofiles.open(path, 'w') as f:
664
await f.write("\n".join(lines))
665
666
return f"Inserted text at line {insert_line}"
667
668
async def delete(self, command: BetaMemoryTool20250818DeleteCommand) -> str:
669
path = self._resolve_path(command["path"])
670
671
if not path.exists():
672
return f"Error: File not found: {command['path']}"
673
674
path.unlink()
675
return f"Deleted: {command['path']}"
676
677
async def rename(self, command: BetaMemoryTool20250818RenameCommand) -> str:
678
old_path = self._resolve_path(command["old_path"])
679
new_path = self._resolve_path(command["new_path"])
680
681
if not old_path.exists():
682
return f"Error: Source not found: {command['old_path']}"
683
if new_path.exists():
684
return f"Error: Destination exists: {command['new_path']}"
685
686
new_path.parent.mkdir(parents=True, exist_ok=True)
687
old_path.rename(new_path)
688
return f"Renamed {command['old_path']} to {command['new_path']}"
689
690
# Use async memory
691
client = AsyncAnthropic()
692
memory = AsyncFileSystemMemoryTool()
693
694
runner = client.beta.messages.tool_runner(
695
model="claude-sonnet-4-5",
696
max_tokens=1024,
697
tools=[memory],
698
messages=[{"role": "user", "content": "Remember my API key is xyz123"}],
699
betas=["memory-2025-08-18"]
700
)
701
702
final_message = await runner.until_done()
703
```
704
705
## Types
706
707
### Memory Command Types
708
709
```python { .api }
710
BetaMemoryTool20250818Command = (
711
BetaMemoryTool20250818ViewCommand
712
| BetaMemoryTool20250818CreateCommand
713
| BetaMemoryTool20250818StrReplaceCommand
714
| BetaMemoryTool20250818InsertCommand
715
| BetaMemoryTool20250818DeleteCommand
716
| BetaMemoryTool20250818RenameCommand
717
)
718
```
719
720
Union of all memory command types.
721
722
### Memory Tool Parameter
723
724
```python { .api }
725
class BetaMemoryTool20250818Param(TypedDict):
726
"""
727
Memory tool definition for the API.
728
"""
729
type: Literal["memory_20250818"]
730
name: Literal["memory"]
731
cache_control: BetaCacheControlEphemeralParam | None # optional
732
```
733