Repository navigation
Releases: johanneslatzel/llm-chat-memory
Releases · johanneslatzel/llm-chat-memory
Release list
v0.3.0
Breaking
- Memory JSON files now persist directly in
memoryDirinstead ofmemoryDir/memories/; theMemoryConfiguration.memoriesDirgetter is removed. - The directed link graph is stored in each memory file's
linksfield; the separatelinks.jsonfile is no longer read or written. MemoryService.save()is removed; links persist eagerly on every link/unlink/isolate/sync mutation.- Mutable
Memoryfields are native get/set accessors now (mem.score,mem.summary,mem.content,mem.tags,mem.embedding,mem.links,mem.cachedAt,mem.changedAt,mem.recalledAt) instead of method calls;id()andcreatedAt()remain methods. LinkPoolis constructed with just the pool:new LinkPool(pool); the config argument is removed.
Features
- Every memory file carries its own outgoing
linksfield, flushed eagerly whenever the graph changes; the graph rebuilds from the memory files on init. - Every
MemoryPoolis backed by anObjectStore<MemoryJSON>from@johannes.latzel/json-file-store; the default is an in-memoryMemoryStore, andMemoryServiceuses aJsonFileStoreoverconfig.memoryDirfor durable storage.
Fixes
MemoryToolPackagetutorial no longer claims semantic similarity links are computed automatically
Internal
- Refactored
LinkPoolto a nestedMap<from, Map<to, MemoryLink>>working graph, so persisting a source only rewrites its own outgoing links (O(outgoing)). - Updated docs to reflect per-memory link storage, the store-always design, native accessors, and corrected summary constraints.
- Added
prepackandcheck:linesscripts with@johannes.latzel/check-line-limits;verifynow runscheck:lines. - Add
@johannes.latzel/json-file-store ^0.1.0dependency. - Bump dev deps:
@types/nodeto 26.4.0,@typescript-eslint/*to 8.68.0,vitest/@vitest/coverage-v8to 4.1.11,eslintto 10.9.1,lint-stagedto 17.4.1. - Bump version to 0.3.0.
Migrating data from v0.2.0
v0.2.0 stored memory files in memoryDir/memories/ and the directed link graph in a separate links.json. v0.3.0 stores each memory file directly in memoryDir and embeds its outgoing links in the file's links field.
import { readFileSync, writeFileSync, readdirSync, rmSync, existsSync } from 'node:fs';
import { join } from 'node:path';
type MemoryLinkJSON =
| { type: 'constant'; to: string }
| { type: 'semantic'; to: string; weight: number; cachedAt: number };
type LegacyLink = {
type: 'constant' | 'semantic';
from: string;
to: string;
weight?: number;
cachedAt?: number;
};
const dir = process.env.LLM_CHAT_MEMORY_DIR ?? './memories';
const nested = join(dir, 'memories');
// Fold the legacy links.json entries into each memory file's `links` field.
const bySource = new Map<string, MemoryLinkJSON[]>();
try {
const links = JSON.parse(readFileSync(join(dir, 'links.json'), 'utf-8')) as LegacyLink[];
for (const link of links) {
const entry: MemoryLinkJSON =
link.type === 'semantic'
? { type: 'semantic', to: link.to, weight: link.weight ?? 0, cachedAt: link.cachedAt ?? Date.now() }
: { type: 'constant', to: link.to };
const existing = bySource.get(link.from) ?? [];
existing.push(entry);
bySource.set(link.from, existing);
}
} catch {
// no legacy links.json; only the file move is needed
}
// Move each memory file up to <dir>/ and embed its outgoing links.
for (const name of readdirSync(nested)) {
if (!name.endsWith('.json')) continue;
const id = name.replace(/\.json$/, '');
const source = join(nested, name);
const target = join(dir, name);
if (existsSync(target)) continue;
const stored = JSON.parse(readFileSync(source, 'utf-8')) as { links?: MemoryLinkJSON[] };
if (!Array.isArray(stored.links)) stored.links = bySource.get(id) ?? [];
writeFileSync(target, JSON.stringify(stored, null, 2));
rmSync(source);
}
rmSync(nested, { recursive: true, force: true });Full Changelog: v0.2.0...v0.3.0
v0.2.0
Breaking
- Node minimum raised from 18 to
>=20.9.0(required by thesharpoverride)
Features
MemoryPoolandLinkPoolexpose lifecycle hooks through a fluent builder:
onCreate,onUpdate,onDelete,onRecall,onLink,onUnlinkMemoryService.hook()routes to both pools; callbacks are best-effort and
can never break the emitting operation- New hook builder and event types exported from the package
Internal
- CI: add dependabot and label sync workflows, bump GitHub Actions and Node to
24, addnpm run auditgate - Dependencies:
@johannes.latzel/llm-chatto 0.7.0,@johannes.latzel/pagerank
to 1.0.3 - Dev dependencies: eslint 10,
@types/node26.2.0, lint-staged 17.3.0 - Add
overridesforadm-zipandsharpto clear audit advisories - Add
sideEffects: false
Full Changelog: v0.1.3...v0.2.0
v0.1.3
fix: export ReadonlyMemory from module
fix: use latest npm in publish.yml (otherwise provenance publishing doesn't work)
Full Changelog: v0.1.2...v0.1.3
v0.1.2
fix: graceful failure on LinkPool.load() when no links.json file exists
Full Changelog: v0.1.1...v0.1.2
v0.1.1
fix: ensureEmbeddings() missing from MemoryPoolInterface
docs: outdated docs
Full Changelog: v0.1.0...v0.1.1
v0.1.0
Initial Release
- tagged, linked, and scored memories with embedding support
- active recall via dedicated tool calls
- passive recall via automatic chat lifecycle hooks automatically injecting memories
- embedding-based semantic similarity search
- manual (constant) and automatic (semantic similarity) memory linking
- PageRank-based importance scoring over the link graph
- pluggable selection strategies: MMR (default) and weighted random
- JSON file persistence for memories and links
- configurable via
MemoryConfigurationobjects and environment variables
Full Changelog: https://github.com/johanneslatzel/llm-chat-memory/commits/v0.1.0