Build Circleback
KINDAback to the verdict
A local meeting notes pipeline whose hard problems are a speech model that invents text during silence, speaker labels that are wrong often enough to matter, and notes that must be traceable to audio because people act on them.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the interface and the transcription worker.
Get it python.org, or your package manager. open ↗
Verify
python3 --version prints 3.12 or higher - installfree
Why Every input is normalised to single-channel sixteen kilohertz before anything else touches it, and this is where half the format problems disappear.
Get it Your package manager, or ffmpeg.org. open ↗
Verify
ffmpeg -version prints a version and a test file converts - installfree, at the cost of disk and time
Why Running it locally keeps recordings of colleagues on your machine, and the detection pre-pass is the mitigation for the documented failure mode.
Get it A maintained local implementation with a bundled detector and per-segment scores exposed.
Verify
A file with silence in the middle produces no segments for it - installfree, with an account for the model where required
Why Attribution is a separate model from transcription, and the maintained pipelines publish their error rates so you can state them honestly.
Get it A current open pipeline with published benchmark numbers, accepting its licence terms.
Verify
A two-speaker recording produces two labels with sensible boundaries - decidefree
Why Both are legal questions before they are product questions, and the build enforces whatever you write down here.
Get it Decide what participants are told, how the telling is recorded, and how long audio and transcripts are kept.
Verify
Both appear in the README and are enforced by code rather than by intention
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- meetings(id, title, started_at, duration_s, source_path, normalised_path, consent_recorded_at, consent_note, retention_until, state) - segments(id, meeting_id, start_ms, end_ms, text, avg_logprob, no_speech_prob, compression_ratio, flagged_reason) · one row per transcribed segment with the numbers that predict a bad one - speech_regions(meeting_id, start_ms, end_ms) · from voice activity detection, before transcription touches the audio - speakers(id, meeting_id, label, display_name, confirmed_by_user) · a guess until somebody confirms it - segment_speakers(segment_id, speaker_id, overlap_ratio) · attribution kept separately from the words - notes(id, meeting_id, kind, text, segment_ids_json, model_id, created_at) · kind is summary, decision or action; the segment list is mandatory - actions(id, note_id, owner_speaker_id, due_text, confidence, accepted_by_user) · nothing is assigned to a person until a human accepts it - Nothing in the notes table can exist without segment identifiers, and the schema enforces it rather than the prompt.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ./data/meetings.db | Meetings, segments, speakers and notes. Contains what colleagues said, so treat it accordingly. |
AUDIO_DIR | required | ./data/audio | Normalised audio. Deleted at the retention date along with the original. |
RETENTION_DAYS | required | 90 | Default retention set at import. Can be shortened per meeting, never silently extended. |
ASR_MODEL | required | a local speech model identifier | Stored per meeting, because a different model gives a different transcript. |
NOTES_MODEL | required | a local or hosted model identifier | Used only for the notes pass. If hosted, the transcript leaves the machine and the README must say so. |
VAD_MIN_SILENCE_MS | required | 2000 | Non-vocal stretches longer than this are cut before transcription. |
FLAG_COMPRESSION_RATIO | required | 2.4 | Above this a segment is flagged as probably repeating rather than transcribing. |
The build, in order
Consent first, audio second
No audio is retained until a consent record exists, and every recording states its retention.
Not a checkbox that can be added afterwards.
Files
src/meetings.pysrc/db/schema.sqlOne sample rate and one channel removes a category of later failures.
Files
src/audio.pytests/test_audio.pyRecordings of colleagues should have an expiry date.
Files
src/retention.py
done when · tick each as it passeswatch out- Building capture first and consent as a checkbox later means the first real recordings in the database have no record of whether anyone was told, and the one meeting you later need to justify is one of those. Make the consent row a foreign key the audio path depends on, so the sequence cannot be done in the other order.
A transcript that does not invent
Silence produces nothing, repetition loops are caught, and doubtful segments are marked.
The pre-pass removes the exact condition that produces invented text.
Files
src/vad.pytests/test_silence.pyAnd state the trade-off in the README rather than hiding it.
Files
src/transcribe.pyA doubtful segment should look doubtful in the interface.
Files
src/transcribe.pysrc/templates/transcript.html
done when · tick each as it passeswatch out- Feeding an hour of audio straight in and trusting the output puts plausible sentences into the transcript during the four minutes when somebody was screen sharing in silence, and they read exactly like the rest, which is why the published work found harms in over a third of the hallucinations it collected. Cut to speech regions first, keep the per-segment scores, and show flagged segments differently.
Speaker labels that admit what they are
Attribution is a suggestion the user confirms, and every label can be checked against the audio.
A name is a claim about who said something.
Files
src/diarise.pysrc/templates/speakers.htmlPeople talk over each other and the transcript should show it.
Files
src/diarise.pyChecking a line should take one click.
Files
src/templates/transcript.html
done when · tick each as it passeswatch out- Presenting diarisation output as names in the notes produces sentences like a named person committing to a deadline they never mentioned, and on distant-microphone meeting audio the best open pipelines still misattribute around a fifth of speech under a strict scoring protocol. Keep labels anonymous until confirmed, store the overlap ratio, and make every attributed line playable.
Notes that cite the transcript
Every summary line, decision and action item points at the segments it came from, or it is not shown.
Verify they exist, because a model will invent an identifier too.
Files
src/notes.pytests/test_citations.pyA caveat is read as confidence by the third reader.
Files
src/notes.pyAssigning work from a guess about who spoke is how this goes wrong.
Files
src/actions.pysrc/templates/notes.html
done when · tick each as it passeswatch out- A summary produced from the whole transcript in one pass reads beautifully and contains a decision nobody made, because the model resolved an ambiguous exchange into a conclusion, and the person who reads the notes was not in the meeting and cannot tell. Require identifiers, verify they exist, and delete anything that arrives without them rather than showing it with a caveat.
Search, redaction and leavingproduct builder
Meetings are findable, a participant can be removed, and everything exports as files.
Then prove it by searching for the phrase afterwards.
Files
src/delete.pytests/test_delete.pyRedacted text over intact audio is not redaction.
Files
src/redact.pyWith the citations intact in the JSON.
Files
src/export.py
done when · tick each as it passeswatch out- Deleting a meeting row and leaving the derived rows behind means the transcript is still searchable and the audio is still on disk, which turns a deletion request into a false statement that you honoured it. Make deletion one operation over every table plus the files, and test it by searching for a distinctive phrase afterwards.
That is the whole plan for Circleback. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- A bot that joins calls automatically.
- Live transcription during the meeting.
- Cross-meeting analytics about named people.
- Calendar and CRM integrations.
- cross-call team analytics
- meeting-bot auto-join
- live multi-speaker accuracy
- calendar and CRM integrations
- A glossary of names and jargon supplied to the transcriber, which fixes more errors than a bigger model does
- Topic segmentation within a meeting, once citations are trusted enough to hang it on
- A weekly file of open action items, generated locally and written to disk rather than emailed
Need the files? The project pack on the verdict page hands your agent the whole brief · more meeting notes.