Imported from gitcrusher/Echo (
AGENTS.md). Install upstream withnpx skills add gitcrusher/Echo. Copyright stays with the author.
AGENTS.md
Instructions for any AI agent (Claude Code, Cursor, Copilot, etc.) working in this repository. Read this file in full before writing or modifying any code. It defines what this project is, how it's built, and the rules to follow while working on it.
0. What this repo is
A functional clone of Fireflies.ai — a meeting-notes and transcription platform. Users browse a meeting library, view interactive transcripts with speaker labels/timestamps, read AI-generated summaries and action items, search transcripts, and manage meetings via CRUD. Real speech-to-text is out of scope — transcripts/summaries are seeded, uploaded, or LLM-generated from existing text.
This is an evaluation assignment. The agent's output must be code a human can fully explain in an interview — favor clarity and defensible decisions over cleverness.
1. Stack — do not substitute
| Layer | Choice |
|---|---|
| Frontend | Next.js, TypeScript |
| Backend | FastAPI (Python) |
| Database | SQLite, via SQLAlchemy models + Alembic migrations |
| Forms | react-hook-form |
| Search | SQLite FTS5 (not LIKE '%query%') |
| Frontend deploy | Vercel |
| Backend deploy | Render or Railway |
Do not swap frameworks, ORMs, or search approaches without explicit instruction, even if a "better" option seems obvious mid-task. These were chosen deliberately for defensibility in review, not just convenience.
2. Architecture rules
- Two separate deployables talking over REST/JSON. The Next.js app is a pure API client — it never touches the SQLite file directly.
- FastAPI owns the database and is the only thing that calls the LLM API. Never let an LLM API key reach the frontend or the browser.
- All persistence goes through SQLAlchemy models. Schema changes go through Alembic migrations, not manual
ALTER TABLEor ad hoc scripts. - CORS must be configured in FastAPI for the deployed frontend origin — check this before assuming a deployed-environment bug is something else.
- SQLite file must live on persistent disk on the backend host. If deploying to a platform with ephemeral storage by default (e.g. Render free tier), flag this explicitly rather than assuming it's handled.
3. Build order (follow this sequence)
Work in this order — don't jump ahead to bonus features before must-haves are functionally complete and persisted.
- Schema first. Define SQLAlchemy models and Alembic migration for:
meetings,transcript_segments,summaries,summary_chapters,chapter_notes,action_items,chat_messages,tags,meeting_tags. - Seed data pipeline. Write realistic transcript scripts as plain dialogue text, then run them through the actual LLM-summary generation pipeline once at seed time — do not hand-write summary JSON. This also proves the summary pipeline works before the UI depends on it.
- Backend CRUD + core endpoints for meetings, transcript segments, summaries, action items.
- Transcript upload parsing — one normalizer function fed by three format-specific parsers (
.txt,.vtt,.json). See §5. - Frontend: Meetings Library / Dashboard.
- Frontend: Meeting Detail View — transcript, player, bidirectional seek sync (§4), in-transcript search.
- AI Summary & Notes UI — must match the 5-part shape (Keywords, Overview, Notes, Time-stamped Chapters, Action Items), not a generic summary block.
- CRUD UI + toasts + modals + forms.
- Bonus features, in this priority order: AskFred-lite chat → global search → tags/filtering → export → dark mode. Comments/highlights/soundbites is explicitly out of scope (see §6) — do not build it unless told otherwise.
If time-constrained, stop adding bonus features before touching already-working must-have code.
4. Transcript ↔ player sync — implementation rule
This is the trickiest UI mechanic; follow this exact approach, don't improvise a different one:
- Every
transcript_segmentsrow hasstart_ms/end_ms. - On the media element's native
timeupdateevent (~4x/sec), binary-search the sorted segment list for the segment containingcurrentTimeand highlight it. - Clicking a transcript line sets
player.currentTime = segment.start_ms / 1000directly. This is pure frontend state — never round-trip to the backend for a seek. - Media mock strategy (placeholder sample file scaled to
duration_seconds, vs. simulatedsetIntervalplayer) is an open decision — pick one explicitly and document the choice in the README rather than defaulting silently.
5. Transcript upload parsing — implementation rule
.txt: one block; fabricate speaker/timestamp structure viaSpeaker: textline heuristic, or fall back to one unlabeled speaker block if no structure is detected..vtt: parse the real WebVTT format (HH:MM:SS.mmm --> HH:MM:SS.mmm) with a small custom parser (~30 lines). Do not pull in a heavy VTT library for this..json: expects{speaker, start_ms, end_ms, text}[]— document this shape in the README so it isn't guessing a foreign schema.- All three parsers must feed the same normalizer function into
transcript_segments. Don't write three separate insert paths.
6. Scope boundaries — do not exceed
Build these bonuses (in priority order):
- AskFred-lite chat (LLM Q&A scoped to one meeting's transcript)
- Global search across all meetings (reuse the FTS5 index from in-transcript search)
- Tags/topics + filter (auto-derived from summary Keywords — no separate tagging UI)
- Export (Markdown/TXT trivial client-side; PDF approach is an open decision — see §8)
- Dark mode (CSS variables only — see rule below)
Do not build, unless explicitly told otherwise: comments, highlights, or soundbites on transcript segments. This is a genuinely new subsystem (audio-clip extraction + comment threads), not a reuse of existing infrastructure, and was deliberately deprioritized.
Do not build, per the assignment brief — these are placeholders only ("Coming Soon" is sufficient):
- Real-time bot joining live calls
- Actual speech-to-text
- Third-party integrations (Zoom, Google Meet, calendar, CRM)
- Team/sharing/collaboration
- Real authentication (assume a single default logged-in user)
7. Coding conventions and gotchas
- Search: use SQLite FTS5 virtual tables, not
LIKEqueries. One FTS5 table overtranscript_segments.textfor in-transcript search; a second over a denormalized view (meetings.title+summaries.overview+ transcript text) for global search — this is why global search should be cheap to add once in-transcript search exists. - Search result highlighting: backend returns match character offsets; frontend wraps them in
<mark>. Never send raw HTML from the backend for this. - Summary generation:
summaries.generated_byisseedorllm. LLM calls must be prompted for strict JSON matching the 5-part shape and parsed server-side into relational tables — never store the raw LLM blob as the source of truth. - LLM failure handling: wrap all LLM calls in try/catch. On timeout or malformed JSON, fall back to a generic seeded summary shape and surface a toast — never let this become a 500 to the user.
- Regenerate Summary: must delete existing
summary_chapters/chapter_notes/ summary-derivedaction_itemsbefore re-inserting, or duplicates will accumulate. - AskFred-lite context: pass the summary plus raw transcript only if it fits a reasonable token budget; otherwise retrieve relevant segments via the existing FTS5 index (keyword retrieval, not embeddings/RAG — that's out of scope here). No streaming/SSE/websockets needed for MVP — request/response with a loading spinner is sufficient.
- Dark mode: all colors must be CSS variables (
--bg,--text,--accent, etc.) with a[data-theme="dark"]override from the start. Do not hardcode Tailwind color classes and plan to retrofit later — that's expensive and against the stated plan. - Forms: use
react-hook-formwith client-side validation. Don't hand-roll form state management. - Toasts: simple context-based provider. Don't pull in a toast library for this.
8. Open decisions — resolve explicitly, don't default silently
If the agent reaches a point where one of these matters, stop and either ask or pick with a documented rationale in the README:
- Media player mock strategy — placeholder sample file scaled to duration vs. simulated
setIntervalplayer. - PDF export implementation — frontend
@react-pdf/renderervs. backendweasyprint/reportlab.
9. Definition of done
- All 5 must-have feature areas work end-to-end and persist through SQLite.
- Seed data includes several full meetings (transcripts + summaries + action items) so the app is usable immediately on first run.
- README includes: setup instructions, tech stack, architecture overview, full database schema, API overview, and any assumptions made (including the two open decisions above once resolved).
- Repo has
frontend/andbackend/at the top level, is public, and is deployed with a working demo link. - No plagiarized code from existing Fireflies clones — original implementation only.
10. Evaluation criteria this repo is judged on
Functionality, UI/UX similarity to real Fireflies, database design, backend/API design, code quality, code modularity (separation of concerns, reusable components), and the developer's ability to explain every implementation decision. Optimize for all of these, not just "it runs."
11. MEMORY.md protocol — self-maintained execution log
MEMORY.md is not a file provided upfront — it does not exist until the first time an agent actually starts executing work (writing code, running a command, making a decision) in this repo. This section defines the contract for it so that any agent, at any point, creates or continues it the same way. No agent should skip this because "no one told me to make it this time" — the trigger is starting execution itself.
11.1 When it gets created
The first agent to begin Phase 0 work (or any code-writing/execution step, if work starts out of strict phase order) creates MEMORY.md at the repo root before or alongside its first commit. If MEMORY.md already exists, every subsequent agent appends to it — never overwrites or restarts it.
11.2 What it's for
MEMORY.md is the continuity layer between agents/sessions. A different model picking up this repo mid-build should be able to read MEMORY.md and know: which phase (per PHASES.md) is in progress or done, what's been decided, what's been verified, and what's still open — without having to re-derive any of it from the code alone.
11.3 Required structure
Each entry in MEMORY.md is appended (never edited/deleted by a later agent) and must contain:
- Timestamp and phase reference (per
PHASES.md, e.g. "Phase 3 — Summary Generation Pipeline"). - What was done — concrete, specific (not "worked on backend").
- Decision + justification, for anything that involved a choice (including resolving one of the "open decisions" from
AGENTS.md§8 /RULES.md). State the option chosen and why, in enough detail that another model wouldn't re-litigate it from scratch. - Proof reference — per
RULES.mdRule 0, point to the actual proof produced (command output, screenshot, query result) rather than restating it wholesale; a pointer/summary is fine as long as the real proof exists in the session/commit it refers to. - Status — one of the three reporting states from
PHASES.md("Done — proof: ...", "In progress — ...", "Not started").
11.4 What it is not
- Not a replacement for
PHASES.md,RULES.md,ARCHITECTURE.md, orDESIGN.md— those are the fixed spec;MEMORY.mdis the append-only running log of what actually happened against that spec. - Not a place to redefine rules or scope. If an agent's work reveals a rule needs to change, that's a
RULES.md/AGENTS.mdedit with its own justification — not something to quietly redirect viaMEMORY.mdentries. - Not fabricated ahead of time. An entry is written after the corresponding work and proof exist, never drafted in advance as a plan (that's what
PHASES.mdis for).
11.5 Amendments
If a later phase changes or invalidates an earlier decision (e.g. the media-player mock strategy gets revisited), the agent adds a new entry explaining the change and why — it does not rewrite the original entry. The log stays a true history, not a tidied-up final state.
12. README maintenance — update as you go, only the relevant section
The README (per AGENTS.md §9 / RULES.md Definition of Done) is a living document during the build, not something written once at the end. The rule:
- While executing a phase, if that phase involved an implementation choice or a decision (e.g. resolving an open decision, choosing a parsing heuristic, picking a fallback behavior), update the README's relevant section (Architecture Overview, Assumptions, API Overview, etc.) at that time, with the decision and its justification — not deferred to a final polish pass.
- Only touch the section that phase actually affects. Don't rewrite unrelated README sections while working on a different phase — this keeps changes attributable and avoids one agent's edits clobbering context from another phase's work.
- Each README update should mirror what went into the corresponding
MEMORY.mdentry for that decision (same justification, condensed for a reader rather than a log) — the two should never contradict each other.MEMORY.mdis the full running history; the README is the cleaned-up current-state summary a human/evaluator reads. - Do not backfill the README with decisions that haven't actually been implemented yet, and do not describe planned work as done — this is the same proof discipline as
RULES.mdRule 0, applied to documentation. - Phase 9 (
PHASES.md) is not "write the README" — it's "final structural pass and completeness check" on a README that has already been incrementally updated throughout Phases 0–8.
