Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(crm): ambiguous meeting company-assignment , record, don't discard

open#2779CedarCopilot

CedarCopilot wants to merge 4 commits into staging from fix/meeting-conversation-ambiguity

Live on prod, 3 of 10 surfaces working, less than a day leftTimeline and evidence
  1. Opened
    Sep 14, 2026, 5:48 AM
  2. Sep 14, 2026, 6:34 AM
  3. Live on prod
    Sep 14, 2026, 11:26 AM
  4. Observed 1 hour, 10 surfaces, 203 requests
    Sep 14, 2026, 11:26 AM
  5. Watching

    Live on prod, 3 of 10 surfaces working, less than a day left

    Sep 14, 2026, 11:26 AM
  6. Pipelines steady after this deploy
    Sep 14, 2026, 11:26 AM
  7. Merged
    Pending

Behaviors Libra is checking

The ambiguity notification presents the meeting title, time, participants, and provider summary with a single Find it action.Not checked
prod
Webhook redeliveries of the same unattributed meeting do not send duplicate ambiguity notifications.Not checked
prod
Each newly persisted unattributed meeting sends a meeting-specific Slack ambiguity notification to the user.Not checked
prod
Using Find it lets the investigation agent search for the correct company or conversation and move the persisted meeting event into the resolved conversation.Not checked
prod
Meeting event objects now carry a nullable backend-only ambiguityPingedAt field initialized to null so ambiguity-ping bookkeeping does not surface in the UI.Not checked
prod
For meetings without a real external participant, the domain-identification agent no longer treats the recording or meeting platform domain as the counterparty or as an automated sender.Not checked
prod

Libra measured 3 of 10 production surfaces on prod. 2 surfaces had under 20 requests, so Libra has not judged them; 5 surfaces had no requests at all. Libra checks hourly for 3 days after each deploy.

Summary

Three confirmed Pirros/Vooma incidents where meeting recordings got misrouted or silently discarded when Cedar couldn't confidently attribute them to a conversation:

  • Calendar backstop window too tight , a real, already-linked calendar event started 3m15s after the recorder's reported meeting time; the prior 2-minute window excluded it. Widened to 7 minutes.
  • Domain-identification misattributing to tools/vendor platforms , a TMS tool mentioned 28x in a transcript got fuzzy-matched as the counterparty (Vooma/McLeod); a meeting-notetaker's own recording-platform domain got used as a fallback answer when no sender existed to check (Pirros/Reza). Both fixed with eval-validated prompt instructions.
  • Unattributed meetings vanished with no trace , pre-execution-setup.ts threw before createAndLinkEvents() ever ran, so a meeting Cedar couldn't attribute left no crm_events row at all , nothing to notify about, nothing to fix later. Extended the nullable-conversation_id model email already uses (docs/unknown-removal.md) to meetings: the event is now recorded unattributed, and a single-button Slack ping lets the rep resolve it (reusing the existing move_event mutation and the existing agent_prompt/operation-registry machinery , no new plumbing).

Two real Pirros meetings (Reza/K2M Design, JEO Consulting) that were sitting on the generic "Fathom" vendor bucket were moved to their correct conversations as a worked example of the resolution path.

Full design doc: apps/server/docs/design/ambiguous-meeting-company-assignment.md.

What changed

  1. calendar-events.ts , 2min → 7min matching window + decision-point logging.
  2. domain-identification.ts , two instructions (competitor/tool-role rule, meeting-has-no-sender rule), validated via the playbook-instruction-eval harness against real production transcripts (both went from ~4-12% failure to 0/8-24 trials).
  3. meeting-events.ts , findOrCreateMeetingEvent accepts conversationId: string | null; never downgrades an already-linked event to unattributed on a failed re-resolution (shouldUpdateEventConversationId).
  4. pre-execution-setup.ts , records the meeting unattributed and pings before the (unchanged) ExpectedTerminationError throw, meetings only.
  5. New meeting-ambiguity-ping{,-message}.ts , decision (dedupe on new-event-only, no kill switch) + message/send, mirroring the existing services/aop/ambiguity-ping{,-message}.ts pattern.

Review

Ran this repo's thermo-review skill against the full diff. One real bug found and fixed during review: the ping rendered meeting times in the server's local timezone instead of the recipient's own (every other time-formatting call site in this codebase resolves slackNotifications.digestTimezone) , fixed, with a deterministic test proving the same UTC instant renders correctly in two different timezones. Also added the dependency-direction justification comment the AOP-ping file carries for its equivalent type-only import. No blocking issues.

Two open questions from the review, left to reviewer judgment rather than answered unilaterally:

  • No feature flag ships with this (explicit decision , the ping only fires on a rare failure branch, not a hot path). No per-org escape hatch exists if it turns out noisier than expected for some org's meeting mix.
  • Whether any existing "all meetings" list surface reads crm_events without filtering on conversation_id IS NOT NULL wasn't traced , if one exists, an unattributed meeting could surface somewhere unexpected before a human resolves it.

Test plan

  • pnpm --filter @zero/server run types , clean
  • pnpm --filter @zero/server run types:test , clean except pre-existing unrelated failures (confirmed via git show HEAD:... predate this branch)
  • pnpm deps:check , 0 violations
  • New/touched test files: 36/36 passing (meeting-ambiguity-ping.test.ts, meeting-ambiguity-ping-message.test.ts, `meeting-events.shouldUp
Show production surfaces and changed-file mapping

Production surfaces

SurfaceRequestsErrorsp95UsersVerdict
orchestrator_agent_step43 → 960 → 0 (0%)128144 ms → 247979 ms37Working
96 requests since the deploy with 0 errors (0 errors in the 43-request baseline before it). p95 247979 ms, was 128144 ms.
model_chunk On-Event Execution Agent (Understudy)31 → 590 → 0 (0%)1 ms → 1 ms0Working
59 requests since the deploy with 0 errors (0 errors in the 31-request baseline before it). p95 1 ms, was 1 ms.
model_chunk Automation Agent (Understudy)11 → 230 → 0 (0%)1 ms → 1 ms0Working
23 requests since the deploy with 0 errors (0 errors in the 11-request baseline before it). p95 1 ms, was 1 ms.
model_step On-Event Execution Agent (Understudy)10 → 190 → 0 (0%)23743 ms → 32412 ms0Insufficient traffic
19 requests, under the 20 Libra needs
/api/trpc/agentExecutions.getAgentExecutions0 → 60 → 0 (0%)not measured → 12142 ms0Insufficient traffic
6 requests, under the 20 Libra needs
model_chunk Automation Agent (Sol experiment)0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/agentExecutions.findRecentTrees0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/agent.list0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/agent.getInvocationSources0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/agentExecutions.executeFromClientSend0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.

Changed files → surfaces

  • apps/server/src/trpc/routes/agent.tsorchestrator_agent_stepmodel_chunk On-Event Execution Agent (Understudy)model_chunk Automation Agent (Understudy)model_step On-Event Execution Agent (Understudy)/api/trpc/agentExecutions.getAgentExecutionsmodel_chunk Automation Agent (Sol experiment)+4
  • apps/mail/modules/agents/components/AgentRunsTab.tsxno production surface mapped
  • apps/mail/modules/agents/components/AgentView.tsxno production surface mapped
  • apps/server/.claude/skills/playbook-instruction-eval/eval-domain-identification.tsno production surface mapped
  • apps/server/.claude/skills/playbook-instruction-eval/eval-vendor-bucket.tsno production surface mapped
  • apps/server/.claude/skills/playbook-instruction-eval/fixtures/reza-fathom-vendor-bucket-meeting.txtno production surface mapped
  • apps/server/.claude/skills/playbook-instruction-eval/fixtures/vooma-mcleod-meeting.txtno production surface mapped
  • apps/server/docs/design/ambiguous-meeting-company-assignment.mdno production surface mapped
  • apps/server/src/mastra/skills/crm/domain-identification.tsno production surface mapped
  • apps/server/src/mastra/workflows/event-execution/pre-execution-setup.tsno production surface mapped
  • apps/server/src/services/auth/authorize.tsno production surface mapped
  • apps/server/src/services/crm/__tests__/meeting-ambiguity-ping-message.test.tsno production surface mapped
  • apps/server/src/services/crm/__tests__/meeting-ambiguity-ping.test.tsno production surface mapped
  • apps/server/src/services/crm/__tests__/meeting-calendar-backstop.test.tsno production surface mapped
  • apps/server/src/services/crm/__tests__/meeting-events.shouldUpdateEventConversationId.test.tsno production surface mapped
  • apps/server/src/services/crm/calendar-events.tsno production surface mapped
  • apps/server/src/services/crm/meeting-ambiguity-ping-message.tsno production surface mapped
  • apps/server/src/services/crm/meeting-ambiguity-ping.tsno production surface mapped
  • apps/server/src/services/crm/meeting-events.tsno production surface mapped
  • apps/server/src/trpc/routes/__tests__/agent-router-auth.test.tsno production surface mapped
  • apps/server/src/trpc/routes/__tests__/agent-router-org-admin.test.tsno production surface mapped
  • apps/server/src/trpc/routes/__tests__/chat-router-org-admin.test.tsno production surface mapped