Build your own Granola with open models

Build your own Granola with open models

Record and transcribe your meetings with open models, turn them into structured data, and build a searchable memory of every conversation you have had.

Decisions, a client's objections, the reason an idea got dropped, what you promised to deliver by the end of the week. An absurd amount of a company's information goes through conversations and disappears the moment a Meet or a Teams call ends.

That is why we use Granola, Fathom or any other tool that records a meeting, transcribes it and generates notes.

But if you like open software and building your own tools exactly the way you want them, you can also build a recording and transcription product with open AI models.

And the interesting part is that, once you have the transcripts, you can go quite a bit further than generating a summary of each meeting.

We are going to build it in three layers:

Notes → Memory → Intelligence

First, a notetaker that records your meetings and generates notes. Then, a memory across every meeting you have had. And finally, we will look at how that same pipeline can be taken to the conversations of an entire company.

This is the recipe I used.

What we are going to build

The first version is simple:

Step 01, from audio to notes: recording produces two tracks, the microphone audio and the system audio. Whisper large-v3 transcribes and pyannote 3.1 separates the speakers. DeepSeek V4 Flash turns the transcript into structured notes with a JSON schema and qwen3-embedding indexes them. The result is a summary, decisions, tasks, open questions and quotes.

You record a meeting. Whisper turns it into text. A model transforms that text into a summary, decisions, tasks and open questions. An embedding model turns the fragments of the conversation into vectors and stores them in an index.

From there you can ask things like:

What did I promise to deliver to Carlos by the end of the week?
Why did we decide not to launch the free plan?

That is already a lot more useful than a folder full of call summaries nobody ever opens again.

The recipe

We need four models.

PieceWhat forModelWhere it runs
TranscriptionAudio to textWhisper large-v3Helmcode API
SpeakersKnowing who is talkingpyannote 3.1Local
NotesTurning conversation into dataDeepSeek V4 FlashHelmcode API
MemoryRepresenting and searching meetingsqwen3-embeddingHelmcode API

Then, some other important pieces:

PortAudio captures the audio. ffmpeg prepares it. SQLite stores the index and FTS5 adds keyword search.

In our implementation we packaged everything into helmcode-whisper. Three commands do almost all of the real work:

hcw record -t "sprint review"
hcw process
hcw search "what did we say about the enterprise tier"

The first one records. The second one transcribes, separates voices, generates the notes and indexes the meeting. The third one searches across every meeting you have processed.

What leaves your machine and what does not

A meeting recording contains prices, salaries, client names, internal problems and the voice of every participant, so quite often you do not want that data going to a third party:

StepWhere it runsWhat leaves your machine
RecordingYour machineNothing
TranscriptionHelmcode APIChunks of the audio
Voice separationYour machineNothing
NotesHelmcode APIThe transcript text
IndexSQLite on your machineThe passages, to vectorise them
StorageYour machineNothing

The record command does not need to make a single network request, and diarization (telling apart the voices on the call) with pyannote runs locally. If you enable it, Hugging Face shows up during installation so you can accept the terms and download the model weights, but the content of the meeting is never sent there.

The trick is recording two tracks and never mixing them

There is a decision here that hugely simplifies the diarization problem (separating the voices): your microphone is you, and the system audio is everyone except you.

So we record both into separate files:

audio-mic.wav       → you
audio-system.wav    → everyone else
Output of `hcw record` in the terminal. At the top, a warning that recording a conversation without telling the other people in it is illegal in many places. Below it, the two captured devices, the microphone and the system audio in loopback mode, and two independent counters running in parallel, one for "me" and one for "others".

That solves half of diarization for free. Instead of handing a mixed recording to a model and asking it to work out who is who, we already know from the start that everything on the first track is you. So pyannote only has to separate the voices on the other side.

On a two-person call the output can literally be:

Me
SPEAKER_00

If that is enough for your use case, you can even turn diarization off and stick with Me and Others, without installing torch.

One problem can show up on a call: echo. If you are not wearing headphones, your microphone can pick up again what comes out of the speakers, and a mix of noise and other voices ends up transcribing some things twice or badly.

The solution we use compares each microphone segment against whatever the other track was saying during that same time window. Comparing segment against segment caught 41% of the echo in our tests. Comparing it against the whole time window took that to 79%, without deleting real speech. Discarded segments still show up in transcript.json with the reason, so the process stays auditable.

Although the best solution to this is putting your headphones on.

How to go from audio to data

A transcript on its own is not especially useful either. What we want is to transform a conversation like this:

12:41 Ana:
So we push the launch to October.

12:47 Carlos:
Yes. But first we have to sort out the onboarding.

12:53 Ana:
I will take that. I will try to have it by Friday.

Into something like this:

{
  "decisions": [
    {
      "decision": "Move the launch to October"
    }
  ],
  "action_items": [
    {
      "owner": "Ana",
      "task": "Sort out the onboarding",
      "due_date": "Friday"
    }
  ]
}

That is where the usefulness of the meeting changes, because you go from having text to having data. To me that is the most important part, because it is the one that later lets you build a real layer of company knowledge.

In our case DeepSeek V4 Flash uses json_schema to generate five sections: summary, decisions, tasks with an owner and a date, open questions and quotes.

What matters here is that we go from having pretty notes to having indexable information. With a json_schema we want every meeting to produce exactly the same structure.

That is why each meeting ends up generating:

audio-mic.wav
audio-system.wav
transcript.json
notes.json
notes.md
notes.html
meta.json

notes.md is for humans, notes.html is for sharing and notes.json is for building things on top.

Output of `hcw process` in the terminal, showing the path of the meeting folder and one line per audio track reporting how many minutes of speech it found on each and how many chunks it will be transcribed in.

The prompt should not be buried in the code either

This is another advantage of building it yourself: the prompt that decides what your notes look like should be yours. And you can change it however you want, whenever you want.

In our implementation it lives in:

templates/notes.md

You can decide what counts as a decision, change the language, ask for a different format or add a risks section. Or whatever else comes to mind.

A sales company, for example, could add:

## Objections

Extract the objections raised by the client.
For each one include:

- objection
- the rep's answer
- whether it was resolved
- product or competitor mentioned

While a product team could ask for:

## Product feedback

Extract:

- requested features
- current problems
- workarounds mentioned
- level of urgency

Same audio, same pipeline, different data.

The model is swappable too:

HCW_NOTES_MODEL=qwen3.6 hcw process --force

That is precisely the point of building it with open models. And it is pretty cool.

A meeting's notes open in helmcode-whisper-ui. On the left, the list of meetings grouped by team. On the right, the meeting title with its date, duration and number of voices, the notes and transcript tabs, and below them the summary generated by the model followed by the list of decisions. At the very bottom, a player for the original audio.

How to turn notes into memory

Now we have a folder full of perfectly structured meetings, but nobody is going to open 300 notes.md files to find anything.

So we need search.

Step 02, from notes to memory: the transcript.json files of every processed meeting go through two indexes in parallel, embeddings with qwen3-embedding for semantic search and SQLite FTS5 running locally for keyword search. A reranker combines and orders the results from both, and the result is a single index you can question in natural language.

We combine vector search with SQLite FTS5. The first finds conceptually similar fragments; the second works especially well with specific names, products, clients or exact terms. Then a reranker orders the union of both sets of results.

For example, if you ask:

What number did we say we were going to charge?

Vector search can find a conversation about pricing even if the word "price" never literally appears. If you are looking for the exact name of a product or a client, FTS5 can do it better.

You can try it directly:

hcw search "why did we drop that onboarding idea"

And search across every conversation you have processed.

At this point you no longer just have a notes app. You have memory.

This is where our repo ends. Now let us take the idea further

Everything above exists today in helmcode-whisper: capture, transcription, diarization, structured notes, embeddings, hybrid search and reranking.

What comes next is not implemented in the repo, but we are leaving you the idea and the structure in case you want to implement it yourself.

It is the architecture we would build on top if we wanted to go from a personal tool to an intelligence layer for a whole company.

The fundamental pipeline would not have to change. You would still be recording, transcribing, separating voices, extracting information and indexing it. What changes is where the result ends up.

Instead of storing each meeting only in ~/helmcode-whisper/, you would need central storage, users, an organisation attached to each meeting, teams, permissions and a shared index.

Conceptually:

Step 03, from memory to company intelligence, marked as a proposed architecture not included in the repo. Conversations from sales, product, hiring, customer success and leadership all go through the same recording, transcription, diarization, notes and embeddings pipeline, and end up in a shared organisation memory with users, permissions, teams, filters and per-department extraction templates. On top of that memory sits the company intelligence layer: patterns, better decisions, shared knowledge and automated reports.

This architecture lets you start asking questions a personal tool cannot answer:

What are the five most repeated objections this month?
Which competitors come up most in the opportunities we lose?
Which features have at least three different clients asked for?
Which problems come up repeatedly in onboarding calls?

And you could go a step further: instead of waiting for someone to ask a question, run periodic jobs over those conversations to cluster objections, spot recurring requests or generate reports.

But that is already the next layer you have to build on top of the repo, and it is your call how you use it.

How to get it running

If you just want to try our implementation:

uv tool install git+https://github.com/helmcode/helmcode-whisper

mkdir -p ~/helmcode-whisper
echo "HELMCODE_API_KEY=sk-your-key" > ~/helmcode-whisper/.env

hcw doctor

You get the key at cloud.helmcode.com, and with it you have access to every model in the recipe except pyannote, which you can download with open weights from Hugging Face.

hcw doctor checks audio devices, ffmpeg, access to the models and whether torch is seeing your GPU correctly. It is the first thing to run, because it tells you exactly what is missing.

Then:

hcw record -t "sprint review"

End the meeting with Ctrl+C and run:

hcw process

And try:

hcw search "what decisions did we make about pricing"

You run all of this from the terminal. But you can add an interface on top, like we did. It is very useful for everyone who is not technical. Our UI is open too if you want to use it, in this repo: helmcode-whisper-ui

Fork it and build your own Granola

You do not have to start from an empty repository. We published helmcode-whisper as an open source project under Apache-2.0 precisely so you can take it apart, change it or build on top of it.

Fork helmcode-whisper , open it with Claude Code, Codex, OpenCode or whatever agent you use, and give it something like this:

This repository is a reference implementation of a meeting intelligence
system built with open models.

I want to turn it into my own personal Granola.

Before writing any code:

1. Read README.md.
2. Read docs/DATA.md.
3. Review templates/notes.md.
4. Review examples/.
5. Identify the full pipeline from record to search.
6. Run the existing tests.
7. Briefly explain the architecture to me and which interfaces we can reuse.

Do not rewrite the existing pipeline.

Keep:

- separate recording of microphone and system audio
- Whisper for transcription
- local diarization
- structured notes via JSON schema
- embeddings + FTS5 + reranking
- per-stage cache
- transcript.json and notes.json as data interfaces

On top of that I want to build a personal web interface where I can:

- start and stop a recording
- see my previous meetings
- open a meeting
- read the transcript
- see the summary, decisions and action items
- search across all my meetings
- ask questions in natural language about previous meetings

Use notes.json, transcript.json and the existing progress stream as contracts
with the UI wherever possible.

Do not add organisations, multi-tenancy, permissions or aggregated
intelligence yet. That will be a second phase.

First give me a short implementation plan based on the code that actually
exists in the repository. Then start implementing it in phases, keeping the
existing tests green.

That saves you building the hard part from scratch. Audio capture, track separation, chunking, diarization, echo suppression, structured storage, caching and search are already there.

The repo is at github.com/helmcode/helmcode-whisper under Apache-2.0. The four models in the recipe run on Helmcode: EU infrastructure, no logs, flat rate.

undefined

The Helmcode digest: open models, releases, what is happening in open AI, opinions and common sense. Published twice a month.