Make meeting lifecycle stateful

This commit is contained in:
2026-05-27 12:55:17 +02:00
parent d607b957bb
commit e85274829a
91 changed files with 4076 additions and 479 deletions
@@ -0,0 +1,15 @@
## Why
Meeting Assistant currently has one global configuration, one hotkey, and one implicit ASR/runtime setup. In practice, the user needs to start meetings with different speech-recognition settings, such as German-only versus English-only recognition, without editing configuration and restarting between meetings.
## What Changes
- Add named launch profiles to configuration.
- Treat the root `MeetingAssistant` configuration as the `default` launch profile.
- Resolve non-default launch profiles by copying the default settings and binding the named profile override onto that copy.
- Add profile-aware endpoint routes while preserving the existing default-profile URLs.
- Register one global hotkey per launch profile, requiring distinct hotkey definitions.
- Add an `english` launch profile that keeps the current default settings except for English Azure Speech recognition and its own hotkey.
## Impact
- Existing URLs and the existing hotkey continue to operate the default profile.
- Named profiles are addressed through profile URLs, allowing diagnostics and recording commands to select the desired backend/language profile.
- Speech pipeline creation becomes profile-aware.
@@ -0,0 +1,33 @@
## ADDED Requirements
### Requirement: Recording can be launched through named profiles
Meeting Assistant SHALL treat the root `MeetingAssistant` configuration as a launch profile named `default`.
Meeting Assistant SHALL allow additional named launch profiles to be configured as overrides of the default profile. A named profile SHALL be resolved by binding the default profile first, then binding the named profile override onto the same option object.
Meeting Assistant SHALL preserve existing recording endpoint URLs as default-profile URLs and SHALL provide equivalent named-profile URLs that include the profile name.
Each launch profile SHALL have a distinct global hotkey. Pressing a profile hotkey SHALL toggle recording using that profile's resolved configuration.
Meeting Assistant SHALL use the selected launch profile's vault and recording storage settings when it creates meeting notes, transcripts, assistant context notes, summary notes, temporary recordings, and dictation-word inputs for that meeting.
Meeting Assistant SHALL use the selected launch profile's summary-agent settings when it runs the summary pipeline for that meeting.
#### Scenario: Default profile keeps existing recording URL
- **WHEN** the user calls the existing recording start URL without a profile name
- **THEN** Meeting Assistant starts recording with the `default` launch profile
#### Scenario: Named profile starts recording
- **WHEN** the user calls the named-profile recording start URL for profile `english`
- **THEN** Meeting Assistant starts recording using the resolved `english` launch profile configuration
#### Scenario: Named profile stores meeting artifacts in profile folders
- **GIVEN** launch profile `english` overrides vault folders and summary-agent settings
- **WHEN** the user starts and stops a recording with launch profile `english`
- **THEN** Meeting Assistant creates the meeting note, transcript, assistant context note, and summary note using the resolved `english` vault folders
- **AND** Meeting Assistant runs the summary pipeline using the resolved `english` summary-agent settings
#### Scenario: Profile hotkeys must be distinct
- **GIVEN** two launch profiles configure the same toggle hotkey
- **WHEN** Meeting Assistant resolves launch profiles
- **THEN** Meeting Assistant rejects the configuration before registering global hotkeys
@@ -0,0 +1,16 @@
## ADDED Requirements
### Requirement: Speech recognition diagnostics are launch-profile aware
Meeting Assistant SHALL preserve existing ASR diagnostic endpoint URLs as default-profile URLs.
Meeting Assistant SHALL provide equivalent named-profile ASR diagnostic endpoint URLs that include the profile name.
When a named-profile ASR diagnostic endpoint is used, Meeting Assistant SHALL create the speech recognition pipeline from the resolved profile configuration.
#### Scenario: Default ASR diagnostic URL uses default profile
- **WHEN** the user submits a WAV file to the existing ASR diagnostic endpoint URL
- **THEN** Meeting Assistant streams the WAV through the default launch profile's configured speech recognition pipeline
#### Scenario: Named ASR diagnostic URL uses named profile
- **WHEN** the user submits a WAV file to the ASR diagnostic endpoint URL for profile `english`
- **THEN** Meeting Assistant streams the WAV through the resolved `english` launch profile's configured speech recognition pipeline
@@ -0,0 +1,18 @@
## 1. Specification
- [x] 1.1 Define launch profile requirements for recording control and endpoint routing.
- [x] 1.2 Define launch profile requirements for profile-aware ASR pipeline selection.
## 2. Implementation
- [x] 2.1 Add launch profile option resolution with default-plus-override merge behavior.
- [x] 2.2 Preserve existing default endpoint URLs and add named profile URL variants.
- [x] 2.3 Pass selected launch profiles into recording and ASR diagnostics pipeline creation.
- [x] 2.4 Register distinct hotkeys for all launch profiles.
- [x] 2.5 Add an `english` launch profile override to appsettings.
- [x] 2.6 Use the selected launch profile for meeting artifact storage paths, dictation words, and summary-agent settings.
## 3. Validation
- [x] 3.1 Add tests for default and named launch profile option resolution.
- [x] 3.2 Add tests for profile-aware diagnostic endpoint routing.
- [x] 3.3 Add tests for distinct profile hotkey planning.
- [x] 3.4 Add tests for profile-scoped meeting artifact paths and summary-agent settings.
- [x] 3.5 Run focused tests.
@@ -0,0 +1,17 @@
# Add Speaker Identity Learning
## Why
Meeting Assistant currently receives diarized speaker labels from ASR backends, but those labels are backend-local names such as `Guest01` or `Speaker 0`. Users need transcripts to converge toward real attendee names without manually maintaining a roster.
## What Changes
- Add a local SQLite-backed speaker identity store under the user's application data folder.
- Store a small bounded set of WAV snippets per learned speaker identity.
- Match unknown diarized speakers against known identities during and after transcription using a dedicated Azure Speech diarization verifier component separate from the configured ASR pipeline.
- Confirm matches before applying them, then rewrite transcript speaker labels and use the known name for later segments.
- Learn new identities automatically from unmatched diarized speakers using meeting attendees as candidate names.
- Eliminate candidate names across future meetings until a canonical speaker name is known.
## Impact
- Adds EF Core SQLite as a runtime dependency.
- Adds speaker identity services and a dedicated Azure Speech identity matching component to the transcription flow.
- Adds persisted local voice snippets; these are explicit identity artifacts, not temporary full recordings.
@@ -0,0 +1,165 @@
## ADDED Requirements
### Requirement: Meeting Assistant learns speaker identities locally
Meeting Assistant SHALL maintain a local SQLite speaker identity database in the user's application data folder.
The speaker identity database SHALL store speaker identities, optional canonical names, aliases, candidate names, a transcription participation counter, and a bounded set of WAV snippets per identity.
Each speaker identity SHALL store a last-modified timestamp used by active-age filtering, and Meeting Assistant SHALL update it whenever the identity is created or modified by identification, candidate updates, snippet changes, or merge operations.
The configured maximum snippet count per identity SHALL prevent unbounded growth.
#### Scenario: Unknown speaker is learned from meeting attendees
- **WHEN** a finished transcript contains an unmatched diarized speaker and the meeting note has attendees
- **THEN** Meeting Assistant stores a new unnamed speaker identity with candidate names from the attendees that were not already matched in that meeting
#### Scenario: Speaker snippets are bounded
- **WHEN** Meeting Assistant adds a snippet for an identity that already has the configured maximum number of snippets
- **THEN** Meeting Assistant does not store more snippets for that identity
#### Scenario: Identity modification updates active-age timestamp
- **WHEN** Meeting Assistant creates, identifies, updates candidates for, stores snippets for, or merges a speaker identity
- **THEN** Meeting Assistant updates that identity's last-modified timestamp
### Requirement: Speaker identities can be merged diagnostically
Meeting Assistant SHALL expose a diagnostic endpoint that merges duplicate speaker identities.
The merge process SHALL compare recently-created identities, using a configurable recent age that defaults to two weeks, against all other identities in matcher batches bounded by the configured batch size.
The merge process SHALL require a match and a second validation match using a different source sample before merging two identities.
When identities are merged, Meeting Assistant SHALL retain one identity, move useful names from the merged identity into aliases, combine transcription counts, and retain a bounded set of snippets from both identities.
#### Scenario: Recently-created duplicate identity is merged
- **GIVEN** a recently-created identity and an older identity have matching speaker snippets
- **WHEN** the diagnostic merge endpoint is triggered
- **THEN** Meeting Assistant validates the match twice with different source snippets
- **AND** merges the recent identity into the older identity
- **AND** stores the recent identity name as an alias on the retained identity
#### Scenario: Old identities are not used as merge sources
- **GIVEN** two identities older than the configured recent age
- **WHEN** the diagnostic merge endpoint is triggered
- **THEN** Meeting Assistant does not compare them as source identities
### Requirement: Speaker candidates are eliminated across meetings
Meeting Assistant SHALL update candidate names for a matched unnamed identity using the intersection of its existing candidate names and the current meeting attendees.
Meeting Assistant SHALL treat identity aliases as acceptable names during candidate elimination and when excluding already-matched attendees from new unmatched speaker candidates.
When the candidate intersection leaves exactly one candidate, Meeting Assistant SHALL promote that candidate to the canonical speaker name.
When the candidate intersection is empty, Meeting Assistant SHALL replace the oldest stored snippet for that identity and reset candidates from the current meeting attendees, because the previous snippet may have been dirty.
#### Scenario: Candidate elimination promotes canonical name
- **GIVEN** an unnamed identity has candidate names `John` and `Mike`
- **WHEN** that identity matches a speaker in a later meeting with attendees `Jane`, `John`, and `Chris`
- **THEN** Meeting Assistant removes `Mike` from the identity candidates and promotes `John` as the canonical name
#### Scenario: Alias participates in candidate elimination
- **GIVEN** an unnamed identity has candidate name `Michael` and alias `Mike`
- **WHEN** that identity matches a speaker in a later meeting with attendee `Mike`
- **THEN** Meeting Assistant treats `Mike` as matching `Michael`
- **AND** promotes `Michael` as the canonical name
#### Scenario: Empty candidate intersection resets candidates
- **GIVEN** an unnamed identity has candidate names `John` and `Mike`
- **WHEN** that identity matches a speaker in a later meeting with attendees `Jane` and `Chris`
- **THEN** Meeting Assistant resets the identity candidates to `Jane` and `Chris`
- **AND** replaces the oldest stored snippet for that identity with the current speaker snippet
### Requirement: Speaker identity matches relabel transcripts
Meeting Assistant SHALL attempt to match unknown diarized speaker snippets against known speaker identities ordered by transcription participation count.
Speaker identity matching SHALL use a dedicated Azure Speech diarization verifier component rather than the configured speech recognition pipeline.
The matcher SHALL test at most the configured batch size of known people per diarization session and continue with later batches until a match is found or no candidates remain.
The matcher SHALL prioritize identities whose canonical name or aliases match current meeting attendees.
After attendee-matched identities, the matcher SHALL order identities by transcription participation count, filter out non-attendee identities whose last update is older than the configured active age, and cap the candidate set at the configured maximum match candidate count.
When a match is confirmed and the identity has a canonical name, Meeting Assistant SHALL rewrite finished transcript segments for that diarized speaker with the canonical name.
When a match is confirmed and the matched speaker is not already listed in meeting note attendees by display name or alias, Meeting Assistant SHALL add the speaker display name to the attendee list.
When Meeting Assistant writes attendees from calendar metadata, it SHALL match attendee display names exactly against known identity canonical names and aliases, replace matches with the identity display name, and deduplicate attendees that map to the same identity.
#### Scenario: Finished transcript is relabeled after a confirmed match
- **GIVEN** the speaker identity database contains canonical speaker `Chris`
- **WHEN** a finished transcript has diarized speaker `Guest03` and the matching backend confirms it is `Chris`
- **THEN** Meeting Assistant rewrites `Guest03` segments in the transcript as `Chris`
#### Scenario: Confirmed match adds missing attendee
- **GIVEN** the speaker identity database contains canonical speaker `Chris`
- **AND** the meeting note attendees do not contain `Chris` or one of that identity's aliases
- **WHEN** live or final speaker matching confirms a diarized speaker is `Chris`
- **THEN** Meeting Assistant adds `Chris` to the meeting note attendees
#### Scenario: Confirmed match does not duplicate attendee aliases
- **GIVEN** the speaker identity database contains canonical speaker `Christopher` with alias `Chris`
- **AND** the meeting note attendees already contain `Chris <chris@example.com>`
- **WHEN** live or final speaker matching confirms a diarized speaker is `Christopher`
- **THEN** Meeting Assistant does not add another attendee for `Christopher`
#### Scenario: Calendar attendee aliases are deduplicated
- **GIVEN** the speaker identity database contains canonical speaker `Karl Berger` with alias `Berger, Karl`
- **WHEN** calendar metadata provides attendees `Karl Berger` and `Berger, Karl`
- **THEN** Meeting Assistant writes a single attendee `Karl Berger` to the meeting note
#### Scenario: Attendee identities are tried first
- **GIVEN** the meeting attendees include names matching known identity display names or aliases
- **WHEN** Meeting Assistant tries to identify a diarized speaker
- **THEN** attendee-matched identities are tried before non-attendee identities
#### Scenario: Stale non-attendee identities are skipped
- **GIVEN** a known identity has not matched within the configured active age
- **AND** that identity does not match the current meeting attendees
- **WHEN** Meeting Assistant tries to identify a diarized speaker
- **THEN** that stale identity is not included in the match candidates
### Requirement: Speaker matching runs during active transcription
Meeting Assistant SHALL start speaker identity matching only after the configured initial transcription duration has elapsed.
For backends that emit live diarized transcript segments, Meeting Assistant SHALL keep a bounded in-memory sliding audio buffer with chunk timestamps and extract candidate WAV snippets from that buffer when live diarized segments arrive.
Meeting Assistant SHALL keep only the configured best candidate snippets per diarized speaker in memory. Better snippets SHALL be preferred when the segment looks like a continuous medium-length sentence.
Meeting Assistant SHALL periodically match unresolved diarized speaker samples while transcription is active and attempt to match them against the local identity database.
Meeting Assistant SHALL run live matching incrementally at the configured interval only when at least one new unmapped diarized speaker sample appears or the meeting note attendee frontmatter changes while unmapped speaker samples still exist.
When a speaker is matched during transcription, Meeting Assistant SHALL rewrite already-written live transcript segments for that diarized speaker and write future transcript segments using the canonical name.
Live speaker matching SHALL be read-only with respect to the speaker identity database. Candidate elimination, canonical promotion, transcription counters, stored snippet updates, and new unmatched identity creation SHALL happen only after transcription is finished, using the latest meeting note frontmatter.
For backends that only provide diarization after finalization, Meeting Assistant SHALL defer speaker identity matching until finished diarization is available, extract candidate snippets from the completed temporary recording, complete identity matching, and only then allow summary generation to start.
#### Scenario: Matching waits for useful speech duration
- **WHEN** transcription has been active for less than the configured speaker identification initial delay
- **THEN** Meeting Assistant does not run speaker identity matching yet
#### Scenario: Live matching uses in-memory speaker samples
- **WHEN** a live diarized transcript segment identifies an unresolved speaker
- **THEN** Meeting Assistant extracts a WAV snippet for that segment from the in-memory sliding audio buffer
- **AND** uses retained speaker samples for live identity matching without reading the temporary recording file
#### Scenario: Live match rewrites current and future transcript writes
- **WHEN** periodic matching confirms that diarized speaker `Guest03` is canonical speaker `Chris`
- **THEN** already-written live transcript segments for `Guest03` are rewritten as `Chris`
- **AND** later live transcript segments for `Guest03` are written as `Chris`
#### Scenario: New live speaker triggers another identification round
- **GIVEN** live matching already checked the current unresolved speaker samples
- **WHEN** a new unmapped diarized speaker sample appears
- **THEN** Meeting Assistant runs another live matching round at the next configured interval
#### Scenario: Attendee changes trigger another identification round
- **GIVEN** live matching already checked unresolved speaker samples
- **WHEN** the meeting note attendee frontmatter changes
- **THEN** Meeting Assistant runs another live matching round at the next configured interval using the latest attendees
#### Scenario: Live matching does not make final identity decisions
- **WHEN** live matching finds a possible speaker identity during transcription
- **THEN** Meeting Assistant does not change candidate names, promote canonical names, increment counters, store snippets, or create unmatched identities
- **AND** the final speaker identity pass uses the latest meeting note attendees after transcription finishes
@@ -0,0 +1,36 @@
## 1. Specification
- [x] 1.1 Define requirements for local speaker identity storage and candidate elimination.
- [x] 1.2 Define requirements for live periodic matching and transcript relabeling.
## 2. Implementation
- [x] 2.1 Add SQLite speaker identity database in app data.
- [x] 2.2 Add speaker snippet extraction and matching orchestration.
- [x] 2.3 Learn unmatched speaker identities from meeting attendees at transcript completion.
- [x] 2.4 Eliminate candidate names on future matches and promote a canonical name.
- [x] 2.5 Relabel finished transcript segments when canonical identity matches are found.
- [x] 2.6 Add live periodic matching after an initial delay and apply matches to future transcript writes.
- [x] 2.7 Keep timestamped in-memory audio samples for live diarized speaker matching and reserve recording-file extraction for final-only diarization.
- [x] 2.8 Add speaker aliases and a diagnostic duplicate-identity merge process.
- [x] 2.9 Use aliases during candidate elimination and unmatched-candidate filtering.
- [x] 2.10 Keep live speaker matching read-only so final identity decisions use the latest meeting note frontmatter.
- [x] 2.11 Rewrite already-written live transcript segments when a live speaker match is found.
- [x] 2.12 Prioritize attendee-matched speaker identities, filter stale non-attendees, and cap match candidates.
- [x] 2.13 Run live speaker identification incrementally when new unmapped speakers appear or attendees change.
- [x] 2.14 Maintain speaker identity last-modified timestamps for active-age filtering.
- [x] 2.15 Add identified speaker display names to meeting note attendees without duplicating display names or aliases.
- [x] 2.16 Canonicalize calendar attendee names through identity canonical names and aliases before writing meeting notes.
## 3. Validation
- [x] 3.1 Add tests for candidate creation, elimination, canonical promotion, and reset on empty candidate intersection.
- [x] 3.2 Add tests for recording coordinator transcript relabel integration.
- [x] 3.3 Add tests for in-memory speaker samples and rolling audio buffer extraction.
- [x] 3.4 Add tests for duplicate speaker identity merging and the diagnostic endpoint.
- [x] 3.5 Add tests for alias-aware candidate elimination.
- [x] 3.6 Add tests that live matching does not mutate speaker identities.
- [x] 3.7 Add tests that live speaker matches relabel current and future transcript segments.
- [x] 3.8 Add tests for speaker identity candidate ordering, active-age filtering, and candidate caps.
- [x] 3.9 Add tests for incremental live speaker identification triggers.
- [x] 3.10 Add tests for speaker identity last-modified timestamp creation, updates, and schema upgrade.
- [x] 3.11 Add tests for adding matched speaker identities to meeting attendees and alias duplicate suppression.
- [x] 3.12 Add tests for calendar attendee canonicalization and alias duplicate suppression.
- [x] 3.13 Run `dotnet test`.
@@ -0,0 +1,14 @@
# Configure Summary Agent Instructions
## Why
The summary agent prompt is currently hard-coded, so changing agent behavior requires code changes. Project-specific agent instructions also live in project folders and should be supplied to the summarizer when a meeting is bound to those projects.
## What Changes
- Add a configurable summary agent initial prompt in appsettings.
- Keep the current prompt in code as the default when settings do not provide one.
- Append project instruction context for meeting-bound projects.
- For each bound project with an `AGENTS.md` file in the project folder, append the project name and file content under a `projects:` section.
## Impact
- Changes summary agent prompt construction.
- Reads configured project folders and current meeting note frontmatter before starting the summary agent.
@@ -0,0 +1,33 @@
## ADDED Requirements
### Requirement: Summary agent instructions are configurable
Meeting Assistant SHALL allow the summary agent initial prompt to be configured through application settings.
When no configured initial prompt is supplied, Meeting Assistant SHALL use the built-in default prompt.
#### Scenario: Configured initial prompt is used
- **WHEN** `MeetingAssistant:Agent:InitialPrompt` is configured with non-empty content
- **THEN** the summary agent uses that configured prompt as its base instructions
#### Scenario: Empty initial prompt falls back to built-in default
- **WHEN** `MeetingAssistant:Agent:InitialPrompt` is missing or blank
- **THEN** the summary agent uses the built-in default instructions
### Requirement: Summary agent receives bound project instructions
Meeting Assistant SHALL append project instructions to the summary agent instructions for projects bound to the meeting note frontmatter.
For each bound project folder under the configured projects folder, when an `AGENTS.md` file exists directly in that project folder, Meeting Assistant SHALL append the project name and the `AGENTS.md` content under a project instructions section.
When no bound projects have `AGENTS.md`, Meeting Assistant SHALL not append the project instructions section.
#### Scenario: Project AGENTS files are appended
- **GIVEN** the meeting note frontmatter lists projects `Alpha` and `Beta`
- **AND** both configured project folders contain `AGENTS.md`
- **WHEN** the summary agent is created
- **THEN** its instructions include `---`, `projects:`, `# Alpha`, Alpha's `AGENTS.md`, `# Beta`, and Beta's `AGENTS.md`
#### Scenario: Projects without AGENTS files are skipped
- **GIVEN** the meeting note frontmatter lists project `Alpha`
- **AND** the configured project folder does not contain `AGENTS.md`
- **WHEN** the summary agent is created
- **THEN** no empty project instruction entry is appended for `Alpha`
@@ -0,0 +1,13 @@
## 1. Specification
- [x] 1.1 Define configurable initial prompt and project instruction append behavior.
## 2. Implementation
- [x] 2.1 Add `Agent.InitialPrompt` setting with the current prompt in appsettings.
- [x] 2.2 Keep the current prompt as the code default when no setting is configured.
- [x] 2.3 Build summary agent instructions by appending `AGENTS.md` content for bound projects.
- [x] 2.4 Use the built instructions when creating the summary agent.
## 3. Validation
- [x] 3.1 Test configurable prompt fallback and override behavior.
- [x] 3.2 Test project `AGENTS.md` instruction appendix formatting.
- [x] 3.3 Run targeted and full tests.
@@ -0,0 +1,12 @@
## Why
Azure Speech post-stream refinement can improve final segment quality while preserving the existing low-latency streaming transcription flow.
## What Changes
- Add an `AzureSpeech.PostProcessingOption` setting.
- Keep the default option unset while the live Azure backend uses `ConversationTranscriber`.
- Apply the configured option only for compatible Azure Speech SDK recognizer paths; skip it for `ConversationTranscriber` to preserve working live diarized transcription.
- Use a Speech resource region that supports post-stream refinement.
## Impact
- Affected specs: `meeting-transcription`
- Affected code: Azure Speech options, Azure streaming transcription provider, appsettings, Azure provider tests
@@ -0,0 +1,27 @@
## ADDED Requirements
### Requirement: Azure Speech post-stream refinement is configurable
Meeting Assistant SHALL allow the Azure Speech streaming transcription provider to configure the Speech SDK post-processing option.
When configured for a compatible Azure Speech SDK recognizer, Meeting Assistant SHALL set `SpeechServiceResponse_PostProcessingOption` on the Azure Speech SDK `SpeechConfig`.
The default application configuration SHALL keep `PostProcessingOption` empty while using live `ConversationTranscriber` and SHALL use a region that supports post-stream refinement for future compatible recognizer paths.
Meeting Assistant SHALL keep using the existing Azure live streaming transcription flow rather than switching to batch or offline processing.
When the Azure live streaming backend uses `ConversationTranscriber`, Meeting Assistant SHALL prefer working live diarized transcription over applying a post-processing option that is only documented for `SpeechRecognizer`.
#### Scenario: Azure Speech post-refinement is configured
- **WHEN** `AzureSpeech.PostProcessingOption` is `PostRefinement`
- **AND** the configured Azure Speech recognizer supports SDK post-processing
- **THEN** Meeting Assistant sets `SpeechServiceResponse_PostProcessingOption` to `PostRefinement` on the Azure Speech SDK configuration
#### Scenario: Azure live conversation transcription remains available
- **WHEN** `AzureSpeech.PostProcessingOption` is `PostRefinement`
- **AND** the Azure backend is using live `ConversationTranscriber`
- **THEN** Meeting Assistant skips the post-processing SDK property
- **AND** continues using live conversation transcription
#### Scenario: Azure Speech post-processing is unset
- **WHEN** `AzureSpeech.PostProcessingOption` is empty
- **THEN** Meeting Assistant does not set a post-processing option on the Azure Speech SDK configuration
@@ -0,0 +1,14 @@
## 1. Specification
- [x] 1.1 Define Azure Speech post-stream refinement configuration behavior.
## 2. Implementation
- [x] 2.1 Add configurable Azure Speech post-processing option.
- [x] 2.2 Apply the configured option only to compatible Azure Speech SDK recognizer paths and skip it for live `ConversationTranscriber`.
- [x] 2.3 Configure Azure Speech for a post-stream-refinement-supported region.
- [x] 2.4 Provision or reuse an Azure Speech resource and set the local key environment variable.
## 3. Validation
- [x] 3.1 Add provider tests for the post-processing option.
- [x] 3.2 Run focused provider tests.
- [x] 3.3 Run full test suite.
- [x] 3.4 Run OpenSpec validation.
@@ -0,0 +1,19 @@
## Why
Meeting Assistant currently focuses V1 runtime support on Windows capture and Outlook enrichment. We need a separate future change to define macOS and Linux build targets without expanding the V1 scope.
## Status
Not planned for V1. This change is intentionally iced without implementation or spec updates.
## What Changes
- Define supported macOS and Linux build targets for Meeting Assistant.
- Keep platform-specific capture, hotkey, calendar, and shell integrations isolated per target.
- Ensure non-Windows builds compile without Windows COM, WASAPI, or user32 dependencies.
- Define platform-specific test selection so Windows-only tests do not run on macOS/Linux and future macOS/Linux tests do not run on incompatible targets.
## Impact
- Affected specs: build-targets
- Affected code: project target frameworks, platform service registration, platform integration tests
@@ -0,0 +1,19 @@
## ADDED Requirements
### Requirement: Meeting Assistant defines macOS and Linux build targets
Meeting Assistant SHALL define macOS and Linux build targets separately from the Windows build target.
Windows-only integrations such as WASAPI capture, global hotkeys through user32, and Outlook Classic COM SHALL NOT be compiled into macOS or Linux targets.
#### Scenario: Non-Windows target compiles
- **WHEN** Meeting Assistant is built for macOS or Linux
- **THEN** Windows-only source files and package dependencies are excluded
- **AND** the application compiles with portable service registrations or explicit unsupported implementations
#### Scenario: Windows target keeps Windows integrations
- **WHEN** Meeting Assistant is built for Windows
- **THEN** WASAPI audio capture, Windows global hotkeys, and Outlook Classic COM enrichment remain available
#### Scenario: Platform-specific tests are gated
- **WHEN** tests run for a platform target
- **THEN** tests for incompatible platform APIs are excluded or compiled only for their matching target
@@ -0,0 +1,15 @@
## Status
Not planned for V1; archived without spec changes.
## 1. Build Targets
- [x] 1.1 Not planned for V1: add explicit macOS and Linux target framework/runtime build coverage.
- [x] 1.2 Not planned for V1: keep Windows-only implementations excluded from macOS and Linux builds.
- [x] 1.3 Not planned for V1: add platform service registration for unsupported or future macOS/Linux capture and hotkey implementations.
## 2. Tests
- [x] 2.1 Not planned for V1: split platform-specific tests by compilation target or runtime guard.
- [x] 2.2 Not planned for V1: add CI/build commands that verify Windows, macOS, and Linux compile targets.
- [x] 2.3 Not planned for V1: add at least one portable compile test that proves Windows-only packages are not required by non-Windows builds.
@@ -0,0 +1,12 @@
## Why
Speaker identity usage is currently tracked as a denormalized transcript count. That loses which meeting files caused the count and prevents Meeting Assistant from updating past transcript files when an identity is later named or merged.
## What Changes
- Store speaker identity meeting participation as reference rows containing meeting note and transcript file addresses.
- Calculate identity usage counts from references when ordering matching candidates.
- Add transcript audit lines to referenced transcripts when an identity is named or two identities are merged.
## Impact
- Speaker identity SQLite schema gains a references table.
- Matching, attendee canonicalization, and merge ordering use calculated reference counts instead of a persisted counter.
- Existing databases keep working; legacy transcript-count columns may remain unused.
@@ -0,0 +1,107 @@
## MODIFIED Requirements
### Requirement: Meeting Assistant learns speaker identities locally
Meeting Assistant SHALL maintain a local SQLite speaker identity database in the user's application data folder.
The speaker identity database SHALL store speaker identities, optional canonical names, aliases, candidate names, meeting file references, and a bounded set of WAV snippets per identity.
Meeting file references SHALL include the meeting note file address and the transcript file address.
Meeting Assistant SHALL calculate speaker identity participation counts from meeting file references when needed instead of persisting a denormalized transcript count.
Each speaker identity SHALL store a last-modified timestamp used by active-age filtering, and Meeting Assistant SHALL update it whenever the identity is created or modified by identification, candidate updates, snippet changes, reference changes, or merge operations.
The configured maximum snippet count per identity SHALL prevent unbounded growth.
#### Scenario: Unknown speaker is learned from meeting attendees
- **WHEN** a finished transcript contains an unmatched diarized speaker and the meeting note has attendees
- **THEN** Meeting Assistant stores a new unnamed speaker identity with candidate names from the attendees that were not already matched in that meeting
- **AND** stores a meeting file reference for that identity
#### Scenario: Speaker snippets are bounded
- **WHEN** Meeting Assistant adds a snippet for an identity that already has the configured maximum number of snippets
- **THEN** Meeting Assistant does not store more snippets for that identity
#### Scenario: Identity modification updates active-age timestamp
- **WHEN** Meeting Assistant creates, identifies, updates candidates for, stores snippets for, stores references for, or merges a speaker identity
- **THEN** Meeting Assistant updates that identity's last-modified timestamp
### Requirement: Speaker candidates are eliminated across meetings
Meeting Assistant SHALL update candidate names for a matched unnamed identity using the intersection of its existing candidate names and the current meeting attendees.
Meeting Assistant SHALL treat identity aliases as acceptable names during candidate elimination and when excluding already-matched attendees from new unmatched speaker candidates.
When the candidate intersection leaves exactly one candidate, Meeting Assistant SHALL promote that candidate to the canonical speaker name.
When an identity receives a canonical name, Meeting Assistant SHALL append an audit line to each referenced transcript in the form `<date> <speaker label> was identified as <name>`.
When the candidate intersection is empty, Meeting Assistant SHALL replace the oldest stored snippet for that identity and reset candidates from the current meeting attendees, because the previous snippet may have been dirty.
#### Scenario: Candidate elimination promotes canonical name
- **GIVEN** an unnamed identity has candidate names `John` and `Mike`
- **WHEN** that identity matches a speaker in a later meeting with attendees `Jane`, `John`, and `Chris`
- **THEN** Meeting Assistant removes `Mike` from the identity candidates and promotes `John` as the canonical name
- **AND** appends the identity naming audit line to the identity's referenced transcripts
#### Scenario: Alias participates in candidate elimination
- **GIVEN** an unnamed identity has candidate name `Michael` and alias `Mike`
- **WHEN** that identity matches a speaker in a later meeting with attendee `Mike`
- **THEN** Meeting Assistant treats `Mike` as matching `Michael`
- **AND** promotes `Michael` as the canonical name
#### Scenario: Empty candidate intersection resets candidates
- **GIVEN** an unnamed identity has candidate names `John` and `Mike`
- **WHEN** that identity matches a speaker in a later meeting with attendees `Jane` and `Chris`
- **THEN** Meeting Assistant resets the identity candidates to `Jane` and `Chris`
- **AND** replaces the oldest stored snippet for that identity with the current speaker snippet
### Requirement: Speaker identity matches relabel transcripts
Meeting Assistant SHALL attempt to match unknown diarized speaker snippets against known speaker identities ordered by calculated meeting reference count.
Speaker identity matching SHALL use a dedicated Azure Speech diarization verifier component rather than the configured speech recognition pipeline.
The matcher SHALL test at most the configured batch size of known people per diarization session and continue with later batches until a match is found or no candidates remain.
The matcher SHALL prioritize identities whose canonical name or aliases match current meeting attendees.
After attendee-matched identities, the matcher SHALL order identities by calculated meeting reference count, filter out non-attendee identities whose last update is older than the configured active age, and cap the candidate set at the configured maximum match candidate count.
When a match is confirmed in final identity processing, Meeting Assistant SHALL store a meeting file reference for that identity.
When a match is confirmed and the identity has a canonical name, Meeting Assistant SHALL rewrite finished transcript segments for that diarized speaker with the canonical name.
When a match is confirmed and the matched speaker is not already listed in meeting note attendees by display name or alias, Meeting Assistant SHALL add the speaker display name to the attendee list.
When Meeting Assistant writes attendees from calendar metadata, it SHALL match attendee display names exactly against known identity canonical names and aliases, replace matches with the identity display name, and deduplicate attendees that map to the same identity.
#### Scenario: Finished transcript is relabeled after a confirmed match
- **GIVEN** the speaker identity database contains canonical speaker `Chris`
- **WHEN** a finished transcript has diarized speaker `Guest03` and the matching backend confirms it is `Chris`
- **THEN** Meeting Assistant rewrites `Guest03` segments in the transcript as `Chris`
#### Scenario: Confirmed match stores meeting reference
- **GIVEN** the speaker identity database contains canonical speaker `Chris`
- **WHEN** a finished transcript has diarized speaker `Guest03` and the matching backend confirms it is `Chris`
- **THEN** Meeting Assistant stores the meeting note and transcript file addresses as a reference for `Chris`
### Requirement: Speaker identities can be merged diagnostically
Meeting Assistant SHALL expose a diagnostic endpoint that merges duplicate speaker identities.
The merge process SHALL compare recently-created identities, using a configurable recent age that defaults to two weeks, against all other identities in matcher batches bounded by the configured batch size.
The merge process SHALL require a match and a second validation match using a different source sample before merging two identities.
When identities are merged, Meeting Assistant SHALL retain one identity, move useful names from the merged identity into aliases, combine meeting file references, retain a bounded set of snippets from both identities, and append an audit line to each referenced transcript in the form `<date> <name 1> and <name 2> were merged`.
#### Scenario: Recently-created duplicate identity is merged
- **GIVEN** a recently-created identity and an older identity have matching speaker snippets
- **WHEN** the diagnostic merge endpoint is triggered
- **THEN** Meeting Assistant validates the match twice with different source snippets
- **AND** merges the recent identity into the older identity
- **AND** stores the recent identity name as an alias on the retained identity
- **AND** keeps meeting file references from both identities
- **AND** appends the merge audit line to the referenced transcripts
#### Scenario: Old identities are not used as merge sources
- **GIVEN** two identities older than the configured recent age
- **WHEN** the diagnostic merge endpoint is triggered
- **THEN** Meeting Assistant does not compare them as source identities
@@ -0,0 +1,10 @@
## 1. Implementation
- [x] Add speaker identity reference storage and schema creation.
- [x] Replace transcript-count ordering and match candidate data with calculated reference counts.
- [x] Record meeting note/transcript references when final identity processing matches or creates an identity.
- [x] Append transcript audit lines when identities are named or merged.
## 2. Verification
- [x] Add/adjust behavior tests for references, calculated counts, naming audit lines, and merge audit lines.
- [x] Run focused speaker identity tests.
- [x] Run `openspec validate add-speaker-identity-references --strict`.
@@ -0,0 +1,12 @@
## Why
The summary agent can edit supporting files such as project notes or the dictation dictionary. Those writes are currently invisible after the run unless the user notices file changes manually.
## What Changes
- Track in-memory diffs for summary-agent writes outside the summary and assistant context files.
- Use a real diff implementation so whole-file rewrites produce useful changed-line output.
- Append the collected diff audit to the assistant context when the summary agent finishes.
## Impact
- Adds a .NET diff package dependency.
- Summary tool write paths gain audit capture for project files and dictation dictionary writes.
- Assistant context files receive an appended audit section for external files changed by the agent.
@@ -0,0 +1,82 @@
## MODIFIED Requirements
### Requirement: Meeting Assistant generates meeting outputs
Meeting Assistant SHALL generate a summary, decisions, and next steps from the meeting transcript, metadata, user notes, and assistant-discovered context.
Meeting Assistant SHALL use a Microsoft Agent Framework pipeline for the first summary implementation. The pipeline SHALL use a configurable OpenAI-compatible endpoint, model, and API key source, including support for a direct key or an environment variable name.
The summary pipeline SHALL retry transient model endpoint failures according to configurable reconnection attempts and delay settings.
The summary pipeline SHALL track context-window usage for the configured model using response usage when available and request-size estimates otherwise. The context-window limit, maximum output reserve, compaction enablement, compaction threshold, and Responses compact endpoint path SHALL be configurable.
When only the configured remaining context ratio is available, the summary pipeline SHALL try to compact the conversation through the configured OpenAI-compatible `POST /v1/responses/compact` endpoint. If that endpoint is unavailable or returns invalid data, the pipeline SHALL fall back to Microsoft Agent Framework compaction.
Fallback compaction SHALL become increasingly aggressive: collapse old tool results, summarize older conversation spans, keep only the last four user turns, and drop oldest groups if still over budget.
When summary generation fails after retries, Meeting Assistant SHALL write a markdown failure report to the configured summary note path. The failure report SHALL include a clickable retry link, error details, and the meeting artifact paths needed to diagnose or retry the run.
After transcript processing finishes for a recording, Meeting Assistant SHALL automatically invoke the summary pipeline for that meeting.
Meeting Assistant SHALL expose an API operation to retry summary generation for a given summary note path. The retry operation SHALL resolve the linked meeting artifacts from the meeting note frontmatter and SHALL overwrite the existing summary note with either the new summary or a new failure report.
The summary pipeline SHALL expose tools scoped to the current meeting:
- `read_meetingnote`
- `read_transcript`
- `read_context`
- `read_usernotes`
- `read_glossary`
- `write_summary`
- `write_context`
- `list_projects`
- `list_projectfiles`
- `read_projectfile`
- `write_projectfile`
- `search`
All summary-agent read tools that return file-like content SHALL support either reading the whole file or reading a clamped inclusive 1-based line range when `from` and `to` are supplied.
The summary pipeline SHALL write the summary note as a markdown artifact linked to the meeting note, transcript, and assistant context.
The summary agent SHALL be able to read the meeting note, transcript, assistant context, glossary, and bound project files through tools.
The summary agent SHALL be able to write the summary and assistant context files as its owned artifacts.
For summary-agent writes to any file other than the summary file and assistant context file, Meeting Assistant SHALL record an in-memory diff containing removed and added lines.
Meeting Assistant SHALL use a diff implementation that can reduce whole-file rewrites to changed lines instead of treating every rewrite as a full replacement.
When the summary agent finishes, Meeting Assistant SHALL append the collected external write diffs to the assistant context file.
After writing the meeting summary, the summary agent SHALL update existing project files when the meeting produced project-relevant knowledge.
The assistant context note SHALL keep `title`, `start_time`, `end_time`, `meeting`, `transcript`, `summary`, `state`, and `agenda` in frontmatter. Meeting Assistant SHALL write `title` and `start_time` when the assistant context note is created, update `title` when later meeting metadata is discovered, and write `end_time` when transcript processing stops. When a Teams appointment is detected at recording start, the assistant context note SHALL also keep `scheduled_end` in frontmatter. The `state` value SHALL be one of `transcribing`, `speaker recognition`, `summarizing`, `finished`, or `error`.
The summary note SHALL keep frontmatter links to meeting artifacts and SHALL copy `title`, `start_time`, and `end_time` from the meeting note when available. If the meeting note has no title, the summary agent SHALL be able to provide a title when writing the summary.
The summary agent SHALL be able to read and write the assistant context body as its own notebook using overwrite, replace, and insert line modes.
#### Scenario: Assistant context note is initialized
- **WHEN** Meeting Assistant starts a meeting session
- **THEN** it creates the assistant context note with frontmatter links to the meeting note, transcript note, and summary note
- **AND** the assistant context frontmatter state is `transcribing`
- **AND** the assistant context frontmatter includes `agenda`, empty when no agenda is known
- **AND** the assistant context frontmatter includes `scheduled_end` when Teams metadata supplied a scheduled end time
#### Scenario: Assistant context state follows meeting processing
- **WHEN** transcription, final speaker recognition, and summary generation progress
- **THEN** Meeting Assistant updates assistant context frontmatter state to `transcribing`, `speaker recognition`, `summarizing`, `finished`, or `error` as appropriate
- **AND** preserves existing `agenda` and `scheduled_end` frontmatter values
#### Scenario: External project write is audited
- **WHEN** the summary agent writes a bound project file
- **THEN** Meeting Assistant records the changed removed and added lines for that project file
- **AND** appends that diff to the assistant context after the agent finishes
#### Scenario: Dictionary write is audited
- **WHEN** the summary agent adds a dictation word and the dictionary file changes
- **THEN** Meeting Assistant records the changed removed and added lines for the dictionary file
- **AND** appends that diff to the assistant context after the agent finishes
#### Scenario: Owned artifact writes are not audited
- **WHEN** the summary agent writes the summary or assistant context file
- **THEN** Meeting Assistant does not include those writes in the external write diff audit
@@ -0,0 +1,9 @@
## 1. Implementation
- [x] Add an in-memory summary-agent write audit component.
- [x] Capture project-file and dictation-dictionary diffs while excluding summary and assistant-context writes.
- [x] Append the collected audit to assistant context after the summary agent finishes.
## 2. Verification
- [x] Add behavior tests for project-file diff capture, dictionary diff capture, and summary/context exclusion.
- [x] Run focused summary/project tool tests.
- [x] Run `openspec validate add-summary-agent-write-diff-audit --strict`.
@@ -0,0 +1,11 @@
## Why
Users can stop one meeting and immediately start the next while the previous meeting is still draining transcription, speaker recognition, or summarization. Per-run buffers and artifacts must remain isolated so old transcription output cannot be written to the new meeting.
## What Changes
- Keep meeting note, transcript session, artifact paths, and run options bound to the run that created them.
- Allow a new recording to start after the previous capture has stopped even while the previous run continues final processing.
- Ensure rapidly-created artifact filenames are distinct.
## Impact
- Recording coordinator finalization paths use immutable per-run state instead of mutable current-run fields.
- Transcript, meeting note, assistant context, and summary filenames include higher-resolution timestamps.
@@ -0,0 +1,22 @@
## MODIFIED Requirements
### Requirement: Stopping recording drains captured audio through transcription
Meeting Assistant SHALL stop capturing new audio when recording mode is stopped, but it SHALL allow already captured audio to finish running through the configured speech recognition pipeline before the recording session completes.
When a recording is stopped, Meeting Assistant SHALL stop audio capture for that run and MAY continue draining transcription, speaker recognition, and summary generation for that stopped run.
Meeting Assistant SHALL allow a new recording to start after the previous run's capture has stopped, even if the previous run is still draining transcription, speaker recognition, or summary generation.
Each run SHALL keep its own transcript session, meeting note path, artifact paths, options, audio buffer, live transcript buffer, and speaker mappings isolated from later runs.
Rapidly-created meeting note, transcript, assistant context, and summary artifact filenames SHALL be distinct.
#### Scenario: Hotkey stops recording with buffered audio
- **WHEN** the user presses the hotkey to stop recording while captured audio is still buffered for transcription
- **THEN** Meeting Assistant stops capturing new audio and appends transcription output for the already captured audio before reporting the recording stopped
#### Scenario: New capture starts while previous run is finalizing
- **GIVEN** a recording has been stopped and is still finalizing its transcript or summary
- **WHEN** the user starts another recording
- **THEN** Meeting Assistant starts the new capture
- **AND** final transcription and summary output from the stopped run use the stopped run's files
- **AND** live transcription buffers from the stopped run are not written to the new run
@@ -0,0 +1,8 @@
## 1. Implementation
- [x] Add regression coverage for rapid stop/start while the first run is still finalizing.
- [x] Move finalization and summary reads/writes to per-run meeting note and artifact state.
- [x] Make rapidly-created artifact filenames distinct.
## 2. Verification
- [x] Run focused recording coordinator tests.
- [x] Run `openspec validate allow-overlapping-meeting-finalization --strict`.
@@ -0,0 +1,11 @@
## Why
Meeting artifact notes currently emit frontmatter properties that can point back to the same note. Self-references add noise and make backlinks less useful.
## What Changes
- Meeting note frontmatter keeps links to transcript, assistant context, and summary.
- Transcript, assistant context, and summary notes keep links only to the other notes from the same run.
- The frontmatter property that would reference the note itself is omitted.
## Impact
- Shared artifact frontmatter rendering changes for transcript, assistant context, and summary notes.
- Existing resolver behavior based on meeting note links is unchanged.
@@ -0,0 +1,16 @@
## MODIFIED Requirements
### Requirement: Meeting note template is coded and round-trippable
Meeting Assistant SHALL link the note files generated for one meeting run through frontmatter.
The meeting note frontmatter SHALL link to the transcript, assistant context, and summary notes.
Generated artifact notes SHALL link only to the other notes from the same run and SHALL omit the frontmatter property that would reference themselves.
#### Scenario: Meeting note links to generated artifacts
- **WHEN** Meeting Assistant creates a meeting note
- **THEN** the note frontmatter links to the configured transcript, assistant context, and summary note locations
#### Scenario: Generated artifacts do not self-reference
- **WHEN** Meeting Assistant writes transcript, assistant context, or summary artifact frontmatter
- **THEN** the artifact frontmatter links to the other run notes
- **AND** the artifact frontmatter omits the property for the artifact's own note type
@@ -0,0 +1,8 @@
## 1. Implementation
- [x] Omit empty/self artifact link fields from rendered artifact frontmatter.
- [x] Ensure transcript, assistant context, and summary frontmatter include only other run artifacts.
## 2. Verification
- [x] Add/update artifact frontmatter behavior tests.
- [x] Run focused artifact/summary tests.
- [x] Run `openspec validate remove-artifact-self-frontmatter-links --strict`.