Vibecode Fireflies.ai
track this build5 phases, 15 steps, beginner friendly0%The summarizer is easy; the meeting bot, calendar routing, integrations, team search, and admin layer make it more than a one-prompt clone.
You are building a lean indie version of Fireflies.ai. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Fireflies.ai · indie build A recorder that joins a meeting as a clearly labelled participant, announces itself, captures audio, transcribes with speaker labels and word timings, produces summaries and action items where every line cites a timestamp, and deletes recordings on a schedule you configured. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Joining | A headless browser participant, or the platform's own recording interface where one exists | the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback | | Runtime | Node 22 with PostgreSQL | meetings, transcripts and retention are relational and consequential | | Transcription | whisper.cpp with word timings, run locally | recordings of your colleagues' conversations should not need to leave the machine | | Speakers | Diarisation refined by hand, seeded from the participant list | a summary attributing a decision to the wrong person is worse than an unattributed one | | Storage | Recordings encrypted with an expiry, transcripts kept separately | the two have different retention needs and should not share one policy by accident | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the bot, the pipeline and the interface. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your confirmed recording consent position** · an hour of advice, and it is a prerequisite rather than a formality - Why: It decides whether the bot may record at all and what it must say first. Some jurisdictions require every participant's agreement, and a meeting with attendees in different places takes the strictest applicable rule. - Get it: Identify where participants typically join from, take the strictest rule, and confirm with someone qualified if this is for a business. Record what you concluded and when. - Verify: A written note names the jurisdictions, the rule applied and the date - [ ] **A check of the meeting platform's terms on automated participants** · free - Why: Some platforms restrict or prohibit bots joining, and building against one that does means a tool you cannot use. - Get it: Read the terms for the platform you use, and prefer its own recording interface where one exists, since that route is sanctioned. - Verify: The position is recorded with a date and the chosen joining method - [ ] **whisper.cpp with a model supporting word timings** · free, but transcription takes real machine time - Why: Citations and playback both need word-level timings, and running locally keeps colleagues' conversations off a third party. - Get it: Build whisper.cpp and download a model sized for your machine. - Verify: Output contains per-word start and end times - [ ] **Three real meetings you have permission to record** · free - Why: Diarisation, audio levels and summary quality are all only judgeable against real conversation with more than two people talking over each other. - Get it: Ask colleagues, explain what you are testing, and record with their agreement. One meeting should have at least four participants. - Verify: Three recordings exist with documented consent - [ ] **A retention policy per artefact** · free - Why: Recordings, transcripts, summaries and consent records may need different periods, and automatic deletion has to be built rather than remembered. - Get it: Write it before recording anything real: what is kept, how long, and why. - Verify: The policy is written and the deletion job implements it ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Bot reliability across every meeting platform. - Integrations into customer and project systems. - Organisational permission structures. - Legal advice about recording consent. - native meeting bot reliability - integrations with CRM/project tools - team permissions - shared topic tracking - compliance controls If one of those is essential to you, that is the reason to keep paying for Fireflies.ai, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Fireflies.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a meeting recorder that joins the call, transcribes it, and produces notes that cite the recording to replace Fireflies.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A bot joining a meeting is a recording device entering a room, and everyone in that room has a right to know. Announce it on join, make it visible in the participant list under a name that says what it is, and give the host a way to remove it that actually works. Consent requirements for recording differ by jurisdiction and several require every participant's agreement rather than one person's; a meeting with attendees in different places takes the strictest rule that applies. Check the platform's terms too, since some restrict automated participants. The second rule is about the notes. A summary that states something nobody said is worse than no summary, because people act on it without listening back. Every claim in the output cites a moment in the transcript and is rejected if it cannot. ### Stack (fixed, do not substitute) - Joining: A headless browser participant, or the platform's own recording interface where one exists · the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback - Runtime: Node 22 with PostgreSQL · meetings, transcripts and retention are relational and consequential - Transcription: whisper.cpp with word timings, run locally · recordings of your colleagues' conversations should not need to leave the machine - Speakers: Diarisation refined by hand, seeded from the participant list · a summary attributing a decision to the wrong person is worse than an unattributed one - Storage: Recordings encrypted with an expiry, transcripts kept separately · the two have different retention needs and should not share one policy by accident ### Data model (create this before Phase 1) - meetings(id, external_id, title, platform, started_at, ended_at, host_email, consent_state, announced_at) - participants(id, meeting_id, name, email, joined_at, left_at, objected_at) - recordings(meeting_id, storage_key, encrypted, expires_at, deleted_at) - words(meeting_id, ordinal, text, start_ms, end_ms, speaker_label) · the spine everything cites - segments(id, meeting_id, start_ms, end_ms, speaker_label, text) - outputs(id, meeting_id, kind, body, cited_segment_ids_json, validated) · kind is summary, actions or decisions - A meeting where a participant objected has no recording and no transcript. The objection is the record. ### Phase 1 · Joining visibly and announcing Build: Join a meeting as a participant named so its purpose is obvious. Announce on joining that the meeting is being recorded, by whom and why. Provide a way for anyone to object that stops the recording, and honour it. Trap: Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. Done when: The bot appears in the participant list under a name identifying it as a recorder. An announcement is delivered on joining and its time is recorded. An objection stops the recording within seconds and no recording file remains, verified by checking storage. The host removing the bot ends the recording cleanly. Do not build yet: No transcription, summaries or retention. ### Phase 2 · Audio worth transcribing Build: Capture the meeting audio, normalise loudness across participants who joined at different levels, and detect a failure to capture rather than producing silence. Store encrypted with an expiry set at creation. Trap: A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. Done when: A recording of a three person meeting contains all three audibly, verified by listening. A participant who never rises above the noise floor is reported rather than silently missing. The recording is encrypted at rest with an expiry set at creation. A failed capture is reported rather than producing a silent file. Do not build yet: No transcription or speakers yet. ### Phase 3 · Transcript with speakers you can trust Build: Transcribe with word-level timings. Assign speaker labels, seeded from the participant list where the platform provides one, and make corrections easy and persistent. Show confidence so an uncertain attribution is visible. Trap: Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. Done when: Playing a single word's range plays that word. Speaker labels are assigned and a correction propagates to that speaker's other segments. Low confidence attributions are visibly marked. A meeting where diarisation failed reports that rather than assigning everything to one speaker. Do not build yet: No summaries yet. ### Phase 4 · Notes that cite the recording Build: Generate a summary, decisions and action items from the transcript segments. Require a segment citation per line. Validate that every cited identifier exists and reject the output if one does not. Attribute an action item only where the transcript supports it. Trap: A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. Done when: Every summary line cites a segment that resolves to real text. A crafted output citing a nonexistent segment is rejected, tested by feeding one to the validator. Clicking a citation plays that moment. An action item with no explicit owner in the transcript is left unassigned rather than attributed. Do not build yet: No search or retention yet. ### Phase 5 · Search across meetings Build: Full text search across transcripts with speaker and date filters. Results link to the moment. Restrict search to meetings the searcher attended or was granted access to, enforced server side. Trap: Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. Done when: A phrase search returns the meeting and the moment. A user cannot search a meeting they did not attend and were not granted, verified by a direct request. Speaker and date filters narrow correctly. Search across a few hundred meetings returns in well under a hundred milliseconds. Do not build yet: Retention and deletion come last. ### Phase 6 · Retention, deletion and the record Build: Automatic deletion of recordings past their retention, with the run logged. A deletion request removing the recording, transcript, summaries and search entries together. Keep the consent and announcement record separately and for longer. Trap: Deleting the recording and keeping the transcript satisfies neither the policy nor the participant's expectation, since the transcript is the more readable record of what they said. Decide what each period covers, apply it to every artefact, and keep the announcement record longer because it is your evidence that the recording was disclosed. Done when: Recordings past retention are deleted automatically and each run logs what it removed. A deletion request removes recording, transcript, summaries and search entries, verified by searching for a known phrase afterwards. The announcement and consent record survives. Retention periods are configured per artefact and documented. Do not build yet: Nothing after this. ### Out of scope (and why) - Bot reliability across every meeting platform, which is the substantial engineering behind the commercial product and breaks whenever a platform changes. - Integrations pushing notes into customer and project systems, each of which is an API and a permission model. - Team permission structures beyond attendee scoping, which is where an organisation's real requirements live. - Legal advice about recording consent, which differs by jurisdiction and is a prerequisite you settle rather than a feature. ### README must contain - That the bot joins under an identifying name and announces itself, and that objecting stops the recording - The consent position you confirmed, with the jurisdictions covered and the date - That every summary line cites a segment and an uncited output is rejected - That an action item is attributed only where the transcript names an owner - The retention period per artefact and that the announcement record outlives the recording ===== AGENTS.md ===== # Agent instructions · Fireflies.ai indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A headless browser participant, or the platform's own recording interface where one exists, Node 22 with PostgreSQL, whisper.cpp with word timings, run locally, Diarisation refined by hand, seeded from the participant list, Recordings encrypted with an expiry, transcripts kept separately. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. ===== BUILD_PLAN.md ===== # Build plan · Fireflies.ai A recorder that joins a meeting as a clearly labelled participant, announces itself, captures audio, transcribes with speaker labels and word timings, produces summaries and action items where every line cites a timestamp, and deletes recordings on a schedule you configured. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Joining visibly and announcing The bot joins under a name that says what it is and tells the room. ### Steps 1. Join under an obvious name Something nobody has to ask about. This is the cheapest trust you will ever buy. Files: `src/join.ts` 2. Announce and record the announcement In the first seconds, with the time stored on the meeting. Files: `src/announce.ts` 3. Make objection stop everything Recording ends, file removed. Logged and ignored is the failure mode to avoid. Files: `src/consent.ts` ### Done when - [ ] The bot is visible under an identifying name - [ ] The announcement is delivered and its time recorded - [ ] An objection stops recording and leaves no file - [ ] Host removal ends the recording cleanly ### Watch out - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. ## Phase 2 · Audio worth transcribing The recording captures everyone clearly enough to transcribe. ### Steps 1. Capture and normalise Loudness across participants, since people join at wildly different levels. Files: `src/capture.ts` 2. Detect missing participants Anyone who never exceeds the noise floor. Report rather than transcribe a partial meeting. Files: `src/audio-check.ts` 3. Encrypt with an expiry at creation Not later. Retention set when the file exists is retention that happens. Files: `src/storage.ts` ### Done when - [ ] All three participants are audible in a test meeting - [ ] An inaudible participant is reported - [ ] Recordings are encrypted with an expiry at creation - [ ] A failed capture is reported rather than silent ### Watch out - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. ## Phase 3 · Transcript with speakers you can trust Every word has a time and an attribution you can correct. ### Steps 1. Transcribe with word timings Requested explicitly. Segment level cannot support citation later. Files: `src/transcribe.ts` 2. Seed speakers from the participant list Where available. Guessing from audio alone is much worse. Files: `src/speakers.ts` 3. Make corrections propagate Correcting one segment corrects that speaker throughout. Otherwise nobody corrects. Files: `src/speakers.ts` ### Done when - [ ] A single word's range plays that word - [ ] A speaker correction propagates to their other segments - [ ] Low confidence attributions are marked - [ ] Failed diarisation is reported rather than collapsed to one speaker ### Watch out - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. ## Phase 4 · Notes that cite the recording Every line in the summary points at a moment you can play. ### Steps 1. Pass numbered segments and demand citations Structured output so parsing is not a regular expression. Files: `src/summarise.ts` 2. Validate citations A pure function, unit tested with a crafted bad citation rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Require explicit attribution for owners Named in the cited text, or the action stays unassigned. Files: `src/actions.ts` ### Done when - [ ] Every summary line cites a real segment - [ ] A crafted bad citation is rejected - [ ] Clicking a citation plays that moment - [ ] An unattributed action stays unassigned ### Watch out - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. ## Phase 5 · Search across meetings You can find the meeting where something was said. ### Steps 1. Index transcripts with timings So a result links to a moment rather than a meeting. Files: `src/search.ts` 2. Scope to attendees by default Enforced in the query. Sharing beyond that is a deliberate action. Files: `src/access.ts` 3. Test with a direct request As a non-attendee. The interface is not the test. Files: `test/access.test.ts` ### Done when - [ ] A phrase search returns the meeting and moment - [ ] A non-attendee's direct search request is refused - [ ] Speaker and date filters narrow correctly - [ ] Search across hundreds of meetings is fast ### Watch out - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. ## Not in this build - Bot reliability across every meeting platform. - Integrations into customer and project systems. - Organisational permission structures. - Legal advice about recording consent. ## After v1, if you want it - A pre-meeting notice to invitees, which is better consent practice than announcing on the day - Topic segmentation, so a long meeting can be navigated rather than read - Follow-up detection across meetings, which is where a searchable archive starts paying ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Meetings, transcripts and consent records. Personal data with obligations attached. DATABASE_URL=postgres://user:pass@localhost:5432/meetings # Required. Encrypted at rest with lifecycle expiry as a second line of defence. RECORDINGS_BUCKET=meeting-recordings # Required. How the bot appears in the participant list. It should need no explanation. BOT_DISPLAY_NAME=Notetaker (recording) # Required. What the bot says on joining. Configuration so it can differ by jurisdiction. ANNOUNCEMENT_TEXT=./config/announcement.md # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, and a separate decision rather than an inherited one. TRANSCRIPT_RETENTION_DAYS=365 # Required. Longest, because it is the evidence the recording was disclosed. CONSENT_RETENTION_DAYS=2555
You are building a lean indie version of Fireflies.ai. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Fireflies.ai · indie build A recorder that joins a meeting as a clearly labelled participant, announces itself, captures audio, transcribes with speaker labels and word timings, produces summaries and action items where every line cites a timestamp, and deletes recordings on a schedule you configured. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Joining | A headless browser participant, or the platform's own recording interface where one exists | the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback | | Runtime | Node 22 with PostgreSQL | meetings, transcripts and retention are relational and consequential | | Transcription | whisper.cpp with word timings, run locally | recordings of your colleagues' conversations should not need to leave the machine | | Speakers | Diarisation refined by hand, seeded from the participant list | a summary attributing a decision to the wrong person is worse than an unattributed one | | Storage | Recordings encrypted with an expiry, transcripts kept separately | the two have different retention needs and should not share one policy by accident | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the bot, the pipeline and the interface. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your confirmed recording consent position** · an hour of advice, and it is a prerequisite rather than a formality - Why: It decides whether the bot may record at all and what it must say first. Some jurisdictions require every participant's agreement, and a meeting with attendees in different places takes the strictest applicable rule. - Get it: Identify where participants typically join from, take the strictest rule, and confirm with someone qualified if this is for a business. Record what you concluded and when. - Verify: A written note names the jurisdictions, the rule applied and the date - [ ] **A check of the meeting platform's terms on automated participants** · free - Why: Some platforms restrict or prohibit bots joining, and building against one that does means a tool you cannot use. - Get it: Read the terms for the platform you use, and prefer its own recording interface where one exists, since that route is sanctioned. - Verify: The position is recorded with a date and the chosen joining method - [ ] **whisper.cpp with a model supporting word timings** · free, but transcription takes real machine time - Why: Citations and playback both need word-level timings, and running locally keeps colleagues' conversations off a third party. - Get it: Build whisper.cpp and download a model sized for your machine. - Verify: Output contains per-word start and end times - [ ] **Three real meetings you have permission to record** · free - Why: Diarisation, audio levels and summary quality are all only judgeable against real conversation with more than two people talking over each other. - Get it: Ask colleagues, explain what you are testing, and record with their agreement. One meeting should have at least four participants. - Verify: Three recordings exist with documented consent - [ ] **A retention policy per artefact** · free - Why: Recordings, transcripts, summaries and consent records may need different periods, and automatic deletion has to be built rather than remembered. - Get it: Write it before recording anything real: what is kept, how long, and why. - Verify: The policy is written and the deletion job implements it ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Bot reliability across every meeting platform. - Integrations into customer and project systems. - Organisational permission structures. - Legal advice about recording consent. - native meeting bot reliability - integrations with CRM/project tools - team permissions - shared topic tracking - compliance controls If one of those is essential to you, that is the reason to keep paying for Fireflies.ai, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Fireflies.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a meeting recorder that joins the call, transcribes it, and produces notes that cite the recording to replace Fireflies.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A bot joining a meeting is a recording device entering a room, and everyone in that room has a right to know. Announce it on join, make it visible in the participant list under a name that says what it is, and give the host a way to remove it that actually works. Consent requirements for recording differ by jurisdiction and several require every participant's agreement rather than one person's; a meeting with attendees in different places takes the strictest rule that applies. Check the platform's terms too, since some restrict automated participants. The second rule is about the notes. A summary that states something nobody said is worse than no summary, because people act on it without listening back. Every claim in the output cites a moment in the transcript and is rejected if it cannot. ### Stack (fixed, do not substitute) - Joining: A headless browser participant, or the platform's own recording interface where one exists · the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback - Runtime: Node 22 with PostgreSQL · meetings, transcripts and retention are relational and consequential - Transcription: whisper.cpp with word timings, run locally · recordings of your colleagues' conversations should not need to leave the machine - Speakers: Diarisation refined by hand, seeded from the participant list · a summary attributing a decision to the wrong person is worse than an unattributed one - Storage: Recordings encrypted with an expiry, transcripts kept separately · the two have different retention needs and should not share one policy by accident ### Data model (create this before Phase 1) - meetings(id, external_id, title, platform, started_at, ended_at, host_email, consent_state, announced_at) - participants(id, meeting_id, name, email, joined_at, left_at, objected_at) - recordings(meeting_id, storage_key, encrypted, expires_at, deleted_at) - words(meeting_id, ordinal, text, start_ms, end_ms, speaker_label) · the spine everything cites - segments(id, meeting_id, start_ms, end_ms, speaker_label, text) - outputs(id, meeting_id, kind, body, cited_segment_ids_json, validated) · kind is summary, actions or decisions - A meeting where a participant objected has no recording and no transcript. The objection is the record. ### Phase 1 · Joining visibly and announcing Build: Join a meeting as a participant named so its purpose is obvious. Announce on joining that the meeting is being recorded, by whom and why. Provide a way for anyone to object that stops the recording, and honour it. Trap: Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. Done when: The bot appears in the participant list under a name identifying it as a recorder. An announcement is delivered on joining and its time is recorded. An objection stops the recording within seconds and no recording file remains, verified by checking storage. The host removing the bot ends the recording cleanly. Do not build yet: No transcription, summaries or retention. ### Phase 2 · Audio worth transcribing Build: Capture the meeting audio, normalise loudness across participants who joined at different levels, and detect a failure to capture rather than producing silence. Store encrypted with an expiry set at creation. Trap: A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. Done when: A recording of a three person meeting contains all three audibly, verified by listening. A participant who never rises above the noise floor is reported rather than silently missing. The recording is encrypted at rest with an expiry set at creation. A failed capture is reported rather than producing a silent file. Do not build yet: No transcription or speakers yet. ### Phase 3 · Transcript with speakers you can trust Build: Transcribe with word-level timings. Assign speaker labels, seeded from the participant list where the platform provides one, and make corrections easy and persistent. Show confidence so an uncertain attribution is visible. Trap: Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. Done when: Playing a single word's range plays that word. Speaker labels are assigned and a correction propagates to that speaker's other segments. Low confidence attributions are visibly marked. A meeting where diarisation failed reports that rather than assigning everything to one speaker. Do not build yet: No summaries yet. ### Phase 4 · Notes that cite the recording Build: Generate a summary, decisions and action items from the transcript segments. Require a segment citation per line. Validate that every cited identifier exists and reject the output if one does not. Attribute an action item only where the transcript supports it. Trap: A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. Done when: Every summary line cites a segment that resolves to real text. A crafted output citing a nonexistent segment is rejected, tested by feeding one to the validator. Clicking a citation plays that moment. An action item with no explicit owner in the transcript is left unassigned rather than attributed. Do not build yet: No search or retention yet. ### Phase 5 · Search across meetings Build: Full text search across transcripts with speaker and date filters. Results link to the moment. Restrict search to meetings the searcher attended or was granted access to, enforced server side. Trap: Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. Done when: A phrase search returns the meeting and the moment. A user cannot search a meeting they did not attend and were not granted, verified by a direct request. Speaker and date filters narrow correctly. Search across a few hundred meetings returns in well under a hundred milliseconds. Do not build yet: Retention and deletion come last. ### Phase 6 · Retention, deletion and the record Build: Automatic deletion of recordings past their retention, with the run logged. A deletion request removing the recording, transcript, summaries and search entries together. Keep the consent and announcement record separately and for longer. Trap: Deleting the recording and keeping the transcript satisfies neither the policy nor the participant's expectation, since the transcript is the more readable record of what they said. Decide what each period covers, apply it to every artefact, and keep the announcement record longer because it is your evidence that the recording was disclosed. Done when: Recordings past retention are deleted automatically and each run logs what it removed. A deletion request removes recording, transcript, summaries and search entries, verified by searching for a known phrase afterwards. The announcement and consent record survives. Retention periods are configured per artefact and documented. Do not build yet: Nothing after this. ### Out of scope (and why) - Bot reliability across every meeting platform, which is the substantial engineering behind the commercial product and breaks whenever a platform changes. - Integrations pushing notes into customer and project systems, each of which is an API and a permission model. - Team permission structures beyond attendee scoping, which is where an organisation's real requirements live. - Legal advice about recording consent, which differs by jurisdiction and is a prerequisite you settle rather than a feature. ### README must contain - That the bot joins under an identifying name and announces itself, and that objecting stops the recording - The consent position you confirmed, with the jurisdictions covered and the date - That every summary line cites a segment and an uncited output is rejected - That an action item is attributed only where the transcript names an owner - The retention period per artefact and that the announcement record outlives the recording ===== AGENTS.md ===== # Agent instructions · Fireflies.ai indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A headless browser participant, or the platform's own recording interface where one exists, Node 22 with PostgreSQL, whisper.cpp with word timings, run locally, Diarisation refined by hand, seeded from the participant list, Recordings encrypted with an expiry, transcripts kept separately. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. ===== BUILD_PLAN.md ===== # Build plan · Fireflies.ai A recorder that joins a meeting as a clearly labelled participant, announces itself, captures audio, transcribes with speaker labels and word timings, produces summaries and action items where every line cites a timestamp, and deletes recordings on a schedule you configured. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Joining visibly and announcing The bot joins under a name that says what it is and tells the room. ### Steps 1. Join under an obvious name Something nobody has to ask about. This is the cheapest trust you will ever buy. Files: `src/join.ts` 2. Announce and record the announcement In the first seconds, with the time stored on the meeting. Files: `src/announce.ts` 3. Make objection stop everything Recording ends, file removed. Logged and ignored is the failure mode to avoid. Files: `src/consent.ts` ### Done when - [ ] The bot is visible under an identifying name - [ ] The announcement is delivered and its time recorded - [ ] An objection stops recording and leaves no file - [ ] Host removal ends the recording cleanly ### Watch out - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. ## Phase 2 · Audio worth transcribing The recording captures everyone clearly enough to transcribe. ### Steps 1. Capture and normalise Loudness across participants, since people join at wildly different levels. Files: `src/capture.ts` 2. Detect missing participants Anyone who never exceeds the noise floor. Report rather than transcribe a partial meeting. Files: `src/audio-check.ts` 3. Encrypt with an expiry at creation Not later. Retention set when the file exists is retention that happens. Files: `src/storage.ts` ### Done when - [ ] All three participants are audible in a test meeting - [ ] An inaudible participant is reported - [ ] Recordings are encrypted with an expiry at creation - [ ] A failed capture is reported rather than silent ### Watch out - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. ## Phase 3 · Transcript with speakers you can trust Every word has a time and an attribution you can correct. ### Steps 1. Transcribe with word timings Requested explicitly. Segment level cannot support citation later. Files: `src/transcribe.ts` 2. Seed speakers from the participant list Where available. Guessing from audio alone is much worse. Files: `src/speakers.ts` 3. Make corrections propagate Correcting one segment corrects that speaker throughout. Otherwise nobody corrects. Files: `src/speakers.ts` ### Done when - [ ] A single word's range plays that word - [ ] A speaker correction propagates to their other segments - [ ] Low confidence attributions are marked - [ ] Failed diarisation is reported rather than collapsed to one speaker ### Watch out - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. ## Phase 4 · Notes that cite the recording Every line in the summary points at a moment you can play. ### Steps 1. Pass numbered segments and demand citations Structured output so parsing is not a regular expression. Files: `src/summarise.ts` 2. Validate citations A pure function, unit tested with a crafted bad citation rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Require explicit attribution for owners Named in the cited text, or the action stays unassigned. Files: `src/actions.ts` ### Done when - [ ] Every summary line cites a real segment - [ ] A crafted bad citation is rejected - [ ] Clicking a citation plays that moment - [ ] An unattributed action stays unassigned ### Watch out - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. ## Phase 5 · Search across meetings You can find the meeting where something was said. ### Steps 1. Index transcripts with timings So a result links to a moment rather than a meeting. Files: `src/search.ts` 2. Scope to attendees by default Enforced in the query. Sharing beyond that is a deliberate action. Files: `src/access.ts` 3. Test with a direct request As a non-attendee. The interface is not the test. Files: `test/access.test.ts` ### Done when - [ ] A phrase search returns the meeting and moment - [ ] A non-attendee's direct search request is refused - [ ] Speaker and date filters narrow correctly - [ ] Search across hundreds of meetings is fast ### Watch out - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. ## Not in this build - Bot reliability across every meeting platform. - Integrations into customer and project systems. - Organisational permission structures. - Legal advice about recording consent. ## After v1, if you want it - A pre-meeting notice to invitees, which is better consent practice than announcing on the day - Topic segmentation, so a long meeting can be navigated rather than read - Follow-up detection across meetings, which is where a searchable archive starts paying ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Meetings, transcripts and consent records. Personal data with obligations attached. DATABASE_URL=postgres://user:pass@localhost:5432/meetings # Required. Encrypted at rest with lifecycle expiry as a second line of defence. RECORDINGS_BUCKET=meeting-recordings # Required. How the bot appears in the participant list. It should need no explanation. BOT_DISPLAY_NAME=Notetaker (recording) # Required. What the bot says on joining. Configuration so it can differ by jurisdiction. ANNOUNCEMENT_TEXT=./config/announcement.md # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, and a separate decision rather than an inherited one. TRANSCRIPT_RETENTION_DAYS=365 # Required. Longest, because it is the evidence the recording was disclosed. CONSENT_RETENTION_DAYS=2555
You are building a production product version of Fireflies.ai. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Fireflies.ai · product brief ## Problem The summarizer is easy; the meeting bot, calendar routing, integrations, team search, and admin layer make it more than a one-prompt clone. ## Product outcome A note-taker that is visible in the room, stops when someone objects, produces notes whose every line can be played back, restricts search to the people who were there, and deletes what it should on a schedule you can prove ran. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - speech API - calendar API - Zoom/Meet/Teams capture route - hosted backend - database/search ## Explicit non-goals for v1 - Bot reliability across every meeting platform. - Integrations into customer and project systems. - Organisational permission structures. - Legal advice about recording consent. - native meeting bot reliability - integrations with CRM/project tools - team permissions - shared topic tracking - compliance controls ## Success criteria - The bot is confirmed visible and announcing on join - An objection is confirmed to stop recording and leave no file - A crafted bad citation is rejected by the validator - A non-attendee's search request is confirmed refused - The retention job is confirmed to delete and log the run ===== BRIEF.md ===== # Build brief · Fireflies.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a meeting recorder that joins the call, transcribes it, and produces notes that cite the recording to replace Fireflies.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A bot joining a meeting is a recording device entering a room, and everyone in that room has a right to know. Announce it on join, make it visible in the participant list under a name that says what it is, and give the host a way to remove it that actually works. Consent requirements for recording differ by jurisdiction and several require every participant's agreement rather than one person's; a meeting with attendees in different places takes the strictest rule that applies. Check the platform's terms too, since some restrict automated participants. The second rule is about the notes. A summary that states something nobody said is worse than no summary, because people act on it without listening back. Every claim in the output cites a moment in the transcript and is rejected if it cannot. ### Stack (fixed, do not substitute) - Joining: A headless browser participant, or the platform's own recording interface where one exists · the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback - Runtime: Node 22 with PostgreSQL · meetings, transcripts and retention are relational and consequential - Transcription: whisper.cpp with word timings, run locally · recordings of your colleagues' conversations should not need to leave the machine - Speakers: Diarisation refined by hand, seeded from the participant list · a summary attributing a decision to the wrong person is worse than an unattributed one - Storage: Recordings encrypted with an expiry, transcripts kept separately · the two have different retention needs and should not share one policy by accident ### Data model (create this before Phase 1) - meetings(id, external_id, title, platform, started_at, ended_at, host_email, consent_state, announced_at) - participants(id, meeting_id, name, email, joined_at, left_at, objected_at) - recordings(meeting_id, storage_key, encrypted, expires_at, deleted_at) - words(meeting_id, ordinal, text, start_ms, end_ms, speaker_label) · the spine everything cites - segments(id, meeting_id, start_ms, end_ms, speaker_label, text) - outputs(id, meeting_id, kind, body, cited_segment_ids_json, validated) · kind is summary, actions or decisions - A meeting where a participant objected has no recording and no transcript. The objection is the record. ### Phase 1 · Joining visibly and announcing Build: Join a meeting as a participant named so its purpose is obvious. Announce on joining that the meeting is being recorded, by whom and why. Provide a way for anyone to object that stops the recording, and honour it. Trap: Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. Done when: The bot appears in the participant list under a name identifying it as a recorder. An announcement is delivered on joining and its time is recorded. An objection stops the recording within seconds and no recording file remains, verified by checking storage. The host removing the bot ends the recording cleanly. Do not build yet: No transcription, summaries or retention. ### Phase 2 · Audio worth transcribing Build: Capture the meeting audio, normalise loudness across participants who joined at different levels, and detect a failure to capture rather than producing silence. Store encrypted with an expiry set at creation. Trap: A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. Done when: A recording of a three person meeting contains all three audibly, verified by listening. A participant who never rises above the noise floor is reported rather than silently missing. The recording is encrypted at rest with an expiry set at creation. A failed capture is reported rather than producing a silent file. Do not build yet: No transcription or speakers yet. ### Phase 3 · Transcript with speakers you can trust Build: Transcribe with word-level timings. Assign speaker labels, seeded from the participant list where the platform provides one, and make corrections easy and persistent. Show confidence so an uncertain attribution is visible. Trap: Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. Done when: Playing a single word's range plays that word. Speaker labels are assigned and a correction propagates to that speaker's other segments. Low confidence attributions are visibly marked. A meeting where diarisation failed reports that rather than assigning everything to one speaker. Do not build yet: No summaries yet. ### Phase 4 · Notes that cite the recording Build: Generate a summary, decisions and action items from the transcript segments. Require a segment citation per line. Validate that every cited identifier exists and reject the output if one does not. Attribute an action item only where the transcript supports it. Trap: A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. Done when: Every summary line cites a segment that resolves to real text. A crafted output citing a nonexistent segment is rejected, tested by feeding one to the validator. Clicking a citation plays that moment. An action item with no explicit owner in the transcript is left unassigned rather than attributed. Do not build yet: No search or retention yet. ### Phase 5 · Search across meetings Build: Full text search across transcripts with speaker and date filters. Results link to the moment. Restrict search to meetings the searcher attended or was granted access to, enforced server side. Trap: Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. Done when: A phrase search returns the meeting and the moment. A user cannot search a meeting they did not attend and were not granted, verified by a direct request. Speaker and date filters narrow correctly. Search across a few hundred meetings returns in well under a hundred milliseconds. Do not build yet: Retention and deletion come last. ### Phase 6 · Retention, deletion and the record Build: Automatic deletion of recordings past their retention, with the run logged. A deletion request removing the recording, transcript, summaries and search entries together. Keep the consent and announcement record separately and for longer. Trap: Deleting the recording and keeping the transcript satisfies neither the policy nor the participant's expectation, since the transcript is the more readable record of what they said. Decide what each period covers, apply it to every artefact, and keep the announcement record longer because it is your evidence that the recording was disclosed. Done when: Recordings past retention are deleted automatically and each run logs what it removed. A deletion request removes recording, transcript, summaries and search entries, verified by searching for a known phrase afterwards. The announcement and consent record survives. Retention periods are configured per artefact and documented. Do not build yet: Nothing after this. ### Out of scope (and why) - Bot reliability across every meeting platform, which is the substantial engineering behind the commercial product and breaks whenever a platform changes. - Integrations pushing notes into customer and project systems, each of which is an API and a permission model. - Team permission structures beyond attendee scoping, which is where an organisation's real requirements live. - Legal advice about recording consent, which differs by jurisdiction and is a prerequisite you settle rather than a feature. ### README must contain - That the bot joins under an identifying name and announces itself, and that objecting stops the recording - The consent position you confirmed, with the jurisdictions covered and the date - That every summary line cites a segment and an uncited output is rejected - That an action item is attributed only where the transcript names an owner - The retention period per artefact and that the announcement record outlives the recording ===== ARCHITECTURE.md ===== # Architecture · Fireflies.ai ## Stack | Part | Choice | Why | | --- | --- | --- | | Joining | A headless browser participant, or the platform's own recording interface where one exists | the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback | | Runtime | Node 22 with PostgreSQL | meetings, transcripts and retention are relational and consequential | | Transcription | whisper.cpp with word timings, run locally | recordings of your colleagues' conversations should not need to leave the machine | | Speakers | Diarisation refined by hand, seeded from the participant list | a summary attributing a decision to the wrong person is worse than an unattributed one | | Storage | Recordings encrypted with an expiry, transcripts kept separately | the two have different retention needs and should not share one policy by accident | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Join and announce | Visibility and consent. | Platform specific. The visible name and the announcement are not optional whatever the joining method. | | Capture | Audio worth transcribing. | Any capture, provided a partial recording is reported rather than transcribed as complete. | | Transcript | Words, timings and speakers. | Any transcriber with word timings. Correction propagation is what makes attribution trustworthy. | | Outputs | Summaries with validated citations. | Any model. The validator is what makes swapping safe and is the reason the output can be trusted. | | Retention | Automatic deletion per artefact. | Any scheduler, provided the run is logged, since the log is the evidence. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Meetings, transcripts and consent records. Personal data with obligations attached. - `RECORDINGS_BUCKET` · required · Encrypted at rest with lifecycle expiry as a second line of defence. - `BOT_DISPLAY_NAME` · required · How the bot appears in the participant list. It should need no explanation. - `ANNOUNCEMENT_TEXT` · required · What the bot says on joining. Configuration so it can differ by jurisdiction. - `RECORDING_RETENTION_DAYS` · required · Per your written policy. Deletion is automatic and logged. - `TRANSCRIPT_RETENTION_DAYS` · required · Usually longer than the recording, and a separate decision rather than an inherited one. - `CONSENT_RETENTION_DAYS` · required · Longest, because it is the evidence the recording was disclosed. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Fireflies.ai product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A headless browser participant, or the platform's own recording interface where one exists, Node 22 with PostgreSQL, whisper.cpp with word timings, run locally, Diarisation refined by hand, seeded from the participant list, Recordings encrypted with an expiry, transcripts kept separately. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. - Deleting the recording and keeping the transcript satisfies neither the policy nor the participant's expectation, since the transcript is the more readable record of what they said. Decide what each period covers, apply it to every artefact, and keep the announcement record longer because it is your evidence that the recording was disclosed. ===== MILESTONES.md ===== # Delivery milestones · Fireflies.ai Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Joining visibly and announcing The bot joins under a name that says what it is and tells the room. ### Steps 1. Join under an obvious name Something nobody has to ask about. This is the cheapest trust you will ever buy. Files: `src/join.ts` 2. Announce and record the announcement In the first seconds, with the time stored on the meeting. Files: `src/announce.ts` 3. Make objection stop everything Recording ends, file removed. Logged and ignored is the failure mode to avoid. Files: `src/consent.ts` ### Done when - [ ] The bot is visible under an identifying name - [ ] The announcement is delivered and its time recorded - [ ] An objection stops recording and leaves no file - [ ] Host removal ends the recording cleanly ### Watch out - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. ## M2 · Audio worth transcribing The recording captures everyone clearly enough to transcribe. ### Steps 1. Capture and normalise Loudness across participants, since people join at wildly different levels. Files: `src/capture.ts` 2. Detect missing participants Anyone who never exceeds the noise floor. Report rather than transcribe a partial meeting. Files: `src/audio-check.ts` 3. Encrypt with an expiry at creation Not later. Retention set when the file exists is retention that happens. Files: `src/storage.ts` ### Done when - [ ] All three participants are audible in a test meeting - [ ] An inaudible participant is reported - [ ] Recordings are encrypted with an expiry at creation - [ ] A failed capture is reported rather than silent ### Watch out - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. ## M3 · Transcript with speakers you can trust Every word has a time and an attribution you can correct. ### Steps 1. Transcribe with word timings Requested explicitly. Segment level cannot support citation later. Files: `src/transcribe.ts` 2. Seed speakers from the participant list Where available. Guessing from audio alone is much worse. Files: `src/speakers.ts` 3. Make corrections propagate Correcting one segment corrects that speaker throughout. Otherwise nobody corrects. Files: `src/speakers.ts` ### Done when - [ ] A single word's range plays that word - [ ] A speaker correction propagates to their other segments - [ ] Low confidence attributions are marked - [ ] Failed diarisation is reported rather than collapsed to one speaker ### Watch out - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. ## M4 · Notes that cite the recording Every line in the summary points at a moment you can play. ### Steps 1. Pass numbered segments and demand citations Structured output so parsing is not a regular expression. Files: `src/summarise.ts` 2. Validate citations A pure function, unit tested with a crafted bad citation rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Require explicit attribution for owners Named in the cited text, or the action stays unassigned. Files: `src/actions.ts` ### Done when - [ ] Every summary line cites a real segment - [ ] A crafted bad citation is rejected - [ ] Clicking a citation plays that moment - [ ] An unattributed action stays unassigned ### Watch out - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. ## M5 · Search across meetings You can find the meeting where something was said. ### Steps 1. Index transcripts with timings So a result links to a moment rather than a meeting. Files: `src/search.ts` 2. Scope to attendees by default Enforced in the query. Sharing beyond that is a deliberate action. Files: `src/access.ts` 3. Test with a direct request As a non-attendee. The interface is not the test. Files: `test/access.test.ts` ### Done when - [ ] A phrase search returns the meeting and moment - [ ] A non-attendee's direct search request is refused - [ ] Speaker and date filters narrow correctly - [ ] Search across hundreds of meetings is fast ### Watch out - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. ## M6 · Retention, deletion and the record (production only) Recordings expire automatically and a deletion request is honoured completely. ### Steps 1. Delete automatically and log it Per artefact retention. A policy that needs remembering is not automatic. Files: `src/retention.ts` 2. Make deletion complete Every store including the search index. Verify by searching afterwards. Files: `src/delete.ts` 3. Keep the consent record longer It is the evidence the recording was disclosed, and it outlives the recording. Files: `docs/retention.md` ### Done when - [ ] Aged recordings are deleted automatically with a logged run - [ ] A deletion request leaves nothing findable by search - [ ] The announcement record survives the deletion - [ ] Retention is configured per artefact and documented ### Watch out - Deleting the recording and keeping the transcript satisfies neither the policy nor the participant's expectation, since the transcript is the more readable record of what they said. Decide what each period covers, apply it to every artefact, and keep the announcement record longer because it is your evidence that the recording was disclosed. ===== OPERATIONS.md ===== # Operations · Fireflies.ai ## Backup Consent records and transcripts are the durable material. Recordings expire by design, so backing them up would work against the retention policy, which is worth stating rather than assuming. ## Restore Restore the database and confirm consent records survived and the retention job resumes. A restore that reinstates expired recordings has recreated data you were required to delete. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Failed joins, meetings with an inaudible participant, citation validation failures and retention run success. A rising validation failure rate usually means transcript quality dropped rather than the model getting worse. ## Incident checklist If a summary states something nobody said, the citation validator missed a case and the fix belongs there. If a bot joined without announcing, that is the most serious failure here and the announcement should be structurally impossible to skip. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The bot is confirmed visible and announcing on join - [ ] An objection is confirmed to stop recording and leave no file - [ ] A crafted bad citation is rejected by the validator - [ ] A non-attendee's search request is confirmed refused - [ ] The retention job is confirmed to delete and log the run ## Launch constraint Do not market omitted Fireflies.ai capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Meetings, transcripts and consent records. Personal data with obligations attached. DATABASE_URL=postgres://user:pass@localhost:5432/meetings # Required. Encrypted at rest with lifecycle expiry as a second line of defence. RECORDINGS_BUCKET=meeting-recordings # Required. How the bot appears in the participant list. It should need no explanation. BOT_DISPLAY_NAME=Notetaker (recording) # Required. What the bot says on joining. Configuration so it can differ by jurisdiction. ANNOUNCEMENT_TEXT=./config/announcement.md # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, and a separate decision rather than an inherited one. TRANSCRIPT_RETENTION_DAYS=365 # Required. Longest, because it is the evidence the recording was disclosed. CONSENT_RETENTION_DAYS=2555
# Fireflies.ai · indie build A recorder that joins a meeting as a clearly labelled participant, announces itself, captures audio, transcribes with speaker labels and word timings, produces summaries and action items where every line cites a timestamp, and deletes recordings on a schedule you configured. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Joining | A headless browser participant, or the platform's own recording interface where one exists | the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback | | Runtime | Node 22 with PostgreSQL | meetings, transcripts and retention are relational and consequential | | Transcription | whisper.cpp with word timings, run locally | recordings of your colleagues' conversations should not need to leave the machine | | Speakers | Diarisation refined by hand, seeded from the participant list | a summary attributing a decision to the wrong person is worse than an unattributed one | | Storage | Recordings encrypted with an expiry, transcripts kept separately | the two have different retention needs and should not share one policy by accident | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the bot, the pipeline and the interface. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your confirmed recording consent position** · an hour of advice, and it is a prerequisite rather than a formality - Why: It decides whether the bot may record at all and what it must say first. Some jurisdictions require every participant's agreement, and a meeting with attendees in different places takes the strictest applicable rule. - Get it: Identify where participants typically join from, take the strictest rule, and confirm with someone qualified if this is for a business. Record what you concluded and when. - Verify: A written note names the jurisdictions, the rule applied and the date - [ ] **A check of the meeting platform's terms on automated participants** · free - Why: Some platforms restrict or prohibit bots joining, and building against one that does means a tool you cannot use. - Get it: Read the terms for the platform you use, and prefer its own recording interface where one exists, since that route is sanctioned. - Verify: The position is recorded with a date and the chosen joining method - [ ] **whisper.cpp with a model supporting word timings** · free, but transcription takes real machine time - Why: Citations and playback both need word-level timings, and running locally keeps colleagues' conversations off a third party. - Get it: Build whisper.cpp and download a model sized for your machine. - Verify: Output contains per-word start and end times - [ ] **Three real meetings you have permission to record** · free - Why: Diarisation, audio levels and summary quality are all only judgeable against real conversation with more than two people talking over each other. - Get it: Ask colleagues, explain what you are testing, and record with their agreement. One meeting should have at least four participants. - Verify: Three recordings exist with documented consent - [ ] **A retention policy per artefact** · free - Why: Recordings, transcripts, summaries and consent records may need different periods, and automatic deletion has to be built rather than remembered. - Get it: Write it before recording anything real: what is kept, how long, and why. - Verify: The policy is written and the deletion job implements it ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Bot reliability across every meeting platform. - Integrations into customer and project systems. - Organisational permission structures. - Legal advice about recording consent. - native meeting bot reliability - integrations with CRM/project tools - team permissions - shared topic tracking - compliance controls If one of those is essential to you, that is the reason to keep paying for Fireflies.ai, and the README should say so rather than pretend.
# Build brief · Fireflies.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a meeting recorder that joins the call, transcribes it, and produces notes that cite the recording to replace Fireflies.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A bot joining a meeting is a recording device entering a room, and everyone in that room has a right to know. Announce it on join, make it visible in the participant list under a name that says what it is, and give the host a way to remove it that actually works. Consent requirements for recording differ by jurisdiction and several require every participant's agreement rather than one person's; a meeting with attendees in different places takes the strictest rule that applies. Check the platform's terms too, since some restrict automated participants. The second rule is about the notes. A summary that states something nobody said is worse than no summary, because people act on it without listening back. Every claim in the output cites a moment in the transcript and is rejected if it cannot. ### Stack (fixed, do not substitute) - Joining: A headless browser participant, or the platform's own recording interface where one exists · the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback - Runtime: Node 22 with PostgreSQL · meetings, transcripts and retention are relational and consequential - Transcription: whisper.cpp with word timings, run locally · recordings of your colleagues' conversations should not need to leave the machine - Speakers: Diarisation refined by hand, seeded from the participant list · a summary attributing a decision to the wrong person is worse than an unattributed one - Storage: Recordings encrypted with an expiry, transcripts kept separately · the two have different retention needs and should not share one policy by accident ### Data model (create this before Phase 1) - meetings(id, external_id, title, platform, started_at, ended_at, host_email, consent_state, announced_at) - participants(id, meeting_id, name, email, joined_at, left_at, objected_at) - recordings(meeting_id, storage_key, encrypted, expires_at, deleted_at) - words(meeting_id, ordinal, text, start_ms, end_ms, speaker_label) · the spine everything cites - segments(id, meeting_id, start_ms, end_ms, speaker_label, text) - outputs(id, meeting_id, kind, body, cited_segment_ids_json, validated) · kind is summary, actions or decisions - A meeting where a participant objected has no recording and no transcript. The objection is the record. ### Phase 1 · Joining visibly and announcing Build: Join a meeting as a participant named so its purpose is obvious. Announce on joining that the meeting is being recorded, by whom and why. Provide a way for anyone to object that stops the recording, and honour it. Trap: Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. Done when: The bot appears in the participant list under a name identifying it as a recorder. An announcement is delivered on joining and its time is recorded. An objection stops the recording within seconds and no recording file remains, verified by checking storage. The host removing the bot ends the recording cleanly. Do not build yet: No transcription, summaries or retention. ### Phase 2 · Audio worth transcribing Build: Capture the meeting audio, normalise loudness across participants who joined at different levels, and detect a failure to capture rather than producing silence. Store encrypted with an expiry set at creation. Trap: A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. Done when: A recording of a three person meeting contains all three audibly, verified by listening. A participant who never rises above the noise floor is reported rather than silently missing. The recording is encrypted at rest with an expiry set at creation. A failed capture is reported rather than producing a silent file. Do not build yet: No transcription or speakers yet. ### Phase 3 · Transcript with speakers you can trust Build: Transcribe with word-level timings. Assign speaker labels, seeded from the participant list where the platform provides one, and make corrections easy and persistent. Show confidence so an uncertain attribution is visible. Trap: Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. Done when: Playing a single word's range plays that word. Speaker labels are assigned and a correction propagates to that speaker's other segments. Low confidence attributions are visibly marked. A meeting where diarisation failed reports that rather than assigning everything to one speaker. Do not build yet: No summaries yet. ### Phase 4 · Notes that cite the recording Build: Generate a summary, decisions and action items from the transcript segments. Require a segment citation per line. Validate that every cited identifier exists and reject the output if one does not. Attribute an action item only where the transcript supports it. Trap: A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. Done when: Every summary line cites a segment that resolves to real text. A crafted output citing a nonexistent segment is rejected, tested by feeding one to the validator. Clicking a citation plays that moment. An action item with no explicit owner in the transcript is left unassigned rather than attributed. Do not build yet: No search or retention yet. ### Phase 5 · Search across meetings Build: Full text search across transcripts with speaker and date filters. Results link to the moment. Restrict search to meetings the searcher attended or was granted access to, enforced server side. Trap: Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. Done when: A phrase search returns the meeting and the moment. A user cannot search a meeting they did not attend and were not granted, verified by a direct request. Speaker and date filters narrow correctly. Search across a few hundred meetings returns in well under a hundred milliseconds. Do not build yet: Retention and deletion come last. ### Phase 6 · Retention, deletion and the record Build: Automatic deletion of recordings past their retention, with the run logged. A deletion request removing the recording, transcript, summaries and search entries together. Keep the consent and announcement record separately and for longer. Trap: Deleting the recording and keeping the transcript satisfies neither the policy nor the participant's expectation, since the transcript is the more readable record of what they said. Decide what each period covers, apply it to every artefact, and keep the announcement record longer because it is your evidence that the recording was disclosed. Done when: Recordings past retention are deleted automatically and each run logs what it removed. A deletion request removes recording, transcript, summaries and search entries, verified by searching for a known phrase afterwards. The announcement and consent record survives. Retention periods are configured per artefact and documented. Do not build yet: Nothing after this. ### Out of scope (and why) - Bot reliability across every meeting platform, which is the substantial engineering behind the commercial product and breaks whenever a platform changes. - Integrations pushing notes into customer and project systems, each of which is an API and a permission model. - Team permission structures beyond attendee scoping, which is where an organisation's real requirements live. - Legal advice about recording consent, which differs by jurisdiction and is a prerequisite you settle rather than a feature. ### README must contain - That the bot joins under an identifying name and announces itself, and that objecting stops the recording - The consent position you confirmed, with the jurisdictions covered and the date - That every summary line cites a segment and an uncited output is rejected - That an action item is attributed only where the transcript names an owner - The retention period per artefact and that the announcement record outlives the recording
# Agent instructions · Fireflies.ai indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A headless browser participant, or the platform's own recording interface where one exists, Node 22 with PostgreSQL, whisper.cpp with word timings, run locally, Diarisation refined by hand, seeded from the participant list, Recordings encrypted with an expiry, transcripts kept separately. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface.
# Build plan · Fireflies.ai A recorder that joins a meeting as a clearly labelled participant, announces itself, captures audio, transcribes with speaker labels and word timings, produces summaries and action items where every line cites a timestamp, and deletes recordings on a schedule you configured. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Joining visibly and announcing The bot joins under a name that says what it is and tells the room. ### Steps 1. Join under an obvious name Something nobody has to ask about. This is the cheapest trust you will ever buy. Files: `src/join.ts` 2. Announce and record the announcement In the first seconds, with the time stored on the meeting. Files: `src/announce.ts` 3. Make objection stop everything Recording ends, file removed. Logged and ignored is the failure mode to avoid. Files: `src/consent.ts` ### Done when - [ ] The bot is visible under an identifying name - [ ] The announcement is delivered and its time recorded - [ ] An objection stops recording and leaves no file - [ ] Host removal ends the recording cleanly ### Watch out - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. ## Phase 2 · Audio worth transcribing The recording captures everyone clearly enough to transcribe. ### Steps 1. Capture and normalise Loudness across participants, since people join at wildly different levels. Files: `src/capture.ts` 2. Detect missing participants Anyone who never exceeds the noise floor. Report rather than transcribe a partial meeting. Files: `src/audio-check.ts` 3. Encrypt with an expiry at creation Not later. Retention set when the file exists is retention that happens. Files: `src/storage.ts` ### Done when - [ ] All three participants are audible in a test meeting - [ ] An inaudible participant is reported - [ ] Recordings are encrypted with an expiry at creation - [ ] A failed capture is reported rather than silent ### Watch out - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. ## Phase 3 · Transcript with speakers you can trust Every word has a time and an attribution you can correct. ### Steps 1. Transcribe with word timings Requested explicitly. Segment level cannot support citation later. Files: `src/transcribe.ts` 2. Seed speakers from the participant list Where available. Guessing from audio alone is much worse. Files: `src/speakers.ts` 3. Make corrections propagate Correcting one segment corrects that speaker throughout. Otherwise nobody corrects. Files: `src/speakers.ts` ### Done when - [ ] A single word's range plays that word - [ ] A speaker correction propagates to their other segments - [ ] Low confidence attributions are marked - [ ] Failed diarisation is reported rather than collapsed to one speaker ### Watch out - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. ## Phase 4 · Notes that cite the recording Every line in the summary points at a moment you can play. ### Steps 1. Pass numbered segments and demand citations Structured output so parsing is not a regular expression. Files: `src/summarise.ts` 2. Validate citations A pure function, unit tested with a crafted bad citation rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Require explicit attribution for owners Named in the cited text, or the action stays unassigned. Files: `src/actions.ts` ### Done when - [ ] Every summary line cites a real segment - [ ] A crafted bad citation is rejected - [ ] Clicking a citation plays that moment - [ ] An unattributed action stays unassigned ### Watch out - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. ## Phase 5 · Search across meetings You can find the meeting where something was said. ### Steps 1. Index transcripts with timings So a result links to a moment rather than a meeting. Files: `src/search.ts` 2. Scope to attendees by default Enforced in the query. Sharing beyond that is a deliberate action. Files: `src/access.ts` 3. Test with a direct request As a non-attendee. The interface is not the test. Files: `test/access.test.ts` ### Done when - [ ] A phrase search returns the meeting and moment - [ ] A non-attendee's direct search request is refused - [ ] Speaker and date filters narrow correctly - [ ] Search across hundreds of meetings is fast ### Watch out - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. ## Not in this build - Bot reliability across every meeting platform. - Integrations into customer and project systems. - Organisational permission structures. - Legal advice about recording consent. ## After v1, if you want it - A pre-meeting notice to invitees, which is better consent practice than announcing on the day - Topic segmentation, so a long meeting can be navigated rather than read - Follow-up detection across meetings, which is where a searchable archive starts paying
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Meetings, transcripts and consent records. Personal data with obligations attached. DATABASE_URL=postgres://user:pass@localhost:5432/meetings # Required. Encrypted at rest with lifecycle expiry as a second line of defence. RECORDINGS_BUCKET=meeting-recordings # Required. How the bot appears in the participant list. It should need no explanation. BOT_DISPLAY_NAME=Notetaker (recording) # Required. What the bot says on joining. Configuration so it can differ by jurisdiction. ANNOUNCEMENT_TEXT=./config/announcement.md # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, and a separate decision rather than an inherited one. TRANSCRIPT_RETENTION_DAYS=365 # Required. Longest, because it is the evidence the recording was disclosed. CONSENT_RETENTION_DAYS=2555
# Fireflies.ai · product brief ## Problem The summarizer is easy; the meeting bot, calendar routing, integrations, team search, and admin layer make it more than a one-prompt clone. ## Product outcome A note-taker that is visible in the room, stops when someone objects, produces notes whose every line can be played back, restricts search to the people who were there, and deletes what it should on a schedule you can prove ran. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - speech API - calendar API - Zoom/Meet/Teams capture route - hosted backend - database/search ## Explicit non-goals for v1 - Bot reliability across every meeting platform. - Integrations into customer and project systems. - Organisational permission structures. - Legal advice about recording consent. - native meeting bot reliability - integrations with CRM/project tools - team permissions - shared topic tracking - compliance controls ## Success criteria - The bot is confirmed visible and announcing on join - An objection is confirmed to stop recording and leave no file - A crafted bad citation is rejected by the validator - A non-attendee's search request is confirmed refused - The retention job is confirmed to delete and log the run
# Build brief · Fireflies.ai The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a meeting recorder that joins the call, transcribes it, and produces notes that cite the recording to replace Fireflies.ai. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero A bot joining a meeting is a recording device entering a room, and everyone in that room has a right to know. Announce it on join, make it visible in the participant list under a name that says what it is, and give the host a way to remove it that actually works. Consent requirements for recording differ by jurisdiction and several require every participant's agreement rather than one person's; a meeting with attendees in different places takes the strictest rule that applies. Check the platform's terms too, since some restrict automated participants. The second rule is about the notes. A summary that states something nobody said is worse than no summary, because people act on it without listening back. Every claim in the output cites a moment in the transcript and is rejected if it cannot. ### Stack (fixed, do not substitute) - Joining: A headless browser participant, or the platform's own recording interface where one exists · the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback - Runtime: Node 22 with PostgreSQL · meetings, transcripts and retention are relational and consequential - Transcription: whisper.cpp with word timings, run locally · recordings of your colleagues' conversations should not need to leave the machine - Speakers: Diarisation refined by hand, seeded from the participant list · a summary attributing a decision to the wrong person is worse than an unattributed one - Storage: Recordings encrypted with an expiry, transcripts kept separately · the two have different retention needs and should not share one policy by accident ### Data model (create this before Phase 1) - meetings(id, external_id, title, platform, started_at, ended_at, host_email, consent_state, announced_at) - participants(id, meeting_id, name, email, joined_at, left_at, objected_at) - recordings(meeting_id, storage_key, encrypted, expires_at, deleted_at) - words(meeting_id, ordinal, text, start_ms, end_ms, speaker_label) · the spine everything cites - segments(id, meeting_id, start_ms, end_ms, speaker_label, text) - outputs(id, meeting_id, kind, body, cited_segment_ids_json, validated) · kind is summary, actions or decisions - A meeting where a participant objected has no recording and no transcript. The objection is the record. ### Phase 1 · Joining visibly and announcing Build: Join a meeting as a participant named so its purpose is obvious. Announce on joining that the meeting is being recorded, by whom and why. Provide a way for anyone to object that stops the recording, and honour it. Trap: Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. Done when: The bot appears in the participant list under a name identifying it as a recorder. An announcement is delivered on joining and its time is recorded. An objection stops the recording within seconds and no recording file remains, verified by checking storage. The host removing the bot ends the recording cleanly. Do not build yet: No transcription, summaries or retention. ### Phase 2 · Audio worth transcribing Build: Capture the meeting audio, normalise loudness across participants who joined at different levels, and detect a failure to capture rather than producing silence. Store encrypted with an expiry set at creation. Trap: A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. Done when: A recording of a three person meeting contains all three audibly, verified by listening. A participant who never rises above the noise floor is reported rather than silently missing. The recording is encrypted at rest with an expiry set at creation. A failed capture is reported rather than producing a silent file. Do not build yet: No transcription or speakers yet. ### Phase 3 · Transcript with speakers you can trust Build: Transcribe with word-level timings. Assign speaker labels, seeded from the participant list where the platform provides one, and make corrections easy and persistent. Show confidence so an uncertain attribution is visible. Trap: Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. Done when: Playing a single word's range plays that word. Speaker labels are assigned and a correction propagates to that speaker's other segments. Low confidence attributions are visibly marked. A meeting where diarisation failed reports that rather than assigning everything to one speaker. Do not build yet: No summaries yet. ### Phase 4 · Notes that cite the recording Build: Generate a summary, decisions and action items from the transcript segments. Require a segment citation per line. Validate that every cited identifier exists and reject the output if one does not. Attribute an action item only where the transcript supports it. Trap: A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. Done when: Every summary line cites a segment that resolves to real text. A crafted output citing a nonexistent segment is rejected, tested by feeding one to the validator. Clicking a citation plays that moment. An action item with no explicit owner in the transcript is left unassigned rather than attributed. Do not build yet: No search or retention yet. ### Phase 5 · Search across meetings Build: Full text search across transcripts with speaker and date filters. Results link to the moment. Restrict search to meetings the searcher attended or was granted access to, enforced server side. Trap: Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. Done when: A phrase search returns the meeting and the moment. A user cannot search a meeting they did not attend and were not granted, verified by a direct request. Speaker and date filters narrow correctly. Search across a few hundred meetings returns in well under a hundred milliseconds. Do not build yet: Retention and deletion come last. ### Phase 6 · Retention, deletion and the record Build: Automatic deletion of recordings past their retention, with the run logged. A deletion request removing the recording, transcript, summaries and search entries together. Keep the consent and announcement record separately and for longer. Trap: Deleting the recording and keeping the transcript satisfies neither the policy nor the participant's expectation, since the transcript is the more readable record of what they said. Decide what each period covers, apply it to every artefact, and keep the announcement record longer because it is your evidence that the recording was disclosed. Done when: Recordings past retention are deleted automatically and each run logs what it removed. A deletion request removes recording, transcript, summaries and search entries, verified by searching for a known phrase afterwards. The announcement and consent record survives. Retention periods are configured per artefact and documented. Do not build yet: Nothing after this. ### Out of scope (and why) - Bot reliability across every meeting platform, which is the substantial engineering behind the commercial product and breaks whenever a platform changes. - Integrations pushing notes into customer and project systems, each of which is an API and a permission model. - Team permission structures beyond attendee scoping, which is where an organisation's real requirements live. - Legal advice about recording consent, which differs by jurisdiction and is a prerequisite you settle rather than a feature. ### README must contain - That the bot joins under an identifying name and announces itself, and that objecting stops the recording - The consent position you confirmed, with the jurisdictions covered and the date - That every summary line cites a segment and an uncited output is rejected - That an action item is attributed only where the transcript names an owner - The retention period per artefact and that the announcement record outlives the recording
# Architecture · Fireflies.ai ## Stack | Part | Choice | Why | | --- | --- | --- | | Joining | A headless browser participant, or the platform's own recording interface where one exists | the platform's own route is better where it exists because it is sanctioned; a browser bot is the fallback | | Runtime | Node 22 with PostgreSQL | meetings, transcripts and retention are relational and consequential | | Transcription | whisper.cpp with word timings, run locally | recordings of your colleagues' conversations should not need to leave the machine | | Speakers | Diarisation refined by hand, seeded from the participant list | a summary attributing a decision to the wrong person is worse than an unattributed one | | Storage | Recordings encrypted with an expiry, transcripts kept separately | the two have different retention needs and should not share one policy by accident | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Join and announce | Visibility and consent. | Platform specific. The visible name and the announcement are not optional whatever the joining method. | | Capture | Audio worth transcribing. | Any capture, provided a partial recording is reported rather than transcribed as complete. | | Transcript | Words, timings and speakers. | Any transcriber with word timings. Correction propagation is what makes attribution trustworthy. | | Outputs | Summaries with validated citations. | Any model. The validator is what makes swapping safe and is the reason the output can be trusted. | | Retention | Automatic deletion per artefact. | Any scheduler, provided the run is logged, since the log is the evidence. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Meetings, transcripts and consent records. Personal data with obligations attached. - `RECORDINGS_BUCKET` · required · Encrypted at rest with lifecycle expiry as a second line of defence. - `BOT_DISPLAY_NAME` · required · How the bot appears in the participant list. It should need no explanation. - `ANNOUNCEMENT_TEXT` · required · What the bot says on joining. Configuration so it can differ by jurisdiction. - `RECORDING_RETENTION_DAYS` · required · Per your written policy. Deletion is automatic and logged. - `TRANSCRIPT_RETENTION_DAYS` · required · Usually longer than the recording, and a separate decision rather than an inherited one. - `CONSENT_RETENTION_DAYS` · required · Longest, because it is the evidence the recording was disclosed. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Fireflies.ai product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A headless browser participant, or the platform's own recording interface where one exists, Node 22 with PostgreSQL, whisper.cpp with word timings, run locally, Diarisation refined by hand, seeded from the participant list, Recordings encrypted with an expiry, transcripts kept separately. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. - Deleting the recording and keeping the transcript satisfies neither the policy nor the participant's expectation, since the transcript is the more readable record of what they said. Decide what each period covers, apply it to every artefact, and keep the announcement record longer because it is your evidence that the recording was disclosed.
# Delivery milestones · Fireflies.ai Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Joining visibly and announcing The bot joins under a name that says what it is and tells the room. ### Steps 1. Join under an obvious name Something nobody has to ask about. This is the cheapest trust you will ever buy. Files: `src/join.ts` 2. Announce and record the announcement In the first seconds, with the time stored on the meeting. Files: `src/announce.ts` 3. Make objection stop everything Recording ends, file removed. Logged and ignored is the failure mode to avoid. Files: `src/consent.ts` ### Done when - [ ] The bot is visible under an identifying name - [ ] The announcement is delivered and its time recorded - [ ] An objection stops recording and leaves no file - [ ] Host removal ends the recording cleanly ### Watch out - Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored. ## M2 · Audio worth transcribing The recording captures everyone clearly enough to transcribe. ### Steps 1. Capture and normalise Loudness across participants, since people join at wildly different levels. Files: `src/capture.ts` 2. Detect missing participants Anyone who never exceeds the noise floor. Report rather than transcribe a partial meeting. Files: `src/audio-check.ts` 3. Encrypt with an expiry at creation Not later. Retention set when the file exists is retention that happens. Files: `src/storage.ts` ### Done when - [ ] All three participants are audible in a test meeting - [ ] An inaudible participant is reported - [ ] Recordings are encrypted with an expiry at creation - [ ] A failed capture is reported rather than silent ### Watch out - A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete. ## M3 · Transcript with speakers you can trust Every word has a time and an attribution you can correct. ### Steps 1. Transcribe with word timings Requested explicitly. Segment level cannot support citation later. Files: `src/transcribe.ts` 2. Seed speakers from the participant list Where available. Guessing from audio alone is much worse. Files: `src/speakers.ts` 3. Make corrections propagate Correcting one segment corrects that speaker throughout. Otherwise nobody corrects. Files: `src/speakers.ts` ### Done when - [ ] A single word's range plays that word - [ ] A speaker correction propagates to their other segments - [ ] Low confidence attributions are marked - [ ] Failed diarisation is reported rather than collapsed to one speaker ### Watch out - Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments. ## M4 · Notes that cite the recording Every line in the summary points at a moment you can play. ### Steps 1. Pass numbered segments and demand citations Structured output so parsing is not a regular expression. Files: `src/summarise.ts` 2. Validate citations A pure function, unit tested with a crafted bad citation rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Require explicit attribution for owners Named in the cited text, or the action stays unassigned. Files: `src/actions.ts` ### Done when - [ ] Every summary line cites a real segment - [ ] A crafted bad citation is rejected - [ ] Clicking a citation plays that moment - [ ] An unattributed action stays unassigned ### Watch out - A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner. ## M5 · Search across meetings You can find the meeting where something was said. ### Steps 1. Index transcripts with timings So a result links to a moment rather than a meeting. Files: `src/search.ts` 2. Scope to attendees by default Enforced in the query. Sharing beyond that is a deliberate action. Files: `src/access.ts` 3. Test with a direct request As a non-attendee. The interface is not the test. Files: `test/access.test.ts` ### Done when - [ ] A phrase search returns the meeting and moment - [ ] A non-attendee's direct search request is refused - [ ] Speaker and date filters narrow correctly - [ ] Search across hundreds of meetings is fast ### Watch out - Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface. ## M6 · Retention, deletion and the record (production only) Recordings expire automatically and a deletion request is honoured completely. ### Steps 1. Delete automatically and log it Per artefact retention. A policy that needs remembering is not automatic. Files: `src/retention.ts` 2. Make deletion complete Every store including the search index. Verify by searching afterwards. Files: `src/delete.ts` 3. Keep the consent record longer It is the evidence the recording was disclosed, and it outlives the recording. Files: `docs/retention.md` ### Done when - [ ] Aged recordings are deleted automatically with a logged run - [ ] A deletion request leaves nothing findable by search - [ ] The announcement record survives the deletion - [ ] Retention is configured per artefact and documented ### Watch out - Deleting the recording and keeping the transcript satisfies neither the policy nor the participant's expectation, since the transcript is the more readable record of what they said. Decide what each period covers, apply it to every artefact, and keep the announcement record longer because it is your evidence that the recording was disclosed.
# Operations · Fireflies.ai ## Backup Consent records and transcripts are the durable material. Recordings expire by design, so backing them up would work against the retention policy, which is worth stating rather than assuming. ## Restore Restore the database and confirm consent records survived and the retention job resumes. A restore that reinstates expired recordings has recreated data you were required to delete. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Failed joins, meetings with an inaudible participant, citation validation failures and retention run success. A rising validation failure rate usually means transcript quality dropped rather than the model getting worse. ## Incident checklist If a summary states something nobody said, the citation validator missed a case and the fix belongs there. If a bot joined without announcing, that is the most serious failure here and the announcement should be structurally impossible to skip. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The bot is confirmed visible and announcing on join - [ ] An objection is confirmed to stop recording and leave no file - [ ] A crafted bad citation is rejected by the validator - [ ] A non-attendee's search request is confirmed refused - [ ] The retention job is confirmed to delete and log the run ## Launch constraint Do not market omitted Fireflies.ai capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Meetings, transcripts and consent records. Personal data with obligations attached. DATABASE_URL=postgres://user:pass@localhost:5432/meetings # Required. Encrypted at rest with lifecycle expiry as a second line of defence. RECORDINGS_BUCKET=meeting-recordings # Required. How the bot appears in the participant list. It should need no explanation. BOT_DISPLAY_NAME=Notetaker (recording) # Required. What the bot says on joining. Configuration so it can differ by jurisdiction. ANNOUNCEMENT_TEXT=./config/announcement.md # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, and a separate decision rather than an inherited one. TRANSCRIPT_RETENTION_DAYS=365 # Required. Longest, because it is the evidence the recording was disclosed. CONSENT_RETENTION_DAYS=2555
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay because the bot reliably appears, captures, files, and shares notes across the whole team.
xnative meeting bot reliability
xintegrations with CRM/project tools
xteam permissions
xshared topic tracking
xcompliance controls
Don't feel like building it? These folks already made it free.
all 3 free alternatives to Fireflies.ai →· no votes, no pay-to-list · just what's real
Fireflies.ai pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/user | $0/user | Unlimited transcription and AI summaries; 400 storage minutes per team; 20 one-time AI credits per team. |
| pro | $18/user | $10/user | Unlimited transcription and AI summaries; 8,000 storage minutes per seat; 20 one-time AI credits per team. |
| business | $29/user | $19/user | Unlimited transcription, AI summaries, and storage; 30 one-time AI credits per team. |
| enterprise | — | $39/user | Unlimited transcription, AI summaries, and storage; 50 one-time AI credits per team. |
| ai credits 50 | $5/workspace | — | 50 purchased AI credits per month, shared by the team. |
| ai credits 200 | $20/workspace | — | 200 purchased AI credits per month, shared by the team. |
| ai credits 1,000 | $90/workspace | — | 1,000 purchased AI credits per month, shared by the team. |
| ai credits 2,500 | $200/workspace | — | 2,500 purchased AI credits per month, shared by the team. |
| ai credits 5,000 | $375/workspace | — | 5,000 purchased AI credits per month, shared by the team. |
| ai credits 7,500 | $450/workspace | — | 7,500 purchased AI credits per month, shared by the team. |
| ai credits 10,000 | $600/workspace | — | 10,000 purchased AI credits per month, shared by the team. |
free tierUnlimited transcription and AI summaries; 400 storage minutes per team; 20 one-time AI credits shared by the team.
billingmonthly + annual; Enterprise is annual only
hidden costsIncluded AI credits are one-time and shared across the entire team. When a paid team runs out, Fireflies automatically starts the lowest $5/50-credit monthly add-on unless disabled; purchased credits expire at each billing cycle.
verified 2026-08-14 · source ↗
Vibecode Fireflies.ai
Kinda. The core of Fireflies.ai is buildable in a weekend with the prompt on this page, but there are real gaps: native meeting bot reliability, integrations with CRM/project tools. Read the honest list above before committing.
How much does Fireflies.ai cost?
Fireflies.ai costs about $18/month (Pro, checked 2026-07-30), which is $216 per year.
What do I lose by replacing Fireflies.ai?
Honestly: native meeting bot reliability; integrations with CRM/project tools; team permissions; shared topic tracking; compliance controls. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Fireflies.ai?
Yes: OpenWhispr (One installer records meetings without a bot, labels speakers, summarizes them and keeps the archive searchable.) Speakr (A polished self-hosted call archive with transcription, summaries, search and chat; zero subscription, nonzero server.) Fathom (Unlimited recording, transcription, basic summaries, clips and search for one person; the revenue-team machinery remains paid.) All 3 curated free alternatives are at vibecodeit.com/fireflies-ai/alternatives. The prompt is for when you want it exactly your way.