keep-skill 0.1.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- keep/__init__.py +3 -6
- keep/api.py +1052 -145
- keep/cli.py +705 -132
- keep/config.py +172 -41
- keep/context.py +1 -125
- keep/document_store.py +908 -0
- keep/errors.py +33 -0
- keep/indexing.py +1 -1
- keep/logging_config.py +34 -3
- keep/paths.py +81 -17
- keep/pending_summaries.py +52 -40
- keep/providers/embedding_cache.py +59 -46
- keep/providers/embeddings.py +43 -13
- keep/providers/mlx.py +23 -21
- keep/store.py +169 -25
- keep_skill-0.3.0.dist-info/METADATA +218 -0
- keep_skill-0.3.0.dist-info/RECORD +28 -0
- keep_skill-0.1.0.dist-info/METADATA +0 -290
- keep_skill-0.1.0.dist-info/RECORD +0 -26
- {keep_skill-0.1.0.dist-info → keep_skill-0.3.0.dist-info}/WHEEL +0 -0
- {keep_skill-0.1.0.dist-info → keep_skill-0.3.0.dist-info}/entry_points.txt +0 -0
- {keep_skill-0.1.0.dist-info → keep_skill-0.3.0.dist-info}/licenses/LICENSE +0 -0
keep/providers/mlx.py
CHANGED
|
@@ -15,21 +15,18 @@ from .base import EmbeddingProvider, SummarizationProvider, get_registry
|
|
|
15
15
|
|
|
16
16
|
class MLXEmbedding:
|
|
17
17
|
"""
|
|
18
|
-
Embedding provider using
|
|
19
|
-
|
|
20
|
-
Uses sentence-transformer
|
|
21
|
-
|
|
18
|
+
Embedding provider using MPS (Metal) acceleration on Apple Silicon.
|
|
19
|
+
|
|
20
|
+
Uses sentence-transformer models with GPU acceleration via Metal Performance Shaders.
|
|
21
|
+
|
|
22
22
|
Requires: pip install mlx sentence-transformers
|
|
23
23
|
"""
|
|
24
|
-
|
|
25
|
-
def __init__(self, model: str = "
|
|
24
|
+
|
|
25
|
+
def __init__(self, model: str = "all-MiniLM-L6-v2"):
|
|
26
26
|
"""
|
|
27
27
|
Args:
|
|
28
|
-
model: Model name from
|
|
29
|
-
|
|
30
|
-
- mlx-community/bge-small-en-v1.5 (small, fast)
|
|
31
|
-
- mlx-community/bge-base-en-v1.5 (balanced)
|
|
32
|
-
- mlx-community/bge-large-en-v1.5 (best quality)
|
|
28
|
+
model: Model name from sentence-transformers hub.
|
|
29
|
+
Default: all-MiniLM-L6-v2 (384 dims, fast, no auth required)
|
|
33
30
|
"""
|
|
34
31
|
try:
|
|
35
32
|
import mlx.core as mx
|
|
@@ -39,17 +36,22 @@ class MLXEmbedding:
|
|
|
39
36
|
"MLXEmbedding requires 'mlx' and 'sentence-transformers'. "
|
|
40
37
|
"Install with: pip install mlx sentence-transformers"
|
|
41
38
|
)
|
|
42
|
-
|
|
39
|
+
|
|
43
40
|
self.model_name = model
|
|
44
|
-
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
|
|
42
|
+
# Check if model is already cached locally to avoid network calls
|
|
43
|
+
local_only = False
|
|
44
|
+
try:
|
|
45
|
+
from huggingface_hub import try_to_load_from_cache
|
|
46
|
+
repo_id = model if "/" in model else f"sentence-transformers/{model}"
|
|
47
|
+
cached = try_to_load_from_cache(repo_id, "config.json")
|
|
48
|
+
local_only = cached is not None
|
|
49
|
+
except ImportError:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
# Use MPS (Metal) for GPU acceleration on Apple Silicon
|
|
53
|
+
self._model = SentenceTransformer(model, device="mps", local_files_only=local_only)
|
|
54
|
+
|
|
53
55
|
self._dimension: int | None = None
|
|
54
56
|
|
|
55
57
|
@property
|
keep/store.py
CHANGED
|
@@ -49,13 +49,14 @@ class ChromaStore:
|
|
|
49
49
|
pluggable backends (SQLite+faiss, Postgres+pgvector, etc.)
|
|
50
50
|
"""
|
|
51
51
|
|
|
52
|
-
def __init__(self, store_path: Path, embedding_dimension: int):
|
|
52
|
+
def __init__(self, store_path: Path, embedding_dimension: Optional[int] = None):
|
|
53
53
|
"""
|
|
54
54
|
Initialize or open a ChromaDb store.
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
Args:
|
|
57
57
|
store_path: Directory for persistent storage
|
|
58
|
-
embedding_dimension: Expected dimension of embeddings (for validation)
|
|
58
|
+
embedding_dimension: Expected dimension of embeddings (for validation).
|
|
59
|
+
Can be None for read-only access; will be set on first write.
|
|
59
60
|
"""
|
|
60
61
|
try:
|
|
61
62
|
import chromadb
|
|
@@ -123,7 +124,7 @@ class ChromaStore:
|
|
|
123
124
|
) -> None:
|
|
124
125
|
"""
|
|
125
126
|
Insert or update an item in the store.
|
|
126
|
-
|
|
127
|
+
|
|
127
128
|
Args:
|
|
128
129
|
collection: Collection name
|
|
129
130
|
id: Item identifier (URI or custom)
|
|
@@ -131,14 +132,17 @@ class ChromaStore:
|
|
|
131
132
|
summary: Human-readable summary (stored as document)
|
|
132
133
|
tags: All tags (source + system + generated)
|
|
133
134
|
"""
|
|
134
|
-
|
|
135
|
+
# Validate or set embedding dimension
|
|
136
|
+
if self._embedding_dimension is None:
|
|
137
|
+
self._embedding_dimension = len(embedding)
|
|
138
|
+
elif len(embedding) != self._embedding_dimension:
|
|
135
139
|
raise ValueError(
|
|
136
140
|
f"Embedding dimension mismatch: expected {self._embedding_dimension}, "
|
|
137
141
|
f"got {len(embedding)}"
|
|
138
142
|
)
|
|
139
|
-
|
|
143
|
+
|
|
140
144
|
coll = self._get_collection(collection)
|
|
141
|
-
|
|
145
|
+
|
|
142
146
|
# Add timestamp if not present
|
|
143
147
|
now = datetime.now(timezone.utc).isoformat()
|
|
144
148
|
if "_updated" not in tags:
|
|
@@ -154,36 +158,122 @@ class ChromaStore:
|
|
|
154
158
|
tags = {**tags, "_created": now}
|
|
155
159
|
else:
|
|
156
160
|
tags = {**tags, "_created": now}
|
|
157
|
-
|
|
161
|
+
|
|
158
162
|
# Add date portion for easier date queries
|
|
159
163
|
tags = {**tags, "_updated_date": now[:10]}
|
|
160
|
-
|
|
164
|
+
|
|
161
165
|
coll.upsert(
|
|
162
166
|
ids=[id],
|
|
163
167
|
embeddings=[embedding],
|
|
164
168
|
documents=[summary],
|
|
165
169
|
metadatas=[self._tags_to_metadata(tags)],
|
|
166
170
|
)
|
|
171
|
+
|
|
172
|
+
def upsert_version(
|
|
173
|
+
self,
|
|
174
|
+
collection: str,
|
|
175
|
+
id: str,
|
|
176
|
+
version: int,
|
|
177
|
+
embedding: list[float],
|
|
178
|
+
summary: str,
|
|
179
|
+
tags: dict[str, str],
|
|
180
|
+
) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Store an archived version with a versioned ID.
|
|
183
|
+
|
|
184
|
+
The versioned ID format is: {id}@v{version}
|
|
185
|
+
Metadata includes _version and _base_id for filtering/navigation.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
collection: Collection name
|
|
189
|
+
id: Base item identifier (not versioned)
|
|
190
|
+
version: Version number (1=oldest archived)
|
|
191
|
+
embedding: Vector embedding
|
|
192
|
+
summary: Human-readable summary
|
|
193
|
+
tags: All tags from the archived version
|
|
194
|
+
"""
|
|
195
|
+
if self._embedding_dimension is None:
|
|
196
|
+
self._embedding_dimension = len(embedding)
|
|
197
|
+
elif len(embedding) != self._embedding_dimension:
|
|
198
|
+
raise ValueError(
|
|
199
|
+
f"Embedding dimension mismatch: expected {self._embedding_dimension}, "
|
|
200
|
+
f"got {len(embedding)}"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
coll = self._get_collection(collection)
|
|
204
|
+
|
|
205
|
+
# Versioned ID format
|
|
206
|
+
versioned_id = f"{id}@v{version}"
|
|
207
|
+
|
|
208
|
+
# Add version metadata
|
|
209
|
+
versioned_tags = dict(tags)
|
|
210
|
+
versioned_tags["_version"] = str(version)
|
|
211
|
+
versioned_tags["_base_id"] = id
|
|
212
|
+
|
|
213
|
+
coll.upsert(
|
|
214
|
+
ids=[versioned_id],
|
|
215
|
+
embeddings=[embedding],
|
|
216
|
+
documents=[summary],
|
|
217
|
+
metadatas=[self._tags_to_metadata(versioned_tags)],
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def get_content_hash(self, collection: str, id: str) -> Optional[str]:
|
|
221
|
+
"""
|
|
222
|
+
Get the content hash of an existing item.
|
|
223
|
+
|
|
224
|
+
Used to check if content changed (to skip re-embedding).
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
collection: Collection name
|
|
228
|
+
id: Item identifier
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Content hash if item exists and has one, None otherwise
|
|
232
|
+
"""
|
|
233
|
+
coll = self._get_collection(collection)
|
|
234
|
+
result = coll.get(ids=[id], include=["metadatas"])
|
|
235
|
+
|
|
236
|
+
if not result["ids"]:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
metadata = result["metadatas"][0] or {}
|
|
240
|
+
return metadata.get("_content_hash")
|
|
167
241
|
|
|
168
|
-
def delete(self, collection: str, id: str) -> bool:
|
|
242
|
+
def delete(self, collection: str, id: str, delete_versions: bool = True) -> bool:
|
|
169
243
|
"""
|
|
170
244
|
Delete an item from the store.
|
|
171
|
-
|
|
245
|
+
|
|
172
246
|
Args:
|
|
173
247
|
collection: Collection name
|
|
174
248
|
id: Item identifier
|
|
175
|
-
|
|
249
|
+
delete_versions: If True, also delete versioned copies ({id}@v{N})
|
|
250
|
+
|
|
176
251
|
Returns:
|
|
177
252
|
True if item existed and was deleted, False if not found
|
|
178
253
|
"""
|
|
179
254
|
coll = self._get_collection(collection)
|
|
180
|
-
|
|
255
|
+
|
|
181
256
|
# Check existence first
|
|
182
257
|
existing = coll.get(ids=[id])
|
|
183
258
|
if not existing["ids"]:
|
|
184
259
|
return False
|
|
185
|
-
|
|
260
|
+
|
|
186
261
|
coll.delete(ids=[id])
|
|
262
|
+
|
|
263
|
+
if delete_versions:
|
|
264
|
+
# Delete all versioned copies
|
|
265
|
+
# Query by _base_id metadata to find all versions
|
|
266
|
+
try:
|
|
267
|
+
versions = coll.get(
|
|
268
|
+
where={"_base_id": id},
|
|
269
|
+
include=[],
|
|
270
|
+
)
|
|
271
|
+
if versions["ids"]:
|
|
272
|
+
coll.delete(ids=versions["ids"])
|
|
273
|
+
except Exception:
|
|
274
|
+
# Metadata filter might fail if no versions exist
|
|
275
|
+
pass
|
|
276
|
+
|
|
187
277
|
return True
|
|
188
278
|
|
|
189
279
|
def update_summary(self, collection: str, id: str, summary: str) -> bool:
|
|
@@ -222,6 +312,40 @@ class ChromaStore:
|
|
|
222
312
|
)
|
|
223
313
|
return True
|
|
224
314
|
|
|
315
|
+
def update_tags(self, collection: str, id: str, tags: dict[str, str]) -> bool:
|
|
316
|
+
"""
|
|
317
|
+
Update tags of an existing item without changing embedding or summary.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
collection: Collection name
|
|
321
|
+
id: Item identifier
|
|
322
|
+
tags: New tags dict (replaces existing)
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
True if item was updated, False if not found
|
|
326
|
+
"""
|
|
327
|
+
coll = self._get_collection(collection)
|
|
328
|
+
|
|
329
|
+
# Get existing item
|
|
330
|
+
existing = coll.get(ids=[id], include=["metadatas"])
|
|
331
|
+
if not existing["ids"]:
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
# Update timestamp
|
|
335
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
336
|
+
tags = dict(tags) # Copy to avoid mutating input
|
|
337
|
+
tags["_updated"] = now
|
|
338
|
+
tags["_updated_date"] = now[:10]
|
|
339
|
+
|
|
340
|
+
# Convert to metadata format
|
|
341
|
+
metadata = self._tags_to_metadata(tags)
|
|
342
|
+
|
|
343
|
+
coll.update(
|
|
344
|
+
ids=[id],
|
|
345
|
+
metadatas=[metadata],
|
|
346
|
+
)
|
|
347
|
+
return True
|
|
348
|
+
|
|
225
349
|
# -------------------------------------------------------------------------
|
|
226
350
|
# Read Operations
|
|
227
351
|
# -------------------------------------------------------------------------
|
|
@@ -257,7 +381,21 @@ class ChromaStore:
|
|
|
257
381
|
coll = self._get_collection(collection)
|
|
258
382
|
result = coll.get(ids=[id], include=[])
|
|
259
383
|
return bool(result["ids"])
|
|
260
|
-
|
|
384
|
+
|
|
385
|
+
def list_ids(self, collection: str) -> list[str]:
|
|
386
|
+
"""
|
|
387
|
+
List all document IDs in a collection.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
collection: Collection name
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
List of document IDs
|
|
394
|
+
"""
|
|
395
|
+
coll = self._get_collection(collection)
|
|
396
|
+
result = coll.get(include=[])
|
|
397
|
+
return result["ids"]
|
|
398
|
+
|
|
261
399
|
def query_embedding(
|
|
262
400
|
self,
|
|
263
401
|
collection: str,
|
|
@@ -340,27 +478,33 @@ class ChromaStore:
|
|
|
340
478
|
collection: str,
|
|
341
479
|
query: str,
|
|
342
480
|
limit: int = 10,
|
|
481
|
+
where: dict[str, Any] | None = None,
|
|
343
482
|
) -> list[StoreResult]:
|
|
344
483
|
"""
|
|
345
484
|
Query by full-text search on document content (summaries).
|
|
346
|
-
|
|
485
|
+
|
|
347
486
|
Args:
|
|
348
487
|
collection: Collection name
|
|
349
488
|
query: Text to search for
|
|
350
489
|
limit: Maximum results to return
|
|
351
|
-
|
|
490
|
+
where: Optional metadata filter (Chroma where clause)
|
|
491
|
+
|
|
352
492
|
Returns:
|
|
353
493
|
List of matching results
|
|
354
494
|
"""
|
|
355
495
|
coll = self._get_collection(collection)
|
|
356
|
-
|
|
496
|
+
|
|
357
497
|
# Chroma's where_document does substring matching
|
|
358
|
-
|
|
359
|
-
where_document
|
|
360
|
-
limit
|
|
361
|
-
include
|
|
362
|
-
|
|
363
|
-
|
|
498
|
+
get_params = {
|
|
499
|
+
"where_document": {"$contains": query},
|
|
500
|
+
"limit": limit,
|
|
501
|
+
"include": ["documents", "metadatas"],
|
|
502
|
+
}
|
|
503
|
+
if where:
|
|
504
|
+
get_params["where"] = where
|
|
505
|
+
|
|
506
|
+
result = coll.get(**get_params)
|
|
507
|
+
|
|
364
508
|
results = []
|
|
365
509
|
for i, id in enumerate(result["ids"]):
|
|
366
510
|
results.append(StoreResult(
|
|
@@ -368,7 +512,7 @@ class ChromaStore:
|
|
|
368
512
|
summary=result["documents"][i] or "",
|
|
369
513
|
tags=self._metadata_to_tags(result["metadatas"][i]),
|
|
370
514
|
))
|
|
371
|
-
|
|
515
|
+
|
|
372
516
|
return results
|
|
373
517
|
|
|
374
518
|
# -------------------------------------------------------------------------
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: keep-skill
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Semantic memory - remember and search documents by meaning
|
|
5
|
+
Project-URL: Homepage, https://github.com/hughpyle/keep
|
|
6
|
+
Project-URL: Repository, https://github.com/hughpyle/keep
|
|
7
|
+
Author: Hugh Pyle
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,chromadb,embeddings,semantic-memory,vector-search
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: Text Processing
|
|
20
|
+
Requires-Python: <3.14,>=3.11
|
|
21
|
+
Requires-Dist: chromadb>=0.4
|
|
22
|
+
Requires-Dist: tomli-w>=1.0
|
|
23
|
+
Requires-Dist: typer>=0.9
|
|
24
|
+
Provides-Extra: anthropic
|
|
25
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'anthropic'
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
29
|
+
Provides-Extra: documents
|
|
30
|
+
Requires-Dist: beautifulsoup4>=4.9; extra == 'documents'
|
|
31
|
+
Requires-Dist: pypdf>=5.0; extra == 'documents'
|
|
32
|
+
Provides-Extra: gemini
|
|
33
|
+
Requires-Dist: google-genai>=1.0.0; extra == 'gemini'
|
|
34
|
+
Provides-Extra: local
|
|
35
|
+
Requires-Dist: beautifulsoup4>=4.9; extra == 'local'
|
|
36
|
+
Requires-Dist: mlx-lm>=0.10; (platform_system == 'Darwin' and platform_machine == 'arm64') and extra == 'local'
|
|
37
|
+
Requires-Dist: mlx>=0.10; (platform_system == 'Darwin' and platform_machine == 'arm64') and extra == 'local'
|
|
38
|
+
Requires-Dist: pypdf>=5.0; extra == 'local'
|
|
39
|
+
Requires-Dist: sentence-transformers>=2.2; extra == 'local'
|
|
40
|
+
Provides-Extra: mlx
|
|
41
|
+
Requires-Dist: mlx-lm>=0.10; extra == 'mlx'
|
|
42
|
+
Requires-Dist: mlx>=0.10; extra == 'mlx'
|
|
43
|
+
Provides-Extra: openai
|
|
44
|
+
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
45
|
+
Provides-Extra: openclaw
|
|
46
|
+
Requires-Dist: anthropic>=0.18.0; extra == 'openclaw'
|
|
47
|
+
Requires-Dist: google-genai>=1.0.0; extra == 'openclaw'
|
|
48
|
+
Provides-Extra: sentence-transformers
|
|
49
|
+
Requires-Dist: sentence-transformers>=2.2; extra == 'sentence-transformers'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
# keep
|
|
53
|
+
|
|
54
|
+
**Semantic memory with version history.**
|
|
55
|
+
|
|
56
|
+
Index documents and notes. Search by meaning. Track changes over time.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install 'keep-skill[local]'
|
|
60
|
+
keep init
|
|
61
|
+
|
|
62
|
+
# Index content
|
|
63
|
+
keep update path/to/document.md -t project=myapp
|
|
64
|
+
keep update "Rate limit is 100 req/min" -t topic=api
|
|
65
|
+
|
|
66
|
+
# Search by meaning
|
|
67
|
+
keep find "what's the rate limit?"
|
|
68
|
+
|
|
69
|
+
# Track what you're working on
|
|
70
|
+
keep now "Debugging auth flow"
|
|
71
|
+
keep now -V 1 # Previous context
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## What It Does
|
|
77
|
+
|
|
78
|
+
- **Semantic search** — Find by meaning, not just keywords
|
|
79
|
+
- **Version history** — All documents retain history on update
|
|
80
|
+
- **Tag organization** — Filter and navigate with key=value tags
|
|
81
|
+
- **Recency decay** — Recent items rank higher in search
|
|
82
|
+
- **Works offline** — Local embedding models by default
|
|
83
|
+
|
|
84
|
+
Backed by ChromaDB for vectors, SQLite for metadata and versions.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Installation
|
|
89
|
+
|
|
90
|
+
**Python 3.11–3.13 required.**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Recommended: local models (works offline)
|
|
94
|
+
pip install 'keep-skill[local]'
|
|
95
|
+
|
|
96
|
+
# Or with uv (faster):
|
|
97
|
+
uv tool install 'keep-skill[local]'
|
|
98
|
+
|
|
99
|
+
# API-based alternative (requires OPENAI_API_KEY)
|
|
100
|
+
pip install 'keep-skill[openai]'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
First run downloads embedding models (~3-5 minutes).
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Quick Start
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
keep init # Creates .keep/ at repo root
|
|
111
|
+
|
|
112
|
+
# Index files and notes
|
|
113
|
+
keep update file:///path/to/doc.md -t project=myapp
|
|
114
|
+
keep update "Important insight" -t type=note
|
|
115
|
+
|
|
116
|
+
# Search
|
|
117
|
+
keep find "authentication flow" --limit 5
|
|
118
|
+
keep find "auth" --since P7D # Last 7 days
|
|
119
|
+
|
|
120
|
+
# Retrieve
|
|
121
|
+
keep get file:///path/to/doc.md
|
|
122
|
+
keep get ID -V 1 # Previous version
|
|
123
|
+
keep get ID --history # All versions
|
|
124
|
+
|
|
125
|
+
# Tags
|
|
126
|
+
keep tag project=myapp # Find by tag
|
|
127
|
+
keep tag --list # List all tags
|
|
128
|
+
|
|
129
|
+
# Current context
|
|
130
|
+
keep now # Show what you're working on
|
|
131
|
+
keep now "Fixing login bug" # Update context
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Python API
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from keep import Keeper
|
|
138
|
+
|
|
139
|
+
kp = Keeper()
|
|
140
|
+
|
|
141
|
+
# Index
|
|
142
|
+
kp.update("file:///path/to/doc.md", tags={"project": "myapp"})
|
|
143
|
+
kp.remember("Rate limit is 100 req/min", tags={"topic": "api"})
|
|
144
|
+
|
|
145
|
+
# Search
|
|
146
|
+
results = kp.find("rate limit", limit=5)
|
|
147
|
+
for r in results:
|
|
148
|
+
print(f"[{r.score:.2f}] {r.summary}")
|
|
149
|
+
|
|
150
|
+
# Version history
|
|
151
|
+
prev = kp.get_version("doc:1", offset=1)
|
|
152
|
+
versions = kp.list_versions("doc:1")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
See [docs/QUICKSTART.md](docs/QUICKSTART.md) for configuration and more examples.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Documentation
|
|
160
|
+
|
|
161
|
+
- **[docs/QUICKSTART.md](docs/QUICKSTART.md)** — Setup, configuration, lazy summarization
|
|
162
|
+
- **[docs/REFERENCE.md](docs/REFERENCE.md)** — Complete CLI and API reference
|
|
163
|
+
- **[docs/AGENT-GUIDE.md](docs/AGENT-GUIDE.md)** — Working session patterns
|
|
164
|
+
- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** — How it works under the hood
|
|
165
|
+
- **[SKILL.md](SKILL.md)** — The reflective practice (for AI agents)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## For AI Agents
|
|
170
|
+
|
|
171
|
+
This library was designed as an agent skill — persistent memory that helps agents reflect before acting and learn from experience.
|
|
172
|
+
|
|
173
|
+
**The practice:**
|
|
174
|
+
- Pause before acting — `keep find` what you already know
|
|
175
|
+
- Notice breakdowns — when assumptions surface, index them
|
|
176
|
+
- Reflect after — `keep update` learnings for future sessions
|
|
177
|
+
|
|
178
|
+
See **[SKILL.md](SKILL.md)** for the full practice guide.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Status
|
|
183
|
+
|
|
184
|
+
**Current:** v0.3.0
|
|
185
|
+
|
|
186
|
+
**Working:**
|
|
187
|
+
- ✅ Semantic search with embeddings
|
|
188
|
+
- ✅ Document versioning (all updates retain history)
|
|
189
|
+
- ✅ Content-addressed IDs for text (same content = same ID)
|
|
190
|
+
- ✅ Tag queries and full-text search
|
|
191
|
+
- ✅ Current context tracking (`keep now`)
|
|
192
|
+
- ✅ Recency decay (recent items rank higher)
|
|
193
|
+
- ✅ Lazy summarization (background processing)
|
|
194
|
+
- ✅ Provider abstraction (local or API-based)
|
|
195
|
+
|
|
196
|
+
**Planned** (see [later/](later/)):
|
|
197
|
+
- ⏳ Private/shared routing
|
|
198
|
+
- ⏳ Relationship graphs between items
|
|
199
|
+
- ⏳ LLM-based auto-tagging
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Contributing
|
|
210
|
+
|
|
211
|
+
Published on [PyPI as `keep-skill`](https://pypi.org/project/keep-skill/).
|
|
212
|
+
|
|
213
|
+
Issues and PRs welcome:
|
|
214
|
+
- Provider implementations
|
|
215
|
+
- Performance improvements
|
|
216
|
+
- Documentation clarity
|
|
217
|
+
|
|
218
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
keep/__init__.py,sha256=ZHguWMkzsurTDqV7_t5Nlr5VHlwVkmG6pelE0Ivfs6I,1581
|
|
2
|
+
keep/__main__.py,sha256=3Uu70IhIDIjh8OW6jp9jQQ3dF2lKdJWi_3FtRIQMiMY,104
|
|
3
|
+
keep/api.py,sha256=rArSGmbQkVR_x5qcMXVZcQr97daWxyettnHplgnykEM,56951
|
|
4
|
+
keep/chunking.py,sha256=neAXOLSvVwbUxapbqq7nZrbSNSzMXuhxj-ODoOSodsU,11830
|
|
5
|
+
keep/cli.py,sha256=yxO9FS0N1c_ffE02NV8tC-4NNj8ZNK_dkFufjqVG84A,34085
|
|
6
|
+
keep/config.py,sha256=RRnHHvhc9KkJBYt0rpAFIvAVXw40b56xtT74TFIBiDU,15832
|
|
7
|
+
keep/context.py,sha256=CNpjmrv6eW2kV1E0MO6qAQfhYKRlfzAL--6v4Mj1nFY,71
|
|
8
|
+
keep/document_store.py,sha256=UswqKIGSc5E-r7Tg9k0g5-byYnuar3e9FieQ7WNod9k,29109
|
|
9
|
+
keep/errors.py,sha256=G9e5FbdfeugyfHOuL_SPZlM5jgWWnwsX4hM7IzanBZc,857
|
|
10
|
+
keep/indexing.py,sha256=dpPYo3WXnIhFDWinz5ZBZVk7_qumeNpP4EpOIY0zMbs,6063
|
|
11
|
+
keep/logging_config.py,sha256=IGwkgIyg-TfYaT4MnoCXfmjeHAe_wsB_XQ1QhVT_ro8,3503
|
|
12
|
+
keep/paths.py,sha256=Dv7pM6oo2QgjL6sj5wPjhuMOK2wqUkfd4Kz08TwJ1ps,3331
|
|
13
|
+
keep/pending_summaries.py,sha256=_irGe7P1Lmog2c5cEgx-BElpq4YJW-tEmF5A3IUZQbQ,5727
|
|
14
|
+
keep/store.py,sha256=RmKOAWTWvUlMcLoEbyAfR99Cxcshh_2SHhNkKYGAkqw,17509
|
|
15
|
+
keep/types.py,sha256=f6uOSYsYt6mj1ulKn2iRkooi__dWCiOQFPD6he2eID4,2149
|
|
16
|
+
keep/providers/__init__.py,sha256=GFX_12g9OdjmpFUkTekOQBOWvcRW2Ae6yidfVVW2SiI,1095
|
|
17
|
+
keep/providers/base.py,sha256=7Ug4Kj9fK2Dq4zDcZjn-GKsoZBOAlB9b-FMk969ER-g,14590
|
|
18
|
+
keep/providers/documents.py,sha256=EXeSy5i3RUL0kciIC6w3ldAEfbTIyC5fgfzC_WAI0iY,8211
|
|
19
|
+
keep/providers/embedding_cache.py,sha256=gna6PZEJanbn2GUN0vj1b1MC0xVWePM9cot2KgZUdu8,8856
|
|
20
|
+
keep/providers/embeddings.py,sha256=zi8GyitKexdbCJyU1nLrUhGt_zzPn3udYrrPZ5Ak8Wo,9081
|
|
21
|
+
keep/providers/llm.py,sha256=BxROKOklKbkGsHcSADPNNgWQExgSN6Bg4KPQIxVuB3U,12441
|
|
22
|
+
keep/providers/mlx.py,sha256=aNl00r9tGi5tCGj2ArYH7CmDHtL1jLjVzb1rofU1DAo,9050
|
|
23
|
+
keep/providers/summarization.py,sha256=MlVTcYipaqp2lT-QYnznp0AMuPVG36QfcTQnvY7Gb-Q,3409
|
|
24
|
+
keep_skill-0.3.0.dist-info/METADATA,sha256=ET8OzV76N3JI_bWcJt6j6xE0uYmROmgB27U1gq9RAk0,6312
|
|
25
|
+
keep_skill-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
26
|
+
keep_skill-0.3.0.dist-info/entry_points.txt,sha256=W8yiI4kNeW0IC8ji4EHRWrvdhFxzaqTIePUhJAJAMOo,39
|
|
27
|
+
keep_skill-0.3.0.dist-info/licenses/LICENSE,sha256=zsm0tpvtyUkevcjn5BIvs9jAho8iwxq3Ax9647AaOSg,1086
|
|
28
|
+
keep_skill-0.3.0.dist-info/RECORD,,
|