Everre
June 2025 – present · Live, in founder-led go-to-market
AI-native CRM for commercial real estate brokers. The AI does the analyst's work; the broker keeps the relationships. Built by a broker, for the deals on my own desk.
Role
Founder. Product, design, engineering, and the customer.
Stack
I sell multifamily buildings in Chicago. The job is mostly analysis and correspondence wrapped around a small number of relationships: read the rent roll, tie it to the T-12, underwrite, write the offering memorandum, find the owners, follow up. The incumbents charge a lot of rent for antiquated tools and gatekeep public information behind subscriptions, and none of them do the analyst’s work for you.
Everre is the product I wanted on my own desk. The thesis is one sentence: the AI replaces the analyst, not the broker. Every capability has a user interface and an AI interface. A feature reachable only by clicking is half built.
What is in it
A unified, spreadsheet-style database over people, properties, and companies, with formulas, range selection, and undo. Deal pipeline. Financials and underwriting with versioned scenarios. A presentation builder for offering memoranda, flyers, and pitch decks, and published property websites on the broker’s own domain. E-signature with an ESIGN/UETA audit trail. A client portal. Email campaigns sent through the broker’s own connected Gmail or Outlook. A nationwide public property dataset of 4.16 million parcels, built with a Python pipeline over county ArcGIS and Socrata endpoints.
Around all of it, the AI layer: a capability registry that adapts service operations into scoped, risk-graded tools for agents; an MCP server and OAuth authorization server so a broker can connect Claude or ChatGPT to their data without minting a token by hand; email agents that answer at *@ai.everre.co; and usage-based billing with a spend guard that fails closed.
The rest of this page is a teardown of one piece: how a rent roll gets from a PDF into the database. It is the feature I care most about, because it is the one I do by hand on every deal.
Teardown: rent-roll ingestion
The problem from the broker’s chair
A rent roll arrives as whatever the property manager exported: a PDF spanning four pages, an Excel workbook whose first tab is a cover page, a CSV, occasionally a phone photo of a printout. The numbers in it are the inputs to everything downstream. Gross potential rent, vacancy, NOI, the cap rate you put on the cover of the offering memorandum, the price the owner hears.
The failure that shaped the design happened on a real document. An offering memorandum published NOI as $0 on one page and $50,093 on another. A tool that picks one of those and moves on has just put a wrong number in front of a buyer with my name on it. So the standing rule for the whole pipeline is:
The model reads. The code verifies. The broker commits.
The pipeline
- Gate. The property and the file are scoped to the organization, and the format is detected before download. Unknown formats are refused up front, because returning an empty rent roll reads as “this building has no units.”
- Load the source, deterministically. PDFs become one section per page. Workbooks become one section per sheet, rendered pipe-delimited rather than comma-separated so a value containing a comma cannot be mistaken for a column boundary. CSVs are split into 500-row bands with the header repeated in every band. Every section carries a locator (
page 3,sheet Rent Roll,rows 12–511) that survives to the review. - Chunk without splitting. Sections are packed into chunks of at most 40,000 characters, and a section is never split across chunks. A row broken across a boundary is a row read wrong, and section integrity is what keeps citations meaningful. An oversized section goes through alone rather than being dropped.
- Extract. One model call per chunk, with a kind-specific system prompt (rent roll, operating expenses, other income, construction) and the organization’s chart of accounts appended when one applies. The prompt forbids arithmetic: if the document prints a total, use it; if it does not, omit it. A number the model calculated appears nowhere in the document, so verification would reject the model’s own best work.
- Parse leniently. Fenced JSON, then the first object span, then the raw response. A malformed response yields zero rows and never throws. That leaves the broker exactly where they were: reading the document themselves.
- Verify every figure against the source text. Three verdicts:
verified(found in the section the model cited),wrong_section(found elsewhere in the document),unfounded(found nowhere). Matching is lenient about formatting and strict about digits:$1,420and1420.00are the same rent;1420and1450are not. The match uses a digit-boundary pattern so1420does not verify against a total of214200. There is no numeric tolerance. A figure off by one dollar is unfounded. - Surface conflicts, never resolve them. Rows are grouped by label; if two sections disagree, both values come back with their locators. A rent roll saying $1,875 and a lease abstract saying $1,650 is a question for the broker. Answering it automatically is the fastest way to lose their trust.
- Return a review. Write nothing. The response is a list of rows with verdicts, notes, and conflicts, plus a summary: accuracy is verified over total, and
trustworthyrequires every claim to check out and at least one claim to exist. An empty extraction is explicitly not trustworthy. - Commit is a separate, human call. The client sends back the rows it approved. That creates a financial version tagged
ai_extraction, then applies rows one at a time, so a T-12 with one unparseable line still imports the other thirty-nine and the broker can see which one was skipped. Figures are parsed with currency symbols, thousands separators, and accounting parentheses as negatives.
The read and the commit are split on purpose, and the server holds nothing between them, so a stale review cannot be committed by accident. The agent capability exposes only the read half. An agent that could extract and commit would collapse the two steps back into one and produce underwriting inputs nobody looked at, which read exactly like inputs somebody did.
The ingest itself
When approved units land on a property, the write has its own rules, each one written down next to the code:
- A rent roll asserts what exists; it never deletes. Units already on the property that the document does not mention are reported as
not_in_this_rent_roll, never removed. A stale unit is a discrepancy someone can resolve. A deleted one is gone because a PDF was incomplete. - Absent is silence, not null. A column the document omits leaves what is already held.
COALESCE, not overwrite. - Idempotent on (property, unit number). A redelivered email, a re-run job, or a person forwarding the same PDF twice updates in place instead of doubling the building.
- A duplicated unit is a defect in the document, not a reason to fail. Later entry wins, with a warning. Failing outright would lose 83 good units to one bad one.
- One transaction. Half a rent roll is worse than none. A document that fails on unit 61 leaves nothing behind.
- It computes nothing. No NOI, no cap rate, no average rent. Those come from the platform’s scenario model, which keeps them auditable and identical no matter which agent asked.
- An occupied unit with no rent is a warning, because it is either an unreadable cell or a concession, and the difference matters to every figure downstream.
What it costs
Every call goes through one AI gateway that meters usage and enforces caps before the provider call, not at the route, because route middleware cannot see the agentic loop or a background worker, and those spend the most. The spend guard fails closed. The generation before it failed open, and for months the caps enforced nothing in production: no error, no alert, an uncapped provider bill. Spend is now recorded unconditionally after every call rather than only when a ledger debit succeeded, because the old ordering meant a failed debit was never metered and never billed. Unknown model IDs price at the most expensive entry in the table, so a new model surfaces as a complaint about overcharging rather than as quiet margin loss.
Extraction runs on Claude Sonnet with an 8,192-token output budget per chunk. Input scales linearly with document length. Each call is tagged with the file it came from, so a document’s total cost is recoverable from the usage ledger, though nothing reports it yet.
What still breaks
This is the part I would want to hear from an engineer, so here it is.
- Photos are not wired in. The image path is designed correctly: a transcription step that produces text the verifier can check, one call per image so each line has a locator, and an explicit
UNREADABLEsentinel to distinguish “this photo is too poor to read” from “this rent roll has no units.” The transcriber is built and tested. Nothing calls it. An uploaded photo currently passes format detection, loads to zero sections, makes zero model calls, and returns an empty review. And even once wired, a verified figure from an image means “the transcription says this,” which catches the extractor inventing a number but cannot catch the transcription misreading 1450 as 1420. - No prompt caching. The gateway supports cached system prompts and the cost model accounts for them. The extraction loop sends an identical system prompt, including the full chart of accounts, on every chunk and does not use it.
- Section indices disagree. Loaders number sections from one; the prompt examples cite section zero. The verifier’s fallback search rescues correctness, but a model that follows the example literally gets
wrong_sectionon every row. - Unit numbers are matched by exact string.
1A,1-A, andUnit 1Aare three units. - No per-document ceiling. A very long PDF chunks into as many calls as it takes. The organization’s spend cap is the only backstop.
- The ingest write has no tests. The pipeline above it has a hundred. The dedupe, the
COALESCEsemantics, and the not-in-this-roll report are covered by nothing but the comments that explain them. - It was finished and unreachable for a while. The extraction was complete and tested with no UI and no agent capability registered, so nothing could call it. That is the failure mode specific to this codebase, and the reason the capability registry now refuses to boot with an empty catalogue.
The platform around it
The rent-roll pipeline is one of 105 capabilities, and the rules that govern it are the same rules that govern all of them.
Capabilities, not tools
Nothing is registered by being written. A service operation becomes an agent capability by being named in a table with a scope, and an operation with no row is invisible to every agent until someone writes down what it may do. Risk is derived from the verb (read, create, delete) and a custom operation throws rather than defaulting, because guessing in the safe-looking direction is how something that sends email ends up running unattended. Five grades, with the current census:
| Grade | Count | What it means |
|---|---|---|
| read | 41 | Runs. |
| write | 40 | Runs, counted against a per-run budget of 40 write calls. |
| review | 7 | Runs, then hands the result to a person before it is final. |
| irreversible | 15 | Does not run until a person approves the exact arguments. |
| outbound | 2 | Same, for anything that leaves the building: sending mail, requesting a signature. |
The review grade exists so that “a human must see this” and “this cannot be undone” stop being the same claim. A tool description that lies is worse than one that is vague.
Policy runs in a fixed order: scope first, so a caller refused for scope cannot infer whether the call would also have needed approval; then aggregate scale, so 40 writes in one run stops for review before any per-call risk is considered; then the grade. One rule I am proud of: on a run that has read third-party content (a forwarded rent roll, an attachment), an irreversible or outbound call approved with a one-word “send” is escalated to the review queue, where the arguments are visible. Only an approval where the call and its arguments were on screen counts as reviewed. And approval resumes the exact validated call; it never re-plans it, because giving the model another turn between the person reading and the thing happening makes the approval a lie.
There is exactly one sanctioned entry point for executing a capability, and a test enforces it by walking every file in the tree and failing if anything else names the executor. It exists because a new streaming chat path had already taken the shortcut once. At startup, the bootstrap reports operations with no definition and definitions pointing at operations that no longer exist, and refuses to serve traffic with an empty registry.
MCP and OAuth
Sixteen of those capabilities are exposed over MCP, so a broker can attach Claude or ChatGPT to their own data. The MCP server holds real sessions scoped to the token’s organization, so a leaked session id is useless with another org’s token, and the session carries the conversation’s running write count so the 40-write budget spans the whole conversation rather than one tool call. Every tools/call is metered; a client at its cap can still read its own error. Gated actions use elicitation: the response becomes a stream, the question renders every argument verbatim, and the answer arrives on a different HTTP request and is routed to whichever replica holds the paused call. A dismissed dialog is not consent.
In front of it is a full OAuth 2.1 authorization server: PKCE with S256 only, dynamic client registration and client-ID metadata documents so Claude connects without anyone minting a token by hand, a consent screen that shows the redirect hostname because any local process can bind a port and claim to be Claude Code, replayed authorization codes that revoke the grant they produced, refresh tokens rotated on every use. The app’s own login JWT is never accepted at the MCP endpoint, because that is exactly the token-passthrough pattern the spec forbids.
Email agents
Each user gets an address at *@ai.everre.co, handled by a Cloudflare Email Worker. Mail must pass SPF, DKIM, and DMARC, verified against the topmost authentication header so a forged copy appended by the sender is ignored, and the sender must be one of the owner’s verified addresses; anyone else is bounced, not processed. The owner’s words are a trusted instruction. The attachments are not. They are fenced and labelled as untrusted documents, read independently so one unreadable scan does not cost the rent roll beside it, and the run is marked as having touched third-party content, which is what feeds the escalation rule above. The code is explicit that this fencing is not the security boundary. The boundary is at the tools, because the same agent is reachable from email, chat, and MCP, and a control that only sits on the email path is a speed bump on one road.
Anything the agent drafts becomes an approval with a 48-hour token; replying “send” approves it, and every recipient is checked against the organization’s own CRM before the single-use token is spent. Sending goes through the user’s own connected mailbox, not the agent’s address.
Metering
Seven metered resources (AI, signatures, storage, websites, email, API, MCP) report to Stripe meter events, with a free floor per resource and idempotency on the event so a retry cannot double-bill. AI is metered in provider-cost cents at a fixed markup. Caps apply to total spend across every meter, because an account that could spend its ceiling on AI and again on signatures has no ceiling. One known gap: the MCP meter is defined but omitted from two lists, so MCP spend counts toward caps but does not yet show on the usage screen or get its Stripe meter created automatically.
Go-to-market, honestly
I ran founder-led outbound: roughly 190 cold emails to brokers. Clicks, no conversions. A launch-readiness audit thirty days before the planned public launch put it plainly: Everre was not a thin product looking for a story; it was a wide product with a narrative problem and three plumbing problems. Data import returned an error on every attempt, there were no product analytics of any kind, and the nationwide property data the anti-incumbent thesis rests on was not reachable from the product. All three were invisible to the test suite.
The narrative fix was to stop selling an all-in-one CRM, which is the crowded fight and the weakest part of the product, and lead with the thing nobody credible is shipping: the AI analyst and the open API. That repositioning is in progress.
How it was built
Every line went through Claude Code, with me as product manager, reviewer, and the person who decides. The repo’s working agreement is short and specific: deliver the scope asked for, validate at boundaries and trust internal callers, no abstractions for hypothetical futures, and cleanup belongs in the change, not a follow-up ticket. That last rule exists because an August 2026 sweep deleted 20,000 lines nothing referenced, including a 6,900-line duplicate schema and four complete features that could never run.
The discipline that mattered most was writing the “why” next to the code. Almost every rule in the teardown above is a comment in the source explaining the incident that produced it. That is what makes it possible for an agent to keep the invariants a broker cares about, and it is the same thing I would want from any engineering team I worked with.