IdentityDB
IdentityDB is a TypeScript package for building structured AI memory on top of relational databases.
What it is
IdentityDB stores memory as a graph made of:
- Topics — named nodes such as
TypeScript,programming language,2025, orI - Facts — statements that connect multiple topics
- Fact-topic links — the relationships that turn one fact into a bridge between many topics
A single fact like I have worked with TypeScript since 2025. can connect the topics I, TypeScript, and 2025 at the same time.
Current capabilities
- SQLite, PostgreSQL, MySQL, and MariaDB connection adapters
- Automatic schema initialization for
spaces,topics,facts,fact_topics,topic_relations,topic_aliases, andfact_embeddings - High-level APIs for adding topics and facts
- Hard space isolation so independent memory graphs can coexist without cross-linking
- Topic alias and canonical resolution APIs so facts and queries can resolve alternate names
- Semantic fact indexing and search APIs built around provider-agnostic embeddings
- Dedup-aware ingestion hooks that can reuse an existing fact when a semantic near-duplicate is detected
- Pluggable fact extraction so callers can use a small LLM or a deterministic extractor
Install
bun install
Quick start
import { IdentityDB, NaiveExtractor, type EmbeddingProvider } from 'identitydb';
const db = await IdentityDB.connect({
client: 'sqlite',
filename: ':memory:',
});
await db.initialize();
await db.ingestStatement('I have worked with TypeScript since 2025.', {
extractor: new NaiveExtractor(),
});
await db.addFact({
statement: 'TypeScript is a programming language.',
topics: [
{
name: 'TypeScript',
category: 'entity',
granularity: 'concrete',
},
{
name: 'programming language',
category: 'concept',
granularity: 'abstract',
},
],
});
await db.linkTopics({
parentName: 'programming language',
childName: 'TypeScript',
});
await db.addTopicAlias('TypeScript', 'TS');
const provider: EmbeddingProvider = {
model: 'example-embedding-v1',
dimensions: 3,
async embed(input) {
if (input.toLowerCase().includes('typescript')) {
return [1, 0, 0];
}
return [0, 1, 0];
},
};
await db.indexFactEmbeddings({ provider });
const topic = await db.getTopicByName('TS', { includeFacts: true });
const children = await db.getTopicChildren('programming language');
const lineage = await db.getTopicLineage('TS');
const connected = await db.findConnectedTopics('TypeScript');
const matches = await db.searchFacts({
query: 'TypeScript experience',
provider,
limit: 5,
});
console.log(topic?.name);
console.log(children.map((entry) => entry.name));
console.log(lineage.map((entry) => entry.name));
console.log(connected.map((entry) => [entry.name, entry.sharedFactCount]));
console.log(matches.map((entry) => [entry.statement, entry.score]));
await db.close();
Memory spaces
IdentityDB now supports hard isolation via spaces. If you write facts into spaceName: 'A' and spaceName: 'B', they behave like separate dimensions:
- the same topic name can exist in both spaces
- aliases resolve only inside the requested space
- hierarchy, connected-topic traversal, semantic search, and duplicate detection stay inside the same space
await db.upsertSpace({ name: 'A' });
await db.upsertSpace({ name: 'B' });
await db.addFact({
spaceName: 'A',
statement: 'TypeScript belongs to A.',
topics: [{ name: 'TypeScript', category: 'entity', granularity: 'concrete' }],
});
await db.addFact({
spaceName: 'B',
statement: 'TypeScript belongs to B.',
topics: [{ name: 'TypeScript', category: 'entity', granularity: 'concrete' }],
});
const alphaFacts = await db.getTopicFacts('TypeScript', { spaceName: 'A' });
const betaFacts = await db.getTopicFacts('TypeScript', { spaceName: 'B' });
Semantic ingestion and duplicate detection
If you provide an embedding provider during ingestion, IdentityDB can index the new fact automatically and reuse an existing fact when a semantic near-duplicate is already present.
await db.ingestStatement('Bun makes TypeScript tooling fast.', {
extractor: new NaiveExtractor(),
embeddingProvider: provider,
duplicateThreshold: 0.95,
});
LLM-backed extraction
You can bridge any text-generating model into IdentityDB by wrapping it with LlmFactExtractor.
import { LlmFactExtractor } from 'identitydb';
const extractor = new LlmFactExtractor({
model: {
async generateText(prompt) {
return callYourFavoriteLlm(prompt);
},
},
instructions: 'Prefer technology, product, and time topics over generic nouns.',
});
await db.ingestStatement('I have worked with Bun and TypeScript since 2025.', {
extractor,
});
The adapter expects the model to return JSON and will validate the structured response before IdentityDB writes a fact.
Development
bun run test
bun run check
bun run build
Current status
This repository is in active MVP expansion development.
See these implementation plans for the current roadmap:
docs/plans/2026-05-11-identitydb-foundation.mddocs/plans/2026-05-11-identitydb-memory-expansion.md