Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(conversation): de-circularise the vendor naming rule

closed#2700CedarCopilot

CedarCopilot wants to merge 2 commits into staging from fix/vendor-conversation-name-circularity

Not deployedTimeline and evidence
  1. Opened
    Sep 6, 2026, 2:52 PM
  2. Sep 6, 2026, 3:11 PM
  3. Merged, live on staging, live on prod, observed
    Pending
  4. Not deployed

    Not deployed

    Pending

Libra has no production signal for this change yet because it has not deployed. Libra checks hourly for 3 days after each deploy.

Step 2 of the F8 workstream (docs/design/f8-vendor-conversation-routing.md). Step 1 was the domain-identification instructions; this one is the field description that decides what a conversation is called.

The circularity

core-fields.ts:56, the name description handed to the CRM field updater, ended:

Exception: if the conversation is associated with the domain of a single vendor tool and is categorized as Vendor notification (e.g., Zoom, Gong, ChiliPiper), name it after that vendor only (e.g., "Zoom").

The exception is gated on the conversation already being classified as a vendor conversation. So it names a conversation after the vendor only once something else has decided it is a vendor conversation , and when that decision is wrong, the rule that would repair the name never fires. What is left is a conversation whose primary company domain is a vendor's while its NAME is a customer's, which is exactly the record that makes domain-based vendor lookup impossible downstream.

Four production examples, queried 2026-09-06. Three re-confirmed verbatim against the live DB while preparing this PR; the fourth is there under its full name, KBMCo (Keen Battle Mead & Company):

conversation nameprimary domaininbound sendersAOP
Broadband Hospitalityfathom.videofathom.video:289, usepylon.com:4 (owner's own)Deals
Cantodocusign.netdocusign.net:57, account.docusign.net:2Deals
KBMCo (Keen Battle Mead & Company)fathom.videofathom.video:22Deals
SingleStoremg.ironcladapp.comironcladapp:41Deals

None of them holds a single email from the company it is named after.

They are not isolated. A probe of eight vendor-tool domains , fathom.video, zoom.us, calendly.com (on VENDOR_DOMAINS) and docusign.net, ironcladapp.com, mg.ironcladapp.com, otter.ai, pandadoc.net (not) , finds 31 conversations carrying a deal-shaped status, 25 of them named after something other than the tool.

The fix

Drop the and is categorized as Vendor notification clause. The condition becomes two things the agent can read off the thread in front of it: the conversation's own domain belongs to a vendor tool, and its inbound mail is that tool's automated notification stream. Plus the prohibition that was missing entirely , a company that is only the SUBJECT of the messages, with nobody from it writing, must never become the name.

Two sentences hold the rule back from over-reaching in the other direction, and both are carried by a negative control below. A vendor's domain is also the domain of every genuine deal with that vendor, so a real person writing from gong.io to buy your product must still anchor a Gong DEAL rather than a bucket , the line domain-identification.ts:41-60 already draws ("Real human at the same domain → NORMAL external party"). And the prohibition keys on nobody from that company writing, not on where the name appears: a first draft said "never from a company that appears only in a subject line or a message body", which also blocked the signature of a correspondent on a consumer mailbox , the one place their company is ever stated. Greptile caught that; it is fixed and now has a control of its own.

Why the rule is NOT also copied to the domain-identification agent

Deliberate, on three grounds:

  1. It has no naming output to bind. That agent returns conversationId | primaryDomain | reasoning | clean (buildDomainIdentificationSchema). A naming rule placed there is inert prose.
  2. It already carries the same discriminator, at domain-identification.ts:41-60 , automated recap/notification sender versus a real human at the vendor's domain. Restating it in a second file gives us two copies to drift apart, and this rule's whole failure mode was a condition that stopped matching reality.
  3. **The name/domain
Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/server/.claude/skills/playbook-instruction-eval/SKILL.mdno production surface mapped
  • apps/server/.claude/skills/playbook-instruction-eval/conversation-name-eval.tsno production surface mapped
  • apps/server/.claude/skills/playbook-instruction-eval/fixtures/conversation-name-cases.tsno production surface mapped
  • apps/server/src/mastra/utils/context-formatting/__tests__/core-field-descriptions.test.tsno production surface mapped
  • apps/server/src/mastra/utils/context-formatting/core-fields.tsno production surface mapped