npm package: @shrimp-kit/memory
Description: > Three-layer memory system with TTL, garbage collection, deduplication, and promotion.
Latest version: 0.1.0

# @shrimp-kit/memory

> Three-layer memory system with TTL, garbage collection, deduplication, and promotion.

## Install

```bash
bun add @shrimp-kit/memory
```

## Architecture

The memory system uses three layers:

| Layer | Storage | Lifecycle | Description |
|-------|---------|-----------|-------------|
| **L1** | Config files | Permanent | Static configs (read-only, managed externally) |
| **L2** | Markdown | Permanent | Verified knowledge, promoted from L3 |
| **L3** | JSON index | TTL-based | Dynamic entries with auto-expiry and GC |

This package manages **L3** (dynamic memory). For L2 permanent memory, see `@shrimp-kit/companion`'s `PermanentMemory` class.

## Usage

### Basic Operations

```typescript
import { Logger } from "@shrimp-kit/core";
import { MemoryStore } from "@shrimp-kit/memory";

const logger = new Logger({ name: "agent", level: "info" });
const memory = new MemoryStore("./data/memory-index.json", logger);
await memory.load();

// Add entries
memory.add({
  type: "preference",     // context | preference | progress | relation | insight
  key: "timezone",
  value: "User prefers Asia/Taipei timezone",
  importance: 4,          // 1-5 (affects TTL)
});

memory.add({
  type: "context",
  key: "chat_session",
  value: "User mentioned they are working on a trading bot",
  importance: 3,
  keywords: ["trading", "bot", "project"],  // optional, auto-extracted if omitted
});

// Query by similarity
const results = memory.query("what timezone does the user prefer?", 3);
for (const { entry, score } of results) {
  console.log(`${score.toFixed(2)} — ${entry.content}`);
}

// Save to disk
await memory.save();
```

### TTL and Importance

Each memory type has a base TTL, modified by importance:

| Type | Base TTL | Imp 1 | Imp 3 | Imp 5 |
|------|----------|-------|-------|-------|
| context | 14 days | 7d | 14d | 28d |
| preference | 30 days | 15d | 30d | 60d |
| progress | 7 days | 3.5d | 7d | 14d |
| relation | 30 days | 15d | 30d | 60d |
| insight | 21 days | 10.5d | 21d | 42d |

Accessed entries get TTL extensions (+7 days per access, max 90 days total).

### Garbage Collection

```typescript
const result = memory.gc();
// → { expired: 3, merged: 1, kept: 42, promotable: ["id1", "id2"] }

// GC performs three phases:
// 1. Remove expired entries (with access-based TTL extension)
// 2. Dedup scan — merge entries with Jaccard similarity >= 0.55
// 3. Mark promotable — entries accessed >= 3 times
```

### Deduplication

When adding a new entry, the store checks for similar existing entries using Jaccard similarity (threshold: 0.55). If a match is found, the existing entry is updated instead of creating a duplicate.

```typescript
memory.add({ type: "context", key: "k1", value: "User likes TypeScript" });
memory.add({ type: "context", key: "k2", value: "User likes TypeScript a lot" });
// Only 1 entry exists — the second was merged into the first
```

### Promotion

Frequently accessed entries (3+ accesses) are flagged as promotable. Promoted entries are exempt from TTL expiration and GC.

```typescript
// Record access
memory.access("entry-id");

// Promote to permanent
memory.promote("entry-id");
```

### Other Operations

```typescript
// Get all entries (optionally filtered by type)
const all = memory.getEntries();
const prefs = memory.getEntries("preference");

// Get stats
const stats = memory.getStats();
// → { totalCreated: 50, totalPromoted: 3, totalExpired: 12, totalMerged: 5, lastGc: "..." }

// Remove entry
memory.remove("entry-id");

// Entry count
console.log(memory.size); // 42
```

## API Reference

### `MemoryStore`

| Method | Returns | Description |
|--------|---------|-------------|
| `load()` | `Promise<void>` | Load index from disk |
| `save()` | `Promise<void>` | Save index to disk |
| `add(options)` | `MemoryEntry` | Add entry (with dedup) |
| `query(text, limit?)` | `QueryResult[]` | Query by keyword similarity |
| `gc()` | `GcResult` | Run garbage collection |
| `access(id)` | `MemoryEntry \| undefined` | Record an access |
| `promote(id)` | `boolean` | Mark as promoted (permanent) |
| `getEntries(type?)` | `MemoryEntry[]` | Get all entries |
| `getStats()` | `MemoryStats` | Get statistics |
| `remove(id)` | `boolean` | Remove an entry |
| `size` | `number` | Entry count |

See [`src/index.ts`](./src/index.ts) for all exports.