Inspiration
Every CRM makes you describe your business before it lets you record anything. You pick an object model on day one — Lead, Account, Opportunity — and spend the next two years bending reality to fit it. A dental group with three clinics and a logistics firm with forty-two trucks get the same twelve fields, so the part that actually matters ends up in a free-text notes box that nothing can query.
Small businesses and agencies feel this hardest: they abandon CRM setup because the setup comes before the value. I wanted to flip the order — record first, and let the structure follow.
What it does
Formless is a CRM that builds its own database. You paste an email — greeting, signature and all — and it works out what the record is, creates the columns it needs, and files it. You never design a schema.
- The schema grows to fit the data. The first lead creates a
leadstable. An email from a different industry reuses the columns that already mean the same thing and adds only what is new. - It doesn't sprawl. In the demo, three emails describe their sites as "three clinics", "six depots" and "eighteen distribution centres". Formless recognises one fact behind three words and files them all in a single
locations_countcolumn: 3, 6, 18. - You can question a database you never designed. "Which leads have a budget over 5000?" is planned against the live schema and answered — two of three match.
- The same company twice is one record. A second email from a known contact updates it instead of filing a duplicate.
- Different kinds of things get their own tables. A support ticket is not a lead, so it gets a table of its own.
- Every visitor gets a private workspace, scoped by cookie rather than a sign-up wall — a product that works before you configure anything shouldn't open with a registration form.
The paste box is one client. The product is the endpoint behind it: point an inbox, a form or a ticket system at POST /api/ingest and the database builds itself the same way.
How I built it
Evorozen Neural Pulse is the only datastore. There is no Postgres, no Supabase, no local file. Business records, the schema catalogue and workspace isolation all live in the Virtual Database and are read back out of it.
Neural Pulse registers tables into LivingDNA but has no "describe schema" action, so the catalogue lives inside Neural Pulse as an append-only journal — one row per growth event. Folding it in time order yields the live schema of each table:
$$\text{schema}T(t) = \operatorname{dedupe}{\text{name}}\Big(\bigcup_{e \in J,\ e.\text{table} = T,\ e.t \le t} e.\text{columns}\Big)$$
Reasoning runs in two model passes. An architect reads the message alongside the live schema and proposes a table, the missing columns and the record. A reviewer then challenges every proposed column — merging near-duplicates into columns that already exist and dropping incidental noise — before anything is written. The model never writes: it proposes, and the server decides what reaches the database.
One Zod schema drives every model provider — Gemini 3.8 Flash in production, with Claude supported through the same schema — so adding a provider is one function, not a second schema to keep in sync.
The stack is Next.js 16, React 19, strict TypeScript and Tailwind CSS v4, deployed on Vercel, with 57 Vitest tests that never touch the network.
Challenges I ran into
Writes to Neural Pulse are not atomic. I measured it rather than assuming:
concurrent 5 issued → 9 persisted [1,1,1,2,3,3,3,4,5]
sequential 5 issued → 5 persisted [1,2,3,4,5]
Duplicates and losses — a read-modify-write race on the kernel side. My first catalogue wrote one row per column in parallel and silently lost two columns. The fix: writes are queued per table (different tables stay parallel, which I verified separately), and the journal's fold dedupes by name, so a duplicated row folds back into one column instead of corrupting the schema.
The bundled LLM was down. Every call to Neural Pulse's chat action returned All LLM providers failed. The data plane was solid, so reasoning moved to an external model and all state stayed in Neural Pulse.
100 calls a month, with no way to buy more. The upgrade link returns a 404, so the allowance is a hard ceiling. That shaped the architecture: growing a table costs one journal write instead of one per column, page loads are held for 60 seconds, a brand-new workspace is known to be empty so it is never queried, and an activity table that nothing rendered was deleted. When the allowance runs out, the app serves the last real state, labelled "not live", instead of blanking the page.
select_data only matches on equality. There is no > on the wire, so equality conditions are pushed down into the query, and comparisons, sorting and limits are evaluated on the returned rows — still one call.
Number("") is 0. Coercing an unreadable budget stripped every non-digit, leaving an empty string that became zero — finite, so it passed the guard. A lead with an unparseable budget would have been filed as a budget of zero. The test suite caught it on its first run.
Accomplishments that I'm proud of
- A schema nobody designed that stays small: three industries, three different words for "locations", one column.
- Every claim on this page was measured against the live API, not assumed.
- It degrades honestly — rate-limited or offline, it says so and still shows the last real state.
What I learned
Measure an API before trusting it. The race condition, the dead chat endpoint and the equality-only query language were all invisible from the docs and obvious within minutes of testing.
I also learned that the most useful thing to give the model was the live schema itself. With it in front of the architect, clinics, depots and distribution centres landed in one column without anyone telling it to.
What's next for Formless
- A forwarding address — the real wedge. No onboarding, no drop-off, and it spreads every time someone CCs a colleague.
- A distributed lock, so write serialization holds across a horizontally scaled fleet, not just one instance.
- Accounts that adopt a cookie workspace, so a trial becomes a team without re-importing anything.
- Per-record pricing, because the value is in how much gets structured, not how many people log in.
Built With
- anthropic
- evorozen
- gemini
- google-genai
- neural-pulse
- next.js
- node.js
- react
- tailwindcss
- typescript
- vercel
- vercel-analytics
- vitest
- zod
Log in or sign up for Devpost to join the conversation.