← Back to Plugins
Voice

Lily Memory

kevinodell By kevinodell 👁 294 views ▲ 0 votes

Persistent memory plugin for OpenClaw agents β€” FTS5 + vector search, auto-capture/recall, entity management

GitHub

Configuration Example

{
  "plugins": {
    "slots": {
      "memory": "lily-memory"
    },
    "entries": {
      "lily-memory": {
        "enabled": true,
        "config": {
          "dbPath": "~/.openclaw/memory/decisions.db",
          "autoRecall": true,
          "autoCapture": true,
          "maxRecallResults": 10,
          "maxCapturePerTurn": 5,
          "stuckDetection": true,
          "vectorSearch": true,
          "ollamaUrl": "http://localhost:11434",
          "embeddingModel": "nomic-embed-text",
          "consolidation": true,
          "vectorSimilarityThreshold": 0.5,
          "entities": []
        }
      }
    }
  }
}

README

# lily-memory

Persistent memory plugin for OpenClaw agents. Hybrid keyword + semantic search with auto-capture, stuck-detection, and zero npm dependencies.

## Features

- **SQLite FTS5 keyword search + Ollama vector cosine similarity** β€” Find memories by exact keywords and by meaning
- **Auto-capture** β€” Extract facts from conversations via regex patterns and entity validation
- **Auto-recall** β€” Inject relevant memories before each LLM turn
- **Stuck-detection** β€” Jaccard similarity on topic signatures with Reflexion-enhanced nudging
- **Memory consolidation** β€” Dedup on startup and refresh permanent memories on compaction
- **Compaction-aware** β€” Resets topic state and touches permanent facts when context compresses
- **Dynamic entity management** β€” Config-driven allowlist plus runtime tool to add entities
- **Graceful degradation** β€” Works without Ollama (keyword-only mode)
- **Zero npm dependencies** β€” sqlite3 CLI + native fetch

## Requirements

- Node.js 18+ (for native `fetch`)
- SQLite 3.33+ with FTS5 (ships with macOS; `apt install sqlite3` on Linux)
- Optional: Ollama with `nomic-embed-text` model for vector search

## Installation

```bash
mkdir -p ~/.openclaw/extensions/lily-memory
cp -r . ~/.openclaw/extensions/lily-memory/
```

## Configuration

Add this to your `~/.openclaw/openclaw.json`:

```json
{
  "plugins": {
    "slots": {
      "memory": "lily-memory"
    },
    "entries": {
      "lily-memory": {
        "enabled": true,
        "config": {
          "dbPath": "~/.openclaw/memory/decisions.db",
          "autoRecall": true,
          "autoCapture": true,
          "maxRecallResults": 10,
          "maxCapturePerTurn": 5,
          "stuckDetection": true,
          "vectorSearch": true,
          "ollamaUrl": "http://localhost:11434",
          "embeddingModel": "nomic-embed-text",
          "consolidation": true,
          "vectorSimilarityThreshold": 0.5,
          "entities": []
        }
      }
    }
  }
}
```

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `dbPath` | string | `~/.openclaw/memory/decisions.db` | Path to SQLite database (supports `~` for home) |
| `autoRecall` | boolean | `true` | Inject relevant memories before each LLM turn |
| `autoCapture` | boolean | `true` | Extract and store facts from conversation messages |
| `maxRecallResults` | number | `10` | Maximum memories to inject per turn (1-50) |
| `maxCapturePerTurn` | number | `5` | Maximum facts to extract per response (1-20) |
| `stuckDetection` | boolean | `true` | Detect topic repetition and inject nudge |
| `vectorSearch` | boolean | `true` | Enable semantic search via Ollama |
| `ollamaUrl` | string | `http://localhost:11434` | Ollama API endpoint |
| `embeddingModel` | string | `nomic-embed-text` | Ollama embedding model name |
| `consolidation` | boolean | `true` | Dedup memories on plugin startup |
| `vectorSimilarityThreshold` | number | `0.5` | Minimum cosine similarity (0-1) for results |
| `entities` | array | `[]` | Additional entity names to recognize |

## Entity Management

The plugin uses a three-layer entity system:

**Layer 1: Built-in defaults**
- System: `config`, `system`, `note`, `project`

**Layer 2: Config array**
- Any names in the `entities` array in `openclaw.json`
- Example: `"entities": ["kevin", "alice", "trading_system"]`

**Layer 3: Runtime tool**
- Call `memory_add_entity(name)` to dynamically add entities
- Names are stored in the database and persist across sessions

**Special case: PascalCase auto-accept**
- Multi-word PascalCase names (e.g., `TradingSystem`, `MemoryPlugin`) are always accepted
- Useful for project names and system identifiers

## Tools

### memory_search(query: string)
FTS5 keyword search across all facts.
- Extracts keywords from query
- Returns matching entities and facts
- Includes permanent facts first
- Deduplicated results

**Example:**
```
User: "What do I know about TypeScript preferences?"
β†’ Searches for keywords: type, script, preference
β†’ Returns facts containing those terms
```

### memory_entity(entity: string)
Look up all facts for a specific entity.
- Returns complete fact set for that entity
- Sorted by importance (permanent facts first)

**Example:**
```
User: "Show all facts about Kevin"
β†’ Returns all Kevin-related facts in database
```

### memory_store(entity: string, key: string, value: string)
Manually store a fact (bypasses auto-capture).
- TTL defaults to "stable" (90 days) unless explicitly set
- Importance set to 0.9
- Useful for user-provided context

**Example:**
```
User: "Remember: Kevin prefers async/await over promises"
β†’ Stores fact: Kevin.coding_style = "prefers async/await over promises"
```

### memory_semantic_search(query: string)
Vector similarity search via Ollama embeddings.
- Finds semantically related memories even if keywords don't match
- Returns results above similarity threshold
- Falls back to keyword search if Ollama unavailable

**Example:**
```
User: "What about strongly-typed languages?"
β†’ Even if database says "TypeScript" (not "strongly-typed"), vector search finds it
```

### memory_add_entity(name: string)
Add a new entity to the allowlist at runtime.
- Names are validated (2-60 chars, no URLs, no common English words)
- Persisted in database
- Returns success/failure with reason

**Example:**
```
User: "Start remembering facts about Alice"
β†’ memory_add_entity("alice") adds alice to allowlist
```

## Architecture

### Recall Flow (before_agent_start hook)

1. Extract keywords from user message
2. Run FTS5 keyword search against decisions table
3. If Ollama available: embed query, compute cosine similarity against all vectors
4. Merge results, deduplicate by decision ID
5. Format as structured context block
6. Inject into system message

### Capture Flow (agent_end hook)

1. Scan LLM response for factual patterns (`entity: key = value`)
2. Validate each match:
   - Entity passes allowlist?
   - Value >= 15 characters?
   - No noise characters (`?()""<>`)?
   - Not in blocklist?
3. Store valid facts to SQLite (UPSERT on entity+key)
4. Fire-and-forget: embed via Ollama asynchronously
5. Extract topic signature (top 5 content words, excluding stopwords)
6. Compare with previous 3 signatures using Jaccard similarity
7. If stuck (3+ consecutive >60% overlap): build Reflexion nudge for next turn

### Consolidation (startup)

1. Find duplicate (entity, fact_key) groups
2. Keep row with latest `updated_at`, delete rest
3. Boost survivor importance +0.05 (capped at 0.95)
4. Clean orphaned vectors

### Compaction Awareness

- `before_compaction`: Touch permanent memories to refresh timestamps
- `after_compaction`: Reset topic history (conversation effectively starts fresh)

## How It Works

Before each turn: extract keywords from message β†’ FTS5 + vector search β†’ merge β†’ inject context.

After each turn: regex scan for `entity: key = value` patterns β†’ validate against entity allowlist β†’ store β†’ async embed.

Stuck detection: track top 5 content words per response β†’ compare signatures with Jaccard similarity β†’ if 3+ consecutive >60% overlap, inject alternative topics from memory.

## Testing

```bash
node --test test/*.test.js
```

Expected: 120+ tests passing across all modules (sqlite, entities, extraction, capture, recall, embeddings, consolidation, stuck-detection).

Tests use:
- Temporary SQLite databases (cleanup after each test)
- Mocked Ollama responses
- Comprehensive edge case coverage
- Zero external dependencies

## License

MIT
voice

Comments

Sign in to leave a comment

Loading comments...