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.
- errlore-0.1.0/.github/workflows/ci.yml +26 -0
- errlore-0.1.0/.gitignore +27 -0
- errlore-0.1.0/CHANGELOG.md +25 -0
- errlore-0.1.0/LICENSE +21 -0
- errlore-0.1.0/PKG-INFO +214 -0
- errlore-0.1.0/README.md +186 -0
- errlore-0.1.0/benchmarks/bench_retrieval.py +183 -0
- errlore-0.1.0/examples/anthropic_agent.py +150 -0
- errlore-0.1.0/examples/langchain_agent.py +170 -0
- errlore-0.1.0/examples/openai_agent.py +131 -0
- errlore-0.1.0/integrations/openwebui/README.md +47 -0
- errlore-0.1.0/integrations/openwebui/errlore_feedback_action.py +117 -0
- errlore-0.1.0/integrations/openwebui/errlore_memory_filter.py +105 -0
- errlore-0.1.0/pyproject.toml +72 -0
- errlore-0.1.0/src/errlore/__init__.py +16 -0
- errlore-0.1.0/src/errlore/errmem/__init__.py +21 -0
- errlore-0.1.0/src/errlore/errmem/classifier.py +54 -0
- errlore-0.1.0/src/errlore/errmem/injector.py +156 -0
- errlore-0.1.0/src/errlore/errmem/patterns.py +193 -0
- errlore-0.1.0/src/errlore/errmem/tracker.py +157 -0
- errlore-0.1.0/src/errlore/facade.py +602 -0
- errlore-0.1.0/src/errlore/io/__init__.py +21 -0
- errlore-0.1.0/src/errlore/io/jsonl_index.py +199 -0
- errlore-0.1.0/src/errlore/io/jsonl_writer.py +342 -0
- errlore-0.1.0/src/errlore/io/repair.py +199 -0
- errlore-0.1.0/src/errlore/lessons/__init__.py +16 -0
- errlore-0.1.0/src/errlore/lessons/models.py +129 -0
- errlore-0.1.0/src/errlore/lessons/store.py +495 -0
- errlore-0.1.0/src/errlore/py.typed +0 -0
- errlore-0.1.0/src/errlore/retrieval/__init__.py +66 -0
- errlore-0.1.0/src/errlore/retrieval/backend.py +117 -0
- errlore-0.1.0/src/errlore/retrieval/index.py +288 -0
- errlore-0.1.0/src/errlore/sanitize.py +117 -0
- errlore-0.1.0/src/errlore/trust/__init__.py +11 -0
- errlore-0.1.0/src/errlore/trust/engine.py +602 -0
- errlore-0.1.0/tests/conftest.py +13 -0
- errlore-0.1.0/tests/test_errmem.py +282 -0
- errlore-0.1.0/tests/test_facade.py +448 -0
- errlore-0.1.0/tests/test_io.py +583 -0
- errlore-0.1.0/tests/test_lessons.py +384 -0
- errlore-0.1.0/tests/test_openwebui_integration.py +112 -0
- errlore-0.1.0/tests/test_regressions.py +114 -0
- errlore-0.1.0/tests/test_retrieval.py +448 -0
- 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
|
errlore-0.1.0/.gitignore
ADDED
|
@@ -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
|
+
[](https://github.com/Ma4etaSS/errlore/actions/workflows/ci.yml)
|
|
34
|
+
[](https://python.org)
|
|
35
|
+
[](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
|
errlore-0.1.0/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# errlore
|
|
2
|
+
|
|
3
|
+
**Memory for AI agents that learns from failures.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Ma4etaSS/errlore/actions/workflows/ci.yml)
|
|
6
|
+
[](https://python.org)
|
|
7
|
+
[](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())
|