Skip to content

Repository files navigation

folo

One customer record your AI agents can actually write to.

folo answers one question well: who is this person, and what has passed between us?

It stores the part of a customer relationship that exists nowhere else — conversations, support requests, promises you made, preferences they stated, and links to the changelog items or feature requests that concern them. Agents read it before replying and write to it the moment anything happens.

Runs entirely on Cloudflare's free tier. No VPS, no container, no database server.


What folo is not

folo does not mirror Stripe or your product databases.

Money lives in Stripe. Product usage lives in the product. folo stores the identity that ties them together — a person's Stripe customer id, their team id in each product — so an agent can find someone here and then query those systems live.

This is the whole design bet. A copy of billing data starts drifting the day after you write it, and the CRM slowly becomes a place people stop trusting. An index doesn't drift.


The shape of an agent's work

  1. Something arrives with one string attached — an email address, a Stripe id → find_customer
  2. Read everything known about them before replying → get_customer
  3. Write down what just happened → log_entry
  4. Close it when it's done → resolve_entry

Every other tool supports those four.


Data model

Org A company you deal with. Matched on domain first, then name.
Person A human you deal with, optionally at an Org.
Identity The same human's id in another system — Stripe, a product, GitHub, an email address. The join key.
Entry One thing that happened, on a timeline.
Preference Current state rather than history: "prefers WhatsApp", "no calls before 10am".

Two entry kinds carry state — support_request (something they need) and promise (something you said you'd do). Both stay open until resolved, and both show up in the Inbox and the daily digest. Everything else (conversation, preference, feedback, decision, lifecycle, link, note) is a plain record.

The Inbox sorts oldest first. The thing that has been waiting longest is the thing most likely to cost you a customer, and newest-first buries it forever.


Architecture

Browser ──── /api/*  (session cookie) ─┐
                                       ├──► WorkspaceDO  (SQLite, source of truth)
Agents  ──── /mcp    (Bearer token) ───┘         │
                                                 └─► WebSocket broadcast to open tabs
D1: workspaces, members, api_tokens — auth only, never customer data

One Durable Object per workspace, not per customer. A CRM's hot questions are all cross-customer — "who has an open request?", "search everything for this phrase" — so keeping the whole workspace in one DO makes each of them a local indexed SQL query instead of a fan-out across thousands of objects. It also removes the need for a denormalized search index in D1, which is the component most likely to silently drift out of sync.

The cost is that all writes for a workspace serialize through one single-threaded object. That is the feature: two agents upserting the same customer from two different conversations cannot interleave, so upsertPerson's match-then-write is atomic by construction with no locking anywhere in the codebase.

Search is plain LIKE rather than FTS5. At CRM scale (thousands of rows, not millions) an indexed scan inside one DO is already sub-millisecond, and FTS would mean a second copy of every body of text to keep in sync.


Self-host

git clone https://github.com/lomeyollc/folo && cd folo
npm install

npx wrangler d1 create folo          # paste the id into wrangler.jsonc
npm run d1:migrate:remote

npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put SESSION_SECRET   # openssl rand -base64 48

npm run deploy

Then set routes in wrangler.jsonc to your own hostname (or delete it and use the *.workers.dev URL), and add that URL to your Google OAuth client's Authorized JavaScript origins — sign-in fails silently otherwise.

Optional: TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID turn on the new-request ping and the daily digest of what's still open. Leave them unset and notifications are simply skipped.


Connect an agent

Create a Bearer token in Settings, then point any MCP client at https://<your-host>/mcp. Stateless streamable HTTP — every call authenticates itself, no session handshake.

{
  "mcpServers": {
    "folo": {
      "type": "http",
      "url": "https://folo.example.com/mcp",
      "headers": { "Authorization": "Bearer folo_…" }
    }
  }
}

Claude Code:

claude mcp add --transport http folo https://folo.example.com/mcp \
  --header "Authorization: Bearer folo_…"

A token acts only inside workspaces its creating human is an active member of — it never has a wider view than the person who made it.

Tools

find_customer · get_customer · list_customers · search · upsert_customer · upsert_org · list_orgs · add_identity · delete_identity · log_entry · list_entries · list_open · resolve_entry · reopen_entry · delete_entry · set_preference · list_preferences · list_workspaces · workspace_stats


Importing an existing CRM

POST /api/workspaces/<id>/import takes batches of orgs, people and entries and runs them through the same upsert paths as every other write — so running an import twice does not duplicate anything, which matters because a partial run is the normal outcome of a large import.

scripts/migrate-folocrm.py is a working reference implementation (Laravel + D1 → folo, ~3,200 rows). The useful part is the payload shape and what it deliberately doesn't carry over: plan, MRR and trial dates went in as one dated note rather than live fields, with the product ids stored as identities so an agent queries the real source instead.

That route accepts a session cookie or an agent Bearer token — a migration is run from a script, and requiring a browser cookie for it just makes people paste their data somewhere worse.


Stack

Cloudflare Workers · Durable Objects (SQLite) · D1 · React 19 · React Router · Tailwind v4 · Vite · TypeScript · @modelcontextprotocol/sdk

Sibling project: Hive, the same stack applied to task boards.

MIT © Lomeyo LLC

About

Open-source, AI-agent-native CRM. One customer record your agents can write to. Cloudflare Workers + Durable Objects + D1.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages