One graph, two granularities
Memories and ingested documents live in one graph, connected through the entities they mention. Your saved belief “the ingest worker restarts via systemd” and the ops runbook you ingested both mention the entity ingest worker, so the graph puts them two hops apart, and recall’s entity arm follows the same edges. The two granularities:- Storage links at chunk granularity. Entity edges point at individual chunks, so the engine knows which section of a document mentions what.
- Display projects at document granularity. The graph API rolls chunk-level edges up
into one weighted
document → entityedge (weight = how many chunks mention the entity), so a 300-page PDF shows as one node instead of 300.
Node and edge types
Only active memories and edges appear; stale versions and reverted resolutions drop out.
How indexing works
By default, indexing runs automatically: a few seconds after you save a memory or add a file, the daemon extracts entities in the background (debounced, so a folder ingest becomes one run), and the runs show up in the Console like any other. Saves and ingests stay fast because the LLM work never blocks them. Turn it off withmemloom auto-index off (or
the Console’s toggle, or MEMLOOM_AUTO_INDEX=off as the config default) to make
indexing explicit again; offline mode has no auto-indexing since extraction needs
the LLM. Extraction is schema-constrained and
precision-first: it would rather miss an entity than add a wrong one.
- Every unindexed memory and context chunk goes to the LLM with an extraction prompt built
from the graph schema: a closed vocabulary of 8 entity types (
person,organization,project,tool,technology,place,event, andconcept, which is explicitly a last resort). The prompt instructs the model to abstain when in doubt and caps output at the 5 most salient entities per text. - A deterministic validator checks every extraction: it drops anything with an out-of-vocab
type, anything shaped like a math expression (
f(x) = x^3 - 2x^2), sentence fragments (over 40 characters or 5 words), and names with fewer than two letters. - Formula-dominated chunks skip the LLM entirely: a cheap math-density check keeps
math worksheets from becoming graphs of equations (those runs log
skipped: math-dense). - Surviving entities resolve to a node by name key: casefolded, trimmed, a leading
@stripped, whitespace collapsed.@memloom/corein one document andmemloom/corein another land on the same node, and the first-seen spelling and type stay (the node’s identity is its name key alone; type is an attribute, so an extractor that saystechnologytoday andprojecttomorrow cannot fork the node). The prompt also lists the names already in your graph, so the model reuses canonical spellings instead of minting variants. Amentionedge links the source to each resolved entity. - The same call also extracts typed relationships between entities, classified against a
closed predicate vocabulary:
uses,depends_on,part_of,created_by,works_on,works_at,located_in,attended. Out-of-vocab predicates and claims below 0.7 confidence are quarantined as plainmentionedges.
indexed_at) and incremental: re-running it only
touches what’s new. One LLM call per row, so a large PDF makes an index run proportionally
longer. GET /memory/schema reports the vocabularies with live usage counts.
If an older, noisier pipeline already polluted your graph, rebuild it from scratch:
replaces, distinct) are never touched by a rebuild. Note that rebuilding
needs the LLM: run it offline and the wipe still happens. The store stays consistent
(everything is unindexed), but nothing is re-extracted until a key is configured.
Indexed entities also feed retrieval: the entity arm of hybrid recall anchors a query to
entities and pulls in whatever mentions them, including document sections whose wording
shares nothing with your query. See Retrieval.
The schema
The vocabularies live in a registry (the viewer’s schema tab,GET /memory/schema,
memloom schema in the CLI). There is no code to fork:
- Add your own. A user-tier entity type or predicate (say,
medication) joins the prompt on the next index run. The description you write is the extraction rule, so write it like one: “a named drug or supplement” rather than “medications”. - Proposals. When the extractor confidently wants a type or predicate the schema lacks, the name is held out of the graph and queued for review. Approve it and it becomes a user entry; dismiss it and the prompt blocklists the name from ever being proposed again. A name only surfaces after two independent extractions asked for it.
- Disable anything, built-in or yours. Disabled entries leave the prompt; entities already extracted under them stay in the graph.
- Delete a disabled user entry when you’re sure. Built-ins can only be disabled: the system tier is re-seeded by name, so a deleted built-in would come back active. Proposals can only be approved or dismissed; there is no delete for them.
Managing entities
Extraction is precision-first but not perfect, so the schema tab also lists every entity in the graph with its usage (mentions, documents, memories) and the tools to correct it:- Rename. The node re-embeds under the new name. Renaming onto an existing entity’s name is refused; that situation is a merge.
- Retype to any active vocabulary type.
- Merge into another entity: every mention and typed relationship moves to the survivor (duplicates and would-be self-loops are dropped), then the duplicate node is deleted. This is the fix for near-duplicates the name key could not fold.
- Delete an entity and every edge touching it. The memories and documents that mentioned it are untouched.
GET /memory/entities, PATCH /memory/entities/{id},
POST /memory/entities/{id}/merge, and DELETE /memory/entities/{id}.
Navigating in the viewer
memloom ui → the Graph tab:
- Click a document diamond to expand it: its chunks appear as squares with their real chunk-level entity edges, and the rolled-up document edges swap out.
- Click a chunk to read it in the side panel, with breadcrumb navigation
(
document › chunk) to move back up. - A document’s side panel lists all its chunks and has an Open file button that opens the source file with your OS default app.