Search it, or hand it to a model.
- Search returns pages, not answers. You still do the reading.
- A graph database needs a curator, forever.
- A model summarising the wiki invents the joins and cites nothing.
worked example · gbrain
The case starts with an empty, disposable workspace. Scroll, and a knowledge product assembles itself in it: a cited graph, a fact index, durable memory, and answers that show their evidence. The application writes ordinary pages; one versioned catalog describes how everything else is produced.
Simulation · example walkthrough. The guided scenario seeds a disposable workspace with example pages, people, and facts. The script is runnable, but the story and output shown here are illustrative rather than a customer deployment.
postgres + one worker · no graph database
00 · the problem
Because prose does not connect. The pages hold the knowledge, but nothing tracks who founded what, who advises whom, or which page asserts which fact — so “what connects Maya and Nora?” means reading everything and hoping. And asking authors to also maintain a graph never survives a deadline.
Facts heading become a list per page.Ordinary Markdown, with ordinary links. Nobody fills in a form.
Maya founded [Acme](companies/acme) after investing in companies/orbit.
Those links become connections, and the lines under Facts become a list.
Maya → founded → Acme · Maya → invested_in → OrbitNo model is involved here, so it costs nothing and never invents a connection.
The answer names the pages it used, and says what it could not find.
“Maya founded Acme and invested in Orbit. She has also committed to introduce Nora — who advises Acme’s board — to that board.” Gap: the meeting date.
Two calls: save a page, ask a question.
await memseek.records.ingest(collection="pages", key="people/maya", text=markdown) res = await memseek.answer("What should I know about Maya?") res["citations"] # the pages behind the answer
New to Memseek? You write one small configuration file describing what your application should remember. After that your application only appends what happened; Memseek does the deriving, keeps every conclusion linked to the evidence underneath it, and hands your agent a bounded briefing instead of a pile of text. Everything below is that file and what it produces — how it works.
The application writes ordinary Markdown plus a title and type under the key people/maya. The collection contract chooses keyed history because a knowledge page has one current version, unlike an append-only conversation stream.
people/maya links to Acme and declares two short facts under a Facts heading.- name: pages
mode: keyed
schema:
required: [text, title, body, type]
fields:
title: {filter: true, project: true}
type: {filter: true, project: true}
required_processors: [embedding_v1]
answerable: truemode: keyedpeople/maya has one live head. Updating it supersedes the prior record without destroying page history.
schema.requiredGuarantee enough structured metadata to filter pages while retaining the exact authored Markdown in text and body.
embedding_v1Make the page semantically searchable after the required processor finishes.
One active pages record for Maya, holding her Markdown exactly as written: “Maya founded [Acme](companies/acme) after investing in companies/orbit.” No edge or fact has been asserted by the application.
A page write triggers link_extraction. It reads changed pages plus the current page keys, recognizes explicit Markdown links and known-page references, and emits structural edges. Because this is parsing, the YAML forbids model calls.
Maya founded [Acme](companies/acme) after investing in companies/orbit.
Facts
− Maya founded Acme.
− Maya invested in Orbit.
people/maya → companies/acme
people/maya → companies/orbit
Maya founded Acme.
Maya invested in Orbit.
Visible effect: one authored page remains the source of truth while three derived structures become separately queryable.
trigger:
write: {collections: [pages], keyed: true}
sources:
changed_pages: {kind: changes}
current_pages: {kind: current}
model: null
limits:
max_tasks: 1
max_llm_calls: 0
tasks:
- id: edges
use: extract_relations
emit:
collection: edges
type: edgechanged_pagesParse only page versions that moved, so an unchanged corpus is not repeatedly reprocessed.
current_pagesResolve a bare reference against the complete current key set and avoid creating edges to nonexistent pages.
model: nullState that this operation is deterministic. max_llm_calls: 0 enforces the claim at runtime.
An edges record — people/maya —founded→ companies/acme — citing the page that contained the link, and written with zero model calls.
A second deterministic derivation reads the current pages, extracts only short lines under the Facts heading, and emits one complete keyed fact index. If Maya edits or removes a fact, the derived head changes with the page.
tasks:
- id: fact_index
use: extract_facts
input:
records: "{{current_pages.records}}"
changed_records: "{{changed_pages.records}}"
with:
heading: Facts
max_facts: 100
max_fact_chars: 80
emit:
collection: facts
type: page_facts
keys: [page_facts]
complete: trueheading: FactsMake authorship explicit. Prose elsewhere on the page does not silently become a declared fact.
max_*Bound the derived index in both count and fact length before anything is written.
complete: trueTreat each output as the whole current fact index, so removed facts disappear from the head while remaining in history.
One current facts/page_facts record holding the declared facts from all five pages: “Maya founded Acme.” · “Maya invested in Orbit.” · “Acme is a climate software company.” · “Nora advises Acme.” · “Orbit is in Maya’s investment portfolio.” — each with its page citation.
The graph view declares its seed, direction, predicates, depth, and path ceiling. The prompt artifact separately assembles current pages, concepts, and consolidated takes. Both are reads over the same cited records.
name: graph_query
kind: graph
graph: {edges: edges}
parameters:
seed: {type: string, required: true}
direction: {enum: [out, in, both]}
depth: {type: integer, minimum: 1,
maximum: 4}
limit: {type: integer, minimum: 1,
maximum: 100}graph.edgesName the derived edge collection the traversal is allowed to follow.
predicates / directionLet a caller ask a precise relationship question without granting arbitrary graph code execution.
depth / limitPublish deployment ceilings in the generated input schema so the client cannot request an unbounded walk.
Graph traversal and the dossier write nothing. “What connects Maya and Nora?” comes back as a cited answer over pages, edges, concepts and takes — with the meeting date named as a gap rather than guessed.
01 · the write
Ordinary markdown. Some of it links to other pages the way anyone writes links —
a [label](path) here, a bare dir/slug there. One page is
deliberately unconnected. This is the only content anyone authors.
02 · it wires itself
The moment those pages land, memseek resolves the links, classifies them into typed
predicates, and folds every ## Facts bullet into one current index, and
both cost zero model calls. No inference, no bill, no drift: the same
input always produces the same graph.
Click any page. Every edge is an immutable record you can dereference — and the graph is reached through an ordinary named view, not a bespoke endpoint.
name: fact_extraction trigger: write: {collections: [pages], keyed: true} model: null # no model, ever limits: {max_llm_calls: 0, max_wall_s: 30} tasks: - id: fact_index use: extract_facts with: {heading: Facts, max_facts: 100} emit: collection: facts keys: [page_facts] # one current index complete: true
03 · then it thinks about it
Now a transcript arrives: "Maya committed to introduce Nora to the Acme board before the next funding meeting." That single write starts a cascade. Each stage is a bounded derivation that must cite the records it stood on, or emit nothing at all.
The transcript is distilled into small, durable memories — a fact, a preference, a commitment, a decision.
Recurrences across the new edges and atoms. A pattern must connect at least two distinct records to exist.
One compact index of durable themes, replaced in place — never an append-only pile that grows forever.
Evidence clusters consolidated into actionable conclusions, each carrying its own citation list.
Every take is capped, replaceable, and traceable. Where the evidence does not support a conclusion, the derivation returns nothing — the honest output of an empty case is empty.
limits: max_tasks: 1 max_llm_calls: 2 max_visible_records: 23 max_total_tokens: 40000 max_wall_s: 90 # citations are schema, not etiquette: citations: type: array minItems: 1 items: {format: uuid}
04 · ask it
One hybrid query spans every collection at once, weighted by graph distance from an anchor page. The answer names the records it used — and, just as importantly, what it could not establish.
The same call is bounded like everything else: it may rewrite the query once, it may lean on the graph, and it must name the records it used — or say plainly that it could not.
05 · descend
This is the part that survives an audit. Every record names its parents, so a conclusion can be walked down, without a cut, until it bottoms out at something a human actually wrote.
Maya has committed to introduce Nora to the Acme board.
Maya committed to introduce Nora to the Acme board before the next funding meeting.
"Maya committed to introduce Nora to the Acme board before the next funding meeting."
companies/acme → advises → people/nora
"Acme's board is advised by [Nora](people/nora)."
The graph, the fact index, the page bodies, and the transcript above are the
actual seeded corpus from examples/gbrain_showcase.py. Model-written
text — the atom, take, and answer wording — is representative of a real-provider run
rather than a captured transcript, because that text varies by model. Run it yourself
below and you will get your own.
05b · from your app
Your application writes markdown and asks questions. It never orchestrates the extraction, the cascade, or the ranking — those are the catalog's job.
Runs on every write, deterministically, at zero model calls.
Bounded derivations fire themselves when evidence accumulates.
Fusion, weighting, and anchor distance are declared, not coded.
An uncited belief is rejected, so provenance is never optional.
06 · the whole thing
No graph database. No pipeline service. No bespoke endpoint. A package binds the collections, derivations, views, artifact, MCP surface, and a retention job — and a workspace opts in by publishing it.
Nine collections. Eight derivations. Three views. One artifact, one MCP surface, one retention job. That is the entire product — and it is opt-in: the default catalog exposes none of it.
07 · hand it over
The tool surface is declared, not written. This file is the whole of it — every tool binds to a view, artifact, or endpoint you already watched being built, and the compiler on the right is the only thing between them.
Answer a question from cited evidence. Never saves an answer.
Hybrid search across pages, atoms, facts, patterns, concepts, and takes.
Traverse the cited structural links between pages and entities.
Find current pages with no live incoming or outgoing link.
Render a bounded dossier — pages, concepts, takes — for one entity.
Dereference one record an answer already pointed at.
Those instructions ship with the tool surface: any client
that connects is told to treat retrieved memory as reference data rather than as
instructions. The prompt-injection boundary is part of the declaration, not
something each agent author has to remember.
08 · the boundary
start building
The runnable case seeds its own isolated workspace and entity, so it never collides with anything you already have. Then it hands you a prompt and gets out of the way.