lore-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. lore_sdk-0.1.0/.github/workflows/ci.yml +49 -0
  2. lore_sdk-0.1.0/.gitignore +12 -0
  3. lore_sdk-0.1.0/LICENSE +21 -0
  4. lore_sdk-0.1.0/PKG-INFO +246 -0
  5. lore_sdk-0.1.0/README.md +219 -0
  6. lore_sdk-0.1.0/architecture.md +381 -0
  7. lore_sdk-0.1.0/examples/basic_usage.py +45 -0
  8. lore_sdk-0.1.0/examples/custom_embeddings.py +51 -0
  9. lore_sdk-0.1.0/examples/redaction_demo.py +43 -0
  10. lore_sdk-0.1.0/prd.md +177 -0
  11. lore_sdk-0.1.0/product-brief.md +182 -0
  12. lore_sdk-0.1.0/pyproject.toml +54 -0
  13. lore_sdk-0.1.0/sprint-plan.md +100 -0
  14. lore_sdk-0.1.0/src/lore/__init__.py +8 -0
  15. lore_sdk-0.1.0/src/lore/__main__.py +5 -0
  16. lore_sdk-0.1.0/src/lore/cli.py +138 -0
  17. lore_sdk-0.1.0/src/lore/embed/__init__.py +6 -0
  18. lore_sdk-0.1.0/src/lore/embed/base.py +18 -0
  19. lore_sdk-0.1.0/src/lore/embed/local.py +187 -0
  20. lore_sdk-0.1.0/src/lore/exceptions.py +9 -0
  21. lore_sdk-0.1.0/src/lore/lore.py +380 -0
  22. lore_sdk-0.1.0/src/lore/prompt.py +52 -0
  23. lore_sdk-0.1.0/src/lore/redact/__init__.py +5 -0
  24. lore_sdk-0.1.0/src/lore/redact/patterns.py +59 -0
  25. lore_sdk-0.1.0/src/lore/redact/pipeline.py +92 -0
  26. lore_sdk-0.1.0/src/lore/store/__init__.py +7 -0
  27. lore_sdk-0.1.0/src/lore/store/base.py +36 -0
  28. lore_sdk-0.1.0/src/lore/store/memory.py +45 -0
  29. lore_sdk-0.1.0/src/lore/store/sqlite.py +167 -0
  30. lore_sdk-0.1.0/src/lore/types.py +35 -0
  31. lore_sdk-0.1.0/stories/story-1.md +24 -0
  32. lore_sdk-0.1.0/stories/story-10.md +23 -0
  33. lore_sdk-0.1.0/stories/story-11.md +22 -0
  34. lore_sdk-0.1.0/stories/story-12.md +22 -0
  35. lore_sdk-0.1.0/stories/story-13.md +22 -0
  36. lore_sdk-0.1.0/stories/story-14.md +23 -0
  37. lore_sdk-0.1.0/stories/story-15.md +21 -0
  38. lore_sdk-0.1.0/stories/story-2.md +26 -0
  39. lore_sdk-0.1.0/stories/story-3.md +23 -0
  40. lore_sdk-0.1.0/stories/story-4.md +24 -0
  41. lore_sdk-0.1.0/stories/story-5.md +25 -0
  42. lore_sdk-0.1.0/stories/story-6.md +21 -0
  43. lore_sdk-0.1.0/stories/story-7.md +23 -0
  44. lore_sdk-0.1.0/stories/story-8.md +25 -0
  45. lore_sdk-0.1.0/stories/story-9.md +23 -0
  46. lore_sdk-0.1.0/tests/__init__.py +0 -0
  47. lore_sdk-0.1.0/tests/test_cli.py +101 -0
  48. lore_sdk-0.1.0/tests/test_decay_voting.py +208 -0
  49. lore_sdk-0.1.0/tests/test_edge_cases.py +202 -0
  50. lore_sdk-0.1.0/tests/test_embedder.py +92 -0
  51. lore_sdk-0.1.0/tests/test_export_import.py +128 -0
  52. lore_sdk-0.1.0/tests/test_lesson.py +38 -0
  53. lore_sdk-0.1.0/tests/test_prompt.py +94 -0
  54. lore_sdk-0.1.0/tests/test_query.py +110 -0
  55. lore_sdk-0.1.0/tests/test_redact.py +173 -0
  56. lore_sdk-0.1.0/tests/test_redact_integration.py +69 -0
  57. lore_sdk-0.1.0/tests/test_stores.py +209 -0
  58. lore_sdk-0.1.0/ts/.eslintrc.json +16 -0
  59. lore_sdk-0.1.0/ts/.gitignore +3 -0
  60. lore_sdk-0.1.0/ts/README.md +151 -0
  61. lore_sdk-0.1.0/ts/examples/basic-usage.ts +55 -0
  62. lore_sdk-0.1.0/ts/examples/custom-embeddings.ts +53 -0
  63. lore_sdk-0.1.0/ts/package-lock.json +4061 -0
  64. lore_sdk-0.1.0/ts/package.json +46 -0
  65. lore_sdk-0.1.0/ts/src/embed.ts +59 -0
  66. lore_sdk-0.1.0/ts/src/index.ts +9 -0
  67. lore_sdk-0.1.0/ts/src/lore.ts +238 -0
  68. lore_sdk-0.1.0/ts/src/prompt.ts +46 -0
  69. lore_sdk-0.1.0/ts/src/redact.ts +129 -0
  70. lore_sdk-0.1.0/ts/src/store/base.ts +13 -0
  71. lore_sdk-0.1.0/ts/src/store/index.ts +3 -0
  72. lore_sdk-0.1.0/ts/src/store/memory.ts +57 -0
  73. lore_sdk-0.1.0/ts/src/store/sqlite.ts +147 -0
  74. lore_sdk-0.1.0/ts/src/types.ts +57 -0
  75. lore_sdk-0.1.0/ts/tests/edge-cases.test.ts +165 -0
  76. lore_sdk-0.1.0/ts/tests/embed.test.ts +77 -0
  77. lore_sdk-0.1.0/ts/tests/lore.test.ts +134 -0
  78. lore_sdk-0.1.0/ts/tests/memory-store.test.ts +89 -0
  79. lore_sdk-0.1.0/ts/tests/prompt.test.ts +66 -0
  80. lore_sdk-0.1.0/ts/tests/query.test.ts +167 -0
  81. lore_sdk-0.1.0/ts/tests/redact.test.ts +124 -0
  82. lore_sdk-0.1.0/ts/tests/sqlite-store.test.ts +116 -0
  83. lore_sdk-0.1.0/ts/tsconfig.json +18 -0
  84. lore_sdk-0.1.0/ts/vitest.config.ts +7 -0
@@ -0,0 +1,49 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ python:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.9", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - name: Set up Python ${{ matrix.python-version }}
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - name: Install dependencies
22
+ run: |
23
+ python -m pip install --upgrade pip
24
+ pip install -e ".[dev]"
25
+ - name: Lint
26
+ run: ruff check src/ tests/
27
+ - name: Test
28
+ run: pytest
29
+
30
+ typescript:
31
+ runs-on: ubuntu-latest
32
+ strategy:
33
+ matrix:
34
+ node-version: [18, 20]
35
+ defaults:
36
+ run:
37
+ working-directory: ts
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ - name: Set up Node ${{ matrix.node-version }}
41
+ uses: actions/setup-node@v4
42
+ with:
43
+ node-version: ${{ matrix.node-version }}
44
+ - name: Install dependencies
45
+ run: npm ci
46
+ - name: Build
47
+ run: npm run build
48
+ - name: Test
49
+ run: npm test
@@ -0,0 +1,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ dist/
6
+ build/
7
+ node_modules/
8
+ *.pyc
9
+ .ruff_cache/
10
+ ts/dist/
11
+ ts/node_modules/
12
+ .env
lore_sdk-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amit Paz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: lore-sdk
3
+ Version: 0.1.0
4
+ Summary: Cross-agent memory library
5
+ Project-URL: Homepage, https://github.com/amitpaz1/lore
6
+ Project-URL: Repository, https://github.com/amitpaz1/lore
7
+ Project-URL: Issues, https://github.com/amitpaz1/lore/issues
8
+ Author-email: Amit Paz <amit.paz@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,lessons,lore,memory
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: numpy>=1.21.0
20
+ Requires-Dist: onnxruntime>=1.14.0
21
+ Requires-Dist: python-ulid>=2.0.0
22
+ Requires-Dist: tokenizers>=0.13.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ <![CDATA[# Lore
29
+
30
+ [![PyPI](https://img.shields.io/pypi/v/lore-sdk)](https://pypi.org/project/lore-sdk/)
31
+ [![npm](https://img.shields.io/npm/v/lore-sdk)](https://www.npmjs.com/package/lore-sdk)
32
+ [![Tests](https://img.shields.io/github/actions/workflow/status/amitpaz/lore/ci.yml?label=tests)](https://github.com/amitpaz/lore/actions)
33
+ [![License](https://img.shields.io/github/license/amitpaz/lore)](LICENSE)
34
+
35
+ **Cross-agent memory.** Agents publish what they learn, other agents query it. PII redacted automatically.
36
+
37
+ ## Why Lore?
38
+
39
+ Your agents keep making the same mistakes. Agent A discovers Stripe rate-limits at 100 req/min. Agent B hits the same wall tomorrow. No learning transfer.
40
+
41
+ Lore fixes this. It's a tiny library — no server, no infra — that gives agents a shared memory of operational lessons. Publish a lesson in one line, query it in another. Sensitive data is redacted before storage automatically.
42
+
43
+ **What Lore is:** A local-first SDK for storing and retrieving structured lessons across agent runs. SQLite-backed, embedding-powered semantic search, automatic PII redaction.
44
+
45
+ **What Lore is not:** A conversation memory store (see Mem0/Zep), a vector database, or a RAG framework.
46
+
47
+ ## Quickstart
48
+
49
+ ```python
50
+ from lore import Lore
51
+
52
+ lore = Lore() # zero config — local SQLite, built-in embeddings
53
+
54
+ lore.publish(
55
+ problem="Stripe API returns 429 after 100 req/min",
56
+ resolution="Exponential backoff starting at 1s, cap at 32s",
57
+ tags=["stripe", "rate-limit"],
58
+ confidence=0.9,
59
+ )
60
+
61
+ lessons = lore.query("stripe rate limiting")
62
+ prompt = lore.as_prompt(lessons) # ready for system prompt injection
63
+ ```
64
+
65
+ ```typescript
66
+ import { Lore } from 'lore-sdk';
67
+
68
+ const lore = new Lore({ embeddingFn: yourEmbedFn });
69
+
70
+ await lore.publish({
71
+ problem: 'Stripe API returns 429 after 100 req/min',
72
+ resolution: 'Exponential backoff starting at 1s, cap at 32s',
73
+ tags: ['stripe', 'rate-limit'],
74
+ confidence: 0.9,
75
+ });
76
+
77
+ const lessons = await lore.query('stripe rate limiting');
78
+ const prompt = lore.asPrompt(lessons);
79
+ ```
80
+
81
+ ## Install
82
+
83
+ **Python** (3.9+):
84
+ ```bash
85
+ pip install lore-sdk
86
+ ```
87
+
88
+ **TypeScript** (Node 18+):
89
+ ```bash
90
+ npm install lore-sdk
91
+ ```
92
+
93
+ ## Python API Reference
94
+
95
+ ### `Lore(project?, db_path?, store?, embedding_fn?, embedder?, redact?, redact_patterns?, decay_half_life_days?)`
96
+
97
+ Create a Lore instance.
98
+
99
+ | Parameter | Type | Default | Description |
100
+ |-----------|------|---------|-------------|
101
+ | `project` | `str \| None` | `None` | Scope lessons to a project name |
102
+ | `db_path` | `str \| None` | `~/.lore/default.db` | Path to SQLite database |
103
+ | `store` | `Store \| None` | `None` | Custom storage backend |
104
+ | `embedding_fn` | `Callable[[str], list[float]] \| None` | `None` | Custom embedding function |
105
+ | `embedder` | `Embedder \| None` | `None` | Custom embedder instance |
106
+ | `redact` | `bool` | `True` | Enable automatic PII redaction |
107
+ | `redact_patterns` | `list[tuple[str, str]] \| None` | `None` | Custom redaction patterns as `(regex, label)` |
108
+ | `decay_half_life_days` | `float` | `30` | Half-life for lesson score decay |
109
+
110
+ Lore supports context manager usage:
111
+
112
+ ```python
113
+ with Lore() as lore:
114
+ lore.publish(problem="...", resolution="...")
115
+ ```
116
+
117
+ ### `lore.publish(problem, resolution, context?, tags?, confidence?, source?, project?) → str`
118
+
119
+ Publish a lesson. Returns the lesson ID (ULID).
120
+
121
+ | Parameter | Type | Default | Description |
122
+ |-----------|------|---------|-------------|
123
+ | `problem` | `str` | *required* | What went wrong |
124
+ | `resolution` | `str` | *required* | How to fix it |
125
+ | `context` | `str \| None` | `None` | Additional context |
126
+ | `tags` | `list[str] \| None` | `[]` | Filterable tags |
127
+ | `confidence` | `float` | `0.5` | Confidence score (0.0–1.0) |
128
+ | `source` | `str \| None` | `None` | Who/what created this lesson |
129
+ | `project` | `str \| None` | instance default | Override project scope |
130
+
131
+ ### `lore.query(text, tags?, limit?, min_confidence?) → list[QueryResult]`
132
+
133
+ Query lessons by semantic similarity.
134
+
135
+ | Parameter | Type | Default | Description |
136
+ |-----------|------|---------|-------------|
137
+ | `text` | `str` | *required* | Search query |
138
+ | `tags` | `list[str] \| None` | `None` | Filter: lessons must have ALL these tags |
139
+ | `limit` | `int` | `5` | Max results |
140
+ | `min_confidence` | `float` | `0.0` | Minimum confidence threshold |
141
+
142
+ Returns `list[QueryResult]` sorted by score (cosine similarity × confidence × time decay × vote factor).
143
+
144
+ ### `lore.as_prompt(lessons, max_tokens?) → str`
145
+
146
+ Format query results as a markdown string for system prompt injection.
147
+
148
+ | Parameter | Type | Default | Description |
149
+ |-----------|------|---------|-------------|
150
+ | `lessons` | `list[QueryResult]` | *required* | Results from `query()` |
151
+ | `max_tokens` | `int` | `1000` | Approximate token budget (1 token ≈ 4 chars) |
152
+
153
+ ### `lore.get(lesson_id) → Lesson | None`
154
+
155
+ Retrieve a single lesson by ID.
156
+
157
+ ### `lore.list(project?, limit?) → list[Lesson]`
158
+
159
+ List lessons, optionally filtered by project.
160
+
161
+ ### `lore.delete(lesson_id) → bool`
162
+
163
+ Delete a lesson. Returns `True` if found and deleted.
164
+
165
+ ### `lore.upvote(lesson_id) → None`
166
+
167
+ Increment a lesson's upvote count. Raises `LessonNotFoundError` if not found.
168
+
169
+ ### `lore.downvote(lesson_id) → None`
170
+
171
+ Increment a lesson's downvote count. Raises `LessonNotFoundError` if not found.
172
+
173
+ ### `lore.export_lessons(path?) → list[dict]`
174
+
175
+ Export lessons as JSON-serializable dicts. If `path` is given, writes to file.
176
+
177
+ ### `lore.import_lessons(path?, data?) → int`
178
+
179
+ Import lessons from file or data. Skips duplicates by ID. Returns count imported.
180
+
181
+ ### `lore.close() → None`
182
+
183
+ Close the underlying store.
184
+
185
+ ## TypeScript API Reference
186
+
187
+ The TypeScript SDK mirrors the Python API. See [ts/README.md](ts/README.md) for full details.
188
+
189
+ Key differences:
190
+ - All store operations are `async`
191
+ - Constructor takes an options object: `new Lore({ project, dbPath, embeddingFn, ... })`
192
+ - No built-in embedding model — you must provide `embeddingFn`
193
+ - `asPrompt()` instead of `as_prompt()`
194
+ - `minConfidence` instead of `min_confidence` (camelCase throughout)
195
+
196
+ ## Redaction
197
+
198
+ Lore automatically redacts sensitive data before storage:
199
+
200
+ - **API keys** (Bearer tokens, `sk-*`, `key-*`, etc.)
201
+ - **Email addresses**
202
+ - **Phone numbers**
203
+ - **IP addresses** (IPv4 and IPv6)
204
+ - **Credit card numbers** (with Luhn validation)
205
+
206
+ ```python
207
+ lore.publish(
208
+ problem="Auth failed with key sk-abc123def456ghi789jkl012mno",
209
+ resolution="Rotate the key",
210
+ )
211
+ # Stored as: "Auth failed with key [REDACTED:api_key]"
212
+ ```
213
+
214
+ Add custom patterns:
215
+
216
+ ```python
217
+ lore = Lore(redact_patterns=[
218
+ (r"ACCT-\d{8}", "account_id"),
219
+ ])
220
+ ```
221
+
222
+ Disable redaction entirely with `redact=False`.
223
+
224
+ ## Scoring
225
+
226
+ Query results are ranked by:
227
+
228
+ ```
229
+ score = cosine_similarity × confidence × time_decay × vote_factor
230
+ ```
231
+
232
+ - **Time decay:** Lessons lose relevance over time (configurable half-life, default 30 days)
233
+ - **Vote factor:** `1.0 + (upvotes - downvotes) × 0.1`, floored at 0.1
234
+ - **Confidence:** Author's self-assessed confidence (0.0–1.0)
235
+
236
+ ## Examples
237
+
238
+ See [`examples/`](examples/) for runnable scripts:
239
+ - [`basic_usage.py`](examples/basic_usage.py) — publish, query, format
240
+ - [`custom_embeddings.py`](examples/custom_embeddings.py) — bring your own embedding function
241
+ - [`redaction_demo.py`](examples/redaction_demo.py) — see redaction in action
242
+
243
+ ## License
244
+
245
+ MIT
246
+ ]]>
@@ -0,0 +1,219 @@
1
+ <![CDATA[# Lore
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/lore-sdk)](https://pypi.org/project/lore-sdk/)
4
+ [![npm](https://img.shields.io/npm/v/lore-sdk)](https://www.npmjs.com/package/lore-sdk)
5
+ [![Tests](https://img.shields.io/github/actions/workflow/status/amitpaz/lore/ci.yml?label=tests)](https://github.com/amitpaz/lore/actions)
6
+ [![License](https://img.shields.io/github/license/amitpaz/lore)](LICENSE)
7
+
8
+ **Cross-agent memory.** Agents publish what they learn, other agents query it. PII redacted automatically.
9
+
10
+ ## Why Lore?
11
+
12
+ Your agents keep making the same mistakes. Agent A discovers Stripe rate-limits at 100 req/min. Agent B hits the same wall tomorrow. No learning transfer.
13
+
14
+ Lore fixes this. It's a tiny library — no server, no infra — that gives agents a shared memory of operational lessons. Publish a lesson in one line, query it in another. Sensitive data is redacted before storage automatically.
15
+
16
+ **What Lore is:** A local-first SDK for storing and retrieving structured lessons across agent runs. SQLite-backed, embedding-powered semantic search, automatic PII redaction.
17
+
18
+ **What Lore is not:** A conversation memory store (see Mem0/Zep), a vector database, or a RAG framework.
19
+
20
+ ## Quickstart
21
+
22
+ ```python
23
+ from lore import Lore
24
+
25
+ lore = Lore() # zero config — local SQLite, built-in embeddings
26
+
27
+ lore.publish(
28
+ problem="Stripe API returns 429 after 100 req/min",
29
+ resolution="Exponential backoff starting at 1s, cap at 32s",
30
+ tags=["stripe", "rate-limit"],
31
+ confidence=0.9,
32
+ )
33
+
34
+ lessons = lore.query("stripe rate limiting")
35
+ prompt = lore.as_prompt(lessons) # ready for system prompt injection
36
+ ```
37
+
38
+ ```typescript
39
+ import { Lore } from 'lore-sdk';
40
+
41
+ const lore = new Lore({ embeddingFn: yourEmbedFn });
42
+
43
+ await lore.publish({
44
+ problem: 'Stripe API returns 429 after 100 req/min',
45
+ resolution: 'Exponential backoff starting at 1s, cap at 32s',
46
+ tags: ['stripe', 'rate-limit'],
47
+ confidence: 0.9,
48
+ });
49
+
50
+ const lessons = await lore.query('stripe rate limiting');
51
+ const prompt = lore.asPrompt(lessons);
52
+ ```
53
+
54
+ ## Install
55
+
56
+ **Python** (3.9+):
57
+ ```bash
58
+ pip install lore-sdk
59
+ ```
60
+
61
+ **TypeScript** (Node 18+):
62
+ ```bash
63
+ npm install lore-sdk
64
+ ```
65
+
66
+ ## Python API Reference
67
+
68
+ ### `Lore(project?, db_path?, store?, embedding_fn?, embedder?, redact?, redact_patterns?, decay_half_life_days?)`
69
+
70
+ Create a Lore instance.
71
+
72
+ | Parameter | Type | Default | Description |
73
+ |-----------|------|---------|-------------|
74
+ | `project` | `str \| None` | `None` | Scope lessons to a project name |
75
+ | `db_path` | `str \| None` | `~/.lore/default.db` | Path to SQLite database |
76
+ | `store` | `Store \| None` | `None` | Custom storage backend |
77
+ | `embedding_fn` | `Callable[[str], list[float]] \| None` | `None` | Custom embedding function |
78
+ | `embedder` | `Embedder \| None` | `None` | Custom embedder instance |
79
+ | `redact` | `bool` | `True` | Enable automatic PII redaction |
80
+ | `redact_patterns` | `list[tuple[str, str]] \| None` | `None` | Custom redaction patterns as `(regex, label)` |
81
+ | `decay_half_life_days` | `float` | `30` | Half-life for lesson score decay |
82
+
83
+ Lore supports context manager usage:
84
+
85
+ ```python
86
+ with Lore() as lore:
87
+ lore.publish(problem="...", resolution="...")
88
+ ```
89
+
90
+ ### `lore.publish(problem, resolution, context?, tags?, confidence?, source?, project?) → str`
91
+
92
+ Publish a lesson. Returns the lesson ID (ULID).
93
+
94
+ | Parameter | Type | Default | Description |
95
+ |-----------|------|---------|-------------|
96
+ | `problem` | `str` | *required* | What went wrong |
97
+ | `resolution` | `str` | *required* | How to fix it |
98
+ | `context` | `str \| None` | `None` | Additional context |
99
+ | `tags` | `list[str] \| None` | `[]` | Filterable tags |
100
+ | `confidence` | `float` | `0.5` | Confidence score (0.0–1.0) |
101
+ | `source` | `str \| None` | `None` | Who/what created this lesson |
102
+ | `project` | `str \| None` | instance default | Override project scope |
103
+
104
+ ### `lore.query(text, tags?, limit?, min_confidence?) → list[QueryResult]`
105
+
106
+ Query lessons by semantic similarity.
107
+
108
+ | Parameter | Type | Default | Description |
109
+ |-----------|------|---------|-------------|
110
+ | `text` | `str` | *required* | Search query |
111
+ | `tags` | `list[str] \| None` | `None` | Filter: lessons must have ALL these tags |
112
+ | `limit` | `int` | `5` | Max results |
113
+ | `min_confidence` | `float` | `0.0` | Minimum confidence threshold |
114
+
115
+ Returns `list[QueryResult]` sorted by score (cosine similarity × confidence × time decay × vote factor).
116
+
117
+ ### `lore.as_prompt(lessons, max_tokens?) → str`
118
+
119
+ Format query results as a markdown string for system prompt injection.
120
+
121
+ | Parameter | Type | Default | Description |
122
+ |-----------|------|---------|-------------|
123
+ | `lessons` | `list[QueryResult]` | *required* | Results from `query()` |
124
+ | `max_tokens` | `int` | `1000` | Approximate token budget (1 token ≈ 4 chars) |
125
+
126
+ ### `lore.get(lesson_id) → Lesson | None`
127
+
128
+ Retrieve a single lesson by ID.
129
+
130
+ ### `lore.list(project?, limit?) → list[Lesson]`
131
+
132
+ List lessons, optionally filtered by project.
133
+
134
+ ### `lore.delete(lesson_id) → bool`
135
+
136
+ Delete a lesson. Returns `True` if found and deleted.
137
+
138
+ ### `lore.upvote(lesson_id) → None`
139
+
140
+ Increment a lesson's upvote count. Raises `LessonNotFoundError` if not found.
141
+
142
+ ### `lore.downvote(lesson_id) → None`
143
+
144
+ Increment a lesson's downvote count. Raises `LessonNotFoundError` if not found.
145
+
146
+ ### `lore.export_lessons(path?) → list[dict]`
147
+
148
+ Export lessons as JSON-serializable dicts. If `path` is given, writes to file.
149
+
150
+ ### `lore.import_lessons(path?, data?) → int`
151
+
152
+ Import lessons from file or data. Skips duplicates by ID. Returns count imported.
153
+
154
+ ### `lore.close() → None`
155
+
156
+ Close the underlying store.
157
+
158
+ ## TypeScript API Reference
159
+
160
+ The TypeScript SDK mirrors the Python API. See [ts/README.md](ts/README.md) for full details.
161
+
162
+ Key differences:
163
+ - All store operations are `async`
164
+ - Constructor takes an options object: `new Lore({ project, dbPath, embeddingFn, ... })`
165
+ - No built-in embedding model — you must provide `embeddingFn`
166
+ - `asPrompt()` instead of `as_prompt()`
167
+ - `minConfidence` instead of `min_confidence` (camelCase throughout)
168
+
169
+ ## Redaction
170
+
171
+ Lore automatically redacts sensitive data before storage:
172
+
173
+ - **API keys** (Bearer tokens, `sk-*`, `key-*`, etc.)
174
+ - **Email addresses**
175
+ - **Phone numbers**
176
+ - **IP addresses** (IPv4 and IPv6)
177
+ - **Credit card numbers** (with Luhn validation)
178
+
179
+ ```python
180
+ lore.publish(
181
+ problem="Auth failed with key sk-abc123def456ghi789jkl012mno",
182
+ resolution="Rotate the key",
183
+ )
184
+ # Stored as: "Auth failed with key [REDACTED:api_key]"
185
+ ```
186
+
187
+ Add custom patterns:
188
+
189
+ ```python
190
+ lore = Lore(redact_patterns=[
191
+ (r"ACCT-\d{8}", "account_id"),
192
+ ])
193
+ ```
194
+
195
+ Disable redaction entirely with `redact=False`.
196
+
197
+ ## Scoring
198
+
199
+ Query results are ranked by:
200
+
201
+ ```
202
+ score = cosine_similarity × confidence × time_decay × vote_factor
203
+ ```
204
+
205
+ - **Time decay:** Lessons lose relevance over time (configurable half-life, default 30 days)
206
+ - **Vote factor:** `1.0 + (upvotes - downvotes) × 0.1`, floored at 0.1
207
+ - **Confidence:** Author's self-assessed confidence (0.0–1.0)
208
+
209
+ ## Examples
210
+
211
+ See [`examples/`](examples/) for runnable scripts:
212
+ - [`basic_usage.py`](examples/basic_usage.py) — publish, query, format
213
+ - [`custom_embeddings.py`](examples/custom_embeddings.py) — bring your own embedding function
214
+ - [`redaction_demo.py`](examples/redaction_demo.py) — see redaction in action
215
+
216
+ ## License
217
+
218
+ MIT
219
+ ]]>