runtime-memory 3.0.0__tar.gz → 4.0.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 (98) hide show
  1. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/PKG-INFO +101 -50
  2. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/README.md +100 -49
  3. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/__init__.py +1 -1
  4. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/claude_code/commands.py +10 -7
  5. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/claude_code/daemon.py +9 -9
  6. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/claude_code/hooks.py +8 -8
  7. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/cli/main.py +191 -3
  8. runtime_memory-4.0.0/src/runtime_memory/core/attribution.py +326 -0
  9. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/config.py +12 -7
  10. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/embeddings.py +52 -20
  11. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/engine.py +212 -9
  12. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/models.py +26 -1
  13. runtime_memory-4.0.0/src/runtime_memory/core/outcomes.py +118 -0
  14. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/retrieval.py +405 -47
  15. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/storage.py +261 -31
  16. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/extraction/extractor.py +394 -34
  17. runtime_memory-4.0.0/src/runtime_memory/hermes/provider.py +903 -0
  18. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/hermes/tools.py +22 -6
  19. runtime_memory-4.0.0/src/runtime_memory/hermes/trace.py +342 -0
  20. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/adapter.py +18 -0
  21. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/claude_code_adapter.py +9 -2
  22. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/unified_adapter.py +6 -2
  23. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/integration/test_engine_integration.py +105 -6
  24. runtime_memory-4.0.0/tests/unit/test_attribution.py +185 -0
  25. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_claude_code_tasks.py +36 -0
  26. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_cli.py +102 -1
  27. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_commands.py +27 -13
  28. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_config.py +9 -9
  29. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_embeddings.py +34 -0
  30. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_engine.py +103 -5
  31. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_extractor.py +383 -5
  32. runtime_memory-4.0.0/tests/unit/test_hermes_provider.py +1346 -0
  33. runtime_memory-4.0.0/tests/unit/test_outcomes.py +136 -0
  34. runtime_memory-4.0.0/tests/unit/test_retrieval.py +1352 -0
  35. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_storage.py +111 -0
  36. runtime_memory-3.0.0/src/runtime_memory/hermes/provider.py +0 -536
  37. runtime_memory-3.0.0/src/runtime_memory/hermes/trace.py +0 -177
  38. runtime_memory-3.0.0/tests/unit/test_hermes_provider.py +0 -622
  39. runtime_memory-3.0.0/tests/unit/test_retrieval.py +0 -728
  40. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/.gitignore +0 -0
  41. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/LICENSE +0 -0
  42. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/pyproject.toml +0 -0
  43. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/claude_code/__init__.py +0 -0
  44. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/cli/__init__.py +0 -0
  45. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/__init__.py +0 -0
  46. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/exceptions.py +0 -0
  47. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/legacy_env.py +0 -0
  48. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/logging.py +0 -0
  49. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/observability.py +0 -0
  50. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/paths.py +0 -0
  51. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/core/resilience.py +0 -0
  52. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/extraction/__init__.py +0 -0
  53. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/hermes/__init__.py +0 -0
  54. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/hermes/_base.py +0 -0
  55. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/hermes/bridge.py +0 -0
  56. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/hermes/plugin.yaml +0 -0
  57. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/plugin/__init__.py +0 -0
  58. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/sdk/__init__.py +0 -0
  59. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/sdk/client.py +0 -0
  60. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/server/__init__.py +0 -0
  61. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/server/api.py +0 -0
  62. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/server/mcp.py +0 -0
  63. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/server/static/css/styles.css +0 -0
  64. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/server/static/index.html +0 -0
  65. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/server/static/js/api.js +0 -0
  66. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/server/static/js/app.js +0 -0
  67. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/__init__.py +0 -0
  68. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/claude_code_parser.py +0 -0
  69. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/cli_bridge.py +0 -0
  70. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/linking.py +0 -0
  71. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/models.py +0 -0
  72. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/outcomes.py +0 -0
  73. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/src/runtime_memory/tasks/parser.py +0 -0
  74. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/__init__.py +0 -0
  75. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/conftest.py +0 -0
  76. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/functional/__init__.py +0 -0
  77. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/integration/__init__.py +0 -0
  78. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/integration/test_extractor_integration.py +0 -0
  79. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/performance/__init__.py +0 -0
  80. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/security/__init__.py +0 -0
  81. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/uat/__init__.py +0 -0
  82. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/uat/test_uat.py +0 -0
  83. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/__init__.py +0 -0
  84. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_api.py +0 -0
  85. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_beads.py +0 -0
  86. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_config_validation.py +0 -0
  87. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_daemon.py +0 -0
  88. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_exceptions.py +0 -0
  89. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_hooks.py +0 -0
  90. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_legacy_compat.py +0 -0
  91. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_mcp.py +0 -0
  92. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_models.py +0 -0
  93. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_observability.py +0 -0
  94. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_plugin.py +0 -0
  95. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_resilience.py +0 -0
  96. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_sdk.py +0 -0
  97. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_setup.py +0 -0
  98. {runtime_memory-3.0.0 → runtime_memory-4.0.0}/tests/unit/test_v2_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: runtime-memory
3
- Version: 3.0.0
3
+ Version: 4.0.0
4
4
  Summary: Persistent memory for AI coding agents with outcome-based learning
5
5
  Project-URL: Homepage, https://github.com/runtimenoteslabs/memory-layer
6
6
  Project-URL: Documentation, https://github.com/runtimenoteslabs/memory-layer#readme
@@ -66,20 +66,32 @@ Persistent memory for AI coding agents with outcome-based learning.
66
66
 
67
67
  > **New to Runtime Memory?** See the [User Guide](https://github.com/runtimenoteslabs/memory-layer/blob/main/USER_GUIDE.md) for an introduction to using Runtime Memory with Claude Code.
68
68
 
69
- ## What It Does
69
+ ## What it does
70
70
 
71
- Runtime Memory stores knowledge from your coding sessions and learns which memories actually help. When advice works, it gets boosted (+0.2). When it fails, it gets penalized (-0.3). Over time, good memories rise to the top.
71
+ Runtime Memory stores knowledge from your coding sessions and records how often each memory worked and how often it failed. Among the memories relevant to a query, those with a better record rank higher. A memory that keeps failing is left out of retrieval until its failures fade.
72
72
 
73
73
  ## Installation
74
74
 
75
75
  ```bash
76
- pip install runtime-memory
76
+ pip install "runtime-memory[all]"
77
77
  ```
78
78
 
79
+ The base package stores and searches memories by keyword. Each extra adds a part:
80
+
81
+ | Extra | Adds | Without it |
82
+ |-------|------|------------|
83
+ | `embedding` | Semantic search, with `sentence-transformers` | Search is keyword-only |
84
+ | `extraction` | Extracting memories from sessions with Claude | No extraction |
85
+ | `server` | The MCP server, the REST API and the web UI | `mem serve` is unavailable |
86
+ | `all` | All three | |
87
+
88
+ `mem stats` shows which search mode is in use. With `embedding`, the first search
89
+ downloads an embedding model of about 100 MB and caches it.
90
+
79
91
  Or from source:
80
92
 
81
93
  ```bash
82
- pip install git+https://github.com/runtimenoteslabs/memory-layer.git
94
+ pip install "runtime-memory[all] @ git+https://github.com/runtimenoteslabs/memory-layer.git"
83
95
  ```
84
96
 
85
97
  For development:
@@ -95,9 +107,7 @@ repository is still named memory-layer, which is where the project started; the
95
107
  package was renamed in 3.0. An unrelated package holds `memory-layer` on PyPI,
96
108
  so `pip install memory-layer` fetches that one instead of this project.
97
109
 
98
- **Note:** First run downloads an embedding model (~100MB) for semantic search. This happens once and is cached. Subsequent operations are fast (<100ms).
99
-
100
- ## Quick Start
110
+ ## Quick start
101
111
 
102
112
  ### Python SDK
103
113
 
@@ -121,7 +131,7 @@ async with MemoryClient() as client:
121
131
  context = await client.get_context()
122
132
  ```
123
133
 
124
- ### Synchronous Client
134
+ ### Synchronous client
125
135
 
126
136
  ```python
127
137
  from runtime_memory.sdk import SyncMemoryClient
@@ -140,6 +150,12 @@ mem add "Use type hints for better IDE support" -c convention
140
150
  # Search memories
141
151
  mem search "type hints"
142
152
 
153
+ # See why a search returns what it does, and why the rest were left out
154
+ mem why "type hints"
155
+
156
+ # Store statistics: outcome records, search mode, and the Hermes trace if present
157
+ mem stats
158
+
143
159
  # Record outcome
144
160
  mem outcome <memory-id> worked
145
161
 
@@ -170,7 +186,7 @@ curl -X POST http://localhost:8080/memories/search \
170
186
  -d '{"query": "testing"}'
171
187
  ```
172
188
 
173
- ### MCP Server
189
+ ### MCP server
174
190
 
175
191
  For multi-agent setups, Runtime Memory provides an MCP server:
176
192
 
@@ -189,7 +205,7 @@ Configure in your MCP client:
189
205
  }
190
206
  ```
191
207
 
192
- #### Multi-Agent Configurations
208
+ #### Multi-agent configurations
193
209
 
194
210
  All agents share the same memory store. Memories created in Claude Code appear in Cursor, feedback from OpenCode improves results everywhere.
195
211
 
@@ -229,14 +245,14 @@ All agents share the same memory store. Memories created in Claude Code appear i
229
245
  }
230
246
  ```
231
247
 
232
- ### Claude Code Integration
248
+ ### Claude Code integration
233
249
 
234
250
  Runtime Memory integrates with Claude Code via hooks and skills. For a beginner-friendly walkthrough, see the [User Guide](https://github.com/runtimenoteslabs/memory-layer/blob/main/USER_GUIDE.md).
235
251
 
236
252
  **Installation:**
237
253
 
238
254
  ```bash
239
- pip install runtime-memory
255
+ pip install "runtime-memory[all]"
240
256
 
241
257
  # Go to your project directory
242
258
  cd your-project
@@ -275,7 +291,7 @@ The `mem install-plugin` command creates:
275
291
  /memory-context # Get project context
276
292
  ```
277
293
 
278
- ### Task Integration (Beads + Claude Code)
294
+ ### Task integration (Beads and Claude Code)
279
295
 
280
296
  Runtime Memory integrates with task trackers to automatically learn from task outcomes.
281
297
 
@@ -286,7 +302,7 @@ Runtime Memory integrates with task trackers to automatically learn from task ou
286
302
  **How it works:**
287
303
  1. You work on a task, Claude searches for relevant memories
288
304
  2. Those memories get linked to your task
289
- 3. When you mark the task done, linked memories are automatically boosted
305
+ 3. When you mark the task done, the linked memories are recorded as having worked
290
306
 
291
307
  ```bash
292
308
  # Unified task commands (all sources)
@@ -303,25 +319,27 @@ mem beads-context
303
319
  mem beads-stats
304
320
  ```
305
321
 
306
- No setup required - Runtime Memory auto-detects both `.beads/` and `~/.claude/todos/` directories.
322
+ Runtime Memory finds `.beads/` and `~/.claude/todos/` itself; there is nothing to configure.
307
323
 
308
324
  **Environment variables:**
309
325
  - `CLAUDE_CODE_TASK_LIST_ID` - Filter to specific task list
310
326
  - `CLAUDE_CODE_TODOS_DIR` - Custom todos directory location
311
327
 
312
- ### Hermes Agent Integration
328
+ ### Hermes Agent integration
313
329
 
314
330
  Runtime Memory can serve as Hermes Agent's memory provider, replacing its capped
315
331
  note file with retrieval over the same store Claude Code and MCP clients use.
316
332
 
317
333
  ```bash
318
334
  # Install into the environment Hermes runs in
319
- ~/.hermes/hermes-agent/venv/bin/python -m pip install \
320
- git+https://github.com/runtimenoteslabs/memory-layer.git
335
+ ~/.hermes/hermes-agent/venv/bin/python -m pip install "runtime-memory[embedding,extraction]"
321
336
 
322
337
  hermes config set memory.provider runtimememory
323
338
  ```
324
339
 
340
+ Without the `embedding` extra in Hermes' own environment, the provider searches by
341
+ keyword only and logs a warning that names the interpreter.
342
+
325
343
  Hermes finds the provider through the `hermes_agent.memory_providers` entry
326
344
  point, so you do not edit its code or config files by hand. See
327
345
  [docs/hermes.md](https://github.com/runtimenoteslabs/memory-layer/blob/main/docs/hermes.md) for configuration, the tool surface, and the
@@ -347,9 +365,9 @@ mem serve --rest --port 8080
347
365
  - Record outcomes
348
366
  - Light/dark theme
349
367
 
350
- ## Memory Categories
368
+ ## Memory categories
351
369
 
352
- | Category | Use For | Example |
370
+ | Category | Use for | Example |
353
371
  |----------|---------|---------|
354
372
  | `architecture` | System design | "Microservices with event sourcing" |
355
373
  | `convention` | Coding standards | "Use snake_case for Python" |
@@ -361,42 +379,71 @@ mem serve --rest --port 8080
361
379
  | `command` | Useful commands | "npm run test:coverage" |
362
380
  | `preference` | User preferences | "Prefer functional style" |
363
381
 
364
- ## Outcome Scoring
382
+ ## Outcome scoring
365
383
 
366
- | Outcome | Score Change | When to Use |
367
- |---------|--------------|-------------|
368
- | `worked` | +0.2 | Advice solved the problem |
369
- | `failed` | -0.3 | Advice was wrong or unhelpful |
370
- | `partial` | +0.05 | Advice was on the right track |
384
+ | Outcome | Adds | When to use |
385
+ |---------|------|-------------|
386
+ | `worked` | one success | Advice solved the problem |
387
+ | `failed` | one failure | Advice was wrong or unhelpful |
388
+ | `partial` | a quarter of a success | Advice was on the right track |
371
389
 
372
- The asymmetric scoring is intentional: bad advice wastes debugging time and erodes trust, so it's penalized more heavily.
390
+ A memory's outcome score is `(worked - 1.5 x failed) / (worked + 1.5 x failed + 2)`,
391
+ between -1 and 1. One success gives 0.33 and ten give 0.83, so a single
392
+ observation counts for less than a long record. A failure weighs 1.5 successes,
393
+ because following bad advice wastes debugging time. Each count halves every 90
394
+ days.
373
395
 
374
- ## How Retrieval Works
396
+ Retrieval leaves out a memory whose score is -0.5 or lower, which takes two
397
+ failures and no successes. One failure is not enough, because it may have been
398
+ blamed on the wrong memory. The memory is retrieved again once its failures
399
+ have faded.
375
400
 
376
- Runtime Memory uses a 5-signal hybrid retrieval system that combines multiple relevance signals:
401
+ To change these values, see `RetrievalConfig.outcome_model` and
402
+ `RetrievalConfig.failure_gate`.
403
+
404
+ ## How retrieval works
405
+
406
+ Retrieval runs in two stages. Relevance to your query decides which memories
407
+ compete, then the other signals order them.
408
+
409
+ **Stage 1, the relevance pool.** A search keeps the `ceil(limit x 2)` memories
410
+ most relevant to the query and drops any with no relevance at all. Outcome records
411
+ reorder only the memories that match the query.
412
+
413
+ **Stage 2, the score.**
377
414
 
378
415
  | Signal | Weight | Description |
379
416
  |--------|--------|-------------|
380
- | Semantic | 35% | Vector similarity to your query |
417
+ | Semantic | 55% | Vector and keyword similarity to your query |
381
418
  | Outcome | 25% | Learned effectiveness from feedback |
382
- | Recency | 15% | Recent memories weighted higher (30-day half-life) |
383
- | Frequency | 15% | Frequently used memories rise |
384
419
  | Confidence | 10% | Extraction confidence score |
420
+ | Recency | 10% | Newer memories weighted higher (30-day half-life on age) |
421
+ | Frequency | 0% | Off by default; see below |
385
422
 
386
- Two of the five signals, outcome and frequency, come from how memories have
387
- performed rather than from the query, so ranking changes as feedback accumulates.
423
+ Outcome and confidence come from how memories have performed rather than from
424
+ the query, so ranking changes as feedback accumulates.
388
425
 
389
- ### Category Boosting
426
+ **What changed in 4.0.0, and why.** Tier 2 evaluation runs found the older
427
+ scoring deciding retrieval on signals that had nothing to do with the query:
390
428
 
391
- When you ask about errors, troubleshooting memories get a 1.5x boost. Query intent is detected and the right category is prioritized:
429
+ - **Frequency left the default score.** It rewards having been retrieved, which
430
+ is not evidence of having helped, and it compounds: a wrong memory held a top
431
+ place through a whole task sequence on it. Set `frequency_weight` to bring it
432
+ back.
433
+ - **Category boosts are neutral.** Multiplying the whole score by a category
434
+ seated a memory that ranked about 25th on relevance at rank 1, and in another
435
+ run kept the one memory that would have prevented a repeated mistake out of
436
+ every prompt. Pass `category_boosts` to set your own.
437
+ - **Recency decays from a memory's age,** not from when it was last touched.
438
+ Retrieval no longer moves that clock.
392
439
 
393
- | Query Pattern | Boosted Category | Multiplier |
394
- |---------------|------------------|------------|
395
- | "What went wrong..." | troubleshooting | 1.5x |
396
- | "Watch out for..." | gotcha | 1.4x |
397
- | "Why did we choose..." | decision | 1.4x |
398
- | "How should I structure..." | pattern, convention | 1.3x |
399
- | "System design..." | architecture | 1.2x |
440
+ `RetrievalConfig.legacy_3x()` restores the 3.x weights, boosts and single-stage
441
+ scoring if you tuned for them.
442
+
443
+ ### Category routing
444
+
445
+ `CategoryRouter` maps query wording to a category, but no search path calls it.
446
+ It is available to callers that want to pass `category=` themselves.
400
447
 
401
448
  ## Results
402
449
 
@@ -411,25 +458,29 @@ After 12 weeks of use:
411
458
 
412
459
  ## Configuration
413
460
 
414
- ### Environment Variables
461
+ ### Environment variables
415
462
 
416
463
  | Variable | Description | Default |
417
464
  |----------|-------------|---------|
418
465
  | `ANTHROPIC_API_KEY` | For LLM-based extraction | Required for extraction features |
419
- | `MEMORY_LAYER_DB` | Database location | `~/.runtime-memory/memories.db` |
420
- | `MEMORY_LAYER_ENV` | Environment (development/testing/production) | development |
421
- | `MEMORY_LAYER_LOG_LEVEL` | Logging level | WARNING |
466
+ | `RUNTIME_MEMORY_DB` | Database location, read by the CLI, the MCP server and the Hermes provider | `~/.runtime-memory/memories.db` |
467
+ | `RUNTIME_MEMORY_ENV` | Environment (development/testing/production) | development |
468
+ | `RUNTIME_MEMORY_LOG_LEVEL` | Logging level | WARNING |
422
469
  | `CLAUDE_CODE_TASK_LIST_ID` | Filter Claude Code tasks | None |
423
470
  | `CLAUDE_CODE_TODOS_DIR` | Custom todos directory | `~/.claude/todos/` |
424
471
 
425
- ### Data Location
472
+ Variables set with the pre-3.0 prefix `MEMORY_LAYER_` are still read, under their
473
+ `RUNTIME_MEMORY_` names. The Hermes provider's own settings are in
474
+ [docs/hermes.md](https://github.com/runtimenoteslabs/memory-layer/blob/main/docs/hermes.md).
475
+
476
+ ### Data location
426
477
 
427
478
  ```
428
479
  ~/.runtime-memory/
429
480
  └── memories.db # SQLite database
430
481
  ```
431
482
 
432
- ## Project Structure
483
+ ## Project structure
433
484
 
434
485
  ```
435
486
  memory-layer/
@@ -4,20 +4,32 @@ Persistent memory for AI coding agents with outcome-based learning.
4
4
 
5
5
  > **New to Runtime Memory?** See the [User Guide](https://github.com/runtimenoteslabs/memory-layer/blob/main/USER_GUIDE.md) for an introduction to using Runtime Memory with Claude Code.
6
6
 
7
- ## What It Does
7
+ ## What it does
8
8
 
9
- Runtime Memory stores knowledge from your coding sessions and learns which memories actually help. When advice works, it gets boosted (+0.2). When it fails, it gets penalized (-0.3). Over time, good memories rise to the top.
9
+ Runtime Memory stores knowledge from your coding sessions and records how often each memory worked and how often it failed. Among the memories relevant to a query, those with a better record rank higher. A memory that keeps failing is left out of retrieval until its failures fade.
10
10
 
11
11
  ## Installation
12
12
 
13
13
  ```bash
14
- pip install runtime-memory
14
+ pip install "runtime-memory[all]"
15
15
  ```
16
16
 
17
+ The base package stores and searches memories by keyword. Each extra adds a part:
18
+
19
+ | Extra | Adds | Without it |
20
+ |-------|------|------------|
21
+ | `embedding` | Semantic search, with `sentence-transformers` | Search is keyword-only |
22
+ | `extraction` | Extracting memories from sessions with Claude | No extraction |
23
+ | `server` | The MCP server, the REST API and the web UI | `mem serve` is unavailable |
24
+ | `all` | All three | |
25
+
26
+ `mem stats` shows which search mode is in use. With `embedding`, the first search
27
+ downloads an embedding model of about 100 MB and caches it.
28
+
17
29
  Or from source:
18
30
 
19
31
  ```bash
20
- pip install git+https://github.com/runtimenoteslabs/memory-layer.git
32
+ pip install "runtime-memory[all] @ git+https://github.com/runtimenoteslabs/memory-layer.git"
21
33
  ```
22
34
 
23
35
  For development:
@@ -33,9 +45,7 @@ repository is still named memory-layer, which is where the project started; the
33
45
  package was renamed in 3.0. An unrelated package holds `memory-layer` on PyPI,
34
46
  so `pip install memory-layer` fetches that one instead of this project.
35
47
 
36
- **Note:** First run downloads an embedding model (~100MB) for semantic search. This happens once and is cached. Subsequent operations are fast (<100ms).
37
-
38
- ## Quick Start
48
+ ## Quick start
39
49
 
40
50
  ### Python SDK
41
51
 
@@ -59,7 +69,7 @@ async with MemoryClient() as client:
59
69
  context = await client.get_context()
60
70
  ```
61
71
 
62
- ### Synchronous Client
72
+ ### Synchronous client
63
73
 
64
74
  ```python
65
75
  from runtime_memory.sdk import SyncMemoryClient
@@ -78,6 +88,12 @@ mem add "Use type hints for better IDE support" -c convention
78
88
  # Search memories
79
89
  mem search "type hints"
80
90
 
91
+ # See why a search returns what it does, and why the rest were left out
92
+ mem why "type hints"
93
+
94
+ # Store statistics: outcome records, search mode, and the Hermes trace if present
95
+ mem stats
96
+
81
97
  # Record outcome
82
98
  mem outcome <memory-id> worked
83
99
 
@@ -108,7 +124,7 @@ curl -X POST http://localhost:8080/memories/search \
108
124
  -d '{"query": "testing"}'
109
125
  ```
110
126
 
111
- ### MCP Server
127
+ ### MCP server
112
128
 
113
129
  For multi-agent setups, Runtime Memory provides an MCP server:
114
130
 
@@ -127,7 +143,7 @@ Configure in your MCP client:
127
143
  }
128
144
  ```
129
145
 
130
- #### Multi-Agent Configurations
146
+ #### Multi-agent configurations
131
147
 
132
148
  All agents share the same memory store. Memories created in Claude Code appear in Cursor, feedback from OpenCode improves results everywhere.
133
149
 
@@ -167,14 +183,14 @@ All agents share the same memory store. Memories created in Claude Code appear i
167
183
  }
168
184
  ```
169
185
 
170
- ### Claude Code Integration
186
+ ### Claude Code integration
171
187
 
172
188
  Runtime Memory integrates with Claude Code via hooks and skills. For a beginner-friendly walkthrough, see the [User Guide](https://github.com/runtimenoteslabs/memory-layer/blob/main/USER_GUIDE.md).
173
189
 
174
190
  **Installation:**
175
191
 
176
192
  ```bash
177
- pip install runtime-memory
193
+ pip install "runtime-memory[all]"
178
194
 
179
195
  # Go to your project directory
180
196
  cd your-project
@@ -213,7 +229,7 @@ The `mem install-plugin` command creates:
213
229
  /memory-context # Get project context
214
230
  ```
215
231
 
216
- ### Task Integration (Beads + Claude Code)
232
+ ### Task integration (Beads and Claude Code)
217
233
 
218
234
  Runtime Memory integrates with task trackers to automatically learn from task outcomes.
219
235
 
@@ -224,7 +240,7 @@ Runtime Memory integrates with task trackers to automatically learn from task ou
224
240
  **How it works:**
225
241
  1. You work on a task, Claude searches for relevant memories
226
242
  2. Those memories get linked to your task
227
- 3. When you mark the task done, linked memories are automatically boosted
243
+ 3. When you mark the task done, the linked memories are recorded as having worked
228
244
 
229
245
  ```bash
230
246
  # Unified task commands (all sources)
@@ -241,25 +257,27 @@ mem beads-context
241
257
  mem beads-stats
242
258
  ```
243
259
 
244
- No setup required - Runtime Memory auto-detects both `.beads/` and `~/.claude/todos/` directories.
260
+ Runtime Memory finds `.beads/` and `~/.claude/todos/` itself; there is nothing to configure.
245
261
 
246
262
  **Environment variables:**
247
263
  - `CLAUDE_CODE_TASK_LIST_ID` - Filter to specific task list
248
264
  - `CLAUDE_CODE_TODOS_DIR` - Custom todos directory location
249
265
 
250
- ### Hermes Agent Integration
266
+ ### Hermes Agent integration
251
267
 
252
268
  Runtime Memory can serve as Hermes Agent's memory provider, replacing its capped
253
269
  note file with retrieval over the same store Claude Code and MCP clients use.
254
270
 
255
271
  ```bash
256
272
  # Install into the environment Hermes runs in
257
- ~/.hermes/hermes-agent/venv/bin/python -m pip install \
258
- git+https://github.com/runtimenoteslabs/memory-layer.git
273
+ ~/.hermes/hermes-agent/venv/bin/python -m pip install "runtime-memory[embedding,extraction]"
259
274
 
260
275
  hermes config set memory.provider runtimememory
261
276
  ```
262
277
 
278
+ Without the `embedding` extra in Hermes' own environment, the provider searches by
279
+ keyword only and logs a warning that names the interpreter.
280
+
263
281
  Hermes finds the provider through the `hermes_agent.memory_providers` entry
264
282
  point, so you do not edit its code or config files by hand. See
265
283
  [docs/hermes.md](https://github.com/runtimenoteslabs/memory-layer/blob/main/docs/hermes.md) for configuration, the tool surface, and the
@@ -285,9 +303,9 @@ mem serve --rest --port 8080
285
303
  - Record outcomes
286
304
  - Light/dark theme
287
305
 
288
- ## Memory Categories
306
+ ## Memory categories
289
307
 
290
- | Category | Use For | Example |
308
+ | Category | Use for | Example |
291
309
  |----------|---------|---------|
292
310
  | `architecture` | System design | "Microservices with event sourcing" |
293
311
  | `convention` | Coding standards | "Use snake_case for Python" |
@@ -299,42 +317,71 @@ mem serve --rest --port 8080
299
317
  | `command` | Useful commands | "npm run test:coverage" |
300
318
  | `preference` | User preferences | "Prefer functional style" |
301
319
 
302
- ## Outcome Scoring
320
+ ## Outcome scoring
303
321
 
304
- | Outcome | Score Change | When to Use |
305
- |---------|--------------|-------------|
306
- | `worked` | +0.2 | Advice solved the problem |
307
- | `failed` | -0.3 | Advice was wrong or unhelpful |
308
- | `partial` | +0.05 | Advice was on the right track |
322
+ | Outcome | Adds | When to use |
323
+ |---------|------|-------------|
324
+ | `worked` | one success | Advice solved the problem |
325
+ | `failed` | one failure | Advice was wrong or unhelpful |
326
+ | `partial` | a quarter of a success | Advice was on the right track |
309
327
 
310
- The asymmetric scoring is intentional: bad advice wastes debugging time and erodes trust, so it's penalized more heavily.
328
+ A memory's outcome score is `(worked - 1.5 x failed) / (worked + 1.5 x failed + 2)`,
329
+ between -1 and 1. One success gives 0.33 and ten give 0.83, so a single
330
+ observation counts for less than a long record. A failure weighs 1.5 successes,
331
+ because following bad advice wastes debugging time. Each count halves every 90
332
+ days.
311
333
 
312
- ## How Retrieval Works
334
+ Retrieval leaves out a memory whose score is -0.5 or lower, which takes two
335
+ failures and no successes. One failure is not enough, because it may have been
336
+ blamed on the wrong memory. The memory is retrieved again once its failures
337
+ have faded.
313
338
 
314
- Runtime Memory uses a 5-signal hybrid retrieval system that combines multiple relevance signals:
339
+ To change these values, see `RetrievalConfig.outcome_model` and
340
+ `RetrievalConfig.failure_gate`.
341
+
342
+ ## How retrieval works
343
+
344
+ Retrieval runs in two stages. Relevance to your query decides which memories
345
+ compete, then the other signals order them.
346
+
347
+ **Stage 1, the relevance pool.** A search keeps the `ceil(limit x 2)` memories
348
+ most relevant to the query and drops any with no relevance at all. Outcome records
349
+ reorder only the memories that match the query.
350
+
351
+ **Stage 2, the score.**
315
352
 
316
353
  | Signal | Weight | Description |
317
354
  |--------|--------|-------------|
318
- | Semantic | 35% | Vector similarity to your query |
355
+ | Semantic | 55% | Vector and keyword similarity to your query |
319
356
  | Outcome | 25% | Learned effectiveness from feedback |
320
- | Recency | 15% | Recent memories weighted higher (30-day half-life) |
321
- | Frequency | 15% | Frequently used memories rise |
322
357
  | Confidence | 10% | Extraction confidence score |
358
+ | Recency | 10% | Newer memories weighted higher (30-day half-life on age) |
359
+ | Frequency | 0% | Off by default; see below |
323
360
 
324
- Two of the five signals, outcome and frequency, come from how memories have
325
- performed rather than from the query, so ranking changes as feedback accumulates.
361
+ Outcome and confidence come from how memories have performed rather than from
362
+ the query, so ranking changes as feedback accumulates.
326
363
 
327
- ### Category Boosting
364
+ **What changed in 4.0.0, and why.** Tier 2 evaluation runs found the older
365
+ scoring deciding retrieval on signals that had nothing to do with the query:
328
366
 
329
- When you ask about errors, troubleshooting memories get a 1.5x boost. Query intent is detected and the right category is prioritized:
367
+ - **Frequency left the default score.** It rewards having been retrieved, which
368
+ is not evidence of having helped, and it compounds: a wrong memory held a top
369
+ place through a whole task sequence on it. Set `frequency_weight` to bring it
370
+ back.
371
+ - **Category boosts are neutral.** Multiplying the whole score by a category
372
+ seated a memory that ranked about 25th on relevance at rank 1, and in another
373
+ run kept the one memory that would have prevented a repeated mistake out of
374
+ every prompt. Pass `category_boosts` to set your own.
375
+ - **Recency decays from a memory's age,** not from when it was last touched.
376
+ Retrieval no longer moves that clock.
330
377
 
331
- | Query Pattern | Boosted Category | Multiplier |
332
- |---------------|------------------|------------|
333
- | "What went wrong..." | troubleshooting | 1.5x |
334
- | "Watch out for..." | gotcha | 1.4x |
335
- | "Why did we choose..." | decision | 1.4x |
336
- | "How should I structure..." | pattern, convention | 1.3x |
337
- | "System design..." | architecture | 1.2x |
378
+ `RetrievalConfig.legacy_3x()` restores the 3.x weights, boosts and single-stage
379
+ scoring if you tuned for them.
380
+
381
+ ### Category routing
382
+
383
+ `CategoryRouter` maps query wording to a category, but no search path calls it.
384
+ It is available to callers that want to pass `category=` themselves.
338
385
 
339
386
  ## Results
340
387
 
@@ -349,25 +396,29 @@ After 12 weeks of use:
349
396
 
350
397
  ## Configuration
351
398
 
352
- ### Environment Variables
399
+ ### Environment variables
353
400
 
354
401
  | Variable | Description | Default |
355
402
  |----------|-------------|---------|
356
403
  | `ANTHROPIC_API_KEY` | For LLM-based extraction | Required for extraction features |
357
- | `MEMORY_LAYER_DB` | Database location | `~/.runtime-memory/memories.db` |
358
- | `MEMORY_LAYER_ENV` | Environment (development/testing/production) | development |
359
- | `MEMORY_LAYER_LOG_LEVEL` | Logging level | WARNING |
404
+ | `RUNTIME_MEMORY_DB` | Database location, read by the CLI, the MCP server and the Hermes provider | `~/.runtime-memory/memories.db` |
405
+ | `RUNTIME_MEMORY_ENV` | Environment (development/testing/production) | development |
406
+ | `RUNTIME_MEMORY_LOG_LEVEL` | Logging level | WARNING |
360
407
  | `CLAUDE_CODE_TASK_LIST_ID` | Filter Claude Code tasks | None |
361
408
  | `CLAUDE_CODE_TODOS_DIR` | Custom todos directory | `~/.claude/todos/` |
362
409
 
363
- ### Data Location
410
+ Variables set with the pre-3.0 prefix `MEMORY_LAYER_` are still read, under their
411
+ `RUNTIME_MEMORY_` names. The Hermes provider's own settings are in
412
+ [docs/hermes.md](https://github.com/runtimenoteslabs/memory-layer/blob/main/docs/hermes.md).
413
+
414
+ ### Data location
364
415
 
365
416
  ```
366
417
  ~/.runtime-memory/
367
418
  └── memories.db # SQLite database
368
419
  ```
369
420
 
370
- ## Project Structure
421
+ ## Project structure
371
422
 
372
423
  ```
373
424
  memory-layer/
@@ -10,7 +10,7 @@ This package provides:
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- __version__ = "3.0.0"
13
+ __version__ = "4.0.0"
14
14
  __author__ = "exitcode42"
15
15
 
16
16
  from runtime_memory.core.legacy_env import apply_legacy_env
@@ -373,13 +373,16 @@ class CommandHandler:
373
373
  error=f"No memory found with ID: {memory_id}",
374
374
  )
375
375
 
376
- # Record the outcome
376
+ # Record the outcome. The engine stores no notes, so they are only echoed back.
377
377
  old_score = memory.outcome_score
378
- updated_memory = await self.engine.record_outcome(
379
- memory_id=memory_id,
380
- outcome=outcome,
381
- context=notes,
382
- )
378
+ updated = await self.engine.record_outcome([memory_id], outcome)
379
+ if not updated:
380
+ return CommandResult(
381
+ success=False,
382
+ command=CommandType.OUTCOME,
383
+ message="Memory not found",
384
+ error=f"No memory found with ID: {memory_id}",
385
+ )
383
386
 
384
387
  return CommandResult(
385
388
  success=True,
@@ -389,7 +392,7 @@ class CommandHandler:
389
392
  "memory_id": memory_id,
390
393
  "outcome": outcome.value,
391
394
  "old_score": old_score,
392
- "new_score": updated_memory.outcome_score,
395
+ "new_score": updated[0].outcome_score,
393
396
  "notes": notes,
394
397
  },
395
398
  )