Skill: imgolye/agent-memory-persistence (version 0.1.0)

--- name: agent-memory-persistence description: Provide long-term memory persistence for AI agents with SQLite-backed storage, structured metadata, vector embeddings, semantic retrieval, lifecycle management, and queries by user, session, and time. --- # Agent Memory Persistence Use this skill when an agent needs durable memory storage across sessions. ## What it provides - SQLite-backed persistence for text, metadata, and embedding vectors - CRUD operations for memory items - Semantic retrieval with cosine-similarity vector search - Memory lifecycle operations including expiration cleanup - Filters by user, session, type, and time window ## Project structure - `src/MemoryStore.ts`: low-level SQLite storage engine - `src/VectorIndex.ts`: vector similarity search over stored embeddings - `src/MemoryManager.ts`: high-level API used by agents - `src/types.ts`: shared TypeScript contracts ## Usage pattern 1. Create a `MemoryManager` with a SQLite path. 2. Write memories with `content`, optional `metadata`, and optional `embedding`. 3. Query memories by session/user or use `searchByVector()` for semantic lookup. 4. Periodically call `cleanupExpired()` to delete stale memories. ## Notes - Embeddings are stored as JSON arrays in SQLite. - Vector search is implemented in TypeScript using cosine similarity, which keeps deployment simple and avoids SQLite extensions. - If memory volume grows substantially, replace `VectorIndex` with an ANN index or SQLite vector extension while preserving the `MemoryManager` API.

path: README.md
content: # agent-memory-persistence

为 AI Agent 提供长期记忆存储能力，基于 TypeScript、Node.js 和 SQLite 实现。

## 功能

- 记忆存储：文本、结构化元数据、向量嵌入
- 向量检索：基于余弦相似度的语义搜索
- 记忆管理：增删改查、过期清理、统计
- 会话关联：按 `userId`、`sessionId`、时间范围查询

## 安装

```bash
npm install
```

## 开发

```bash
npm run typecheck
npm test
npm run build
```

## 快速使用

```ts
import { MemoryManager } from "agent-memory-persistence";

const manager = new MemoryManager({ dbPath: "./agent-memory.db" });

const memory = manager.create({
  userId: "user-123",
  sessionId: "session-456",
  type: "fact",
  content: "用户偏好简洁回答",
  metadata: { source: "conversation", confidence: 0.92 },
  embedding: [0.12, 0.44, 0.91],
});

const related = manager.searchByVector([0.1, 0.4, 0.9], {
  userId: "user-123",
  limit: 5,
});

const sessionMemories = manager.listBySession("session-456");

manager.cleanupExpired();
manager.close();
```

## API 概览

### `MemoryManager`

- `create(input)`: 创建记忆
- `get(id)`: 根据 ID 查询
- `update(id, input)`: 更新记忆
- `delete(id)`: 删除记忆
- `list(filter)`: 按条件批量查询
- `listByUser(userId, limit?)`: 查询用户关联记忆
- `listBySession(sessionId, limit?)`: 查询会话关联记忆
- `searchByVector(embedding, options?)`: 语义检索
- `cleanupExpired(referenceTime?)`: 清理过期记忆
- `stats()`: 返回统计信息
- `close()`: 关闭数据库连接

## 数据模型

每条记忆记录包含：

- `id`: UUID
- `userId`: 用户 ID
- `sessionId`: 会话 ID
- `type`: 记忆类型，默认 `note`
- `content`: 文本内容
- `metadata`: 任意 JSON 结构化数据
- `embedding`: `number[]`
- `createdAt` / `updatedAt`
- `expiresAt`

## 设计说明

- 持久层使用 `better-sqlite3`，适合本地 Agent 场景下的低延迟同步访问。
- 向量数据以 JSON 数组形式存入 SQLite，便于迁移和调试。
- 当前语义检索采用全量扫描 + 余弦相似度，适合中小规模记忆库；未来可替换成 ANN 或 SQLite 向量扩展。


path: package.json
content: {
  "name": "agent-memory-persistence",
  "version": "0.1.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc -p tsconfig.json",
    "clean": "rm -rf dist",
    "test": "tsx --test tests/basic.test.ts",
    "typecheck": "tsc -p tsconfig.json --noEmit"
  },
  "dependencies": {
    "better-sqlite3": "^11.10.0"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.12",
    "@types/node": "^24.0.0",
    "tsx": "^4.20.0",
    "typescript": "^5.8.2"
  }
}


path: src/MemoryManager.ts
content: import { MemoryStore } from "./MemoryStore";
import { VectorIndex } from "./VectorIndex";
import {
  CreateMemoryInput,
  MemoryFilter,
  MemoryManagerOptions,
  MemoryRecord,
  MemoryStats,
  UpdateMemoryInput,
  VectorSearchOptions,
  VectorSearchResult,
} from "./types";

export class MemoryManager {
  readonly store: MemoryStore;
  readonly vectorIndex: VectorIndex;

  constructor(options: MemoryManagerOptions = {}) {
    this.store = new MemoryStore(options.dbPath);
    this.vectorIndex = new VectorIndex(this.store);
  }

  create(input: CreateMemoryInput): MemoryRecord {
    return this.store.createMemory(input);
  }

  get(id: string): MemoryRecord | null {
    return this.store.getMemory(id);
  }

  update(id: string, input: UpdateMemoryInput): MemoryRecord | null {
    return this.store.updateMemory(id, input);
  }

  delete(id: string): boolean {
    return this.store.deleteMemory(id);
  }

  list(filter: MemoryFilter = {}): MemoryRecord[] {
    return this.store.listMemories(filter);
  }

  searchByVector(
    embedding: number[],
    options: VectorSearchOptions = {},
  ): VectorSearchResult[] {
    return this.vectorIndex.search(embedding, options);
  }

  listBySession(sessionId: string, limit = 100): MemoryRecord[] {
    return this.store.listMemories({ sessionId, limit });
  }

  listByUser(userId: string, limit = 100): MemoryRecord[] {
    return this.store.listMemories({ userId, limit });
  }

  cleanupExpired(referenceTime?: Date): number {
    return this.store.cleanupExpired(referenceTime);
  }

  stats(): MemoryStats {
    return this.store.getStats();
  }

  close(): void {
    this.store.close();
  }
}


path: src/VectorIndex.ts
content: import { MemoryStore } from "./MemoryStore";
import { VectorSearchOptions, VectorSearchResult } from "./types";

function cosineSimilarity(left: number[], right: number[]): number {
  if (left.length === 0 || left.length !== right.length) {
    return -1;
  }

  let dot = 0;
  let leftNorm = 0;
  let rightNorm = 0;

  for (let index = 0; index < left.length; index += 1) {
    const leftValue = left[index]!;
    const rightValue = right[index]!;
    dot += leftValue * rightValue;
    leftNorm += leftValue * leftValue;
    rightNorm += rightValue * rightValue;
  }

  if (leftNorm === 0 || rightNorm === 0) {
    return -1;
  }

  return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
}

export class VectorIndex {
  constructor(private readonly store: MemoryStore) {}

  search(
    embedding: number[],
    options: VectorSearchOptions = {},
  ): VectorSearchResult[] {
    const candidates = this.store.listMemories({
      userId: options.userId,
      sessionId: options.sessionId,
      type: options.type,
      includeExpired: options.includeExpired,
      limit: 10_000,
    });

    const minScore = options.minScore ?? -1;
    const limit = options.limit ?? 10;

    return candidates
      .filter((memory) => Array.isArray(memory.embedding))
      .map((memory) => ({
        memory,
        score: cosineSimilarity(embedding, memory.embedding!),
      }))
      .filter((result) => result.score >= minScore)
      .sort((left, right) => right.score - left.score)
      .slice(0, limit);
  }
}


path: src/index.ts
content: export { MemoryManager } from "./MemoryManager";
export { MemoryStore } from "./MemoryStore";
export { VectorIndex } from "./VectorIndex";
export * from "./types";


path: tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "lib": ["ES2022"],
    "declaration": true,
    "outDir": "dist",
    "rootDir": ".",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*.ts", "tests/**/*.ts"]
}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)