{"slug": "crm-an-open-source-agentic-first-crm", "title": "CRM: An open-source, agentic-first CRM", "summary": "TryComp AI released CRM, an open-source, agentic-first CRM where a durable research agent is the core product and the database serves as its notes, built on Vercel's eve framework. The agent operates autonomously on its own schedule and budget, and the system enforces a strict rule: nothing about a person is guessed, with tools reporting only observed evidence and a ledger pricing it to distinguish strong from weak evidence.", "body_md": "**An open-source, agentic-first CRM.**\n\nA durable research agent is the product. The database is just where it writes things down.\n\n[ The agent](#the-agent) ·\n\n[·](#the-stack)\n\n**Stack**[·](#quick-start)\n\n**Quick start**[·](#configuration)\n\n**Configuration**[·](#deploying)\n\n**Deploying**\n\n**Contributing**\n\nMost CRMs are a database with a form in front of it. The AI ones bolt a chat box onto the side of that form. Both leave the actual work — finding out what is true, and writing it down — to a human who has better things to do.\n\nThis is built the other way round. **The agent is not a feature of the CRM; the CRM is\nwhere the agent keeps its notes.** It runs on its own deployment, on its own schedule,\nagainst its own work queue. It decides what to look at next, books its own follow-ups,\nspends a research budget, and stops when the budget runs out. Nothing about it is\nrequest-response: close the browser and it keeps going.\n\nThe API deliberately has no intelligence in it at all. NestJS reports that *something\nhappened* — a thread was ingested, a company was created, an attendee is unknown — by\nwriting a row to a queue. The agent leases that row and decides what it means. A Nest\nservice that calls an enrichment API is treated as a bug, and\n[ docs/api.md](/trycompai/crm/blob/main/docs/api.md) explains the outage that made that a rule.\n\nThe rule the agent itself never breaks: **nothing about a person is guessed.** No tool\naccepts a confidence score, because a model asked to grade its own certainty will, and\nit will be wrong in the direction that makes it look useful. Tools report what they\n*observed* — `crm.signature-block`\n\n, `github.account-identity`\n\n— and a ledger prices the\nevidence. Strong evidence writes to the record. Weak evidence becomes a suggestion a\nhuman settles. A confidently wrong fact about a customer is worse than a blank field,\nbecause nobody can tell it is wrong.\n\nIt is single-tenant and internal by design. Sign-in is Google, the allow-list is one\nenvironment variable, and everyone who gets in can see everything. That is the whole\nauthorisation model — see [SECURITY.md](/trycompai/crm/blob/main/SECURITY.md) before you point it at real\ncustomer data.\n\n|\n|\n|\n|\n|\n|\n\n[ apps/agent](/trycompai/crm/blob/main/apps/agent) is its own deployment, built on\n\n[— Vercel's filesystem-first framework for durable agents. A tool is a file, a skill is a markdown file, a schedule is a file, and the runtime handles the durable part: sessions that survive a redeploy, work that resumes where it stopped.](https://eve.dev)\n\n**eve** 18 authored tools |\n`read_crm_history` , `search_crm` , `identify_contact` , `research_person` , `enrich_company` , `record_fact` , `schedule_recheck` … |\n4 skills |\n`evidence.md` , `identity-matching.md` , `data-boundaries.md` , `writing-a-brief.md` — prose the agent reads, versioned like code |\n1 schedule |\n`dispatch.ts` , which decides nothing. It leases what is due and starts a session per row. |\nA sandbox |\n`bash` , `grep` , `glob` and a `/workspace` , with `deny-all` egress |\n\n**It runs itself.** `lib/tasks.ts`\n\nis the work queue: `claimDue`\n\nleases rows with\n`FOR UPDATE SKIP LOCKED`\n\n, so two dispatchers take disjoint work and a run that dies\nfrees its row when the lease expires. Anything that looks like \"every N minutes, the\noldest ten contacts\" belongs in a task's `dueAt`\n\n, not in a cron expression. When the\nagent wants another look at somebody it calls `schedule_recheck`\n\nand says why — and\nthe reason is shown to the rep, because an agent that cannot say why it will be back\nin fourteen days does not have a reason, it has a default.\n\n**Every outside source is optional, and it is designed to run with none of them.**\nWith no API keys at all it still works: `read_crm_history`\n\nreads your own threads,\nmeetings and signature blocks, which is free and is the best evidence there is — no\ndata vendor can sell you a reply from the person's own address. Each key opens one\nmore place to look. It is told at the start of every session which ones this install\nhas, so it plans around what it actually has rather than discovering the gaps one\nfailed call at a time, and it prints the list at startup:\n\n```\n[agent] on   LinkedIn (RAPIDAPI_KEY)\n[agent] off  Web research (PERPLEXITY_API_KEY)\n[agent] off  Company brand data (CONTEXT_DEV_API_KEY)\n```\n\n**The sandbox has no network and no database.** Turning it on is what gives the model\na shell — the difference between a tool-caller and something that can keep a dossier,\ndiff this month's profile against last month's, and grep a thread for a signature\nblock. `deny-all`\n\negress costs nothing, because `web_fetch`\n\nruns in the app runtime\nand `web_search`\n\nat the model provider. What it removes is the only path by which a\ncustomer's email body could leave through a shell command. The other half of that rule\nis an absence: **the sandbox is never given DATABASE_URL.** A shell with credentials\nand egress is exfiltration-shaped even in an internal tool; a shell with neither is a\ntext processor.\n\n**You can talk to it, and watch it work.** Every contact, company and deal has an\n**Agent** tab — the steps as it takes them, the leads it throws away and why, and its\nquestions answered in place when it cannot decide between two people. Conversations\nare durable and survive a reload; the record travels in a signed token rather than\nbeing bolted onto the front of your message. Set `AGENT_BRIDGE_SECRET`\n\nto the same\nvalue in both processes to turn it on. Without it the tab reports that it is not\nconfigured, and the agent carries on running its own schedule.\n\n[ docs/agent.md](/trycompai/crm/blob/main/docs/agent.md) is the full write-up.\n\nA [Turborepo](https://turborepo.dev) monorepo on [Bun](https://bun.com), deployed on\n[Vercel](https://vercel.com).\n\nAgent |\n|\n\n**Model**[Vercel AI Gateway](https://vercel.com/docs/ai-gateway)— no provider SDK, and OIDC on Vercel means no key to manage** Sandbox**[Vercel Sandbox](https://vercel.com/docs/vercel-sandbox)in production, Docker or microsandbox locally** Front end**[Next.js](https://nextjs.org)App Router ·[shadcn/ui](https://ui.shadcn.com)·[nuqs](https://nuqs.dev)for URL state** API**[NestJS](https://nestjs.com)with[nestjs-trpc](https://nestjs-trpc.io)— HTTP, auth, tRPC, Google sync** Data**[Prisma](https://prisma.io)· Postgres ([Neon](https://neon.tech)) · optional Redis ([Upstash](https://upstash.com))** Auth**[Better Auth](https://better-auth.com), Google-only, one allow-list** Files**[Vercel Blob](https://vercel.com/docs/vercel-blob)— mirrors profile pictures so they survive the source going away** Tooling**[Biome](https://biomejs.dev)· TypeScript everywhereThe app talks to the API over **tRPC**, and the router type is generated from the\nNestJS routers — so the front end is type-safe from the Prisma row to the table cell.\nList state (filters, sort, page) lives in the URL, so copying the address bar\nreproduces the view.\n\n| Path | |\n|---|---|\n`apps/agent` |\nThe research agent — tools, skills, schedules, sandbox |\n`apps/app` |\nNext.js front end · :3000 |\n`apps/api` |\nNestJS API — HTTP, auth, tRPC, Google sync · :3001 |\n`packages/db` |\nPrisma schema, migrations, shared Postgres client |\n`packages/auth` |\nBetter Auth config and the sign-in allow-list |\n`packages/ui` |\nshadcn/ui components, the Tailwind theme |\n`packages/env` |\nFinds and loads the root `.env` |\n\nWritten up where the work happens, not in a style guide:\n\n**Intelligence never lives in the API**([docs/api.md](/trycompai/crm/blob/main/docs/api.md)). Nest reports that something happened; the agent decides what it means. Two copies of an identity matcher once drifted until one matched every employer on earth.(`packages/ui`\n\nis the only source of UI[docs/design.md](/trycompai/crm/blob/main/docs/design.md)). No overriding styles at the call site.**There are no organizations.** Single tenant, deliberately. An`organizationId`\n\nthat is always the same value is a column, an index and a permissions check that buys nothing and reads like a real one at review time.\n\nYou need [Bun](https://bun.com) and Docker.\n\n```\ngit clone https://github.com/trycompai/crm.git && cd crm\nbun install\n\ndocker compose up -d          # Postgres on :5432\ncp .env.example .env          # then fill in the four values below\n\nbun run db:deploy             # apply migrations\nbun run db:seed               # optional: a believable pipeline to look at\nbun run dev\n```\n\nThe app is on [localhost:3000](http://localhost:3000), the API on\n[localhost:3001](http://localhost:3001).\n\nOpen `.env`\n\nand set these. Everything else in the file is optional and commented out.\n\n| Variable | What to put in it |\n|---|---|\n`BETTER_AUTH_SECRET` |\n`openssl rand -base64 32` |\n`ALLOWED_SIGN_IN` |\nYour email domain, e.g. `acme.com` . Or one address, e.g. `you@gmail.com` . |\n`GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` |\nA Google OAuth client — 2 minutes, below. |\n\n`DATABASE_URL`\n\nalready matches the `docker compose`\n\nPostgres, so leave it alone unless\nyou brought your own.\n\n**Getting the Google OAuth client**\n\n[Google Cloud console](https://console.cloud.google.com/apis/credentials)→**Credentials**→** Create credentials**→** OAuth client ID**→** Web application**.- Under\n**Authorised redirect URIs**, add`http://localhost:3001/api/auth/callback/google`\n\n. - Enable the\n[Gmail API](https://console.cloud.google.com/apis/library/gmail.googleapis.com)and the[Calendar API](https://console.cloud.google.com/apis/library/calendar-json.googleapis.com)for the project. - Copy the client ID and secret into\n`.env`\n\n.\n\nGoogle sign-in is the only way in, so the API will not start without these. If your\naccount is on a Google Workspace domain, set the consent screen to **Internal** and\nnobody outside your org can even reach the prompt.\n\n`ALLOWED_SIGN_IN`\n\nis the entire authorisation model — an unset value means nobody can\nsign in, which is the safe direction to fail. It takes whole domains, individual\naddresses, or a mix:\n\n```\nALLOWED_SIGN_IN=\"acme.com\"                       # everyone at your company\nALLOWED_SIGN_IN=\"acme.com,contractor@gmail.com\"  # …plus one outsider\nALLOWED_SIGN_IN=\"you@gmail.com\"                  # a one-person install\n```\n\n**There is one .env, at the root of the repo**, read by all three processes. Real\nenvironment variables always win, so on a hosting platform you configure it there and\nthe file is purely a local convenience.\n\nBeyond the four required values, everything is optional and the app runs without any\nof it. [ .env.example](/trycompai/crm/blob/main/.env.example) is the full list with a note on each; the\nshort version:\n\n`API_URL` / `APP_URL` |\nWhere the two halves are served. Only needed off localhost. |\n`PERPLEXITY_API_KEY` |\nLets the agent search the open web, with citations. |\n`RAPIDAPI_KEY` |\nLets the agent read LinkedIn profiles for identity. |\n`CONTEXT_DEV_API_KEY` |\nCompany logo, industry and socials from a domain. |\n`AGENT_BRIDGE_SECRET` |\nLets a rep talk to the agent from a contact's Agent tab. |\n`REDIS_URL` |\nA shared cache. Without it, per-instance and in-memory. |\n`CRON_SECRET` |\nGuards the Gmail/Calendar sync route. Required to use it. |\n\n| Command | |\n|---|---|\n`bun run dev` |\nEverything, in watch mode |\n`bun run build` |\nBuild all apps and packages |\n`bun run test` |\nRun the test suite |\n`bun run check-types` |\n`tsc --noEmit` everywhere |\n`bun run lint` / `format` |\n|\n\n`bun run db:migrate`\n\n`bun run db:seed`\n\n`bun run db:studio`\n\n`bun run --filter=api trpc:generate`\n\n`AppRouter`\n\ntype`bun run --filter=api dev:session`\n\nScope any of them with a Turborepo filter: `bun run dev --filter=api`\n\n.\n\nBecause Google is the only door, there is no way to get a session from a terminal —\n`dev:session`\n\nwrites the rows Better Auth would have written and prints the cookie it\nwould have set. It refuses to run with `NODE_ENV=production`\n\n.\n\nThree deployments and a Postgres: the Next.js app, the NestJS API, and the agent.\nThey are independent, and the only thing they must agree on is `DATABASE_URL`\n\nand\n`BETTER_AUTH_SECRET`\n\n— the API mints the session cookie and the app verifies it, so a\nmismatch is a redirect loop rather than an error.\n\nSet `API_URL`\n\nand `APP_URL`\n\nto the real origins, and if the two are on different\nsubdomains of one parent, set `AUTH_COOKIE_DOMAIN`\n\nto the parent so one cookie covers\nboth. Add `http://your-api-host/api/auth/callback/google`\n\nto the OAuth client's\nredirect URIs. Set `CRON_SECRET`\n\nand point a scheduler at\n`POST /internal/sync/google`\n\nto keep the mailbox sync running.\n\n`apps/api/src/generated/server.ts`\n\nis committed and `build`\n\nmust never regenerate it —\nthe generator needs a newer GLIBC than most build images have. Regenerate locally and\ncommit it with the router change that caused it.\n\nWe'd rather have a paragraph you wrote than a pull request an agent wrote. See\n[CONTRIBUTING.md](/trycompai/crm/blob/main/CONTRIBUTING.md).\n\nSecurity issues go through [SECURITY.md](/trycompai/crm/blob/main/SECURITY.md), privately, not a public\nissue.\n\n[MIT](/trycompai/crm/blob/main/LICENSE).", "url": "https://wpnews.pro/news/crm-an-open-source-agentic-first-crm", "canonical_source": "https://github.com/trycompai/crm", "published_at": "2026-08-02 06:59:28+00:00", "updated_at": "2026-08-02 07:22:23.133617+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents", "ai-products", "ai-infrastructure"], "entities": ["TryComp AI", "Vercel", "eve", "CRM"], "alternates": {"html": "https://wpnews.pro/news/crm-an-open-source-agentic-first-crm", "markdown": "https://wpnews.pro/news/crm-an-open-source-agentic-first-crm.md", "text": "https://wpnews.pro/news/crm-an-open-source-agentic-first-crm.txt", "jsonld": "https://wpnews.pro/news/crm-an-open-source-agentic-first-crm.jsonld"}}