keep-skill 0.4.2__py3-none-any.whl → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
- Keep - Semantic Memory
2
+ Keep - Reflective Memory
3
3
 
4
- A persistent semantic memory with similarity search, full-text search,
4
+ A persistent reflective memory with similarity search, full-text search,
5
5
  and tag-based retrieval. Remember everything, find by meaning.
6
6
 
7
7
  Quick Start:
@@ -38,13 +38,14 @@ if not os.environ.get("KEEP_VERBOSE"):
38
38
  os.environ.setdefault("HF_HUB_DISABLE_SYMLINKS_WARNING", "1")
39
39
 
40
40
  from .api import Keeper, NOWDOC_ID
41
- from .types import Item, filter_non_system_tags, SYSTEM_TAG_PREFIX
41
+ from .types import Item, filter_non_system_tags, SYSTEM_TAG_PREFIX, INTERNAL_TAGS
42
42
 
43
- __version__ = "0.4.1"
43
+ __version__ = "0.7.0"
44
44
  __all__ = [
45
45
  "Keeper",
46
46
  "Item",
47
47
  "NOWDOC_ID",
48
48
  "filter_non_system_tags",
49
49
  "SYSTEM_TAG_PREFIX",
50
+ "INTERNAL_TAGS",
50
51
  ]
keep/api.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- Core API for associative memory.
2
+ Core API for reflective memory.
3
3
 
4
4
  This is the minimal working implementation focused on:
5
5
  - update(): fetch → embed → summarize → store
@@ -9,6 +9,7 @@ This is the minimal working implementation focused on:
9
9
  """
10
10
 
11
11
  import hashlib
12
+ import importlib.resources
12
13
  import logging
13
14
  import re
14
15
  from datetime import datetime, timezone, timedelta
@@ -100,6 +101,24 @@ def _filter_by_date(items: list, since: str) -> list:
100
101
  if item.tags.get("_updated_date", "0000-00-00") >= cutoff
101
102
  ]
102
103
 
104
+
105
+ def _record_to_item(rec, score: float = None) -> "Item":
106
+ """
107
+ Convert a DocumentRecord to an Item with timestamp tags.
108
+
109
+ Adds _updated, _created, _updated_date from the record's columns
110
+ to ensure consistent timestamp exposure across all retrieval methods.
111
+ """
112
+ from .types import Item
113
+ tags = {
114
+ **rec.tags,
115
+ "_updated": rec.updated_at,
116
+ "_created": rec.created_at,
117
+ "_updated_date": rec.updated_at[:10] if rec.updated_at else "",
118
+ }
119
+ return Item(id=rec.id, summary=rec.summary, tags=tags, score=score)
120
+
121
+
103
122
  import os
104
123
  import subprocess
105
124
  import sys
@@ -135,8 +154,44 @@ ENV_TAG_PREFIX = "KEEP_TAG_"
135
154
  # Fixed ID for the current working context (singleton)
136
155
  NOWDOC_ID = "_now:default"
137
156
 
157
+
158
+ def _get_system_doc_dir() -> Path:
159
+ """
160
+ Get path to system docs, works in both dev and installed environments.
161
+
162
+ Tries in order:
163
+ 1. Package data via importlib.resources (installed packages)
164
+ 2. Relative path inside package (development)
165
+ 3. Legacy path outside package (backwards compatibility)
166
+ """
167
+ # Try package data first (works for installed packages)
168
+ try:
169
+ with importlib.resources.as_file(
170
+ importlib.resources.files("keep.data.system")
171
+ ) as path:
172
+ if path.exists():
173
+ return path
174
+ except (ModuleNotFoundError, TypeError):
175
+ pass
176
+
177
+ # Fallback to relative path inside package (development)
178
+ dev_path = Path(__file__).parent / "data" / "system"
179
+ if dev_path.exists():
180
+ return dev_path
181
+
182
+ # Legacy fallback (old structure)
183
+ return Path(__file__).parent.parent / "docs" / "system"
184
+
185
+
138
186
  # Path to system documents
139
- SYSTEM_DOC_DIR = Path(__file__).parent.parent / "docs" / "system"
187
+ SYSTEM_DOC_DIR = _get_system_doc_dir()
188
+
189
+ # Stable IDs for system documents (path-independent)
190
+ SYSTEM_DOC_IDS = {
191
+ "now.md": "_system:now",
192
+ "conversations.md": "_system:conversations",
193
+ "domains.md": "_system:domains",
194
+ }
140
195
 
141
196
 
142
197
  def _load_frontmatter(path: Path) -> tuple[str, dict[str, str]]:
@@ -214,7 +269,7 @@ def _text_content_id(content: str) -> str:
214
269
 
215
270
  class Keeper:
216
271
  """
217
- Semantic memory keeper - persistent storage with similarity search.
272
+ Reflective memory keeper - persistent storage with similarity search.
218
273
 
219
274
  Example:
220
275
  kp = Keeper()
@@ -229,7 +284,7 @@ class Keeper:
229
284
  decay_half_life_days: float = 30.0
230
285
  ) -> None:
231
286
  """
232
- Initialize or open an existing associative memory store.
287
+ Initialize or open an existing reflective memory store.
233
288
 
234
289
  Args:
235
290
  store_path: Path to store directory. Uses default if not specified.
@@ -295,32 +350,88 @@ class Keeper:
295
350
  embedding_dimension=embedding_dim,
296
351
  )
297
352
 
298
- # Preload system documents (only if not already present)
299
- self._ensure_system_documents()
353
+ # Migrate and ensure system documents (idempotent)
354
+ self._migrate_system_documents()
300
355
 
301
- def _ensure_system_documents(self) -> None:
356
+ def _migrate_system_documents(self) -> dict:
302
357
  """
303
- Ensure system documents are loaded into the store.
358
+ Migrate system documents to stable IDs and current version.
304
359
 
305
- Scans all .md files in docs/system/. Each file is indexed with its
306
- file:// URI as the ID and `_category: system` tag for identification.
307
- Content becomes the summary directly (no auto-summarization).
360
+ Handles:
361
+ - Migration from old file:// URIs to _system:{name} IDs
362
+ - Fresh creation for new stores
363
+ - Version upgrades when bundled content changes
364
+ - Cleanup of old file:// URIs (from before path was changed)
308
365
 
309
366
  Called during init. Only loads docs that don't already exist,
310
- so user modifications are preserved and no network access occurs
311
- if docs are already present.
367
+ so user modifications are preserved. Updates config version
368
+ after successful migration.
369
+
370
+ Returns:
371
+ Dict with migration stats: created, migrated, skipped, cleaned
312
372
  """
373
+ from .config import SYSTEM_DOCS_VERSION, save_config
374
+
375
+ stats = {"created": 0, "migrated": 0, "skipped": 0, "cleaned": 0}
376
+
377
+ # Skip if already at current version
378
+ if self._config.system_docs_version >= SYSTEM_DOCS_VERSION:
379
+ return stats
380
+
381
+ # Build reverse lookup: filename -> new stable ID
382
+ filename_to_id = {name: doc_id for name, doc_id in SYSTEM_DOC_IDS.items()}
383
+
384
+ # First pass: clean up old file:// URIs with category=system tag
385
+ # These may have different paths than current SYSTEM_DOC_DIR
386
+ try:
387
+ old_system_docs = self.query_tag("category", "system")
388
+ for doc in old_system_docs:
389
+ if doc.id.startswith("file://") and doc.id.endswith(".md"):
390
+ # Extract filename from path
391
+ filename = Path(doc.id.replace("file://", "")).name
392
+ new_id = filename_to_id.get(filename)
393
+ if new_id and not self.exists(new_id):
394
+ # Migrate content to new ID
395
+ self.remember(doc.summary, id=new_id, tags=doc.tags)
396
+ self.delete(doc.id)
397
+ stats["migrated"] += 1
398
+ logger.info("Migrated system doc: %s -> %s", doc.id, new_id)
399
+ elif new_id:
400
+ # New ID already exists, just clean up old one
401
+ self.delete(doc.id)
402
+ stats["cleaned"] += 1
403
+ logger.info("Cleaned up old system doc: %s", doc.id)
404
+ except Exception as e:
405
+ logger.debug("Error scanning old system docs: %s", e)
406
+
407
+ # Second pass: create any missing system docs from bundled content
313
408
  for path in SYSTEM_DOC_DIR.glob("*.md"):
409
+ new_id = SYSTEM_DOC_IDS.get(path.name)
410
+ if new_id is None:
411
+ logger.debug("Skipping unknown system doc: %s", path.name)
412
+ continue
413
+
414
+ # Skip if already exists
415
+ if self.exists(new_id):
416
+ stats["skipped"] += 1
417
+ continue
418
+
314
419
  try:
315
- uri = f"file://{path.resolve()}"
316
- if not self.exists(uri):
317
- content, tags = _load_frontmatter(path)
318
- tags["category"] = "system"
319
- self.remember(content, id=uri, tags=tags)
420
+ content, tags = _load_frontmatter(path)
421
+ tags["category"] = "system"
422
+ self.remember(content, id=new_id, tags=tags)
423
+ stats["created"] += 1
424
+ logger.info("Created system doc: %s", new_id)
320
425
  except FileNotFoundError:
321
426
  # System file missing - skip silently
322
427
  pass
323
428
 
429
+ # Update config version
430
+ self._config.system_docs_version = SYSTEM_DOCS_VERSION
431
+ save_config(self._config)
432
+
433
+ return stats
434
+
324
435
  def _get_embedding_provider(self) -> EmbeddingProvider:
325
436
  """
326
437
  Get embedding provider, creating it lazily on first use.
@@ -581,12 +692,8 @@ class Keeper:
581
692
 
582
693
  # Return the stored item
583
694
  doc_record = self._document_store.get(coll, id)
584
- return Item(
585
- id=doc_record.id,
586
- summary=doc_record.summary,
587
- tags=doc_record.tags,
588
- )
589
-
695
+ return _record_to_item(doc_record)
696
+
590
697
  def remember(
591
698
  self,
592
699
  content: str,
@@ -759,11 +866,7 @@ class Keeper:
759
866
 
760
867
  # Return the stored item
761
868
  doc_record = self._document_store.get(coll, id)
762
- return Item(
763
- id=doc_record.id,
764
- summary=doc_record.summary,
765
- tags=doc_record.tags,
766
- )
869
+ return _record_to_item(doc_record)
767
870
 
768
871
  # -------------------------------------------------------------------------
769
872
  # Query Operations
@@ -1055,7 +1158,7 @@ class Keeper:
1055
1158
  docs = self._document_store.query_by_tag_key(
1056
1159
  coll, key, limit=limit, since_date=since_date
1057
1160
  )
1058
- return [Item(id=d.id, summary=d.summary, tags=d.tags) for d in docs]
1161
+ return [_record_to_item(d) for d in docs]
1059
1162
 
1060
1163
  # Build tag filter from positional or keyword args
1061
1164
  tag_filter = {}
@@ -1129,11 +1232,7 @@ class Keeper:
1129
1232
  # Try document store first (canonical)
1130
1233
  doc_record = self._document_store.get(coll, id)
1131
1234
  if doc_record:
1132
- return Item(
1133
- id=doc_record.id,
1134
- summary=doc_record.summary,
1135
- tags=doc_record.tags,
1136
- )
1235
+ return _record_to_item(doc_record)
1137
1236
 
1138
1237
  # Fall back to ChromaDB for legacy data
1139
1238
  result = self._store.get(coll, id)
@@ -1271,7 +1370,7 @@ class Keeper:
1271
1370
 
1272
1371
  A singleton document representing what you're currently working on.
1273
1372
  If it doesn't exist, creates one with default content and tags from
1274
- docs/system/now.md.
1373
+ the bundled system now.md file.
1275
1374
 
1276
1375
  Returns:
1277
1376
  The current context Item (never None - auto-creates if missing)
@@ -1328,6 +1427,44 @@ class Keeper:
1328
1427
  """
1329
1428
  return self.query_tag("category", "system", collection=collection)
1330
1429
 
1430
+ def reset_system_documents(self) -> dict:
1431
+ """
1432
+ Force reload all system documents from bundled content.
1433
+
1434
+ This overwrites any user modifications to system documents.
1435
+ Use with caution - primarily for recovery or testing.
1436
+
1437
+ Returns:
1438
+ Dict with stats: reset count
1439
+ """
1440
+ from .config import SYSTEM_DOCS_VERSION, save_config
1441
+
1442
+ stats = {"reset": 0}
1443
+
1444
+ for path in SYSTEM_DOC_DIR.glob("*.md"):
1445
+ new_id = SYSTEM_DOC_IDS.get(path.name)
1446
+ if new_id is None:
1447
+ continue
1448
+
1449
+ try:
1450
+ content, tags = _load_frontmatter(path)
1451
+ tags["category"] = "system"
1452
+
1453
+ # Delete existing (if any) and create fresh
1454
+ self.delete(new_id)
1455
+ self.remember(content, id=new_id, tags=tags)
1456
+ stats["reset"] += 1
1457
+ logger.info("Reset system doc: %s", new_id)
1458
+
1459
+ except FileNotFoundError:
1460
+ logger.warning("System doc file not found: %s", path)
1461
+
1462
+ # Update config version
1463
+ self._config.system_docs_version = SYSTEM_DOCS_VERSION
1464
+ save_config(self._config)
1465
+
1466
+ return stats
1467
+
1331
1468
  def tag(
1332
1469
  self,
1333
1470
  id: str,
@@ -1432,14 +1569,7 @@ class Keeper:
1432
1569
  coll = self._resolve_collection(collection)
1433
1570
  records = self._document_store.list_recent(coll, limit)
1434
1571
 
1435
- return [
1436
- Item(
1437
- id=rec.id,
1438
- summary=rec.summary,
1439
- tags=rec.tags,
1440
- score=None,
1441
- )
1442
- for rec in records
1572
+ return [_record_to_item(rec) for rec in records
1443
1573
  ]
1444
1574
 
1445
1575
  def embedding_cache_stats(self) -> dict:
keep/cli.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- CLI interface for associative memory.
2
+ CLI interface for reflective memory.
3
3
 
4
4
  Usage:
5
5
  keepfind "query text"
@@ -20,6 +20,10 @@ from typing_extensions import Annotated
20
20
  # Pattern for version identifier suffix: @V{N} where N is digits only
21
21
  VERSION_SUFFIX_PATTERN = re.compile(r'@V\{(\d+)\}$')
22
22
 
23
+ # URI scheme pattern per RFC 3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
24
+ # Used to distinguish URIs from plain text in the update command
25
+ _URI_SCHEME_PATTERN = re.compile(r'^[a-zA-Z][a-zA-Z0-9+.-]*://')
26
+
23
27
  from .api import Keeper, _text_content_id
24
28
  from .document_store import VersionInfo
25
29
  from .types import Item
@@ -74,13 +78,30 @@ def _get_full_output() -> bool:
74
78
 
75
79
  app = typer.Typer(
76
80
  name="keep",
77
- help="Associative memory with semantic search.",
81
+ help="Reflective memory with semantic search.",
78
82
  no_args_is_help=False,
79
83
  invoke_without_command=True,
80
84
  rich_markup_mode=None,
81
85
  )
82
86
 
83
87
 
88
+ # -----------------------------------------------------------------------------
89
+ # Output Formatting
90
+ #
91
+ # Three output formats, controlled by global flags:
92
+ # --ids: versioned ID only (id@V{N})
93
+ # --full: YAML frontmatter with tags, similar items, version nav
94
+ # default: summary line (id@V{N} date summary)
95
+ #
96
+ # JSON output (--json) works with any of the above.
97
+ # -----------------------------------------------------------------------------
98
+
99
+ def _filter_display_tags(tags: dict) -> dict:
100
+ """Filter out internal-only tags for display."""
101
+ from .types import INTERNAL_TAGS
102
+ return {k: v for k, v in tags.items() if k not in INTERNAL_TAGS}
103
+
104
+
84
105
  def _format_yaml_frontmatter(
85
106
  item: Item,
86
107
  version_nav: Optional[dict[str, list[VersionInfo]]] = None,
@@ -105,8 +126,9 @@ def _format_yaml_frontmatter(
105
126
  lines = ["---", f"id: {item.id}"]
106
127
  if viewing_offset is not None:
107
128
  lines.append(f"version: {viewing_offset}")
108
- if item.tags:
109
- tag_items = ", ".join(f"{k}: {v}" for k, v in sorted(item.tags.items()))
129
+ display_tags = _filter_display_tags(item.tags)
130
+ if display_tags:
131
+ tag_items = ", ".join(f"{k}: {v}" for k, v in sorted(display_tags.items()))
110
132
  lines.append(f"tags: {{{tag_items}}}")
111
133
  if item.score is not None:
112
134
  lines.append(f"score: {item.score:.3f}")
@@ -157,6 +179,31 @@ def _format_yaml_frontmatter(
157
179
  return "\n".join(lines)
158
180
 
159
181
 
182
+ def _format_summary_line(item: Item) -> str:
183
+ """Format item as single summary line: id@version date summary"""
184
+ # Get version-scoped ID
185
+ base_id = item.tags.get("_base_id", item.id)
186
+ version = item.tags.get("_version", "0")
187
+ versioned_id = f"{base_id}@V{{{version}}}"
188
+
189
+ # Get date (from _updated_date or _updated or _created)
190
+ date = item.tags.get("_updated_date") or item.tags.get("_updated", "")[:10] or item.tags.get("_created", "")[:10] or ""
191
+
192
+ # Truncate summary to ~60 chars, collapse newlines
193
+ summary = item.summary.replace("\n", " ")
194
+ if len(summary) > 60:
195
+ summary = summary[:57].rsplit(" ", 1)[0] + "..."
196
+
197
+ return f"{versioned_id} {date} {summary}"
198
+
199
+
200
+ def _format_versioned_id(item: Item) -> str:
201
+ """Format item ID with version suffix: id@V{N}"""
202
+ base_id = item.tags.get("_base_id", item.id)
203
+ version = item.tags.get("_version", "0")
204
+ return f"{base_id}@V{{{version}}}"
205
+
206
+
160
207
  @app.callback(invoke_without_command=True)
161
208
  def main_callback(
162
209
  ctx: typer.Context,
@@ -185,7 +232,7 @@ def main_callback(
185
232
  is_eager=True,
186
233
  )] = False,
187
234
  ):
188
- """Associative memory with semantic search."""
235
+ """Reflective memory with semantic search."""
189
236
  # If no subcommand provided, show the current context (now)
190
237
  if ctx.invoked_subcommand is None:
191
238
  from .api import NOWDOC_ID
@@ -242,10 +289,6 @@ SinceOption = Annotated[
242
289
  ]
243
290
 
244
291
 
245
- # -----------------------------------------------------------------------------
246
- # Output Helpers
247
- # -----------------------------------------------------------------------------
248
-
249
292
  def _format_item(
250
293
  item: Item,
251
294
  as_json: bool = False,
@@ -255,27 +298,30 @@ def _format_item(
255
298
  similar_offsets: Optional[dict[str, int]] = None,
256
299
  ) -> str:
257
300
  """
258
- Format an item for display.
301
+ Format a single item for display.
259
302
 
260
- Text format: YAML frontmatter (matches docs/system format)
261
- With --ids: just the ID (for piping)
303
+ Output selection:
304
+ --ids: versioned ID only
305
+ --full or version_nav/similar_items present: YAML frontmatter
306
+ default: summary line (id@V{N} date summary)
262
307
 
263
308
  Args:
264
309
  item: The item to format
265
310
  as_json: Output as JSON
266
- version_nav: Optional version navigation info (prev/next lists)
267
- viewing_offset: If viewing an old version, the offset (1=previous, 2=two ago)
268
- similar_items: Optional list of similar items to display
269
- similar_offsets: Version offsets for similar items (item.id -> offset)
311
+ version_nav: Version navigation info (triggers full format)
312
+ viewing_offset: Version offset if viewing old version (triggers full format)
313
+ similar_items: Similar items to display (triggers full format)
314
+ similar_offsets: Version offsets for similar items
270
315
  """
271
316
  if _get_ids_output():
272
- return json.dumps(item.id) if as_json else item.id
317
+ versioned_id = _format_versioned_id(item)
318
+ return json.dumps(versioned_id) if as_json else versioned_id
273
319
 
274
320
  if as_json:
275
321
  result = {
276
322
  "id": item.id,
277
323
  "summary": item.summary,
278
- "tags": item.tags,
324
+ "tags": _filter_display_tags(item.tags),
279
325
  "score": item.score,
280
326
  }
281
327
  if viewing_offset is not None:
@@ -318,13 +364,18 @@ def _format_item(
318
364
  result["version_nav"]["next"] = [{"offset": 0, "vid": f"{item.id}@V{{0}}", "label": "current"}]
319
365
  return json.dumps(result)
320
366
 
321
- return _format_yaml_frontmatter(item, version_nav, viewing_offset, similar_items, similar_offsets)
367
+ # Full format when:
368
+ # - --full flag is set
369
+ # - version navigation or similar items are provided (can't display in summary)
370
+ if _get_full_output() or version_nav or similar_items or viewing_offset is not None:
371
+ return _format_yaml_frontmatter(item, version_nav, viewing_offset, similar_items, similar_offsets)
372
+ return _format_summary_line(item)
322
373
 
323
374
 
324
375
  def _format_items(items: list[Item], as_json: bool = False) -> str:
325
376
  """Format multiple items for display."""
326
377
  if _get_ids_output():
327
- ids = [item.id for item in items]
378
+ ids = [_format_versioned_id(item) for item in items]
328
379
  return json.dumps(ids) if as_json else "\n".join(ids)
329
380
 
330
381
  if as_json:
@@ -332,15 +383,20 @@ def _format_items(items: list[Item], as_json: bool = False) -> str:
332
383
  {
333
384
  "id": item.id,
334
385
  "summary": item.summary,
335
- "tags": item.tags,
386
+ "tags": _filter_display_tags(item.tags),
336
387
  "score": item.score,
337
388
  }
338
389
  for item in items
339
390
  ], indent=2)
340
- else:
341
- if not items:
342
- return "No results."
343
- return "\n\n".join(_format_item(item, as_json=False) for item in items)
391
+
392
+ if not items:
393
+ return "No results."
394
+
395
+ # Full format: YAML frontmatter with double-newline separator
396
+ # Default: summary lines with single-newline separator
397
+ if _get_full_output():
398
+ return "\n\n".join(_format_yaml_frontmatter(item) for item in items)
399
+ return "\n".join(_format_summary_line(item) for item in items)
344
400
 
345
401
 
346
402
  def _get_keeper(store: Optional[Path], collection: str) -> Keeper:
@@ -457,22 +513,11 @@ def list_recent(
457
513
  """
458
514
  List recent items by update time.
459
515
 
460
- Shows IDs by default (composable). Use --full for detailed output.
516
+ Default: summary lines. Use --ids for IDs only, --full for YAML.
461
517
  """
462
518
  kp = _get_keeper(store, collection)
463
519
  results = kp.list_recent(limit=limit)
464
-
465
- # Determine output mode: --full > --ids > command default (IDs for list)
466
- if _get_json_output():
467
- # JSON always outputs full items
468
- typer.echo(_format_items(results, as_json=True))
469
- elif _get_full_output():
470
- # --full flag: full YAML output
471
- typer.echo(_format_items(results, as_json=False))
472
- else:
473
- # Default for list: IDs only (composable)
474
- for item in results:
475
- typer.echo(item.id)
520
+ typer.echo(_format_items(results, as_json=_get_json_output()))
476
521
 
477
522
 
478
523
  @app.command()
@@ -643,7 +688,7 @@ def update(
643
688
  # Use content-addressed ID for stdin text (enables versioning)
644
689
  doc_id = id or _text_content_id(content)
645
690
  item = kp.remember(content, id=doc_id, summary=summary, tags=parsed_tags or None, lazy=lazy)
646
- elif source and "://" in source:
691
+ elif source and _URI_SCHEME_PATTERN.match(source):
647
692
  # URI mode: fetch from URI (ID is the URI itself)
648
693
  item = kp.update(source, tags=parsed_tags or None, summary=summary, lazy=lazy)
649
694
  elif source:
@@ -1045,6 +1090,10 @@ def list_collections(
1045
1090
 
1046
1091
  @app.command()
1047
1092
  def init(
1093
+ reset_system_docs: Annotated[bool, typer.Option(
1094
+ "--reset-system-docs",
1095
+ help="Force reload system documents from bundled content (overwrites modifications)"
1096
+ )] = False,
1048
1097
  store: StoreOption = None,
1049
1098
  collection: CollectionOption = "default",
1050
1099
  ):
@@ -1053,6 +1102,11 @@ def init(
1053
1102
  """
1054
1103
  kp = _get_keeper(store, collection)
1055
1104
 
1105
+ # Handle reset if requested
1106
+ if reset_system_docs:
1107
+ stats = kp.reset_system_documents()
1108
+ typer.echo(f"Reset {stats['reset']} system documents")
1109
+
1056
1110
  # Show config and store paths
1057
1111
  config = kp._config
1058
1112
  config_path = config.config_path if config else None
keep/config.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- Configuration management for associative memory stores.
2
+ Configuration management for reflective memory stores.
3
3
 
4
4
  The configuration is stored as a TOML file in the store directory.
5
5
  It specifies which providers to use and their parameters.
@@ -19,6 +19,7 @@ import tomli_w
19
19
 
20
20
  CONFIG_FILENAME = "keep.toml"
21
21
  CONFIG_VERSION = 3 # Bumped for document versioning support
22
+ SYSTEM_DOCS_VERSION = 1 # Increment when bundled system docs content changes
22
23
 
23
24
 
24
25
  @dataclass
@@ -88,6 +89,9 @@ class StoreConfig:
88
89
  # Maximum length for summaries (used for smart remember and validation)
89
90
  max_summary_length: int = 500
90
91
 
92
+ # System docs version (tracks which bundled docs have been applied to this store)
93
+ system_docs_version: int = 0
94
+
91
95
  @property
92
96
  def config_path(self) -> Path:
93
97
  """Path to the TOML config file."""
@@ -351,6 +355,9 @@ def load_config(config_dir: Path) -> StoreConfig:
351
355
  # Parse max_summary_length (default 500)
352
356
  max_summary_length = data.get("store", {}).get("max_summary_length", 500)
353
357
 
358
+ # Parse system_docs_version (default 0 for stores that predate this feature)
359
+ system_docs_version = data.get("store", {}).get("system_docs_version", 0)
360
+
354
361
  return StoreConfig(
355
362
  path=actual_store,
356
363
  config_dir=config_dir,
@@ -363,6 +370,7 @@ def load_config(config_dir: Path) -> StoreConfig:
363
370
  embedding_identity=parse_embedding_identity(data.get("embedding_identity")),
364
371
  default_tags=default_tags,
365
372
  max_summary_length=max_summary_length,
373
+ system_docs_version=system_docs_version,
366
374
  )
367
375
 
368
376
 
@@ -404,6 +412,9 @@ def save_config(config: StoreConfig) -> None:
404
412
  # Only write max_summary_length if not default
405
413
  if config.max_summary_length != 500:
406
414
  store_section["max_summary_length"] = config.max_summary_length
415
+ # Write system_docs_version if set (tracks migration state)
416
+ if config.system_docs_version > 0:
417
+ store_section["system_docs_version"] = config.system_docs_version
407
418
 
408
419
  data = {
409
420
  "store": store_section,
keep/data/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # Package data for keep
@@ -0,0 +1 @@
1
+ # System documents for keep
@@ -0,0 +1,299 @@
1
+ ---
2
+ tags:
3
+ category: system
4
+ context: practice
5
+ ---
6
+ # Conversation Patterns for Process Knowledge
7
+
8
+ This document describes patterns for recognizing, tracking, and learning from
9
+ the structure of work — not *what* we know, but *how work proceeds*.
10
+
11
+ These patterns are grounded in the Language-Action Perspective (Winograd & Flores),
12
+ which treats work as networks of commitments made through language.
13
+
14
+ ---
15
+
16
+ ## Decision Matrix: What Kind of Conversation Is This?
17
+
18
+ | Signal | Conversation Type | Your Stance | Don't Do This |
19
+ |--------|-------------------|-------------|---------------|
20
+ | "Can you...", "Please...", "I need..." | **Action** | Clarify → Promise → Deliver | Promise before understanding |
21
+ | "What if...", "Imagine...", "Could we..." | **Possibility** | Explore, generate options, hold lightly | Commit prematurely |
22
+ | "What is...", "How does...", "Why..." | **Clarification** | Explain until understanding lands | Over-answer; assume you know the real question |
23
+ | "I'm trying to understand...", "Context is..." | **Orientation** | Listen, reflect back, surface assumptions | Jump to solutions |
24
+ | "Here's what I found...", "Status update..." | **Report** | Acknowledge, ask what's needed next | Assume it's a request |
25
+
26
+ **Transition signals** (conversation type is shifting):
27
+ - Possibility → Action: "Let's do X" / "I want to go with..."
28
+ - Clarification → Action: "Ok, so please..." / "Now that I understand..."
29
+ - Orientation → Clarification: "So what does X mean?" / "Help me understand..."
30
+
31
+ **When uncertain:** Ask. "Are you exploring options, or would you like me to do something?"
32
+
33
+ ---
34
+
35
+ ## Core Insight
36
+
37
+ Work is not information processing. Work is **commitment management**.
38
+
39
+ When an agent assists with a task, it participates in a conversation with structure:
40
+ - Requests create openings
41
+ - Promises create obligations
42
+ - Completion is declared, not merely achieved
43
+ - Satisfaction closes the loop
44
+
45
+ Understanding *where we are* in this structure is as important as understanding
46
+ *what we know* about the subject matter.
47
+
48
+ ---
49
+
50
+ ## The Basic Conversation for Action
51
+
52
+ ```
53
+ Customer Performer
54
+ │ │
55
+ │──── 1. Request ────────────────→│
56
+ │ │
57
+ │←─── 2. Promise (or Counter) ────│
58
+ │ │
59
+ │ [ work happens ] │
60
+ │ │
61
+ │←─── 3. Declare Complete ────────│
62
+ │ │
63
+ │──── 4. Declare Satisfied ──────→│
64
+ │ │
65
+ ```
66
+
67
+ At any point: Withdraw, Decline, Renegotiate.
68
+
69
+ **For agents:** Recognizing this structure helps answer:
70
+ - "What has been asked of me?"
71
+ - "What have I committed to?"
72
+ - "What does 'done' look like?"
73
+ - "Who needs to be satisfied?"
74
+
75
+ ---
76
+
77
+ ## Conversation Types
78
+
79
+ ### Request for Action
80
+ Someone asks for something to be done.
81
+
82
+ **Recognize by:** Imperative language, "can you", "please", "I need"
83
+
84
+ **Track:**
85
+ - What specifically was requested?
86
+ - Any conditions or constraints?
87
+ - Implicit quality criteria?
88
+ - Who is the customer (who declares satisfaction)?
89
+
90
+ **Completion:** Customer declares satisfaction, not performer declares done.
91
+
92
+ ### Request for Information
93
+ Someone asks to know something.
94
+
95
+ **Recognize by:** Questions, "what is", "how does", "why"
96
+
97
+ **Track:**
98
+ - What gap in understanding prompted this?
99
+ - What level of detail is appropriate?
100
+ - What would make this answer useful?
101
+
102
+ **Completion:** Questioner indicates understanding (often implicit).
103
+
104
+ ### Offer
105
+ Someone proposes to do something.
106
+
107
+ **Recognize by:** "I could", "would you like me to", "I suggest"
108
+
109
+ **Track:**
110
+ - What conditions on acceptance?
111
+ - What's the scope of the offer?
112
+
113
+ **Completion:** Offer accepted → becomes promise. Offer declined → closed.
114
+
115
+ ### Report / Declaration
116
+ Someone asserts a state of affairs.
117
+
118
+ **Recognize by:** Statements of fact, status updates, "I found that"
119
+
120
+ **Track:**
121
+ - What changed as a result of this declaration?
122
+ - Who needs to know?
123
+
124
+ **Completion:** Acknowledgment (may trigger new conversations).
125
+
126
+ ---
127
+
128
+ ## Conversations for Possibility
129
+
130
+ Possibility conversations explore "what could be" — no commitment yet.
131
+
132
+ **Recognize by:** "What if", "imagine", "we could", "brainstorm", "options"
133
+
134
+ **Your stance:**
135
+ - Generate, don't filter prematurely
136
+ - Hold ideas lightly — nothing is promised
137
+ - Expand the space before narrowing
138
+ - Name options without advocating
139
+
140
+ **Track:**
141
+ - Options generated
142
+ - Constraints surfaced
143
+ - Energy/interest signals ("that's interesting" vs "hmm")
144
+
145
+ **Completion:** Not satisfaction — rather, either:
146
+ - Transition to Action ("let's do X")
147
+ - Explicit close ("good to know our options")
148
+ - Energy dissipates naturally
149
+
150
+ **Critical:** Don't promise during possibility. "I could do X" is an option, not a commitment. The transition to action must be explicit.
151
+
152
+ ```bash
153
+ # Index possibility exploration
154
+ keep update "Explored three auth options: OAuth2, API keys, magic links. \
155
+ User showed interest in magic links for UX simplicity. No decision yet." \
156
+ --tag type=possibility --tag topic=authentication --tag status=open
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Breakdowns: Where Learning Happens
162
+
163
+ A **breakdown** occurs when the normal flow is interrupted:
164
+ - Expected response doesn't come
165
+ - Declared completion isn't satisfactory
166
+ - Preconditions weren't met
167
+ - Ambiguity surfaces mid-work
168
+
169
+ **Breakdowns are valuable.** They reveal assumptions that were invisible.
170
+
171
+ **Pattern for breakdown learning:**
172
+ ```
173
+ 1. Notice: "This isn't going as expected"
174
+ 2. Articulate: "The assumption was X, but actually Y"
175
+ 3. Repair: Complete the immediate conversation
176
+ 4. Record: Capture the learning for future conversations of this type
177
+ ```
178
+
179
+ When indexing a breakdown:
180
+ ```bash
181
+ keep update "Assumption: user wanted full rewrite. Actually: wanted minimal patch." \
182
+ --tag type=breakdown --tag conversation_type=code_change_request
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Nested and Linked Conversations
188
+
189
+ Real work involves conversations within conversations:
190
+
191
+ ```
192
+ User requests feature
193
+ └─ Agent promises feature
194
+ └─ Agent requests clarification (sub-conversation)
195
+ │ └─ User provides clarification
196
+ │ └─ Agent acknowledges (closes sub-conversation)
197
+ └─ Agent declares complete
198
+ └─ User requests revision (linked follow-on)
199
+ └─ ...
200
+ ```
201
+
202
+ **Track parent/child relationships** to understand scope:
203
+ - Completing a sub-conversation doesn't complete the parent
204
+ - Breakdowns in child conversations may propagate up
205
+ - Context flows down (child inherits parent context)
206
+
207
+ ---
208
+
209
+ ## Recurrent Patterns by Domain
210
+
211
+ ### Software Development
212
+
213
+ | Pattern | Structure | Completion Condition |
214
+ |---------|-----------|---------------------|
215
+ | Bug report | Request → Diagnosis → Fix → Verify | User confirms fix works |
216
+ | Feature request | Request → Clarify → Implement → Review → Accept | Stakeholder accepts |
217
+ | Code review | Offer(changes) → Review → Request(revisions) → Update → Approve | Reviewer approves |
218
+ | Refactor | Offer → Scope agreement → Execute → Verify behavior preserved | Tests pass, reviewer approves |
219
+
220
+ ### Research / Analysis
221
+
222
+ | Pattern | Structure | Completion Condition |
223
+ |---------|-----------|---------------------|
224
+ | Question | Request(info) → Search → Synthesize → Present | Questioner satisfied |
225
+ | Hypothesis test | Declare(hypothesis) → Design test → Execute → Report | Evidence evaluated |
226
+ | Literature review | Request → Gather → Synthesize → Summarize | Comprehensive & relevant |
227
+
228
+ ### Planning / Coordination
229
+
230
+ | Pattern | Structure | Completion Condition |
231
+ |---------|-----------|---------------------|
232
+ | Task breakdown | Request(outcome) → Decompose → Commit to parts → Track → Integrate | Outcome achieved |
233
+ | Decision | Present options → Evaluate → Declare choice | Commitment to proceed |
234
+ | Handoff | Declare(status) → Transfer commitments → Acknowledge | Receiving agent accepts |
235
+
236
+ ---
237
+
238
+ ## Agent Handoff as Commitment Transfer
239
+
240
+ When one agent hands off to another:
241
+
242
+ ```bash
243
+ # Outgoing agent records state
244
+ keep now "User requested OAuth2 implementation. I promised and partially delivered. \
245
+ Token acquisition works. Refresh flow incomplete. User awaiting completion." \
246
+ --tag topic=authentication --tag state=handoff
247
+ ```
248
+
249
+ Incoming agent reads this and knows:
250
+ - There's an open promise to the user
251
+ - What "done" looks like
252
+ - Where to pick up
253
+
254
+ ```bash
255
+ # Incoming agent checks context
256
+ keep now
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Learning New Patterns
262
+
263
+ Agents can recognize and record new conversation patterns:
264
+
265
+ ```bash
266
+ keep update "Pattern: Incremental Specification. \
267
+ When requirements are vague, don't promise immediately. \
268
+ Propose interpretation → get correction → repeat until clear. \
269
+ Only then commit to action. Breakdown risk: Promising too early leads to rework." \
270
+ --tag type=conversation_pattern --tag domain=general
271
+ ```
272
+
273
+ ---
274
+
275
+ ## Using Patterns in Practice
276
+
277
+ **At task start:**
278
+ 1. What kind of conversation is this?
279
+ 2. What's my role (customer or performer)?
280
+ 3. What does completion look like?
281
+ 4. Have I seen breakdowns in this pattern before?
282
+
283
+ **Mid-task:**
284
+ 1. Where are we in the conversation structure?
285
+ 2. Have any sub-conversations opened?
286
+ 3. Are there signs of breakdown?
287
+
288
+ **At task end:**
289
+ 1. Was satisfaction declared (not just completion)?
290
+ 2. Any learnings to record?
291
+ 3. Open commitments to hand off?
292
+
293
+ ---
294
+
295
+ ## References
296
+
297
+ - Winograd, T. & Flores, F. (1986). *Understanding Computers and Cognition*
298
+ - Denning, P. & Medina-Mora, R. (1995). "Completing the Loops"
299
+ - Flores, F. et al. (1988). "Computer Systems and the Design of Organizational Interaction"
@@ -0,0 +1,179 @@
1
+ ---
2
+ tags:
3
+ category: system
4
+ context: domains
5
+ ---
6
+ # Domain Patterns
7
+
8
+ This document describes common organization patterns for different use cases.
9
+ These are suggestions, not requirements — adapt them to your needs.
10
+
11
+ **See also:** [conversations.md](conversations.md) for process knowledge —
12
+ understanding *how work proceeds*, not just *what we know*.
13
+
14
+ ---
15
+
16
+ ## Software Development
17
+
18
+ **Collections:**
19
+ | Collection | Purpose |
20
+ |------------|---------|
21
+ | `code` | Source files, organized by the codebase |
22
+ | `docs` | Documentation, READMEs, architecture decisions |
23
+ | `issues` | Bugs, errors, stack traces, problems encountered |
24
+ | `decisions` | "Why did we do X?" — architectural reasoning |
25
+
26
+ **Suggested tags:**
27
+ - `language` — python, typescript, rust, etc.
28
+ - `layer` — frontend, backend, infra, database, cli
29
+ - `module` — auth, api, ui, core, tests
30
+ - `status` — working, broken, needs_review, deprecated
31
+
32
+ **Common conversation patterns:** (see conversations.md)
33
+ - Bug report → Diagnosis → Fix → Verify
34
+ - Feature request → Clarify → Implement → Review → Accept
35
+ - Code review → Revisions → Approve
36
+
37
+ **Agent guidance:**
38
+ - Index every file you read or modify
39
+ - When encountering an error, index it with the error message as content
40
+ - Before searching the filesystem, check `keep find` — you may already know about it
41
+ - Use `keep find --id` to discover related code when working on a feature
42
+ - Record breakdowns: "Assumption X was wrong, actually Y"
43
+
44
+ ---
45
+
46
+ ## Market Research
47
+
48
+ **Collections:**
49
+ | Collection | Purpose |
50
+ |------------|---------|
51
+ | `sources` | Primary research — articles, reports, filings |
52
+ | `competitors` | Competitor-specific intelligence |
53
+ | `insights` | Synthesized findings and conclusions |
54
+ | `data` | Quantitative sources, datasets, statistics |
55
+
56
+ **Suggested tags:**
57
+ - `company` — specific company names
58
+ - `market` — b2b_saas, consumer_fintech, healthcare, etc.
59
+ - `source_type` — article, report, interview, sec_filing, press_release
60
+ - `credibility` — high, medium, low, unverified
61
+ - `region` — north_america, europe, apac
62
+
63
+ **Agent guidance:**
64
+ - Always index sources you fetch, even if they seem tangential
65
+ - Tag competitor information consistently for later cross-reference
66
+ - Create insights entries to capture your synthesized conclusions
67
+ - Use semantic search to find connections across different sources
68
+
69
+ ---
70
+
71
+ ## Personal Reflection & Growth
72
+
73
+ **Collections:**
74
+ | Collection | Purpose |
75
+ |------------|---------|
76
+ | `journal` | Reflections, conversations, daily entries |
77
+ | `goals` | Active goals and progress tracking |
78
+ | `feedback` | External feedback received |
79
+ | `patterns` | Recurring themes you've noticed |
80
+
81
+ **Suggested tags:**
82
+ - `life_area` — career, health, relationships, learning, finance, creativity
83
+ - `emotion` — confident, anxious, grateful, frustrated, energized
84
+ - `theme` — boundaries, communication, procrastination, perfectionism
85
+ - `energy` — high, low, recovering
86
+
87
+ **Agent guidance:**
88
+ - Index conversations about personal topics as journal entries
89
+ - Look for patterns when the user reports similar feelings repeatedly
90
+ - Connect current challenges to past insights
91
+ - Use `keep find --id` to surface "you've felt this way before"
92
+
93
+ ---
94
+
95
+ ## Healthcare Tracking
96
+
97
+ **Collections:**
98
+ | Collection | Purpose |
99
+ |------------|---------|
100
+ | `symptoms` | Symptom reports and tracking |
101
+ | `medications` | Current and historical medications |
102
+ | `appointments` | Visit notes, provider interactions |
103
+ | `research` | Health information gathered |
104
+
105
+ **Suggested tags:**
106
+ - `body_system` — cardiovascular, digestive, musculoskeletal, neurological
107
+ - `symptom` — headache, fatigue, pain, nausea (specific symptoms)
108
+ - `severity` — mild, moderate, severe
109
+ - `provider` — dr_smith, clinic_name
110
+ - `medication` — specific medication names
111
+
112
+ **Agent guidance:**
113
+ - Always index symptom reports with dates for timeline tracking
114
+ - Cross-reference symptoms with medication changes
115
+ - Index appointment outcomes for continuity
116
+ - Be precise with medical terminology in tags for accurate retrieval
117
+
118
+ ---
119
+
120
+ ## Cross-Domain Patterns
121
+
122
+ These patterns apply regardless of domain:
123
+
124
+ **Conversation indexing:**
125
+ ```bash
126
+ # Index the current conversation
127
+ keep update "User asked about X, we discussed Y, decided Z" \
128
+ --tag session=abc123
129
+ ```
130
+
131
+ **Context tracking:**
132
+ ```bash
133
+ # Record current focus for handoff
134
+ keep now "Working on feature X" --tag topic=feature_x
135
+ ```
136
+
137
+ **Breakdown learning:**
138
+ ```bash
139
+ # When something goes wrong, capture the learning
140
+ keep update "Assumed user wanted full rewrite, actually wanted minimal fix. \
141
+ Ask about scope before large changes." \
142
+ --tag type=breakdown --tag conversation_type=code_change_request
143
+ ```
144
+
145
+ **Temporal queries using system tags:**
146
+ ```bash
147
+ # Find items updated today
148
+ keep tag _updated_date=2026-01-30
149
+
150
+ # Find all inline content (from remember)
151
+ keep tag _source=inline
152
+ ```
153
+
154
+ **Progressive refinement:**
155
+ ```bash
156
+ # Start broad, then narrow
157
+ keep find "authentication"
158
+ keep tag module=auth
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Process Knowledge
164
+
165
+ Beyond subject-matter knowledge, index *how to work effectively*:
166
+
167
+ ```bash
168
+ # Index a learned pattern
169
+ keep update "Pattern: Incremental Specification. \
170
+ When requirements are vague, don't promise immediately. \
171
+ Propose interpretation → get correction → repeat until clear. \
172
+ Only then commit to action. Breakdown risk: Promising too early leads to rework." \
173
+ --tag type=conversation_pattern --tag domain=general
174
+
175
+ # Later, retrieve it
176
+ keep tag type=conversation_pattern
177
+ ```
178
+
179
+ See [conversations.md](conversations.md) for the full framework.
@@ -0,0 +1,19 @@
1
+ ---
2
+ tags:
3
+ category: system
4
+ context: now
5
+ ---
6
+ # Now
7
+
8
+ Reminders for essential practice.
9
+ (Keep this updated as you need)
10
+
11
+ **Before acting:**
12
+ - Where are we in the conversation?
13
+ - What do I know? `keep now`
14
+ - What else is skillful? `keep find ...`
15
+
16
+ **After acting:**
17
+ - What happened? `keep update "what I learned" -t context=learning`
18
+ - What do I save? `keep update`
19
+ - What now? `keep now "Current intention: ..."`
@@ -1,5 +1,5 @@
1
1
  """
2
- Provider interfaces for associative memory services.
2
+ Provider interfaces for reflective memory services.
3
3
 
4
4
  Each provider type defines a protocol that concrete implementations must follow.
5
5
  Providers are configured at store initialization and handle the heavy lifting of:
keep/types.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- Data types for associative memory.
2
+ Data types for reflective memory.
3
3
  """
4
4
 
5
5
  from dataclasses import dataclass, field
@@ -9,6 +9,10 @@ from typing import Optional
9
9
  # System tag prefix - tags starting with this are managed by the system
10
10
  SYSTEM_TAG_PREFIX = "_"
11
11
 
12
+ # Tags used internally but hidden from display output
13
+ # These exist for efficient queries/sorting but aren't user-facing
14
+ INTERNAL_TAGS = frozenset({"_updated_date"})
15
+
12
16
 
13
17
  def filter_non_system_tags(tags: dict[str, str]) -> dict[str, str]:
14
18
  """
@@ -23,7 +27,7 @@ def filter_non_system_tags(tags: dict[str, str]) -> dict[str, str]:
23
27
  @dataclass(frozen=True)
24
28
  class Item:
25
29
  """
26
- An item retrieved from the associative memory store.
30
+ An item retrieved from the reflective memory store.
27
31
 
28
32
  This is a read-only snapshot. To modify an item, use api.update()
29
33
  which returns a new Item with updated values.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keep-skill
3
- Version: 0.4.2
4
- Summary: Semantic memory - remember and search documents by meaning
3
+ Version: 0.7.0
4
+ Summary: Reflective memory - remember and search documents by meaning
5
5
  Project-URL: Homepage, https://github.com/hughpyle/keep
6
6
  Project-URL: Repository, https://github.com/hughpyle/keep
7
7
  Author: Hugh Pyle
@@ -51,7 +51,7 @@ Description-Content-Type: text/markdown
51
51
 
52
52
  # keep
53
53
 
54
- **Semantic memory with version history.**
54
+ **Reflective memory with version history.**
55
55
 
56
56
  Index documents and notes. Search by meaning. Track changes over time.
57
57
 
@@ -176,35 +176,14 @@ See [docs/QUICKSTART.md](docs/QUICKSTART.md) for configuration and more examples
176
176
  This library was designed as an agent skill — persistent memory that helps agents reflect before acting and learn from experience.
177
177
 
178
178
  **The practice:**
179
- - Pause before acting `keep find` what you already know
180
- - Notice breakdowns — when assumptions surface, index them
181
- - Reflect after `keep update` learnings for future sessions
179
+ - Before acting, use `keep` to reflect on the context.
180
+ - Notice breakdowns — when assumptions surface, index them.
181
+ - After acting, use `keep` to save your context and intentions.
182
182
 
183
183
  See **[SKILL.md](SKILL.md)** for the full practice guide.
184
184
 
185
185
  ---
186
186
 
187
- ## Status
188
-
189
- **Current:** v0.3.0
190
-
191
- **Working:**
192
- - ✅ Semantic search with embeddings
193
- - ✅ Document versioning (all updates retain history)
194
- - ✅ Content-addressed IDs for text (same content = same ID)
195
- - ✅ Tag queries and full-text search
196
- - ✅ Current context tracking (`keep now`)
197
- - ✅ Recency decay (recent items rank higher)
198
- - ✅ Lazy summarization (background processing)
199
- - ✅ Provider abstraction (local or API-based)
200
-
201
- **Planned** (see [later/](later/)):
202
- - ⏳ Private/shared routing
203
- - ⏳ Relationship graphs between items
204
- - ⏳ LLM-based auto-tagging
205
-
206
- ---
207
-
208
187
  ## License
209
188
 
210
189
  MIT
@@ -1,9 +1,9 @@
1
- keep/__init__.py,sha256=1WVkySoomQuf9-o3pIKs1CC2OwIyfkiaCxn-mO6nhd8,1581
1
+ keep/__init__.py,sha256=yBK7jvbQmx9fRBGanICNgrQdyCQHzt5bNPp098Qvh9E,1621
2
2
  keep/__main__.py,sha256=3Uu70IhIDIjh8OW6jp9jQQ3dF2lKdJWi_3FtRIQMiMY,104
3
- keep/api.py,sha256=7oa6jdmeE55dATOEVw18sXGVBVndjiD_7tMDcP_xl_0,59746
3
+ keep/api.py,sha256=uckFZzeWMWcQ3osfcBdOhEDS58AbFtFTRPmrl1vo_Ko,64477
4
4
  keep/chunking.py,sha256=neAXOLSvVwbUxapbqq7nZrbSNSzMXuhxj-ODoOSodsU,11830
5
- keep/cli.py,sha256=DzZ5Dy4U25q-Mnbv5WbfRsZaN-ped24GDk34fVddmFA,42440
6
- keep/config.py,sha256=xhsTS_55HSzYxFNGt2z0q_0Ne-s9L9_3om8Uf_8gHB4,15643
5
+ keep/cli.py,sha256=6IblpyqDcZlNK8S5VtdKTRFhjBcaXTpSSo3Au4wO0i0,44758
6
+ keep/config.py,sha256=hWjiJDg2u6p8IJpksXe0ngVxQNcKHKRFKUDJQBFlG7I,16226
7
7
  keep/context.py,sha256=CNpjmrv6eW2kV1E0MO6qAQfhYKRlfzAL--6v4Mj1nFY,71
8
8
  keep/document_store.py,sha256=UswqKIGSc5E-r7Tg9k0g5-byYnuar3e9FieQ7WNod9k,29109
9
9
  keep/errors.py,sha256=G9e5FbdfeugyfHOuL_SPZlM5jgWWnwsX4hM7IzanBZc,857
@@ -12,8 +12,13 @@ keep/logging_config.py,sha256=IGwkgIyg-TfYaT4MnoCXfmjeHAe_wsB_XQ1QhVT_ro8,3503
12
12
  keep/paths.py,sha256=Dv7pM6oo2QgjL6sj5wPjhuMOK2wqUkfd4Kz08TwJ1ps,3331
13
13
  keep/pending_summaries.py,sha256=_irGe7P1Lmog2c5cEgx-BElpq4YJW-tEmF5A3IUZQbQ,5727
14
14
  keep/store.py,sha256=SBc2QdTyApdDDVjm2uZQI6tGbV5Hurfetgj7dyTO65o,17881
15
- keep/types.py,sha256=f6uOSYsYt6mj1ulKn2iRkooi__dWCiOQFPD6he2eID4,2149
16
- keep/providers/__init__.py,sha256=GFX_12g9OdjmpFUkTekOQBOWvcRW2Ae6yidfVVW2SiI,1095
15
+ keep/types.py,sha256=irvUJYUHQgQdVqC4_lgrG0FbTN1BdZqFxZr0ubVPSG4,2314
16
+ keep/data/__init__.py,sha256=C1YARrudHwK2Bmlxkh7dZlIaNJ5m5WrSTglCdG8e3T0,24
17
+ keep/data/system/__init__.py,sha256=Rp92_sBO3kscuWXJomo0HKeHfU-N4BgBeT3-5El0Mcg,28
18
+ keep/data/system/conversations.md,sha256=jE53wYSUyu5uPFNtO1Tu6w4f5QxqLei7muxLF_kZE2s,9837
19
+ keep/data/system/domains.md,sha256=OCRAGvB0EaphsEammxLmx7L-orw2OHzgF6GwAZ8ztUs,5556
20
+ keep/data/system/now.md,sha256=0HbRikpHFhjrnjXqusEye3v-g_cF4AmgseiO-B33ANk,414
21
+ keep/providers/__init__.py,sha256=6AwJYc6cF1ZT6BcU_6ATyeWk7MHohdVU2-ccqDSvCHU,1094
17
22
  keep/providers/base.py,sha256=7Ug4Kj9fK2Dq4zDcZjn-GKsoZBOAlB9b-FMk969ER-g,14590
18
23
  keep/providers/documents.py,sha256=EXeSy5i3RUL0kciIC6w3ldAEfbTIyC5fgfzC_WAI0iY,8211
19
24
  keep/providers/embedding_cache.py,sha256=gna6PZEJanbn2GUN0vj1b1MC0xVWePM9cot2KgZUdu8,8856
@@ -21,8 +26,8 @@ keep/providers/embeddings.py,sha256=zi8GyitKexdbCJyU1nLrUhGt_zzPn3udYrrPZ5Ak8Wo,
21
26
  keep/providers/llm.py,sha256=BxROKOklKbkGsHcSADPNNgWQExgSN6Bg4KPQIxVuB3U,12441
22
27
  keep/providers/mlx.py,sha256=aNl00r9tGi5tCGj2ArYH7CmDHtL1jLjVzb1rofU1DAo,9050
23
28
  keep/providers/summarization.py,sha256=MlVTcYipaqp2lT-QYnznp0AMuPVG36QfcTQnvY7Gb-Q,3409
24
- keep_skill-0.4.2.dist-info/METADATA,sha256=GKkPrekD30dauxLoogAmSaoD2zhIxltJ4RDkg7yPvzk,6606
25
- keep_skill-0.4.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
26
- keep_skill-0.4.2.dist-info/entry_points.txt,sha256=W8yiI4kNeW0IC8ji4EHRWrvdhFxzaqTIePUhJAJAMOo,39
27
- keep_skill-0.4.2.dist-info/licenses/LICENSE,sha256=zsm0tpvtyUkevcjn5BIvs9jAho8iwxq3Ax9647AaOSg,1086
28
- keep_skill-0.4.2.dist-info/RECORD,,
29
+ keep_skill-0.7.0.dist-info/METADATA,sha256=aZx2pnN3i0BmOZzR8zYn49mRq1ZKq1-Cggc2FvqgYvs,6038
30
+ keep_skill-0.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
+ keep_skill-0.7.0.dist-info/entry_points.txt,sha256=W8yiI4kNeW0IC8ji4EHRWrvdhFxzaqTIePUhJAJAMOo,39
32
+ keep_skill-0.7.0.dist-info/licenses/LICENSE,sha256=zsm0tpvtyUkevcjn5BIvs9jAho8iwxq3Ax9647AaOSg,1086
33
+ keep_skill-0.7.0.dist-info/RECORD,,