claude_swarm 1.0.5 → 1.0.6
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/CHANGELOG.md +9 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +10 -0
- data/docs/v2/CHANGELOG.swarm_sdk.md +47 -0
- data/docs/v2/README.md +56 -22
- data/docs/v2/guides/MEMORY_DEFRAG_GUIDE.md +811 -0
- data/docs/v2/guides/complete-tutorial.md +2 -2
- data/docs/v2/guides/getting-started.md +6 -6
- data/docs/v2/guides/rails-integration.md +6 -6
- data/docs/v2/reference/architecture-flow.md +407 -0
- data/docs/v2/reference/event_payload_structures.md +471 -0
- data/docs/v2/reference/execution-flow.md +600 -0
- data/docs/v2/reference/swarm_memory_technical_details.md +2 -2
- data/examples/v2/swarm_with_hooks.yml +1 -1
- data/lib/claude_swarm/mcp_generator.rb +4 -10
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
- data/lib/swarm_cli/config_loader.rb +3 -3
- data/lib/swarm_cli/version.rb +1 -1
- data/lib/swarm_memory/adapters/base.rb +4 -4
- data/lib/swarm_sdk/agent/definition.rb +1 -20
- data/lib/swarm_sdk/configuration.rb +34 -10
- data/lib/swarm_sdk/mcp.rb +16 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
- data/lib/swarm_sdk/swarm.rb +32 -50
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
- data/lib/swarm_sdk/tools/stores/storage.rb +4 -4
- data/lib/swarm_sdk/tools/think.rb +4 -1
- data/lib/swarm_sdk/tools/todo_write.rb +20 -8
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk.rb +329 -20
- data/swarm_sdk.gemspec +1 -1
- metadata +8 -172
- data/memory/corpus-self-reflection/.lock +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.md +0 -11
- data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.yml +0 -23
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.md +0 -20
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.yml +0 -22
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.md +0 -24
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.yml +0 -22
- data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.md +0 -18
- data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.yml +0 -21
- data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.md +0 -30
- data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.yml +0 -8
- data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.md +0 -21
- data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.yml +0 -24
- data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.md +0 -18
- data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.yml +0 -24
- data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.md +0 -23
- data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.yml +0 -23
- data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.md +0 -17
- data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.yml +0 -22
- data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.md +0 -18
- data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.yml +0 -22
- data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.md +0 -15
- data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.yml +0 -22
- data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.md +0 -9
- data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.yml +0 -24
- data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.md +0 -13
- data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.yml +0 -24
- data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.md +0 -25
- data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.yml +0 -24
- data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.md +0 -26
- data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.yml +0 -24
- data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.md +0 -17
- data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.yml +0 -22
- data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.md +0 -14
- data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.yml +0 -21
- data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.md +0 -13
- data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.yml +0 -23
- data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.md +0 -22
- data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.yml +0 -23
- data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.md +0 -39
- data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.yml +0 -8
- data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.md +0 -23
- data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.yml +0 -24
- data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.md +0 -15
- data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.yml +0 -22
- data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.md +0 -12
- data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.yml +0 -23
- data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.md +0 -15
- data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.yml +0 -8
- data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.md +0 -28
- data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.yml +0 -8
- data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.md +0 -19
- data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.yml +0 -22
- data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.md +0 -19
- data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.yml +0 -21
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.md +0 -25
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.yml +0 -22
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.emb +0 -8
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.md +0 -30
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.yml +0 -22
- data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.md +0 -21
- data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.yml +0 -22
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.md +0 -37
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.yml +0 -8
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.md +0 -24
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.yml +0 -24
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.md +0 -27
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.yml +0 -24
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.md +0 -26
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.yml +0 -23
- data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.md +0 -37
- data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.yml +0 -25
- data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.md +0 -21
- data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.yml +0 -23
- data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.md +0 -25
- data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.yml +0 -8
- data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.emb +0 -0
- data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.md +0 -11
- data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.yml +0 -21
- data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.emb +0 -0
- data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.md +0 -19
- data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.yml +0 -21
- data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.emb +0 -0
- data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.md +0 -26
- data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.yml +0 -20
- data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.emb +0 -0
- data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.md +0 -23
- data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.yml +0 -21
- data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.emb +0 -0
- data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.md +0 -19
- data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.yml +0 -22
- data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.emb +0 -0
- data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.md +0 -21
- data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.yml +0 -22
- data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.emb +0 -0
- data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.md +0 -28
- data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.yml +0 -23
- data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.emb +0 -0
- data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.md +0 -21
- data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.yml +0 -20
- data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.emb +0 -0
- data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.md +0 -21
- data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.yml +0 -25
- data/memory/corpus-self-reflection/experience/local-change-without-continuity.emb +0 -0
- data/memory/corpus-self-reflection/experience/local-change-without-continuity.md +0 -30
- data/memory/corpus-self-reflection/experience/local-change-without-continuity.yml +0 -22
- data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.emb +0 -0
- data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.md +0 -21
- data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.yml +0 -20
- data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.emb +0 -0
- data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.md +0 -25
- data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.yml +0 -23
- data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.emb +0 -0
- data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.md +0 -32
- data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.yml +0 -22
- data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.emb +0 -0
- data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.md +0 -21
- data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.yml +0 -21
- data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.emb +0 -0
- data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.md +0 -21
- data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.yml +0 -21
- data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.emb +0 -0
- data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.md +0 -22
- data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.yml +0 -22
- data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.emb +0 -0
- data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.md +0 -28
- data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.yml +0 -24
- data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.emb +0 -0
- data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.md +0 -17
- data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.yml +0 -20
- data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.emb +0 -0
- data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.md +0 -9
- data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.yml +0 -22
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
# MemoryDefrag Tool - Complete Guide
|
|
2
|
+
|
|
3
|
+
**Comprehensive documentation for implementing MemoryDefrag support in custom storage adapters.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [What is MemoryDefrag?](#1-what-is-memorydefrag)
|
|
10
|
+
2. [Interface Requirements](#2-interface-requirements)
|
|
11
|
+
3. [How MemoryDefrag Works](#3-how-memorydefrag-works)
|
|
12
|
+
4. [Usage Patterns](#4-usage-patterns)
|
|
13
|
+
5. [Adapter Compatibility](#5-adapter-compatibility)
|
|
14
|
+
6. [PostgreSQL Implementation](#6-postgresql-implementation)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. What is MemoryDefrag?
|
|
19
|
+
|
|
20
|
+
### Overview
|
|
21
|
+
|
|
22
|
+
**MemoryDefrag** is a comprehensive memory optimization tool that analyzes and maintains the quality of agent memory storage. It's like a **defragmentation and optimization utility** for AI memory.
|
|
23
|
+
|
|
24
|
+
### Purpose
|
|
25
|
+
|
|
26
|
+
MemoryDefrag solves several critical problems:
|
|
27
|
+
|
|
28
|
+
1. **Duplicate Detection**: Finds similar/duplicate entries that waste space
|
|
29
|
+
2. **Quality Assessment**: Identifies entries with poor metadata
|
|
30
|
+
3. **Archival Management**: Finds old, unused entries that could be deleted
|
|
31
|
+
4. **Knowledge Graph Building**: Discovers and creates relationships between entries
|
|
32
|
+
5. **Storage Optimization**: Merges duplicates, cleans up stubs, compacts low-value entries
|
|
33
|
+
|
|
34
|
+
### When to Use
|
|
35
|
+
|
|
36
|
+
- **Every 15-20 new entries**: Light analysis
|
|
37
|
+
- **Every 50 entries**: Medium check (analyze + find issues)
|
|
38
|
+
- **Every 100 entries**: Heavy maintenance (full optimization)
|
|
39
|
+
- **When searches return irrelevant results**: Quality degradation
|
|
40
|
+
- **Before major tasks**: Check memory health
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 2. Interface Requirements
|
|
45
|
+
|
|
46
|
+
### Required Adapter Methods
|
|
47
|
+
|
|
48
|
+
MemoryDefrag uses the **standard adapter interface**. No special methods required!
|
|
49
|
+
|
|
50
|
+
#### Core Methods Used
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# 1. LIST - Get all entries
|
|
54
|
+
@adapter.list(prefix: nil)
|
|
55
|
+
# Returns: Array<Hash> with { path:, title:, size:, updated_at: }
|
|
56
|
+
|
|
57
|
+
# 2. READ_ENTRY - Get full entry with metadata
|
|
58
|
+
@adapter.read_entry(file_path: "concept/ruby/classes.md")
|
|
59
|
+
# Returns: Core::Entry with content, title, metadata, embedding, size, updated_at
|
|
60
|
+
|
|
61
|
+
# 3. WRITE - Update entries (for merging, linking)
|
|
62
|
+
@adapter.write(
|
|
63
|
+
file_path: "concept/ruby/classes.md",
|
|
64
|
+
content: "...",
|
|
65
|
+
title: "...",
|
|
66
|
+
embedding: [...],
|
|
67
|
+
metadata: { "type" => "concept", "related" => [...], ... }
|
|
68
|
+
)
|
|
69
|
+
# Returns: Core::Entry
|
|
70
|
+
|
|
71
|
+
# 4. DELETE - Remove entries (for cleanup, compact)
|
|
72
|
+
@adapter.delete(file_path: "concept/ruby/classes.md")
|
|
73
|
+
# Returns: void
|
|
74
|
+
|
|
75
|
+
# 5. TOTAL_SIZE - Get storage size
|
|
76
|
+
@adapter.total_size
|
|
77
|
+
# Returns: Integer (bytes)
|
|
78
|
+
|
|
79
|
+
# 6. ALL_ENTRIES - Get all entries with full content (for duplicate detection)
|
|
80
|
+
@adapter.all_entries
|
|
81
|
+
# Returns: Hash<String, Core::Entry>
|
|
82
|
+
# { "concept/ruby/classes.md" => Entry(...), ... }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Optional Method (Highly Recommended)
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
# ALL_ENTRIES - Required for duplicate detection and relationship discovery
|
|
89
|
+
def all_entries
|
|
90
|
+
entries = {}
|
|
91
|
+
|
|
92
|
+
@adapter.list.each do |entry_info|
|
|
93
|
+
entries[entry_info[:path]] = @adapter.read_entry(file_path: entry_info[:path])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
entries
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Why it's needed:**
|
|
101
|
+
- Used by `find_duplicates` to compare all entry pairs
|
|
102
|
+
- Used by `find_related` to build knowledge graph
|
|
103
|
+
- Used by `link_related_active` to update relationship metadata
|
|
104
|
+
- Without it: Defrag operations limited to analysis only
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 3. How MemoryDefrag Works
|
|
109
|
+
|
|
110
|
+
### Architecture
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
┌─────────────────────────────────────────────────────────┐
|
|
114
|
+
│ MemoryDefrag Tool (RubyLLM::Tool) │
|
|
115
|
+
│ • Agent-facing interface │
|
|
116
|
+
│ • Parameter validation │
|
|
117
|
+
│ • Action routing │
|
|
118
|
+
└────────────────┬────────────────────────────────────────┘
|
|
119
|
+
│
|
|
120
|
+
│ Delegates to
|
|
121
|
+
│
|
|
122
|
+
▼
|
|
123
|
+
┌─────────────────────────────────────────────────────────┐
|
|
124
|
+
│ Optimization::Defragmenter │
|
|
125
|
+
│ • Core optimization logic │
|
|
126
|
+
│ • Duplicate detection (Jaccard + cosine similarity) │
|
|
127
|
+
│ • Quality scoring (metadata-based) │
|
|
128
|
+
│ • Merge strategies (keep_newer, keep_larger, combine) │
|
|
129
|
+
│ • Link creation (bidirectional) │
|
|
130
|
+
└────────────────┬────────────────────────────────────────┘
|
|
131
|
+
│
|
|
132
|
+
│ Uses
|
|
133
|
+
│
|
|
134
|
+
▼
|
|
135
|
+
┌─────────────────────────────────────────────────────────┐
|
|
136
|
+
│ Optimization::Analyzer │
|
|
137
|
+
│ • Health score calculation (0-100) │
|
|
138
|
+
│ • Coverage metrics (metadata, tags, links, embeddings) │
|
|
139
|
+
│ • Distribution analysis (by type, confidence) │
|
|
140
|
+
└─────────────────────────────────────────────────────────┘
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Operations
|
|
144
|
+
|
|
145
|
+
#### Read-Only Analysis (Safe)
|
|
146
|
+
|
|
147
|
+
**1. analyze** - Overall health report
|
|
148
|
+
```ruby
|
|
149
|
+
MemoryDefrag(action: "analyze")
|
|
150
|
+
```
|
|
151
|
+
- Calculates health score (0-100)
|
|
152
|
+
- Shows metadata coverage, embedding coverage, tag usage
|
|
153
|
+
- Breaks down by type (concept, fact, skill, experience)
|
|
154
|
+
- Breaks down by confidence (high, medium, low)
|
|
155
|
+
|
|
156
|
+
**2. find_duplicates** - Identify similar entries
|
|
157
|
+
```ruby
|
|
158
|
+
MemoryDefrag(action: "find_duplicates", similarity_threshold: 0.85)
|
|
159
|
+
```
|
|
160
|
+
- Uses **Jaccard similarity** (text-based) + **Cosine similarity** (embedding-based)
|
|
161
|
+
- Takes highest similarity score
|
|
162
|
+
- Default threshold: 85%
|
|
163
|
+
- Returns pairs with similarity scores
|
|
164
|
+
|
|
165
|
+
**3. find_low_quality** - Find entries with poor metadata
|
|
166
|
+
```ruby
|
|
167
|
+
MemoryDefrag(action: "find_low_quality", confidence_filter: "low")
|
|
168
|
+
```
|
|
169
|
+
- Scores quality 0-100 based on metadata completeness
|
|
170
|
+
- Flags: no metadata, low confidence, no tags, no links, not embedded
|
|
171
|
+
- Helps identify entries to improve or delete
|
|
172
|
+
|
|
173
|
+
**4. find_archival_candidates** - Find old, unused entries
|
|
174
|
+
```ruby
|
|
175
|
+
MemoryDefrag(action: "find_archival_candidates", age_days: 90)
|
|
176
|
+
```
|
|
177
|
+
- Lists entries not updated in N days
|
|
178
|
+
- Candidates for deletion or archival
|
|
179
|
+
- Default: 90 days
|
|
180
|
+
|
|
181
|
+
**5. find_related** - Discover entries that should be linked
|
|
182
|
+
```ruby
|
|
183
|
+
MemoryDefrag(action: "find_related", min_similarity: 0.60, max_similarity: 0.85)
|
|
184
|
+
```
|
|
185
|
+
- Finds pairs with 60-85% semantic similarity (related but not duplicates)
|
|
186
|
+
- Uses **pure semantic similarity** (no keyword boost)
|
|
187
|
+
- Shows current linking status (unlinked, one-way, bidirectional)
|
|
188
|
+
|
|
189
|
+
#### Active Optimization (Modifies Memory)
|
|
190
|
+
|
|
191
|
+
**CRITICAL:** All active operations default to `dry_run=true` for safety!
|
|
192
|
+
|
|
193
|
+
**6. link_related** - Create bidirectional links
|
|
194
|
+
```ruby
|
|
195
|
+
# Preview first
|
|
196
|
+
MemoryDefrag(action: "link_related", min_similarity: 0.60, max_similarity: 0.85, dry_run: true)
|
|
197
|
+
|
|
198
|
+
# Execute after review
|
|
199
|
+
MemoryDefrag(action: "link_related", dry_run: false)
|
|
200
|
+
```
|
|
201
|
+
- Finds related entries (60-85% similarity)
|
|
202
|
+
- Updates `related` metadata arrays
|
|
203
|
+
- Creates bidirectional links (`memory://path1` ↔ `memory://path2`)
|
|
204
|
+
- Skips already-linked pairs
|
|
205
|
+
|
|
206
|
+
**7. merge_duplicates** - Merge similar entries
|
|
207
|
+
```ruby
|
|
208
|
+
# Preview first
|
|
209
|
+
MemoryDefrag(action: "merge_duplicates", similarity_threshold: 0.85, dry_run: true)
|
|
210
|
+
|
|
211
|
+
# Execute
|
|
212
|
+
MemoryDefrag(action: "merge_duplicates", merge_strategy: "keep_newer", dry_run: false)
|
|
213
|
+
```
|
|
214
|
+
- Merges duplicate entries
|
|
215
|
+
- Strategies:
|
|
216
|
+
- `keep_newer`: Keep most recently updated
|
|
217
|
+
- `keep_larger`: Keep larger content
|
|
218
|
+
- `combine`: Merge both contents
|
|
219
|
+
- Creates **stub files** with auto-redirect
|
|
220
|
+
|
|
221
|
+
**8. cleanup_stubs** - Remove old redirect stubs
|
|
222
|
+
```ruby
|
|
223
|
+
MemoryDefrag(action: "cleanup_stubs", age_days: 30, max_hits: 3, dry_run: false)
|
|
224
|
+
```
|
|
225
|
+
- Deletes stub files that are old AND rarely accessed
|
|
226
|
+
- Default: 90 days old, max 10 hits
|
|
227
|
+
- Keeps frequently-accessed stubs
|
|
228
|
+
|
|
229
|
+
**9. compact** - Delete low-value entries
|
|
230
|
+
```ruby
|
|
231
|
+
MemoryDefrag(action: "compact", min_quality_score: 20, min_age_days: 30, max_hits: 0, dry_run: false)
|
|
232
|
+
```
|
|
233
|
+
- **PERMANENTLY deletes** entries matching ALL criteria:
|
|
234
|
+
- Quality score < threshold
|
|
235
|
+
- Age > min_age_days
|
|
236
|
+
- Hits <= max_hits
|
|
237
|
+
- Frees up storage space
|
|
238
|
+
|
|
239
|
+
**10. full** - Complete optimization workflow
|
|
240
|
+
```ruby
|
|
241
|
+
# Preview
|
|
242
|
+
MemoryDefrag(action: "full", dry_run: true)
|
|
243
|
+
|
|
244
|
+
# Execute
|
|
245
|
+
MemoryDefrag(action: "full", dry_run: false)
|
|
246
|
+
```
|
|
247
|
+
- Runs: `merge_duplicates` → `cleanup_stubs` → `compact`
|
|
248
|
+
- Shows health score improvement
|
|
249
|
+
- **ALWAYS preview first!**
|
|
250
|
+
- Does NOT include `link_related` (run separately)
|
|
251
|
+
|
|
252
|
+
### Similarity Algorithms
|
|
253
|
+
|
|
254
|
+
#### Jaccard Similarity (Text-based)
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# lib/swarm_memory/search/text_similarity.rb
|
|
258
|
+
def self.jaccard(text1, text2)
|
|
259
|
+
# Tokenize into words
|
|
260
|
+
words1 = text1.downcase.scan(/\w+/)
|
|
261
|
+
words2 = text2.downcase.scan(/\w+/)
|
|
262
|
+
|
|
263
|
+
# Calculate Jaccard coefficient
|
|
264
|
+
set1 = Set.new(words1)
|
|
265
|
+
set2 = Set.new(words2)
|
|
266
|
+
|
|
267
|
+
intersection = (set1 & set2).size
|
|
268
|
+
union = (set1 | set2).size
|
|
269
|
+
|
|
270
|
+
return 0.0 if union.zero?
|
|
271
|
+
intersection.to_f / union
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Use case:** Fast, always available (no embeddings needed)
|
|
276
|
+
|
|
277
|
+
#### Cosine Similarity (Embedding-based)
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
# lib/swarm_memory/search/text_similarity.rb
|
|
281
|
+
def self.cosine(vector1, vector2)
|
|
282
|
+
dot_product = vector1.zip(vector2).sum { |a, b| a * b }
|
|
283
|
+
magnitude1 = Math.sqrt(vector1.sum { |x| x**2 })
|
|
284
|
+
magnitude2 = Math.sqrt(vector2.sum { |x| x**2 })
|
|
285
|
+
|
|
286
|
+
return 0.0 if magnitude1.zero? || magnitude2.zero?
|
|
287
|
+
dot_product / (magnitude1 * magnitude2)
|
|
288
|
+
end
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Use case:** More accurate semantic similarity, requires embeddings
|
|
292
|
+
|
|
293
|
+
#### Hybrid Approach
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
# lib/swarm_memory/optimization/defragmenter.rb:68
|
|
297
|
+
text_sim = Search::TextSimilarity.jaccard(entry1.content, entry2.content)
|
|
298
|
+
semantic_sim = if entry1.embedded? && entry2.embedded?
|
|
299
|
+
Search::TextSimilarity.cosine(entry1.embedding, entry2.embedding)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Use highest similarity score
|
|
303
|
+
similarity = [text_sim, semantic_sim].compact.max
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Result:** Best of both worlds - works without embeddings, more accurate with them
|
|
307
|
+
|
|
308
|
+
### Quality Scoring
|
|
309
|
+
|
|
310
|
+
Metadata-based quality score (0-100):
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
def calculate_quality_from_metadata(metadata)
|
|
314
|
+
score = 0
|
|
315
|
+
|
|
316
|
+
score += 20 if metadata["type"] # Has type
|
|
317
|
+
score += 20 if metadata["confidence"] # Has confidence
|
|
318
|
+
score += 15 unless metadata["tags"].empty? # Has tags
|
|
319
|
+
score += 15 unless metadata["related"].empty? # Has links
|
|
320
|
+
score += 10 if metadata["domain"] # Has domain
|
|
321
|
+
score += 10 if metadata["last_verified"] # Has verification
|
|
322
|
+
score += 10 if metadata["confidence"] == "high" # High confidence
|
|
323
|
+
|
|
324
|
+
score
|
|
325
|
+
end
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Health Score Calculation
|
|
329
|
+
|
|
330
|
+
Overall memory health (0-100):
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
score = 0
|
|
334
|
+
|
|
335
|
+
# Metadata coverage (30 points)
|
|
336
|
+
score += 30 if frontmatter_pct > 80
|
|
337
|
+
score += 20 if frontmatter_pct > 50
|
|
338
|
+
|
|
339
|
+
# Tags coverage (20 points)
|
|
340
|
+
score += 20 if tags_pct > 60
|
|
341
|
+
score += 10 if tags_pct > 30
|
|
342
|
+
|
|
343
|
+
# Links coverage (20 points)
|
|
344
|
+
score += 20 if links_pct > 40
|
|
345
|
+
score += 10 if links_pct > 20
|
|
346
|
+
|
|
347
|
+
# Embedding coverage (15 points)
|
|
348
|
+
score += 15 if embedding_pct > 80
|
|
349
|
+
score += 8 if embedding_pct > 50
|
|
350
|
+
|
|
351
|
+
# High confidence ratio (15 points)
|
|
352
|
+
score += 15 if high_confidence_pct > 50
|
|
353
|
+
score += 8 if high_confidence_pct > 25
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Interpretation:**
|
|
357
|
+
- **80-100**: Excellent - well-organized
|
|
358
|
+
- **60-79**: Good - decent but improvable
|
|
359
|
+
- **40-59**: Fair - needs defrag
|
|
360
|
+
- **20-39**: Poor - significant cleanup needed
|
|
361
|
+
- **0-19**: Critical - immediate attention required
|
|
362
|
+
|
|
363
|
+
### Stub Files
|
|
364
|
+
|
|
365
|
+
When entries are merged or moved, MemoryDefrag creates **stub files** that automatically redirect:
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
def create_stub(from:, to:, reason:)
|
|
369
|
+
stub_content = "# #{reason} → #{to}\n\nThis entry was #{reason} into #{to}."
|
|
370
|
+
|
|
371
|
+
@adapter.write(
|
|
372
|
+
file_path: from,
|
|
373
|
+
content: stub_content,
|
|
374
|
+
title: "[STUB] → #{to}",
|
|
375
|
+
metadata: {
|
|
376
|
+
"stub" => true,
|
|
377
|
+
"redirect_to" => to,
|
|
378
|
+
"reason" => reason # "merged" or "moved"
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
end
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Detection:**
|
|
385
|
+
```ruby
|
|
386
|
+
# Storage#read_entry automatically follows redirects
|
|
387
|
+
if entry.metadata["stub"] == true
|
|
388
|
+
redirect_target = entry.metadata["redirect_to"]
|
|
389
|
+
return read_entry(file_path: redirect_target, visited: visited + [normalized_path])
|
|
390
|
+
end
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Cleanup:**
|
|
394
|
+
- Stubs older than N days AND rarely accessed (< N hits) can be cleaned up
|
|
395
|
+
- Default: 90 days old, max 10 hits
|
|
396
|
+
- Keeps frequently-accessed stubs even if old
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## 4. Usage Patterns
|
|
401
|
+
|
|
402
|
+
### Agent Workflow
|
|
403
|
+
|
|
404
|
+
```ruby
|
|
405
|
+
# 1. Check health
|
|
406
|
+
MemoryDefrag(action: "analyze")
|
|
407
|
+
# Output: Health score 65/100
|
|
408
|
+
|
|
409
|
+
# 2. Find issues
|
|
410
|
+
MemoryDefrag(action: "find_duplicates")
|
|
411
|
+
# Output: Found 3 duplicate pairs
|
|
412
|
+
|
|
413
|
+
MemoryDefrag(action: "find_low_quality")
|
|
414
|
+
# Output: Found 5 entries with quality issues
|
|
415
|
+
|
|
416
|
+
# 3. Preview fixes
|
|
417
|
+
MemoryDefrag(action: "merge_duplicates", dry_run: true)
|
|
418
|
+
# Output: Would merge 3 pairs
|
|
419
|
+
|
|
420
|
+
# 4. Execute if preview looks good
|
|
421
|
+
MemoryDefrag(action: "merge_duplicates", dry_run: false)
|
|
422
|
+
# Output: Merged 3 pairs, freed 12.5KB
|
|
423
|
+
|
|
424
|
+
# 5. Verify improvement
|
|
425
|
+
MemoryDefrag(action: "analyze")
|
|
426
|
+
# Output: Health score 75/100 (+10)
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Maintenance Schedule
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
# Light check (every 15-20 new entries)
|
|
433
|
+
MemoryDefrag(action: "analyze")
|
|
434
|
+
|
|
435
|
+
# Medium check (every 50 entries)
|
|
436
|
+
MemoryDefrag(action: "analyze")
|
|
437
|
+
MemoryDefrag(action: "find_duplicates")
|
|
438
|
+
MemoryDefrag(action: "find_low_quality")
|
|
439
|
+
|
|
440
|
+
# Heavy maintenance (every 100 entries)
|
|
441
|
+
MemoryDefrag(action: "full", dry_run: true) # Preview
|
|
442
|
+
MemoryDefrag(action: "full", dry_run: false) # Execute
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Tool Invocation
|
|
446
|
+
|
|
447
|
+
MemoryDefrag is available as a **memory tool** when memory is enabled:
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
# Automatically added to agents with memory configured
|
|
451
|
+
agent :assistant do
|
|
452
|
+
memory do
|
|
453
|
+
directory ".swarm/assistant-memory"
|
|
454
|
+
mode :researcher # MemoryDefrag included
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Tool modes:
|
|
459
|
+
# - :assistant - MemoryDefrag NOT included (too advanced)
|
|
460
|
+
# - :researcher - MemoryDefrag included (all optimization tools)
|
|
461
|
+
# - :retrieval - MemoryDefrag NOT included (read-only mode)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## 5. Adapter Compatibility
|
|
467
|
+
|
|
468
|
+
### Minimum Requirements
|
|
469
|
+
|
|
470
|
+
To support MemoryDefrag **analysis operations** (read-only):
|
|
471
|
+
|
|
472
|
+
✅ **REQUIRED:**
|
|
473
|
+
```ruby
|
|
474
|
+
def list(prefix: nil)
|
|
475
|
+
# Return array of entry metadata
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def read_entry(file_path:)
|
|
479
|
+
# Return Core::Entry with metadata
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def total_size
|
|
483
|
+
# Return total storage size in bytes
|
|
484
|
+
end
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
✅ **OPTIONAL but RECOMMENDED:**
|
|
488
|
+
```ruby
|
|
489
|
+
def all_entries
|
|
490
|
+
# Return hash of path => Entry for duplicate detection
|
|
491
|
+
end
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Full Compatibility
|
|
495
|
+
|
|
496
|
+
To support MemoryDefrag **active operations** (modification):
|
|
497
|
+
|
|
498
|
+
✅ **REQUIRED:**
|
|
499
|
+
```ruby
|
|
500
|
+
def write(file_path:, content:, title:, embedding: nil, metadata: nil)
|
|
501
|
+
# Write/update entry
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def delete(file_path:)
|
|
505
|
+
# Delete entry permanently
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def all_entries
|
|
509
|
+
# Required for merge and link operations
|
|
510
|
+
end
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Implementation Checklist
|
|
514
|
+
|
|
515
|
+
- [ ] `list(prefix:)` returns `Array<Hash>` with `:path`, `:title`, `:size`, `:updated_at`
|
|
516
|
+
- [ ] `read_entry(file_path:)` returns `Core::Entry` with all 6 fields
|
|
517
|
+
- [ ] `write(...)` updates existing entries (upsert behavior)
|
|
518
|
+
- [ ] `delete(file_path:)` permanently removes entries
|
|
519
|
+
- [ ] `total_size` returns current storage size in bytes
|
|
520
|
+
- [ ] `all_entries` returns `Hash<String, Core::Entry>` (recommended)
|
|
521
|
+
- [ ] Entry metadata uses **string keys** (not symbols)
|
|
522
|
+
- [ ] Entry metadata includes: `type`, `confidence`, `tags`, `related`, `domain`
|
|
523
|
+
- [ ] Stub detection: check `metadata["stub"] == true` and `metadata["redirect_to"]`
|
|
524
|
+
|
|
525
|
+
### Metadata Requirements
|
|
526
|
+
|
|
527
|
+
MemoryDefrag expects entries to have metadata with **string keys**:
|
|
528
|
+
|
|
529
|
+
```ruby
|
|
530
|
+
metadata = {
|
|
531
|
+
"type" => "concept", # Required for categorization
|
|
532
|
+
"confidence" => "high", # Required for quality scoring
|
|
533
|
+
"tags" => ["ruby", "oop"], # Required for quality scoring
|
|
534
|
+
"related" => [ # Optional, used by link operations
|
|
535
|
+
"memory://concept/ruby/modules.md"
|
|
536
|
+
],
|
|
537
|
+
"domain" => "programming/ruby", # Optional, used by quality scoring
|
|
538
|
+
"last_verified" => "2024-01-15", # Optional, used by quality scoring
|
|
539
|
+
"hits" => 5, # Optional, tracks read count
|
|
540
|
+
"stub" => false, # Internal, set by merge operations
|
|
541
|
+
"redirect_to" => nil # Internal, set by merge operations
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**IMPORTANT:** Keys must be **strings**, not symbols!
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## 6. PostgreSQL Implementation
|
|
550
|
+
|
|
551
|
+
### Database Requirements
|
|
552
|
+
|
|
553
|
+
Your PostgreSQL schema should support:
|
|
554
|
+
|
|
555
|
+
```sql
|
|
556
|
+
CREATE TABLE agent_memories (
|
|
557
|
+
id BIGSERIAL PRIMARY KEY,
|
|
558
|
+
file_path VARCHAR(500) NOT NULL UNIQUE,
|
|
559
|
+
content TEXT NOT NULL,
|
|
560
|
+
title VARCHAR(200) NOT NULL,
|
|
561
|
+
embedding vector(384),
|
|
562
|
+
metadata JSONB NOT NULL, -- ← CRITICAL for MemoryDefrag
|
|
563
|
+
size INTEGER NOT NULL,
|
|
564
|
+
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
565
|
+
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
-- Indexes for performance
|
|
569
|
+
CREATE INDEX idx_file_path ON agent_memories(file_path);
|
|
570
|
+
CREATE INDEX idx_updated_at ON agent_memories(updated_at DESC);
|
|
571
|
+
|
|
572
|
+
-- GIN index for metadata queries
|
|
573
|
+
CREATE INDEX idx_metadata ON agent_memories USING GIN(metadata);
|
|
574
|
+
CREATE INDEX idx_metadata_type ON agent_memories((metadata->>'type'));
|
|
575
|
+
CREATE INDEX idx_metadata_stub ON agent_memories((metadata->>'stub'));
|
|
576
|
+
|
|
577
|
+
-- Vector index for semantic duplicate detection
|
|
578
|
+
CREATE INDEX idx_embedding ON agent_memories
|
|
579
|
+
USING hnsw (embedding vector_cosine_ops);
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Adapter Implementation
|
|
583
|
+
|
|
584
|
+
```ruby
|
|
585
|
+
class PostgresAdapter < SwarmMemory::Adapters::Base
|
|
586
|
+
# ... standard methods ...
|
|
587
|
+
|
|
588
|
+
# ALL_ENTRIES - Required for MemoryDefrag duplicate/link operations
|
|
589
|
+
def all_entries
|
|
590
|
+
result = @connection.exec("SELECT * FROM agent_memories ORDER BY file_path")
|
|
591
|
+
|
|
592
|
+
entries = {}
|
|
593
|
+
result.each do |row|
|
|
594
|
+
# Parse embedding
|
|
595
|
+
embedding = if row['embedding']
|
|
596
|
+
row['embedding'].tr('[]', '').split(',').map(&:to_f)
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# Parse metadata (CRITICAL: ensure string keys)
|
|
600
|
+
metadata = if row['metadata']
|
|
601
|
+
JSON.parse(row['metadata']) # JSON.parse returns string keys ✅
|
|
602
|
+
else
|
|
603
|
+
{}
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
entries[row['file_path']] = SwarmMemory::Core::Entry.new(
|
|
607
|
+
content: row['content'],
|
|
608
|
+
title: row['title'],
|
|
609
|
+
updated_at: Time.parse(row['updated_at']),
|
|
610
|
+
size: row['size'].to_i,
|
|
611
|
+
embedding: embedding,
|
|
612
|
+
metadata: metadata # String keys from JSON.parse
|
|
613
|
+
)
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
entries
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# LIST - Used by health analysis
|
|
620
|
+
def list(prefix: nil)
|
|
621
|
+
sql = "SELECT file_path, title, size, updated_at FROM agent_memories"
|
|
622
|
+
params = []
|
|
623
|
+
|
|
624
|
+
if prefix
|
|
625
|
+
sql += " WHERE file_path LIKE $1"
|
|
626
|
+
params << "#{prefix}%"
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
sql += " ORDER BY file_path"
|
|
630
|
+
result = @connection.exec_params(sql, params)
|
|
631
|
+
|
|
632
|
+
result.map do |row|
|
|
633
|
+
{
|
|
634
|
+
path: row['file_path'],
|
|
635
|
+
title: row['title'],
|
|
636
|
+
size: row['size'].to_i,
|
|
637
|
+
updated_at: Time.parse(row['updated_at'])
|
|
638
|
+
}
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# READ_ENTRY - Used by quality analysis
|
|
643
|
+
def read_entry(file_path:)
|
|
644
|
+
result = @connection.exec_params(
|
|
645
|
+
"SELECT * FROM agent_memories WHERE file_path = $1",
|
|
646
|
+
[file_path]
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
raise ArgumentError, "memory://#{file_path} not found" if result.ntuples == 0
|
|
650
|
+
|
|
651
|
+
row = result[0]
|
|
652
|
+
|
|
653
|
+
# Parse embedding
|
|
654
|
+
embedding = row['embedding'] ? row['embedding'].tr('[]', '').split(',').map(&:to_f) : nil
|
|
655
|
+
|
|
656
|
+
# Parse metadata (string keys)
|
|
657
|
+
metadata = row['metadata'] ? JSON.parse(row['metadata']) : {}
|
|
658
|
+
|
|
659
|
+
SwarmMemory::Core::Entry.new(
|
|
660
|
+
content: row['content'],
|
|
661
|
+
title: row['title'],
|
|
662
|
+
updated_at: Time.parse(row['updated_at']),
|
|
663
|
+
size: row['size'].to_i,
|
|
664
|
+
embedding: embedding,
|
|
665
|
+
metadata: metadata # String keys ✅
|
|
666
|
+
)
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
# WRITE - Used by merge and link operations
|
|
670
|
+
def write(file_path:, content:, title:, embedding: nil, metadata: nil)
|
|
671
|
+
# Validate size limits
|
|
672
|
+
content_size = content.bytesize
|
|
673
|
+
raise ArgumentError, "Content exceeds maximum size" if content_size > MAX_ENTRY_SIZE
|
|
674
|
+
|
|
675
|
+
# Prepare embedding for pgvector
|
|
676
|
+
embedding_sql = embedding ? "[#{embedding.join(',')}]" : "NULL"
|
|
677
|
+
|
|
678
|
+
# Ensure metadata has string keys (defensive)
|
|
679
|
+
metadata_hash = metadata ? Utils.stringify_keys(metadata) : {}
|
|
680
|
+
|
|
681
|
+
# Upsert
|
|
682
|
+
result = @connection.exec_params(
|
|
683
|
+
<<~SQL,
|
|
684
|
+
INSERT INTO agent_memories
|
|
685
|
+
(file_path, content, title, embedding, metadata, size)
|
|
686
|
+
VALUES ($1, $2, $3, $4::vector, $5, $6)
|
|
687
|
+
ON CONFLICT (file_path) DO UPDATE SET
|
|
688
|
+
content = EXCLUDED.content,
|
|
689
|
+
title = EXCLUDED.title,
|
|
690
|
+
embedding = EXCLUDED.embedding,
|
|
691
|
+
metadata = EXCLUDED.metadata,
|
|
692
|
+
size = EXCLUDED.size,
|
|
693
|
+
updated_at = NOW()
|
|
694
|
+
RETURNING updated_at
|
|
695
|
+
SQL
|
|
696
|
+
[file_path, content, title, embedding_sql, metadata_hash.to_json, content_size]
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
SwarmMemory::Core::Entry.new(
|
|
700
|
+
content: content,
|
|
701
|
+
title: title,
|
|
702
|
+
updated_at: Time.parse(result[0]['updated_at']),
|
|
703
|
+
size: content_size,
|
|
704
|
+
embedding: embedding,
|
|
705
|
+
metadata: metadata_hash
|
|
706
|
+
)
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
# DELETE - Used by cleanup and compact operations
|
|
710
|
+
def delete(file_path:)
|
|
711
|
+
result = @connection.exec_params(
|
|
712
|
+
"DELETE FROM agent_memories WHERE file_path = $1 RETURNING size",
|
|
713
|
+
[file_path]
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
raise ArgumentError, "memory://#{file_path} not found" if result.ntuples == 0
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# TOTAL_SIZE - Used by health report
|
|
720
|
+
def total_size
|
|
721
|
+
result = @connection.exec_params(
|
|
722
|
+
"SELECT COALESCE(SUM(size), 0) as total FROM agent_memories",
|
|
723
|
+
[]
|
|
724
|
+
)
|
|
725
|
+
result[0]['total'].to_i
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Performance Optimization
|
|
731
|
+
|
|
732
|
+
For large memory stores (1000+ entries), optimize `all_entries`:
|
|
733
|
+
|
|
734
|
+
```ruby
|
|
735
|
+
def all_entries
|
|
736
|
+
# Use cursor for memory efficiency
|
|
737
|
+
entries = {}
|
|
738
|
+
|
|
739
|
+
@connection.exec("DECLARE entry_cursor CURSOR FOR SELECT * FROM agent_memories")
|
|
740
|
+
|
|
741
|
+
loop do
|
|
742
|
+
result = @connection.exec("FETCH 100 FROM entry_cursor")
|
|
743
|
+
break if result.ntuples == 0
|
|
744
|
+
|
|
745
|
+
result.each do |row|
|
|
746
|
+
# ... parse row ...
|
|
747
|
+
entries[row['file_path']] = entry
|
|
748
|
+
end
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
@connection.exec("CLOSE entry_cursor")
|
|
752
|
+
entries
|
|
753
|
+
end
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### Multi-Bank Support
|
|
757
|
+
|
|
758
|
+
For multi-bank adapters, filter by bank:
|
|
759
|
+
|
|
760
|
+
```ruby
|
|
761
|
+
def all_entries
|
|
762
|
+
# Only load entries for current/default bank
|
|
763
|
+
bank_filter = @current_bank || @default_bank
|
|
764
|
+
|
|
765
|
+
result = @connection.exec_params(
|
|
766
|
+
"SELECT * FROM agent_memories WHERE bank = $1",
|
|
767
|
+
[bank_filter]
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
# ... parse results ...
|
|
771
|
+
end
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
## Summary
|
|
777
|
+
|
|
778
|
+
### Key Takeaways
|
|
779
|
+
|
|
780
|
+
1. **MemoryDefrag is a maintenance tool** - analyzes and optimizes memory quality
|
|
781
|
+
2. **Uses standard adapter interface** - no special methods required (except `all_entries`)
|
|
782
|
+
3. **Safe by default** - active operations require `dry_run=false` to execute
|
|
783
|
+
4. **Metadata-based** - quality scoring, duplicate detection, relationship discovery
|
|
784
|
+
5. **Hybrid similarity** - Jaccard (text) + Cosine (embeddings) for best accuracy
|
|
785
|
+
6. **Stub redirects** - merged entries leave redirects for backward compatibility
|
|
786
|
+
7. **Health scoring** - 0-100 score based on metadata coverage and quality
|
|
787
|
+
|
|
788
|
+
### Adapter Checklist
|
|
789
|
+
|
|
790
|
+
To ensure full MemoryDefrag compatibility:
|
|
791
|
+
|
|
792
|
+
- [ ] Implement `all_entries()` returning `Hash<String, Core::Entry>`
|
|
793
|
+
- [ ] Ensure `list()` returns entries with `:updated_at`
|
|
794
|
+
- [ ] Ensure `read_entry()` includes metadata with **string keys**
|
|
795
|
+
- [ ] Ensure `write()` supports upsert (update existing entries)
|
|
796
|
+
- [ ] Ensure `delete()` permanently removes entries
|
|
797
|
+
- [ ] Ensure `total_size()` returns accurate byte count
|
|
798
|
+
- [ ] Store metadata with: `type`, `confidence`, `tags`, `related`, `domain`
|
|
799
|
+
- [ ] Support stub metadata: `stub`, `redirect_to`, `reason`
|
|
800
|
+
- [ ] Test all 10 MemoryDefrag actions (5 read-only + 5 active)
|
|
801
|
+
|
|
802
|
+
### PostgreSQL-Specific
|
|
803
|
+
|
|
804
|
+
- ✅ Use JSONB for metadata storage (efficient, indexable)
|
|
805
|
+
- ✅ Add GIN index on metadata for fast queries
|
|
806
|
+
- ✅ Use `JSON.parse()` for string keys (not `YAML.load`)
|
|
807
|
+
- ✅ Store embeddings as `vector(384)` for semantic duplicate detection
|
|
808
|
+
- ✅ Add HNSW index on embeddings for performance
|
|
809
|
+
- ✅ Use cursor for `all_entries` on large datasets (1000+ entries)
|
|
810
|
+
|
|
811
|
+
Your PostgreSQL adapters are **fully compatible** with MemoryDefrag if they implement the standard adapter interface correctly! 🎉
|