From ae056a7ad1b81fd417597d9a22716883db51ea37 Mon Sep 17 00:00:00 2001 From: Shinwoo PARK Date: Mon, 11 May 2026 14:49:57 +0900 Subject: [PATCH] docs: add wiki guide for isolated memory spaces --- Getting-Started.md | 31 +++++++--- Home.md | 3 + Memory-Spaces.md | 143 +++++++++++++++++++++++++++++++++++++++++++++ _Sidebar.md | 1 + 4 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 Memory-Spaces.md diff --git a/Getting-Started.md b/Getting-Started.md index b012057..7085084 100644 --- a/Getting-Started.md +++ b/Getting-Started.md @@ -25,6 +25,7 @@ await db.initialize(); This creates the tables IdentityDB needs: +- `spaces` - `topics` - `facts` - `fact_topics` @@ -32,7 +33,23 @@ This creates the tables IdentityDB needs: - `topic_aliases` - `fact_embeddings` -## 3. Add structured facts directly +## 3. Create or use isolated memory spaces + +If you want independent memory graphs for different people, tenants, projects, or contexts, use spaces. + +```ts +await db.upsertSpace({ name: 'A' }); +await db.upsertSpace({ name: 'B' }); + +const spaces = await db.listSpaces(); +const alpha = await db.getSpaceByName('A'); +``` + +Any write or read can then opt into a specific space with `spaceName`. + +See [Memory Spaces](Memory-Spaces) for the full model. + +## 4. Add structured facts directly Use `addFact()` when your application already knows the topics it wants to attach. @@ -54,7 +71,7 @@ await db.addFact({ }); ``` -## 4. Model topic hierarchy explicitly +## 5. Model topic hierarchy explicitly Use `linkTopics()` when you want hierarchy to be explicit rather than inferred. @@ -74,7 +91,7 @@ This is useful for reasoning such as: - `Bun` is a kind of `runtime` - `PostgreSQL` is a kind of `database` -## 5. Add aliases for canonical topic resolution +## 6. Add aliases for canonical topic resolution ```ts await db.addTopicAlias('TypeScript', 'TS'); @@ -84,7 +101,7 @@ const canonicalTopic = await db.getTopicByName('TS', { includeFacts: true }); This keeps one canonical topic row while still allowing alternate spellings or shorthand forms. -## 6. Ingest free-form text through an extractor +## 7. Ingest free-form text through an extractor When your application starts from raw text, use `ingestStatement()`. @@ -119,7 +136,7 @@ await db.ingestStatement('I have worked with Bun and TypeScript since 2025.', { See [Extractors](Extractors) for a deeper explanation of the trade-offs. -## 7. Add semantic search +## 8. Add semantic search IdentityDB keeps semantic search provider-agnostic through an `EmbeddingProvider` interface. @@ -147,7 +164,7 @@ const matches = await db.searchFacts({ }); ``` -## 8. Enable duplicate-aware ingestion +## 9. Enable duplicate-aware ingestion If you also provide an embedding provider during ingestion, IdentityDB can check whether a semantically similar fact already exists. @@ -161,7 +178,7 @@ await db.ingestStatement('Bun makes TypeScript tooling fast.', { If a close enough match already exists, IdentityDB can return the existing fact instead of writing a duplicate. -## 9. Close the connection +## 10. Close the connection ```ts await db.close(); diff --git a/Home.md b/Home.md index e41f77a..3af68aa 100644 --- a/Home.md +++ b/Home.md @@ -16,6 +16,7 @@ IdentityDB is designed as the answer to those problems. IdentityDB turns memory into a relational graph with a stable application API: +- **Spaces** isolate independent memory graphs such as `A` and `B` so they behave like separate dimensions - **Topics** are named nodes such as `TypeScript`, `Bun`, `2025`, or `programming language` - **Facts** are statements such as `I have worked with TypeScript since 2025.` - **Fact-topic links** connect one fact to many topics, which lets a single statement become a graph edge between concepts @@ -30,6 +31,7 @@ This gives you a memory system that is easier to inspect than a black-box vector - Connect to **SQLite, PostgreSQL, MySQL, and MariaDB** - Initialize the required schema automatically - Add facts and topics directly through a typed API +- Split memory into hard-isolated spaces so one tenant, person, or project cannot accidentally connect to another - Ingest free-form text through pluggable extractors - Resolve aliases to canonical topics - Traverse parent/child topic relationships @@ -60,6 +62,7 @@ That means IdentityDB can answer more than plain keyword lookup. It can tell you ## Recommended reading order - [Getting Started](Getting-Started) — installation, initialization, and concrete examples +- [Memory Spaces](Memory-Spaces) — how to keep separate memory graphs isolated - [Extractors](Extractors) — when to use `NaiveExtractor` vs `LlmFactExtractor` ## Repository diff --git a/Memory-Spaces.md b/Memory-Spaces.md new file mode 100644 index 0000000..93ca4dc --- /dev/null +++ b/Memory-Spaces.md @@ -0,0 +1,143 @@ +# Memory Spaces + +IdentityDB supports **hard-isolated memory spaces** so different people, tenants, projects, or contexts can live in separate graphs. + +Think of each space as a separate dimension: + +- topics in space `A` do not automatically connect to topics in space `B` +- aliases are resolved inside one space only +- hierarchy traversal stays inside one space +- semantic search stays inside one space +- duplicate detection stays inside one space + +That means you can safely store `TypeScript` in both `A` and `B` without merging them. + +## When spaces are useful + +Spaces are a good fit when you need to isolate memory by: + +- user +- customer or tenant +- project +- environment +- agent persona +- private vs shared knowledge + +## Core API + +You can manage spaces explicitly: + +```ts +await db.upsertSpace({ name: 'A' }); +await db.upsertSpace({ name: 'B' }); + +const spaces = await db.listSpaces(); +const alpha = await db.getSpaceByName('A'); +``` + +And then scope reads and writes with `spaceName`: + +```ts +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' }, + ], +}); +``` + +## Same topic name, different spaces + +The same normalized topic name can exist independently in multiple spaces. + +```ts +const alphaTopic = await db.getTopicByName('TypeScript', { spaceName: 'A' }); +const betaTopic = await db.getTopicByName('TypeScript', { spaceName: 'B' }); +``` + +These are separate topic rows with separate facts and relationships. + +## Alias isolation + +Aliases are also scoped by space. + +```ts +await db.addTopicAlias('TypeScript', 'TS', { spaceName: 'A' }); +await db.addTopicAlias('TeamSpeak', 'TS', { spaceName: 'B' }); + +const inA = await db.resolveTopic('TS', { spaceName: 'A' }); +const inB = await db.resolveTopic('TS', { spaceName: 'B' }); +``` + +The same alias can therefore mean different canonical topics in different spaces. + +## Hierarchy isolation + +Hierarchy is isolated too. + +```ts +await db.linkTopics({ + spaceName: 'A', + parentName: 'programming language', + childName: 'TypeScript', +}); + +await db.linkTopics({ + spaceName: 'B', + parentName: 'language family', + childName: 'TypeScript', +}); + +const alphaParents = await db.getTopicParents('TypeScript', { spaceName: 'A' }); +const betaParents = await db.getTopicParents('TypeScript', { spaceName: 'B' }); +``` + +Those queries can return different results even though the child topic name is identical. + +## Semantic search and duplicate detection isolation + +Search and duplicate detection also stay inside the requested space. + +```ts +await db.indexFactEmbeddings({ provider, spaceName: 'A' }); +await db.indexFactEmbeddings({ provider, spaceName: 'B' }); + +const alphaMatches = await db.searchFacts({ + spaceName: 'A', + query: 'TypeScript runtime tooling', + provider, +}); + +await db.ingestStatement('Bun makes TypeScript tooling fast.', { + spaceName: 'A', + extractor: new NaiveExtractor(), + embeddingProvider: provider, + duplicateThreshold: 0.95, +}); +``` + +A fact in space `B` will not be used as a duplicate candidate for a write in space `A`. + +## Default behavior + +If you do not provide `spaceName`, IdentityDB uses the **default** space. + +That preserves backwards compatibility with older code while still letting new code opt into explicit isolation. + +## Practical recommendation + +A simple pattern is: + +- use one space per user if you want personal memory isolation +- use one space per tenant if you are building multi-tenant software +- use one space per project if the same agent needs independent project memories +- keep shared organizational knowledge in a separate dedicated shared space diff --git a/_Sidebar.md b/_Sidebar.md index 409222e..22392de 100644 --- a/_Sidebar.md +++ b/_Sidebar.md @@ -2,5 +2,6 @@ - [Home](Home) - [Getting Started](Getting-Started) +- [Memory Spaces](Memory-Spaces) - [Extractors](Extractors) - [Repository](https://git.psw.kr/p-sw/IdentityDB)