We Open-Sourced the Engine That Holds Your Relationships
wend-core is public under AGPLv3: the graph schema, the propose-and-confirm commit path, semantic recall, connection paths, the name-match heuristics, the AES-256-GCM secret box, and the MCP server core. What is in the repo, what stays closed and why, why AGPL rather than MIT, why provenance cannot be retrofitted, and how to run the whole thing yourself.
Ansh Vasani
Co-Founder and CEO of Wend
This week Wend started letting other companies’ agents write to your memory. Claude, ChatGPT, Gemini, and Cursor can now read your relationship graph and propose changes to it through our MCP server. That is the feature we most wanted to ship, and it is also the moment our whole trust story stopped being rhetorical.
Because here is what we have been telling people for months: every fact in Wend has a source, and no AI write lands in your graph until you approve it. Fine claim. Completely unverifiable from the outside. Once several models from several companies are writing into one store of who you know, “trust our governance” is not an answer, it is a request.
So the engine is now open source. wend-core is public under AGPL-3.0-only: the graph schema, the commit path that applies confirmed proposals, recall, connection paths, the name matcher, the secret box, and the MCP server core. If you want to know how a fact gets into a Wend graph, you no longer have to read a blog post about it. You can read the function.
What is actually in the repo
Not a stripped demo. These are the code paths our production app runs, exported with a fresh history and no proprietary glue. The shape of it:
schema/, 11 Postgres migrations. The per-user ontology layer (node_types,link_types,detail_definitions), the instance layer (nodes,links,node_details,link_details), thesourcestable that provenance hangs on, theconflictsqueue,pending_writes, the row-level security policies, a pgvector recall function, node aliases, and the API-key table behind our MCP auth.src/core/apply.ts, the commit path. About a thousand lines, and the least glamorous file in the project. It is what happens after a human presses approve: fuzzy dedup against existing nodes, alias preservation, minting an ontology type at confirm time when the graph meets a new kind of thing, multi-value detail handling, location-specificity collapse, orphan-person safety nets. Every unpleasant lesson from a year of real graphs is in there as a comment above the code that fixes it.src/core/tool-definitions.tsanddispatch.ts. The governed tool surface an agent sees, and the dispatcher that routes it. Reads query the graph. Writes create proposals.recall.ts,path.ts,paginate.ts. Semantic recall over pgvector, the breadth-first search that answers “how am I connected to her?”, and the paging helper that exists because a truncated read makes a graph search lie to you with total confidence.name-match.ts. Identity heuristics with match-quality tiers. This is the file that keeps “Alinda” from quietly merging into “Kalinda.” In a relationship graph, a confident partial match is not a small bug, it is two different people becoming one person forever.crypto/secret-box.ts,embed/provider.ts. AES-256-GCM encryption for tokens and other secrets at rest, and a pluggable embedding provider so you can bring any 1024-dimension model instead of ours.src/mcp/server.tsandexamples/serve.ts. The MCP protocol core, plus a self-hosted server overnode:httpin about ninety lines, so “point my own agent at my own graph” is an afternoon rather than a project.
The most important line in the repo is missing
Open the tool definitions and count the capabilities. There are nine: findNodeByName, recallNodes, getNodeDetails, proposeCreateNode, proposeCreateLink, proposeAddDetail, proposeAddLinkDetail, proposeEditNode, flagPromise.
There is no confirm. Not disabled, not permission-gated, not discouraged in a system prompt. It does not exist in the surface an agent can reach. Confirmation is a separate concern that the person deploying the engine has to build, and we left it out on purpose so that no amount of clever prompting gets a model to approve its own write.
What we deliberately did not open
Open core, honestly labeled. Four things stay closed, and we would rather say which than let you find out by grepping:
- The prompt library. The tool descriptions in the repo are functionally complete. The ones in production are much longer and heavily tuned, because getting a model to extract people from a messy email thread without inventing three strangers is most of the actual work. That library ships as a model pack, and it stays ours. It is also the piece with the shortest shelf life, since every frontier model release moves it.
- Integration glue. The Gmail, Calendar, Contacts, and LinkedIn plumbing, the OAuth flows, the push subscriptions, the ingest schedulers. Partly because it is entangled with credentials and verification status that cannot be handed out, partly because it is the part with the least value to a reader and the most to a competitor.
- The product. The review UI, the outreach features, drafting, scheduling links, meeting briefs, billing, the mobile surface, hosted operations. If you self host, you get an engine and you build the experience.
- The extraction agent loop, for now. The loop that turns raw text into candidate proposals is not in the repo yet. It is on the roadmap to open. We are saying that as an intention rather than a promise with a date on it.
The line we drew: the part that governs your data is open, the part that makes it convenient is not. If we vanish tomorrow, the trust model survives and someone else can run it. That is the specific thing open source is good at, and we did not want it to depend on us being around.
Why AGPLv3, and not MIT
We considered MIT for about a day. It would have been easier to get adopted and easier to explain. Three arguments moved us off it.
One: the loophole MIT leaves is exactly our category. Permissive licenses let anyone take the engine, run it as a hosted service, and never publish a line of what they changed. For a build tool, fine. For the thing holding a decade of somebody’s relationships, the whole value of publishing is that a user can check whether the governance still holds in the copy that is actually running. AGPL is the license that says: if you serve this to other people over a network, they get the source too. That is not incidental to the product. It is the product.
Two: a DCO instead of a CLA, so we cannot betray this later. Contributions come in under a Developer Certificate of Origin. Contributors keep their copyright. We do not collect the assignment that would let us relicense the project unilaterally down the road. Plenty of companies have gone open, gathered a community, then quietly moved to a source-available license once revenue pressure hit. Every time it happens the reaction is swift and the goodwill does not come back. Refusing the CLA is how you make that impossible instead of merely unlikely, and it is the reason our license commitment is worth more than the paragraph you are reading.
Three: the moat was never the code. Nothing in that repo is hard to write if you know what to write. What is hard is a user’s accumulated confirmed graph, which compounds in wall-clock time and cannot be cloned; the integration rights and security review that let you touch real inboxes; and the years of taste in the prompt pack. We are not protecting the schema. We are betting that publishing it buys more trust than it costs us in advantage.
One boundary worth stating: the license grants no trademark rights. Run it, fork it, sell a service built on it. Just don’t call your fork Wend.
Why provenance cannot be retrofitted
This is the argument I most want engineers to take away, because it applies to memory systems generally and it is almost always discovered too late.
In the schema, node_details.source_id is not null references public.sources (id). A fact that cannot say where it came from cannot be inserted. Not discouraged, not flagged in review. Rejected by the database.
create table public.node_details (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users (id) on delete cascade,
node_id uuid not null references public.nodes (id) on delete cascade,
detail_definition_id uuid not null
references public.detail_definitions (id) on delete restrict,
value jsonb not null,
confidence numeric(3, 2) not null default 1.0,
source_id uuid not null references public.sources (id) on delete restrict,
user_confirmed boolean not null default false,
...
);Now imagine adding that constraint to a memory store that has been running for two years without it. You have a million facts. Where did each one come from? The email is gone, the chat session rolled off, the extraction that produced it happened in a process that logged nothing. You have three options, and all of them are bad: leave the column nullable and lose the guarantee, invent a placeholder source for historical rows and lie in a way that survives forever, or throw away the facts.
Everybody picks the placeholder. And a provenance system where most rows point at 'legacy_import' is theater, because the question users actually ask is about the fact they are looking at right now, which is probably old.
The same argument covers the confirm boundary. If your product spent two years auto-committing AI writes, you cannot retroactively decide the graph is human-approved. The approvals never happened. You can start asking today, but every fact from before is unaudited, and there is no migration that can fix it because the missing artifact is a human decision, not a column.
So provenance is not a feature you add when a customer asks. It is a constraint you accept on day one, when it is most annoying and least rewarded, or you never really have it. That asymmetry is why we think publishing the schema is a stronger competitive statement than keeping it. Anyone can copy the DDL. Nobody can copy having enforced it since the first row.
The other half: the MCP server is live
Open code is only interesting if something is using it. The hosted MCP server went live alongside this, at https://www.trywend.io/api/mcp.
It exposes exactly two tools. search discovers capabilities, optionally filtered by keyword. execute runs one by name. Behind those two sit roughly twenty capabilities, the same set our own in-app agent uses: find a person, recall by meaning, pull someone’s details with sources, propose a node or a link or a detail, flag a promise, draft an email, create a scheduling link. Two meta-tools instead of twenty declarations keeps a client’s context small, which matters when the user is paying for those tokens somewhere else.
Auth is OAuth 2.1 with PKCE and dynamic client registration, which in practice means there is nothing to configure. You paste the URL into your client’s connector settings and leave the client ID and secret blank; the client registers itself, generates an S256 challenge, and sends you to our consent screen, which names the three things the connector can do and says plainly that nothing is saved until you approve it. Then it says “Connected to Wend.” Access tokens live 30 days, authorization codes are single-use and expire in 60 seconds, refresh tokens rotate, the PKCE comparison is constant-time, and the consent POST carries an origin check. An API-key path (wend_live_…, stored only as a SHA-256 hash) still exists for scripts and headless setups.
We drove the whole journey by hand in a browser before claiming it worked, on claude.ai and in the Claude Code CLI. ChatGPT, Gemini, and Cursor speak the same registration flow. One thing we learned the tedious way: client UIs move. The steps we wrote for finding the connector settings in claude.ai were stale within hours, which is a decent argument for documenting the protocol and not the menu path.
Writes through MCP behave exactly like writes from our own agent, because they are the same code. An outside model proposes; the proposal appears in your review queue; you confirm or you don’t. When Claude tells you it saved something to Wend, what it saved is a request.
Running it yourself
The whole point of publishing is that you can. The short version:
# 1. A Postgres with pgvector (a free Supabase project is fine)
# 2. Apply schema/*.sql in filename order
npm install
# 3. Optional: bring your own embeddings for semantic recall
# (any 1024-dim model)
import { setEmbeddingProvider } from "wend-core";
setEmbeddingProvider(async (text) => yourEmbedding(text));
# 4. Run the example MCP server
SUPABASE_URL=… SUPABASE_SERVICE_ROLE_KEY=… WEND_USER_ID=… \
npx tsx examples/serve.ts
# 5. Point an agent at it
claude mcp add --transport http wend http://localhost:8787 \
--header "Authorization: Bearer dev"Then ask your agent who you know at some company, or tell it you met someone at a conference. The first is a read and answers immediately. The second lands in pending_writes and waits, because you have not built a confirm UI yet. That gap is not an oversight. It is the separation of powers, and you get to decide what approval looks like in your deployment.
Two honest v0 caveats, also stated in the README. The RLS policies reference Supabase’s auth.users and auth.uid(), so a non-Supabase Postgres needs that layer adapted; the engine code itself only wants a Postgres client. And the recall_nodes function is security definer and assumes an authenticated context, which needs adjusting for a pure service-role setup. Both are good first contributions if you want one.
What we want back
Not stars. Three specific things, in order of usefulness: adapters that free the schema from Supabase-shaped auth, embedding providers beyond the one we use, and bug reports against apply.ts from people running real graphs, because dedup and identity matching only fail in ways that real data invents. Private vulnerability reporting is enabled on the repository; please use it rather than a public issue for anything security-shaped, and see our security page for the rest of the posture.
What we are not offering is support. This is the engine we run, published so it can be audited and reused. The hosted product is where the convenience lives, and it has a free tier that includes provenance, full export, and the MCP connector, because a trust feature behind a paywall is not a trust feature.
Closing
The thesis, once more, in the shortest form I have: anyone can vibe code an app now, and nobody can vibe code a relationship. Models are getting swapped out every few months while your network compounds for the rest of your life. So the memory has to outlive the model, which means it cannot live inside one, which means it needs to sit in a substrate you can inspect and leave.
A substrate you cannot read is not one you own. That is the entire reason this repo is public. Every fact has a source, and you approved it, and now you can go check that we meant it.
The repo: github.com/a-finance-bro/wend-core. Adjacent posts: why Wend never silently updates a fact, auditing every AI inference, and why the graph lives in plain Postgres. The reasoning behind all of it is in the manifesto.