base
← Blog

The memory layers. How Base actually remembers your company

A technical write-up of the layer model behind Base: source data, signals, business identity, goals and decisions, memory, actions and ontology, plus entity resolution, the Contradicts pattern, and read-only agent access.


Connecting tools is the easy part. Anyone can call the Gmail API, page through HubSpot contacts and mirror Stripe invoices into a table. That work is well understood and mostly mechanical.

The hard part starts immediately afterwards. Your CRM says a deal closes in November. An email from the same customer last Tuesday says they have pushed the decision to Q1. Your calendar has a meeting with someone whose name is spelled two ways across three systems, and your invoicing tool knows them as a company rather than a person. None of these systems is wrong. They just disagree, and nothing reconciles them except you, in your head, at eleven at night.

So the interesting problem is not ingestion. It is what you do with contradictory, partial, differently shaped facts about the same business, and how you keep a system honest about which of those facts it actually knows versus which it merely overheard.

This is how we solved it in Base. Internally we call it the memory layers, and the names below are the real ones used in the code and the schema, not a marketing diagram drawn after the fact.

Seven layers, and one thing that is not a layer

There are seven places knowledge can live. Anything that does not fit one of them is a bug in our thinking, not a reason to invent an eighth.

Source data is everything synced from a connected tool, mirrored into our own tables: contacts, deals, invoices, calendar events, Slack messages, support conversations. It is the boring layer and it is the load-bearing one. Every integration does a genuine full historical backfill on first connection, with resumable cursors held in a backfill state table, and then moves to incremental sync. We do not read live from a third-party API at render time, because a dashboard that depends on someone else's rate limit is not a dashboard.

Signals are the second layer, and the new one. A signal is a single extracted, unstructured, atomic claim about one entity, taken from one real source row. "They have moved their renewal to March." Not a summary, not a tone reading, not a conclusion joined together from two sentences. Signals live in their own table and they are never mixed into source data.

Business identity is who the company is, in the founder's own words: what you sell, who you sell it to, how you talk. It is authored, never synced and never extracted. Nothing writes to it except a founder in settings. Everything else reads from it, which is why drafted emails sound like you rather than like a model's idea of a founder.

Goals and decisions is what the company said it would do and what a founder actually chose. Objectives, key results and a decisions table. Decisions matter more than goals in practice, because a decision explains why the obvious next action is not the right one.

Memory is everything remembered that is not a structured record: founder decisions, the reasons proposals were dismissed, and free-text notes attached to any entity. It is exposed as one view so that a single query can ask "what do we know about this customer that is not in a field anywhere".

Actions is prepared work waiting on a human: drafted replies, proposals, suggestions. Nothing in this layer is sent automatically. That is a product decision as much as a technical one, and it is enforced at the data model rather than left to a UI checkbox.

Ontology is the module system itself. Twenty-odd modules, each with a shape: what a contact is, what an invoice is, what a support conversation is. It is the layer that lets everything else be generic, because a new module that declares its shape correctly becomes queryable, drillable and searchable without new bespoke code.

The thing that is not a layer is reasoning. Insight generation, the coaching engine, the standing rules, decay of stale facts. These are mechanisms that read the layers above and write into Actions. They store nothing themselves. Keeping reasoning stateless is what stops the system slowly accumulating a private, unauditable opinion about your business.

Entity resolution, because the same human arrives five times

Nothing above works if the same person is five different rows. A founder connects Gmail, HubSpot, Stripe, Intercom and a calendar, and the same customer arrives as an email address, a CRM contact ID, a Stripe customer, a support requester and a meeting attendee, with different names and often different email addresses.

Resolution runs at the database level rather than in application code, through security-definer functions that webhook handlers and sync workers call directly. The primitives are small on purpose: resolve a person by email, resolve a company by domain, upsert a link from a source system onto a canonical entity, and merge two canonical entities when they turn out to be the same.

Every link records how it was resolved and how confident that resolution was. Email match is treated as strong. Domain match on a company is strong unless the domain is a public mailbox provider. Name matching is weak and never merges anything on its own. A model-assisted match is recorded as such, so a wrong merge can be traced to the mechanism that made it rather than appearing as an unexplained fact.

Two consequences fall out of this. First, brain queries stop double counting: one human with three source IDs is one node, not three. Second, dedupe becomes explainable. When we group multiple partner contacts under one VC fund, or flag two contact rows as the same person, we can show the evidence for it rather than asking you to trust a similarity score.

The Contradicts pattern

This is the part we are most pleased with, and the part most systems get wrong.

The naive design is to let a language model read your emails and update your CRM. It demos beautifully. It is also the fastest way to destroy a founder's trust, because the first time an inferred sentence quietly overwrites a real field, every other number in the product becomes suspect. You cannot tell which figures were entered and which were guessed, so you stop relying on all of them.

So we made an absolute rule: signals never overwrite structured data.

When a signal is extracted, the caller passes in the structured values it already holds for that entity. The claim is then classified against them. If it agrees with the structured field, it lands as confirms. If it says something the structured record does not, it lands as new. If it directly disagrees with a field we hold, it lands as contradicts, and the row keeps both sides: which field it disagrees with, and the structured value it disagrees with.

A contradiction does not change anything. It becomes a tension, surfaced as a proposal, showing the claim, the exact source excerpt it came from, the timestamp of that source, and the structured value it sits against. A founder resolves it: accept the claim and update the record, dismiss it, or leave it open. The resolution is written back to the signal, which means the same disagreement is not raised again next week as if it were new.

Extraction itself is deliberately timid, and the constraints are enforced in code rather than trusted to a prompt. One flat claim per signal, so there are no chains of reasoning and no signal about another signal. Every signal names the exact source row it came from. Claims below a confidence floor are dropped, hedged language is rejected outright, and anything about how someone seems to feel is rejected regardless of confidence, because sentiment is not a fact.

There is also no scan-everything pipeline and no extraction cron. Signals are only pulled from text Base is already reading for another reason: drafting a support reply, gathering context for deal health, preparing a meeting follow-up. Extraction is capped per source, deduplicated by a normalised claim key so a redrafted reply updates a row instead of creating a second one, and it fails silently. If a claim cannot be extracted, the thing the founder actually asked for still completes.

The net effect is a system that can tell you "your CRM says November, but Priya wrote this on Tuesday, here are her exact words" instead of silently showing you Q1 and hoping you agree.

External agent access, read-only and audited

Once the layers hold real reconciled data, the obvious next question is whether something outside Base can read them. Founders already live in Claude, ChatGPT, Cursor and Claude Code, and asking one of those "what is going on with this account" should not require a tab switch.

Base exposes an MCP server. The design constraints were set before any of it was written.

Tools are generated, not hand-written. Each readable module declares its table, an explicit column allowlist, which columns are searchable and how rows are ordered. Search and get tools are derived from that registry, which means a module becomes queryable the day it ships, and it also means there is no select-star anywhere and no table is reachable unless it is named in that one file.

There is one fusion tool on top: summarize company. It returns the structured CRM records alongside the signals attached to them, in separate blocks, with each signal keeping its status, confidence and source reference. An external agent therefore cannot present an overheard claim as a verified fact, because the payload does not let the two look the same.

Access is granted per module, never as a blanket permission. A grants table records which external client may read which modules, for which workspace, granted by which founder. Absence of a grant is a denial, not a fallback to allow.

Read-only is enforced server-side, not by convention. Every external call passes through one guard, and the guard checks the tool name against read verbs, verifies the token, resolves the caller to exactly one workspace, and looks up a live grant. The workspace-scoped database client only exists inside that guarded callback, so a tool that forgets to use the guard has no database access at all rather than having unchecked access.

Every call is logged, allowed or denied, with the client, the tool, the module, a short argument summary and the number of rows returned. That log is surfaced in settings, so you can see what an agent actually read rather than what it was theoretically permitted to read. Grants are re-read on every call, which means revoking one takes effect on the next request instead of whenever a token happens to expire.

Business identity is readable through this layer. The founder's voice text is not, because voice is the one thing that would let an external system impersonate you convincingly.

Why any of this matters

None of the above is exotic. It is provenance, reconciliation and permissions, which are old ideas from data engineering applied to the messy commercial surface of a small company.

But the discipline is the product. A system that will not overwrite your records, that shows the exact sentence behind every claim it makes, that resolves the same human across five tools before it counts anything, and that can prove what an external agent read, is a system you can leave running while you build.

The alternative is a confident assistant that is occasionally, invisibly wrong. Those are easy to build, and impossible to trust twice.

If you want to see the layers with your own data in them, start with Base and connect one tool. The backfill will tell you more about your company in an hour than a demo will.