claude_memory 0.7.0 → 0.7.1
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.
- checksums.yaml +4 -4
- data/.claude/memory.sqlite3 +0 -0
- data/.claude/memory.sqlite3-shm +0 -0
- data/.claude/memory.sqlite3-wal +0 -0
- data/.claude/settings.local.json +3 -1
- data/.claude-plugin/marketplace.json +1 -1
- data/.claude-plugin/plugin.json +1 -2
- data/CHANGELOG.md +25 -0
- data/CLAUDE.md +3 -1
- data/README.md +1 -1
- data/docs/improvements.md +45 -47
- data/docs/influence/lossless-claw.md +409 -0
- data/lib/claude_memory/commands/index_command.rb +30 -2
- data/lib/claude_memory/mcp/error_classifier.rb +171 -0
- data/lib/claude_memory/mcp/instructions_builder.rb +62 -4
- data/lib/claude_memory/mcp/query_guide.rb +41 -22
- data/lib/claude_memory/mcp/response_formatter.rb +3 -1
- data/lib/claude_memory/mcp/server.rb +1 -0
- data/lib/claude_memory/mcp/text_summary.rb +2 -1
- data/lib/claude_memory/mcp/tool_definitions.rb +3 -2
- data/lib/claude_memory/mcp/tools.rb +20 -15
- data/lib/claude_memory/recall.rb +51 -5
- data/lib/claude_memory/sweep/maintenance.rb +126 -0
- data/lib/claude_memory/sweep/sweeper.rb +81 -75
- data/lib/claude_memory/version.rb +1 -1
- data/lib/claude_memory.rb +1 -0
- data/v0.6.0.ANNOUNCE +32 -0
- metadata +5 -1
|
@@ -10,6 +10,10 @@ module ClaudeMemory
|
|
|
10
10
|
default_budget_seconds: 5
|
|
11
11
|
}.freeze
|
|
12
12
|
|
|
13
|
+
# Three-level escalation ensures sweep always makes progress.
|
|
14
|
+
# Source: lossless-claw three-level escalation pattern
|
|
15
|
+
ESCALATION_LEVELS = %i[normal aggressive fallback].freeze
|
|
16
|
+
|
|
13
17
|
def initialize(store, config: {})
|
|
14
18
|
@store = store
|
|
15
19
|
@config = DEFAULT_CONFIG.merge(config)
|
|
@@ -24,16 +28,19 @@ module ClaudeMemory
|
|
|
24
28
|
proposed_facts_expired: 0,
|
|
25
29
|
disputed_facts_expired: 0,
|
|
26
30
|
orphaned_provenance_deleted: 0,
|
|
27
|
-
old_content_pruned: 0
|
|
31
|
+
old_content_pruned: 0,
|
|
32
|
+
escalation_level: :normal
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
maintenance = build_maintenance(:normal)
|
|
36
|
+
|
|
37
|
+
run_if_within_budget { @stats[:proposed_facts_expired] = maintenance.expire_proposed_facts }
|
|
38
|
+
run_if_within_budget { @stats[:disputed_facts_expired] = maintenance.expire_disputed_facts }
|
|
39
|
+
run_if_within_budget { @stats[:orphaned_provenance_deleted] = maintenance.prune_orphaned_provenance }
|
|
40
|
+
run_if_within_budget { @stats[:old_content_pruned] = maintenance.prune_old_content }
|
|
41
|
+
run_if_within_budget { @stats[:vec_backfilled] = maintenance.backfill_vec_index }
|
|
42
|
+
run_if_within_budget { @stats[:vec_cleaned] = maintenance.cleanup_vec_expired }
|
|
43
|
+
run_if_within_budget { @stats[:wal_checkpointed] = maintenance.checkpoint_wal }
|
|
37
44
|
|
|
38
45
|
@stats[:elapsed_seconds] = Time.now - @start_time
|
|
39
46
|
@stats[:budget_honored] = @stats[:elapsed_seconds] <= budget
|
|
@@ -41,90 +48,89 @@ module ClaudeMemory
|
|
|
41
48
|
message: "Sweep complete",
|
|
42
49
|
elapsed_seconds: @stats[:elapsed_seconds].round(3),
|
|
43
50
|
budget_honored: @stats[:budget_honored],
|
|
51
|
+
escalation_level: @stats[:escalation_level],
|
|
44
52
|
proposed_expired: @stats[:proposed_facts_expired],
|
|
45
53
|
disputed_expired: @stats[:disputed_facts_expired])
|
|
46
54
|
@stats
|
|
47
55
|
end
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
# Run sweep with escalation: if normal sweep makes no progress,
|
|
58
|
+
# escalate to aggressive (halved TTLs), then fallback (force-expire oldest).
|
|
59
|
+
# Returns stats hash with :escalation_level indicating which level succeeded.
|
|
60
|
+
#
|
|
61
|
+
# Source: lossless-claw three-level escalation pattern
|
|
62
|
+
def run_with_escalation!(budget_seconds: nil)
|
|
63
|
+
stats = run!(budget_seconds: budget_seconds)
|
|
64
|
+
total = stats[:proposed_facts_expired] + stats[:disputed_facts_expired] +
|
|
65
|
+
stats[:orphaned_provenance_deleted] + stats[:old_content_pruned]
|
|
66
|
+
|
|
67
|
+
if total == 0
|
|
68
|
+
# Escalate to aggressive — halved TTLs
|
|
69
|
+
aggressive_maintenance = build_maintenance(:aggressive)
|
|
70
|
+
added = 0
|
|
71
|
+
added += aggressive_maintenance.expire_proposed_facts
|
|
72
|
+
added += aggressive_maintenance.expire_disputed_facts
|
|
73
|
+
added += aggressive_maintenance.prune_old_content
|
|
74
|
+
|
|
75
|
+
if added > 0
|
|
76
|
+
stats[:proposed_facts_expired] += added
|
|
77
|
+
stats[:escalation_level] = :aggressive
|
|
78
|
+
else
|
|
79
|
+
# Fallback — force-expire oldest proposed/disputed facts
|
|
80
|
+
fallback_count = force_expire_oldest
|
|
81
|
+
stats[:proposed_facts_expired] += fallback_count
|
|
82
|
+
stats[:escalation_level] = :fallback if fallback_count > 0
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
stats[:elapsed_seconds] = Time.now - @start_time
|
|
86
|
+
end
|
|
55
87
|
|
|
56
|
-
|
|
57
|
-
cutoff = (Time.now - @config[:proposed_fact_ttl_days] * 86400).utc.iso8601
|
|
58
|
-
@stats[:proposed_facts_expired] = @store.facts
|
|
59
|
-
.where(status: "proposed")
|
|
60
|
-
.where { created_at < cutoff }
|
|
61
|
-
.update(status: "expired")
|
|
88
|
+
stats
|
|
62
89
|
end
|
|
63
90
|
|
|
64
|
-
|
|
65
|
-
cutoff = (Time.now - @config[:disputed_fact_ttl_days] * 86400).utc.iso8601
|
|
66
|
-
@stats[:disputed_facts_expired] = @store.facts
|
|
67
|
-
.where(status: "disputed")
|
|
68
|
-
.where { created_at < cutoff }
|
|
69
|
-
.update(status: "expired")
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def prune_orphaned_provenance
|
|
73
|
-
fact_ids = @store.facts.select(:id)
|
|
74
|
-
@stats[:orphaned_provenance_deleted] = @store.provenance
|
|
75
|
-
.exclude(fact_id: fact_ids)
|
|
76
|
-
.delete
|
|
77
|
-
end
|
|
91
|
+
private
|
|
78
92
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
fts.remove_content_item(row[:id], row[:raw_text])
|
|
90
|
-
rescue
|
|
91
|
-
# FTS entry may not exist; skip
|
|
93
|
+
def build_maintenance(level)
|
|
94
|
+
config = case level
|
|
95
|
+
when :aggressive
|
|
96
|
+
{
|
|
97
|
+
proposed_fact_ttl_days: @config[:proposed_fact_ttl_days] / 2,
|
|
98
|
+
disputed_fact_ttl_days: @config[:disputed_fact_ttl_days] / 2,
|
|
99
|
+
content_retention_days: @config[:content_retention_days] / 2
|
|
100
|
+
}
|
|
101
|
+
else
|
|
102
|
+
@config.slice(:proposed_fact_ttl_days, :disputed_fact_ttl_days, :content_retention_days)
|
|
92
103
|
end
|
|
93
104
|
|
|
94
|
-
@
|
|
105
|
+
Maintenance.new(@store, config: config)
|
|
95
106
|
end
|
|
96
107
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
# Force-expire the oldest proposed or disputed facts regardless of TTL.
|
|
109
|
+
# Guarantees progress when normal and aggressive sweeps find nothing.
|
|
110
|
+
# Limited to 10 facts per invocation for safety.
|
|
111
|
+
def force_expire_oldest(limit: 10)
|
|
112
|
+
oldest_ids = @store.facts
|
|
113
|
+
.where(status: %w[proposed disputed])
|
|
114
|
+
.order(:created_at)
|
|
115
|
+
.limit(limit)
|
|
116
|
+
.select(:id)
|
|
117
|
+
.map { |r| r[:id] }
|
|
118
|
+
|
|
119
|
+
return 0 if oldest_ids.empty?
|
|
120
|
+
|
|
121
|
+
@store.facts
|
|
122
|
+
.where(id: oldest_ids)
|
|
123
|
+
.update(status: "expired")
|
|
107
124
|
end
|
|
108
125
|
|
|
109
|
-
def
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
# (remove_embedding manages vec_indexed_at)
|
|
113
|
-
stale_ids = @store.facts
|
|
114
|
-
.where(status: %w[superseded expired])
|
|
115
|
-
.where(Sequel.~(vec_indexed_at: nil))
|
|
116
|
-
.select(:id)
|
|
117
|
-
.limit(100)
|
|
118
|
-
.map { |r| r[:id] }
|
|
119
|
-
|
|
120
|
-
stale_ids.each { |fact_id| vec_index.remove_embedding(fact_id) }
|
|
121
|
-
@stats[:vec_cleaned] = stale_ids.size
|
|
122
|
-
end
|
|
126
|
+
def run_if_within_budget
|
|
127
|
+
return unless within_budget?
|
|
128
|
+
yield
|
|
123
129
|
end
|
|
124
130
|
|
|
125
|
-
def
|
|
126
|
-
@
|
|
127
|
-
@
|
|
131
|
+
def within_budget?
|
|
132
|
+
budget = @config[:default_budget_seconds]
|
|
133
|
+
(Time.now - @start_time) < budget
|
|
128
134
|
end
|
|
129
135
|
end
|
|
130
136
|
end
|
data/lib/claude_memory.rb
CHANGED
|
@@ -116,6 +116,7 @@ require_relative "claude_memory/resolve/predicate_policy"
|
|
|
116
116
|
require_relative "claude_memory/resolve/resolver"
|
|
117
117
|
require_relative "claude_memory/store/sqlite_store"
|
|
118
118
|
require_relative "claude_memory/store/store_manager"
|
|
119
|
+
require_relative "claude_memory/sweep/maintenance"
|
|
119
120
|
require_relative "claude_memory/sweep/sweeper"
|
|
120
121
|
require_relative "claude_memory/version"
|
|
121
122
|
|
data/v0.6.0.ANNOUNCE
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
**ClaudeMemory v0.6.0 — Native Vector Search, Benchmarks, and Critical Bug Fixes**
|
|
2
|
+
|
|
3
|
+
New release of ClaudeMemory, the long-term self-managed memory gem for Claude Code.
|
|
4
|
+
|
|
5
|
+
**What's new:**
|
|
6
|
+
|
|
7
|
+
**sqlite-vec Native Vector Storage** — Vector search is now backed by sqlite-vec for fast KNN queries instead of loading all embeddings into Ruby. Embeddings are dual-written to both JSON and vec0 virtual tables, with automatic fallback if sqlite-vec isn't available. The `doctor` command reports vec coverage and the sweeper cleans up stale entries.
|
|
8
|
+
|
|
9
|
+
**SessionStart Context Injection** — Memory now automatically injects relevant facts at the start of each session via `hookSpecificOutput.additionalContext`, so Claude has context before you even ask.
|
|
10
|
+
|
|
11
|
+
**Database Maintenance** — New `compact` (VACUUM + integrity check) and `export` (JSON backup) commands for database housekeeping.
|
|
12
|
+
|
|
13
|
+
**Comparative Benchmarks (DevMemBench)** — Head-to-head retrieval benchmarks against QMD and grepai across 50 queries at 3 difficulty levels:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Adapter Recall@5 MRR
|
|
17
|
+
QMD-Vector 0.842 0.930
|
|
18
|
+
ClaudeMemory (hybrid) 0.712 0.732
|
|
19
|
+
FTS-only 0.712 0.732
|
|
20
|
+
QMD-BM25 0.350 0.400
|
|
21
|
+
grepai 0.000 0.000
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
QMD-Vector leads on easy/medium queries thanks to its custom fine-tuned query expansion model (Qwen3-1.7B). ClaudeMemory edges ahead on hard cross-category queries (0.358 vs 0.352 Recall@5) where multi-fact reasoning matters. Truth maintenance remains at 100% accuracy across all 100 test cases. Full benchmark suite runs locally at zero cost.
|
|
25
|
+
|
|
26
|
+
**Critical Bug Fixes:**
|
|
27
|
+
- **Recall returned no results** — `DualQueryTemplate` accessed stores before initializing them, silently returning empty arrays for all queries. This was the root cause of `claude-memory recall` never finding facts.
|
|
28
|
+
- **Doctor crashed with `no such module: vec0`** — Schema health checks tried to count rows in sqlite-vec virtual tables without loading the extension first.
|
|
29
|
+
|
|
30
|
+
21 MCP tools · 22 CLI commands · 1,316 tests
|
|
31
|
+
|
|
32
|
+
<https://github.com/codenamev/claude_memory/releases/tag/v0.6.0>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude_memory
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Valentino Stoll
|
|
@@ -132,6 +132,7 @@ files:
|
|
|
132
132
|
- docs/influence/episodic-memory.md
|
|
133
133
|
- docs/influence/grepai.md
|
|
134
134
|
- docs/influence/kbs.md
|
|
135
|
+
- docs/influence/lossless-claw.md
|
|
135
136
|
- docs/influence/qmd.md
|
|
136
137
|
- docs/organizational_memory_playbook.md
|
|
137
138
|
- docs/plans/feature_adoption_plan.md
|
|
@@ -238,6 +239,7 @@ files:
|
|
|
238
239
|
- lib/claude_memory/ingest/tool_filter.rb
|
|
239
240
|
- lib/claude_memory/ingest/transcript_reader.rb
|
|
240
241
|
- lib/claude_memory/logging/logger.rb
|
|
242
|
+
- lib/claude_memory/mcp/error_classifier.rb
|
|
241
243
|
- lib/claude_memory/mcp/instructions_builder.rb
|
|
242
244
|
- lib/claude_memory/mcp/query_guide.rb
|
|
243
245
|
- lib/claude_memory/mcp/response_formatter.rb
|
|
@@ -256,6 +258,7 @@ files:
|
|
|
256
258
|
- lib/claude_memory/shortcuts.rb
|
|
257
259
|
- lib/claude_memory/store/sqlite_store.rb
|
|
258
260
|
- lib/claude_memory/store/store_manager.rb
|
|
261
|
+
- lib/claude_memory/sweep/maintenance.rb
|
|
259
262
|
- lib/claude_memory/sweep/sweeper.rb
|
|
260
263
|
- lib/claude_memory/templates/hooks.example.json
|
|
261
264
|
- lib/claude_memory/templates/output-styles/memory-aware.md
|
|
@@ -269,6 +272,7 @@ files:
|
|
|
269
272
|
- skills/memory-first-workflow/SKILL.md
|
|
270
273
|
- skills/memory/SKILL.md
|
|
271
274
|
- skills/setup-memory/SKILL.md
|
|
275
|
+
- v0.6.0.ANNOUNCE
|
|
272
276
|
homepage: https://github.com/codenamev/claude_memory
|
|
273
277
|
licenses:
|
|
274
278
|
- MIT
|