feat(desktop): opt-in source-linked meeting memory briefs
Summary
Adds an opt-in, quiet macOS meeting-memory pilot on the existing Conversations page. Within two hours of an upcoming local calendar event, it can show a short card from a prior, confidently matched Omi conversation: a decision, the user's open follow-up, and/or an unresolved question. Every displayed item opens its source transcript. When a completed meeting has cited, user-owned commitments, the conversation menu can prepare an editable, copy-only follow-up draft. The app never sends it.
This deliberately does not create a Chat card or notification. Automatic cards in Chat were removed by #20665; this pilot keeps meeting context in the Conversations destination and only when the user enables it.
Matching and safety
- Match by the same local system-calendar event ID, or by an exactly normalized event title of at least 8 characters and an overlapping attendee email. A shorter, generic title ("Sync", "1:1") needs the exact event ID, because one shared attendee cannot tell recurring meetings apart. Never infer the match from an AI-generated conversation title alone.
- Limit candidate conversations to completed, unlocked, non-deleted, non-discarded meetings within 180 days. Skip a recent match with no cited facts and try an older cited meeting; otherwise show nothing.
- Show only structured summary sections or explicit user-owned, incomplete action items that carry transcript segment IDs. Clicking a fact opens the source conversation and focuses those segments.
- Find a decision or open question first under a matching heading ("Decisions", "Open questions"), otherwise in the first cited summary line that states it. Production summaries title sections by topic, so most outcomes live in the text. One veto covers both paths: any negation (except one that negates the agreed action itself: "agreed not to ask", "decided never to store", "chose to not renew"), dispute ("disagreed", "objected"), discussion, proposal, question, or pending wording omits the line. The text fallback also needs a settled verb ("decided", "agreed", "chose", "approved", "made a decision"), so "We will discuss the decision next week" is not a decision. Imperatives under a real Decisions heading ("Ship the smaller pilot first") still count. Lines are classified whole and only shortened for the card, at the last whole word with an ellipsis. When matched meetings yield no fact, the app logs a count-only line.
- The brief setting offers an explicit Calendar access request; background brief refreshes never prompt. Brief reads are read-only and do not upload calendar data. (The pre-existing recording-context feature has its own independent permission path.) A missing grant, match, citation, owner, or network response fails quietly.
- Fence conversation reads and source navigation to the current authorization snapshot so an account switch cannot display another account's result. Dismissal keys include owner and meeting identity.
- Follow-up drafts require cited user-owned actions, stay editable, include every eligible action in full (no truncation, so dates, links and qualifiers survive), and have only a Copy action (no recipient or send action).
Product invariants affected
INV-AUTH-1: optional conversation list/detail reads capture the current authorization snapshot, recheck it after awaits, and never publish stale owner content. The existing session invalidation and 401 policy are unchanged.INV-NAV-1: the brief is added to the canonical Conversations page, preserving its established search, filters, folders, selection, pagination, and navigation. No alternate shell-specific destination is introduced.
Failure-Class: new
New class: Calendar purpose string without signing entitlement. The initial pilot bundle had the Calendar purpose string but not the hardened-runtime Calendar entitlement. tccd explicitly refused to prompt (kTCCServiceCalendar requires entitlement com.apple.security.personal-information.calendars), so the app never appeared in System Settings. Both development and release signing inputs now include the entitlement, and the named-bundle fallback has a regression assertion.
Verification
- Composer tests: 23 passed, 0 failed.
testANegativeDecisionIsStillADecisionis built from the live production summary line, and restoring the old blanket negation veto fails it with 5 assertions.testLongCardLinesEndAtAWordWithAnEllipsisfails 2 assertions against the old raw 240-character cut. - Targeted Swift tests: 44 passed, 0 failed (matching, omission/locking, citation focus, calendar read permissions, conversation decoding, draft safety, and visible card rendering).
- SwiftLint: 0 new violations across 1,817 Swift files.
- Pinned swift-format and
git diff --checkpassed on the changed Swift and patch. - The entitlement regression test and
plutil -lintpassed for development/release entitlements andInfo.plist. - Isolated named macOS bundle
com.omi.omi-meeting-brief-pilotpassed the full dependency audit, installed, and launched signed in. Its effective code signature containscom.apple.security.personal-information.calendars=true. After the user-requested permission test, macOS listed the exact bundle with Full Access on, and the Meeting Briefs setting became on. Audio and screen capture were disabled in this test bundle.
Live permissions/settings proof; this does not assert that a source-matched card was shown.
UI fixture only, not a live matched calendar meeting or before/after proof.- Tracked evidence: .agent-artifacts/ui-evidence/20824/001-desktop-macos-meeting-brief-calendar-enabled.png and .agent-artifacts/ui-evidence/20824/002-desktop-macos-meeting-brief-card-fixture.png.
- Live source-matched card, verified against production (Oct 12). I created a "Brief test sync" event on the synced calendar and joined a real Discord voice call during it. The meeting detector marked the session
role=meetingand stored the event. A scripted two-voice planning meeting (macOSsay) then played through the speakers: 12 min recorded, 6 min of speech, with one decision, one assigned follow-up, one open question and an explicit "pricing is not decided". Production (api.omi.me) summarized it and linked it to the event. I then moved the event 40 minutes later. The Conversations page showed the card for the upcoming occurrence, matched by the same system-calendar event ID.
Decided and Open question, both from the production summary. "Pricing was not decided" was correctly not shown as a decision. Captured before the ellipsis change, so the lines still end mid-word ("permissi"). They now end at a whole word with "…".
The source arrow opens the conversation with the transcript drawer on the cited segment.- Tracked evidence: .agent-artifacts/ui-evidence/20824/003-desktop-macos-meeting-brief-live-card.png and .agent-artifacts/ui-evidence/20824/004-desktop-macos-meeting-brief-live-source.png.
- The live run first showed only the open question. The decision line ("The team agreed not to ask for calendar access at first launch") was vetoed by its "not". That is why negations that scope the agreed action now count (above). No "You owe" line appeared because production created no user-owned action item: the synthetic voice assigned the note to a name spoken by Speaker 2, not to the account owner. This is the intended fail-safe. The follow-up path stays covered by tests, not by this live run.
Pilot scope and follow-up
The first version searches the newest 100 completed conversations, so it intentionally returns no card if the relevant prior meeting is older than that fetch window. It has no external model calls and no new server persistence. Dogfood should measure useful, source-confirmed briefs, wrong matches, dismissals, and reviewed follow-ups before considering broader exposure.