CRM
CRM in sidanclaw is people, companies, and deals: the durable graph of who the team talks to, where the relationship stands, and what it's worth. First-party so the brain reads and writes contacts the same way it reads memories: same database, no translation layer. Attio / HubSpot are sync targets, not the primary surface.
Three tables, one schema
Contacts (people), companies (organizations), deals (opportunities). Frozen v1: eight columns max per table plus timestamps. No custom fields, no priority, no description. Cross-workspace links are blocked by triggers, not just FKs. Tags are TEXT[] with a GIN index; external_ref JSONB is a free-form passthrough for synced rows.
Deal stages
Six values, locked: lead, qualified, proposal, negotiation, won, lost. Adding a stage requires a migration plus a tool-description update plus an analytics taxonomy update, by design. No custom pipelines in v1.
Amounts and dates
amount is NUMERIC(15,2) decimal dollars (USD-equivalent), not cents. Users type "50000", not "5000000". close_date is a calendar DATE; "Q3 close" isn't a wall-clock instant. Currency is implicit USD; a currency_code column ships when multi-currency does.
Chat tools
Every assistant with the crm capability gets the full CRUD surface plus advanceDealStage as the canonical stage-transition verb (separate from updateDeal, which won't accept stage). No delete tools in v1: soft-delete is advanceDealStage(id, 'lost') and field-nulling.
saveContact / getContact / listContacts / updateContact · saveCompany / getCompany / listCompanies / updateCompany · saveDeal / getDeal / listDeals / updateDeal / advanceDealStage
Relationships across the brain
Every CRM row is an entity in the underlying graph. Save a memory about a contact, link a task to a deal, or open an explicit edge via the universal links param: the entity rollup (getEntity) returns the contact, every memory anchored to it, and the deals it's attached to in one call.