errlore 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 (44) hide show
  1. errlore-0.1.0/.github/workflows/ci.yml +26 -0
  2. errlore-0.1.0/.gitignore +27 -0
  3. errlore-0.1.0/CHANGELOG.md +25 -0
  4. errlore-0.1.0/LICENSE +21 -0
  5. errlore-0.1.0/PKG-INFO +214 -0
  6. errlore-0.1.0/README.md +186 -0
  7. errlore-0.1.0/benchmarks/bench_retrieval.py +183 -0
  8. errlore-0.1.0/examples/anthropic_agent.py +150 -0
  9. errlore-0.1.0/examples/langchain_agent.py +170 -0
  10. errlore-0.1.0/examples/openai_agent.py +131 -0
  11. errlore-0.1.0/integrations/openwebui/README.md +47 -0
  12. errlore-0.1.0/integrations/openwebui/errlore_feedback_action.py +117 -0
  13. errlore-0.1.0/integrations/openwebui/errlore_memory_filter.py +105 -0
  14. errlore-0.1.0/pyproject.toml +72 -0
  15. errlore-0.1.0/src/errlore/__init__.py +16 -0
  16. errlore-0.1.0/src/errlore/errmem/__init__.py +21 -0
  17. errlore-0.1.0/src/errlore/errmem/classifier.py +54 -0
  18. errlore-0.1.0/src/errlore/errmem/injector.py +156 -0
  19. errlore-0.1.0/src/errlore/errmem/patterns.py +193 -0
  20. errlore-0.1.0/src/errlore/errmem/tracker.py +157 -0
  21. errlore-0.1.0/src/errlore/facade.py +602 -0
  22. errlore-0.1.0/src/errlore/io/__init__.py +21 -0
  23. errlore-0.1.0/src/errlore/io/jsonl_index.py +199 -0
  24. errlore-0.1.0/src/errlore/io/jsonl_writer.py +342 -0
  25. errlore-0.1.0/src/errlore/io/repair.py +199 -0
  26. errlore-0.1.0/src/errlore/lessons/__init__.py +16 -0
  27. errlore-0.1.0/src/errlore/lessons/models.py +129 -0
  28. errlore-0.1.0/src/errlore/lessons/store.py +495 -0
  29. errlore-0.1.0/src/errlore/py.typed +0 -0
  30. errlore-0.1.0/src/errlore/retrieval/__init__.py +66 -0
  31. errlore-0.1.0/src/errlore/retrieval/backend.py +117 -0
  32. errlore-0.1.0/src/errlore/retrieval/index.py +288 -0
  33. errlore-0.1.0/src/errlore/sanitize.py +117 -0
  34. errlore-0.1.0/src/errlore/trust/__init__.py +11 -0
  35. errlore-0.1.0/src/errlore/trust/engine.py +602 -0
  36. errlore-0.1.0/tests/conftest.py +13 -0
  37. errlore-0.1.0/tests/test_errmem.py +282 -0
  38. errlore-0.1.0/tests/test_facade.py +448 -0
  39. errlore-0.1.0/tests/test_io.py +583 -0
  40. errlore-0.1.0/tests/test_lessons.py +384 -0
  41. errlore-0.1.0/tests/test_openwebui_integration.py +112 -0
  42. errlore-0.1.0/tests/test_regressions.py +114 -0
  43. errlore-0.1.0/tests/test_retrieval.py +448 -0
  44. errlore-0.1.0/tests/test_trust.py +435 -0
@@ -0,0 +1,26 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Install
20
+ run: pip install -e .[dev]
21
+ - name: Ruff
22
+ run: ruff check .
23
+ - name: Mypy
24
+ run: mypy
25
+ - name: Tests
26
+ run: pytest --cov=errlore --cov-fail-under=80
@@ -0,0 +1,27 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+
10
+ # Tooling
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .coverage
15
+ htmlcov/
16
+
17
+ # Data — runtime data never belongs in git
18
+ *.jsonl
19
+ *.idx
20
+ *.lock
21
+ agent_memory/
22
+
23
+ # OS / editors
24
+ .DS_Store
25
+ .idea/
26
+ .vscode/
27
+ dist/
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-07-05
9
+
10
+ ### Added
11
+
12
+ - `AgentMemory` facade with four core methods: `log_error`, `resolve`, `inject_for`, `report_outcome`.
13
+ - Lesson store with JSONL persistence, deduplication (exact + 85% word overlap), confidence tracking, and automatic decay of unused lessons.
14
+ - Per-model, per-task-type error tracking and known-issue warning injection.
15
+ - `TrustEngine` with Bayesian cold-start blending, adaptive learning rate, volatility damping, domain-specific EMA bias, entropy enforcement, and temporal decay.
16
+ - `best_model(domain)` convenience method for model routing based on trust weights.
17
+ - Closed reinforcement loop: `report_outcome` reinforces/decays lessons and updates trust weights; idempotent (no double-reinforcement).
18
+ - Injection handle persistence (`injections.jsonl`) for cross-restart outcome reporting.
19
+ - Optional semantic retrieval via FastEmbed embeddings (`pip install errlore[embeddings]`), with automatic fallback to word-overlap.
20
+ - Lesson text sanitization (rejects raw JSON, code-only, too-short content).
21
+ - Atomic file rewrites for lesson/error updates (no data loss on crash).
22
+ - Thread-safe API (all public methods are safe to call from multiple threads).
23
+ - Integration examples for OpenAI, Anthropic, and LangChain (all runnable offline).
24
+ - Retrieval benchmark (`benchmarks/bench_retrieval.py`) with adversarial gold set.
25
+ - 155 tests, mypy strict, ruff linting.
errlore-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ma4etaSS
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.
errlore-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: errlore
3
+ Version: 0.1.0
4
+ Summary: Memory for AI agents that learns from failures: lessons, known-issues injection, and per-model trust — embedded, file-based, no server.
5
+ Project-URL: Homepage, https://github.com/Ma4etaSS/errlore
6
+ Author: Ma4etaSS
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: agents,error-learning,lessons,llm,memory,trust
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: filelock>=3.13
17
+ Provides-Extra: dev
18
+ Requires-Dist: fastembed<0.9,>=0.6; extra == 'dev'
19
+ Requires-Dist: mypy>=1.11; extra == 'dev'
20
+ Requires-Dist: numpy>=1.26; extra == 'dev'
21
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
22
+ Requires-Dist: pytest>=8.0; extra == 'dev'
23
+ Requires-Dist: ruff>=0.6; extra == 'dev'
24
+ Provides-Extra: embeddings
25
+ Requires-Dist: fastembed<0.9,>=0.6; extra == 'embeddings'
26
+ Requires-Dist: numpy>=1.26; extra == 'embeddings'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # errlore
30
+
31
+ **Memory for AI agents that learns from failures.**
32
+
33
+ [![CI](https://github.com/Ma4etaSS/errlore/actions/workflows/ci.yml/badge.svg)](https://github.com/Ma4etaSS/errlore/actions/workflows/ci.yml)
34
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](https://python.org)
35
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
36
+
37
+ Extracted from a 324K LOC production multi-LLM orchestration system, keeping the one part that demonstrably worked: the error-memory loop that made agents stop repeating mistakes.
38
+
39
+ Your agent keeps making the same mistakes. errlore fixes that:
40
+
41
+ - **Lessons** -- every resolved failure becomes a lesson; relevant lessons are injected
42
+ into the prompt for similar future tasks.
43
+ - **Known issues** -- per-model weakness tracking ("gpt-5.5 keeps hallucinating dates in
44
+ extraction tasks") injected as warnings.
45
+ - **Trust** -- Bayesian per-model, per-domain trust weights: know which model to pick
46
+ for which job, based on observed outcomes.
47
+ - **Closed loop** -- errlore tracks whether an injected lesson actually helped and
48
+ reinforces or decays it automatically.
49
+
50
+ Embedded, file-based (JSONL), no server, no database, no API keys required.
51
+ Works fully offline. Your data never leaves your machine.
52
+
53
+ ## Quickstart (< 5 minutes)
54
+
55
+ ```bash
56
+ pip install errlore
57
+ ```
58
+
59
+ ```python
60
+ from errlore import AgentMemory
61
+
62
+ mem = AgentMemory("./agent_memory")
63
+
64
+ # 1. Agent failed -- record it
65
+ err_id = mem.log_error("gpt-5.5", "extraction", error="hallucinated dates")
66
+
67
+ # 2. You fixed it -- extract a lesson
68
+ mem.resolve(err_id, "Added date format validation",
69
+ lesson="For date extraction, demand ISO-8601 and verify against source")
70
+
71
+ # 3. Next similar task -- lessons + known issues injected automatically
72
+ inj = mem.inject_for("extract dates from contract", model="gpt-5.5",
73
+ task_type="extraction")
74
+ prompt = f"Your task: extract dates\n{inj.text}"
75
+ print(prompt)
76
+
77
+ # 4. Close the loop -- did the lesson help?
78
+ mem.report_outcome(inj, success=True)
79
+
80
+ # 5. Check stats
81
+ print(mem.stats())
82
+ # {'errors_total': 1, 'errors_resolved': 1, 'errors_unresolved': 0,
83
+ # 'lessons_total': 1, 'lessons_applied': 1, 'pending_injections': 0,
84
+ # 'trust': {'gpt-5.5': 0.55}}
85
+ ```
86
+
87
+ No API keys needed. errlore itself never calls any LLM -- it manages local
88
+ JSONL files and does text matching. LLM calls are yours to make (or not).
89
+
90
+ ## How it works
91
+
92
+ errlore runs three reinforcement loops around your agent:
93
+
94
+ ### 1. Lesson loop
95
+
96
+ ```
97
+ Agent fails --> log_error() --> resolve() + lesson
98
+ |
99
+ Agent runs <-- inject_for() <--------+
100
+ |
101
+ +--> report_outcome(success=True) --> lesson confidence +0.1
102
+ +--> report_outcome(success=False) --> lesson confidence -0.1
103
+ ```
104
+
105
+ Lessons with high confidence surface first. Unused lessons decay over time.
106
+
107
+ ### 2. Known-issue loop
108
+
109
+ Per-model, per-task-type error tracking. When a model has failed on a task
110
+ type before, `inject_for` adds a warning block to the prompt. Separate from
111
+ lessons: lessons are *solutions*, known issues are *warnings*.
112
+
113
+ ### 3. Trust loop
114
+
115
+ Bayesian per-model weights with adaptive learning rate, cold-start blending,
116
+ entropy enforcement, and temporal decay. After enough observations, call
117
+ `mem.best_model("code_generation")` to pick the model that historically
118
+ performs best on that domain.
119
+
120
+ ## Semantic retrieval (optional)
121
+
122
+ By default, errlore finds relevant lessons via word overlap (zero
123
+ dependencies). For higher recall on paraphrased queries, enable embedding
124
+ search:
125
+
126
+ ```bash
127
+ pip install errlore[embeddings] # installs fastembed + numpy
128
+ ```
129
+
130
+ ```python
131
+ mem = AgentMemory("./agent_memory", embeddings=True)
132
+ ```
133
+
134
+ ### Benchmark (adversarial paraphrasing)
135
+
136
+ Tested on 40 lessons with adversarially paraphrased queries
137
+ (`benchmarks/bench_retrieval.py`):
138
+
139
+ | Metric | word-overlap | embeddings |
140
+ |-----------|-------------|------------|
141
+ | recall@1 | 0.000 | 0.375 |
142
+ | recall@3 | 0.000 | 0.575 |
143
+ | recall@5 | 0.000 | 0.675 |
144
+ | MRR | 0.000 | 0.290 |
145
+
146
+ The gold set is intentionally adversarial (queries share few literal words
147
+ with the lesson text), which is why word-overlap scores zero. On natural
148
+ queries with shared vocabulary, word-overlap works fine.
149
+
150
+ ## Integrations
151
+
152
+ errlore is framework-agnostic. It produces a text block; you put it in the
153
+ system prompt.
154
+
155
+ | Provider | Example |
156
+ |------------|------------------------------------------------------|
157
+ | OpenAI | [examples/openai_agent.py](examples/openai_agent.py) |
158
+ | Anthropic | [examples/anthropic_agent.py](examples/anthropic_agent.py) |
159
+ | LangChain | [examples/langchain_agent.py](examples/langchain_agent.py) |
160
+
161
+ All examples run offline with `python examples/<name>.py` (mock responses,
162
+ no API keys). Set `use_api=True` to call real models.
163
+
164
+ ## API overview
165
+
166
+ The main entry point is `AgentMemory`. All other classes are internal --
167
+ you only need them for advanced use.
168
+
169
+ | Method / Property | Description |
170
+ |-------------------------------|------------------------------------------------|
171
+ | `log_error(model, task_type, error)` | Record an error. Returns error ID. |
172
+ | `resolve(err_id, resolution, lesson)` | Mark error fixed, extract a lesson. |
173
+ | `inject_for(task, model)` | Build prompt injection (lessons + warnings). |
174
+ | `report_outcome(inj, success)` | Close the loop: reinforce lessons, update trust.|
175
+ | `add_lesson(pattern, solution)` | Add a lesson directly (sanitized). |
176
+ | `lessons(limit)` | List all lessons (sorted by confidence). |
177
+ | `best_model(domain)` | Model with the highest trust weight. |
178
+ | `model_penalty(model, task_type)` | Error-history penalty `[0, 1]`. |
179
+ | `pending_injections()` | Injections not yet reported. |
180
+ | `stats()` | Aggregate counts + trust weights. |
181
+ | `.trust` | Access the underlying `TrustEngine` (or None). |
182
+
183
+ ### Supporting classes (advanced)
184
+
185
+ | Class | Purpose |
186
+ |-------------------|--------------------------------------------|
187
+ | `LessonStore` | Low-level lesson CRUD + search. |
188
+ | `TrustEngine` | Bayesian trust weights with persistence. |
189
+ | `FeedbackSignal` | Typed quality signal for trust updates. |
190
+ | `Injection` | Dataclass returned by `inject_for`. |
191
+
192
+ ## Data & privacy
193
+
194
+ - All data is stored in local JSONL files in the directory you specify.
195
+ - Nothing is sent to any server. errlore itself makes zero network calls.
196
+ - Works fully offline -- no API keys, no accounts, no telemetry.
197
+ - Files: `errors.jsonl`, `lessons.jsonl`, `injections.jsonl`, `trust.json`,
198
+ `model_accuracy.jsonl`.
199
+ - Sidecar files (auto-managed): `*.idx` (byte-offset index), `*.lock`
200
+ (filelock), `vectors.npy` (embedding vectors), `vector_meta.json`
201
+ (embedding metadata), `trust.json` (trust engine state).
202
+
203
+ ## Roadmap
204
+
205
+ - [ ] Log compaction for injections journal
206
+ - [ ] Async API (`alog_error`, `ainject_for`, etc.)
207
+ - [ ] Multi-agent shared memory (multiple agents, one lesson store)
208
+ - [ ] Lesson clustering and auto-summarization
209
+ - [ ] Dashboard / CLI for browsing lessons and trust weights
210
+ - [ ] Export/import for lesson sharing between projects
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,186 @@
1
+ # errlore
2
+
3
+ **Memory for AI agents that learns from failures.**
4
+
5
+ [![CI](https://github.com/Ma4etaSS/errlore/actions/workflows/ci.yml/badge.svg)](https://github.com/Ma4etaSS/errlore/actions/workflows/ci.yml)
6
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](https://python.org)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
8
+
9
+ Extracted from a 324K LOC production multi-LLM orchestration system, keeping the one part that demonstrably worked: the error-memory loop that made agents stop repeating mistakes.
10
+
11
+ Your agent keeps making the same mistakes. errlore fixes that:
12
+
13
+ - **Lessons** -- every resolved failure becomes a lesson; relevant lessons are injected
14
+ into the prompt for similar future tasks.
15
+ - **Known issues** -- per-model weakness tracking ("gpt-5.5 keeps hallucinating dates in
16
+ extraction tasks") injected as warnings.
17
+ - **Trust** -- Bayesian per-model, per-domain trust weights: know which model to pick
18
+ for which job, based on observed outcomes.
19
+ - **Closed loop** -- errlore tracks whether an injected lesson actually helped and
20
+ reinforces or decays it automatically.
21
+
22
+ Embedded, file-based (JSONL), no server, no database, no API keys required.
23
+ Works fully offline. Your data never leaves your machine.
24
+
25
+ ## Quickstart (< 5 minutes)
26
+
27
+ ```bash
28
+ pip install errlore
29
+ ```
30
+
31
+ ```python
32
+ from errlore import AgentMemory
33
+
34
+ mem = AgentMemory("./agent_memory")
35
+
36
+ # 1. Agent failed -- record it
37
+ err_id = mem.log_error("gpt-5.5", "extraction", error="hallucinated dates")
38
+
39
+ # 2. You fixed it -- extract a lesson
40
+ mem.resolve(err_id, "Added date format validation",
41
+ lesson="For date extraction, demand ISO-8601 and verify against source")
42
+
43
+ # 3. Next similar task -- lessons + known issues injected automatically
44
+ inj = mem.inject_for("extract dates from contract", model="gpt-5.5",
45
+ task_type="extraction")
46
+ prompt = f"Your task: extract dates\n{inj.text}"
47
+ print(prompt)
48
+
49
+ # 4. Close the loop -- did the lesson help?
50
+ mem.report_outcome(inj, success=True)
51
+
52
+ # 5. Check stats
53
+ print(mem.stats())
54
+ # {'errors_total': 1, 'errors_resolved': 1, 'errors_unresolved': 0,
55
+ # 'lessons_total': 1, 'lessons_applied': 1, 'pending_injections': 0,
56
+ # 'trust': {'gpt-5.5': 0.55}}
57
+ ```
58
+
59
+ No API keys needed. errlore itself never calls any LLM -- it manages local
60
+ JSONL files and does text matching. LLM calls are yours to make (or not).
61
+
62
+ ## How it works
63
+
64
+ errlore runs three reinforcement loops around your agent:
65
+
66
+ ### 1. Lesson loop
67
+
68
+ ```
69
+ Agent fails --> log_error() --> resolve() + lesson
70
+ |
71
+ Agent runs <-- inject_for() <--------+
72
+ |
73
+ +--> report_outcome(success=True) --> lesson confidence +0.1
74
+ +--> report_outcome(success=False) --> lesson confidence -0.1
75
+ ```
76
+
77
+ Lessons with high confidence surface first. Unused lessons decay over time.
78
+
79
+ ### 2. Known-issue loop
80
+
81
+ Per-model, per-task-type error tracking. When a model has failed on a task
82
+ type before, `inject_for` adds a warning block to the prompt. Separate from
83
+ lessons: lessons are *solutions*, known issues are *warnings*.
84
+
85
+ ### 3. Trust loop
86
+
87
+ Bayesian per-model weights with adaptive learning rate, cold-start blending,
88
+ entropy enforcement, and temporal decay. After enough observations, call
89
+ `mem.best_model("code_generation")` to pick the model that historically
90
+ performs best on that domain.
91
+
92
+ ## Semantic retrieval (optional)
93
+
94
+ By default, errlore finds relevant lessons via word overlap (zero
95
+ dependencies). For higher recall on paraphrased queries, enable embedding
96
+ search:
97
+
98
+ ```bash
99
+ pip install errlore[embeddings] # installs fastembed + numpy
100
+ ```
101
+
102
+ ```python
103
+ mem = AgentMemory("./agent_memory", embeddings=True)
104
+ ```
105
+
106
+ ### Benchmark (adversarial paraphrasing)
107
+
108
+ Tested on 40 lessons with adversarially paraphrased queries
109
+ (`benchmarks/bench_retrieval.py`):
110
+
111
+ | Metric | word-overlap | embeddings |
112
+ |-----------|-------------|------------|
113
+ | recall@1 | 0.000 | 0.375 |
114
+ | recall@3 | 0.000 | 0.575 |
115
+ | recall@5 | 0.000 | 0.675 |
116
+ | MRR | 0.000 | 0.290 |
117
+
118
+ The gold set is intentionally adversarial (queries share few literal words
119
+ with the lesson text), which is why word-overlap scores zero. On natural
120
+ queries with shared vocabulary, word-overlap works fine.
121
+
122
+ ## Integrations
123
+
124
+ errlore is framework-agnostic. It produces a text block; you put it in the
125
+ system prompt.
126
+
127
+ | Provider | Example |
128
+ |------------|------------------------------------------------------|
129
+ | OpenAI | [examples/openai_agent.py](examples/openai_agent.py) |
130
+ | Anthropic | [examples/anthropic_agent.py](examples/anthropic_agent.py) |
131
+ | LangChain | [examples/langchain_agent.py](examples/langchain_agent.py) |
132
+
133
+ All examples run offline with `python examples/<name>.py` (mock responses,
134
+ no API keys). Set `use_api=True` to call real models.
135
+
136
+ ## API overview
137
+
138
+ The main entry point is `AgentMemory`. All other classes are internal --
139
+ you only need them for advanced use.
140
+
141
+ | Method / Property | Description |
142
+ |-------------------------------|------------------------------------------------|
143
+ | `log_error(model, task_type, error)` | Record an error. Returns error ID. |
144
+ | `resolve(err_id, resolution, lesson)` | Mark error fixed, extract a lesson. |
145
+ | `inject_for(task, model)` | Build prompt injection (lessons + warnings). |
146
+ | `report_outcome(inj, success)` | Close the loop: reinforce lessons, update trust.|
147
+ | `add_lesson(pattern, solution)` | Add a lesson directly (sanitized). |
148
+ | `lessons(limit)` | List all lessons (sorted by confidence). |
149
+ | `best_model(domain)` | Model with the highest trust weight. |
150
+ | `model_penalty(model, task_type)` | Error-history penalty `[0, 1]`. |
151
+ | `pending_injections()` | Injections not yet reported. |
152
+ | `stats()` | Aggregate counts + trust weights. |
153
+ | `.trust` | Access the underlying `TrustEngine` (or None). |
154
+
155
+ ### Supporting classes (advanced)
156
+
157
+ | Class | Purpose |
158
+ |-------------------|--------------------------------------------|
159
+ | `LessonStore` | Low-level lesson CRUD + search. |
160
+ | `TrustEngine` | Bayesian trust weights with persistence. |
161
+ | `FeedbackSignal` | Typed quality signal for trust updates. |
162
+ | `Injection` | Dataclass returned by `inject_for`. |
163
+
164
+ ## Data & privacy
165
+
166
+ - All data is stored in local JSONL files in the directory you specify.
167
+ - Nothing is sent to any server. errlore itself makes zero network calls.
168
+ - Works fully offline -- no API keys, no accounts, no telemetry.
169
+ - Files: `errors.jsonl`, `lessons.jsonl`, `injections.jsonl`, `trust.json`,
170
+ `model_accuracy.jsonl`.
171
+ - Sidecar files (auto-managed): `*.idx` (byte-offset index), `*.lock`
172
+ (filelock), `vectors.npy` (embedding vectors), `vector_meta.json`
173
+ (embedding metadata), `trust.json` (trust engine state).
174
+
175
+ ## Roadmap
176
+
177
+ - [ ] Log compaction for injections journal
178
+ - [ ] Async API (`alog_error`, `ainject_for`, etc.)
179
+ - [ ] Multi-agent shared memory (multiple agents, one lesson store)
180
+ - [ ] Lesson clustering and auto-summarization
181
+ - [ ] Dashboard / CLI for browsing lessons and trust weights
182
+ - [ ] Export/import for lesson sharing between projects
183
+
184
+ ## License
185
+
186
+ MIT
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env python3
2
+ """Benchmark: word-overlap vs embedding retrieval on a gold dataset.
3
+
4
+ Loads ``tests/gold/retrieval_gold.jsonl``, indexes all 40 lessons in a
5
+ :class:`~errlore.lessons.store.LessonStore`, and measures recall@k / MRR
6
+ for both retrieval strategies.
7
+
8
+ Exit code:
9
+ 0 -- embeddings recall@5 > word-overlap recall@5 (gate PASS)
10
+ 1 -- embeddings did NOT beat word-overlap (gate FAIL)
11
+
12
+ Usage::
13
+
14
+ .venv/bin/python benchmarks/bench_retrieval.py
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import sys
21
+ import tempfile
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Gold data loader
27
+ # ---------------------------------------------------------------------------
28
+
29
+ _GOLD_PATH = Path(__file__).resolve().parent.parent / "tests" / "gold" / "retrieval_gold.jsonl"
30
+
31
+
32
+ def _load_gold() -> list[dict[str, str]]:
33
+ """Read gold entries from JSONL."""
34
+ entries: list[dict[str, str]] = []
35
+ with open(_GOLD_PATH, encoding="utf-8") as f:
36
+ for line in f:
37
+ line = line.strip()
38
+ if line:
39
+ entries.append(json.loads(line))
40
+ return entries
41
+
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Metrics
45
+ # ---------------------------------------------------------------------------
46
+
47
+
48
+ def _compute_metrics(
49
+ gold: list[dict[str, str]],
50
+ id_map: dict[int, str],
51
+ search_fn: Any, # Callable[[str], list[Lesson]]
52
+ max_k: int = 5,
53
+ ) -> dict[str, float]:
54
+ """Compute recall@1, recall@3, recall@5, and MRR."""
55
+ hits: dict[int, int] = {k: 0 for k in (1, 3, 5)}
56
+ reciprocal_ranks: list[float] = []
57
+
58
+ for idx, entry in enumerate(gold):
59
+ target_id = id_map[idx]
60
+ results = search_fn(entry["query"])
61
+ result_ids = [le.id for le in results]
62
+
63
+ # Reciprocal rank
64
+ rr = 0.0
65
+ for rank, rid in enumerate(result_ids[:max_k], 1):
66
+ if rid == target_id:
67
+ rr = 1.0 / rank
68
+ break
69
+ reciprocal_ranks.append(rr)
70
+
71
+ for k in hits:
72
+ if target_id in result_ids[:k]:
73
+ hits[k] += 1
74
+
75
+ n = len(gold)
76
+ return {
77
+ "recall@1": hits[1] / n,
78
+ "recall@3": hits[3] / n,
79
+ "recall@5": hits[5] / n,
80
+ "MRR": sum(reciprocal_ranks) / n,
81
+ }
82
+
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # Benchmark runners
86
+ # ---------------------------------------------------------------------------
87
+
88
+
89
+ def _run_word_overlap(gold: list[dict[str, str]]) -> tuple[dict[str, float], dict[int, str]]:
90
+ """Run benchmark with word-overlap retrieval (no retriever)."""
91
+ from errlore.lessons.store import LessonStore
92
+
93
+ with tempfile.TemporaryDirectory() as tmp:
94
+ store = LessonStore(Path(tmp))
95
+ id_map: dict[int, str] = {}
96
+
97
+ for idx, entry in enumerate(gold):
98
+ lid = store.log_lesson(
99
+ pattern=entry["lesson_pattern"],
100
+ solution=entry["lesson_solution"],
101
+ )
102
+ id_map[idx] = lid
103
+
104
+ def search(query: str) -> list[Any]:
105
+ return store.search_lessons(query=query, limit=5)
106
+
107
+ metrics = _compute_metrics(gold, id_map, search)
108
+ return metrics, id_map
109
+
110
+
111
+ def _run_embeddings(gold: list[dict[str, str]]) -> tuple[dict[str, float], dict[int, str]]:
112
+ """Run benchmark with embedding retrieval (FastEmbedBackend + VectorIndex)."""
113
+ from errlore.lessons.store import LessonStore
114
+ from errlore.retrieval.backend import FastEmbedBackend
115
+ from errlore.retrieval.index import VectorIndex
116
+
117
+ with tempfile.TemporaryDirectory() as tmp:
118
+ tmp_path = Path(tmp)
119
+ backend = FastEmbedBackend()
120
+ index = VectorIndex(tmp_path, backend)
121
+ store = LessonStore(tmp_path, retriever=index)
122
+ id_map: dict[int, str] = {}
123
+
124
+ for idx, entry in enumerate(gold):
125
+ lid = store.log_lesson(
126
+ pattern=entry["lesson_pattern"],
127
+ solution=entry["lesson_solution"],
128
+ )
129
+ id_map[idx] = lid
130
+
131
+ def search(query: str) -> list[Any]:
132
+ return store.search_lessons(query=query, limit=5)
133
+
134
+ metrics = _compute_metrics(gold, id_map, search)
135
+ return metrics, id_map
136
+
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # Main
140
+ # ---------------------------------------------------------------------------
141
+
142
+
143
+ def main() -> int:
144
+ """Run both benchmarks, print results, return exit code."""
145
+ gold = _load_gold()
146
+ print(f"Gold dataset: {len(gold)} entries from {_GOLD_PATH.name}")
147
+ print()
148
+
149
+ print("Running word-overlap benchmark...")
150
+ wo_metrics, _ = _run_word_overlap(gold)
151
+
152
+ print("Running embeddings benchmark...")
153
+ emb_metrics, _ = _run_embeddings(gold)
154
+
155
+ # -- Pretty table -----------------------------------------------------
156
+ header = f"{'metric':<16} {'word-overlap':>14} {'embeddings':>14}"
157
+ sep = "-" * len(header)
158
+ print()
159
+ print(header)
160
+ print(sep)
161
+ for key in ("recall@1", "recall@3", "recall@5", "MRR"):
162
+ wo_val = wo_metrics[key]
163
+ emb_val = emb_metrics[key]
164
+ print(f"{key:<16} {wo_val:>14.3f} {emb_val:>14.3f}")
165
+ print(sep)
166
+ print()
167
+
168
+ # -- Gate check -------------------------------------------------------
169
+ wo_r5 = wo_metrics["recall@5"]
170
+ emb_r5 = emb_metrics["recall@5"]
171
+ passed = emb_r5 > wo_r5
172
+
173
+ status = "PASS" if passed else "FAIL"
174
+ print(
175
+ f"Gate: embeddings recall@5 ({emb_r5:.3f})"
176
+ f" > word-overlap recall@5 ({wo_r5:.3f}): {status}",
177
+ )
178
+
179
+ return 0 if passed else 1
180
+
181
+
182
+ if __name__ == "__main__":
183
+ sys.exit(main())