Vibecode Thrumble
track this build5 phases, 15 steps, beginner friendly0%A single inbound receptionist agent is a credible weekend build with LiveKit Agents or Pipecat plus a SIP trunk. Thrumble as a product is larger: agent config UI, knowledge bases, tools, call transfer, outbound campaigns, phone numbers, transcripts, and production telephony. You can DIY the core conversation loop; you still own carriers, media ports, failed calls, and ops.
You are building a lean indie version of Thrumble. 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 ===== # Thrumble · indie build One inbound number answered by a speech to model to speech loop that discloses what it is, obtains consent before recording, keeps the response latency inside the budget a conversation tolerates, hands off to a person when it should, 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 | | --- | --- | --- | | Telephony | A provider offering a media stream over a socket | you need the raw audio in and out, which not every telephony product exposes | | Speech in | Streaming transcription with interim results | waiting for a final transcript before thinking adds a second the caller feels as a pause | | Reasoning | A model with a short system prompt and one or two tools | an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable | | Speech out | Streaming synthesis started on the first sentence | generating the whole reply before speaking wastes the time you just spent saving | | Storage | PostgreSQL for calls and outcomes, with recordings encrypted and expiring | a call recording is personal data with a retention obligation attached | ## 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 media pipeline and the agent loop. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your confirmed consent and disclosure position** · an hour of advice, and it is the cheapest part of getting this wrong - Why: It determines the first sentence the agent says and whether you may record at all. Consent rules differ by jurisdiction and disclosure of automation is required in some places, so this decides the build rather than following it. - Get it: Identify every jurisdiction a caller might be in, take the strictest applicable rule, and confirm your position with someone qualified if this is commercial. Record what you concluded and when. - Verify: A written note names the jurisdictions, the rule applied and the date confirmed - [ ] **A telephony provider exposing a media stream** · a number is typically a few US dollars a month plus per-minute charges - Why: You need the raw audio both ways. Many products give you call control without the media, which cannot support this. - Get it: Choose a provider that offers a bidirectional media stream over a socket, and take a number in the country you will answer in. Confirm media streaming is available on your plan before building. - Verify: Audio arrives from a real call and audio you send is heard - [ ] **Streaming transcription, a model and streaming synthesis** · priced per minute and per token; a three minute call is typically a few cents and adds up quickly under load - Why: All three must stream. Any one of them batching adds a second the caller feels. - Get it: Providers for each, chosen for streaming support rather than accuracy alone. Set spend limits when you create the keys. - Verify: Each streams partial output rather than returning at the end - [ ] **A phone you can call from, in a quiet room and a noisy one** · free - Why: Latency, interruption and transcription accuracy all behave differently on a real call than in a local test, and background noise is the normal case. - Get it: Your own phone. Test from a car or a street as well as from a desk. - Verify: Ten real calls have been made and their timings recorded - [ ] **A retention policy per artefact** · free - Why: Recordings, transcripts, summaries and consent records may have different periods, and automatic deletion has to be built rather than remembered. - Get it: Write it before launch: what is kept, for how long, and why. Consent records usually outlive recordings because they are the evidence you were entitled to record. - 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: - Outbound campaigns and their separate legal surface. - Production telephony operations at volume. - Legal advice about your consent position. - The agent quality of a platform tuned on many thousands of calls. - hosted agent builder, knowledge bases, tools, and transfer destinations - outbound campaign dialer and contact workflows - managed phone numbers, concurrency, and telephony debugging - call logs, recordings, post-call extraction, and analytics - uptime, support, and multi-tenant workspace polish If one of those is essential to you, that is the reason to keep paying for Thrumble, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Thrumble 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 voice agent that answers one phone number, does something useful and logs the outcome to replace Thrumble. 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 Settle the legal position before writing any code, because it decides what the agent must say in its first sentence. As checked in September 2026: in the United States 38 states and the District of Columbia require one party's consent to record while 12 require all parties, including California, Illinois, Florida, Massachusetts, Pennsylvania and Washington. Under the General Data Protection Regulation recording is data processing and consent must be freely given, specific, informed and unambiguous, with silence not counting; callers must be told who is recording, why, for how long and how to withdraw. In the United Kingdom the Lawful Business Practice regulations permit recording for training, quality and dispute purposes, and UK data protection still requires a lawful basis, a recorded announcement, encryption and a defined retention period with automatic deletion. Separately, disclosure that the caller is speaking to an automated system is now required in some jurisdictions, California among them, and the direction of travel elsewhere is the same. Build the agent so it discloses and obtains consent first, every time, and take the strictest rule that could apply rather than the one where you happen to sit. This is not legal advice; confirm your own position. ### Stack (fixed, do not substitute) - Telephony: A provider offering a media stream over a socket · you need the raw audio in and out, which not every telephony product exposes - Speech in: Streaming transcription with interim results · waiting for a final transcript before thinking adds a second the caller feels as a pause - Reasoning: A model with a short system prompt and one or two tools · an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable - Speech out: Streaming synthesis started on the first sentence · generating the whole reply before speaking wastes the time you just spent saving - Storage: PostgreSQL for calls and outcomes, with recordings encrypted and expiring · a call recording is personal data with a retention obligation attached ### Data model (create this before Phase 1) - calls(id, direction, from_hash, to_number, started_at, ended_at, outcome, disclosed_at, consent_state, jurisdiction) - turns(id, call_id, ordinal, speaker, text, started_ms, ended_ms, latency_ms) · speaker is caller or agent - recordings(call_id, storage_key, encrypted, expires_at, deleted_at) · nullable, because consent may be refused - tool_calls(id, call_id, turn_id, name, arguments_json, result_summary, duration_ms) - handoffs(call_id, at, reason, destination) - retention_runs(at, deleted_count) · proof that automatic deletion actually ran - A call with consent refused has no recording row. The absence is the record, and the transcript retention is a separate decision. ### Phase 1 · Answer, disclose, and ask before recording Build: Answer an inbound call and play a disclosure that states this is an automated system, who is calling on behalf of, that the call may be recorded, why, and how to object. Capture the response. Only start recording after affirmative consent, and continue the call without recording if it is refused. Trap: Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. Done when: An inbound call is answered and the disclosure plays before anything is written to disk. Refusing consent continues the call with no recording created, verified by checking storage. The consent state and the disclosure timestamp are recorded on the call. The disclosure text is configuration rather than code, so it can be changed per jurisdiction. Do not build yet: No transcription, reasoning or tools. ### Phase 2 · The latency budget, measured Build: Stream transcription with interim results, start the model as soon as the caller stops, and start speaking on the first sentence rather than the whole reply. Measure each stage and record the total per turn. Trap: Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. Done when: Median time from the caller finishing to the agent starting to speak is measured and recorded per turn. A test conversation of ten turns has no turn where the gap exceeds the configured budget without being logged as a breach. The pipeline overlaps stages rather than running them in sequence, verified by the per-stage timings. A slow tool call speaks a holding phrase rather than leaving silence. Do not build yet: No tools or handoff yet. ### Phase 3 · Interruption, which is how people talk Build: Detect caller speech while the agent is speaking, stop synthesis immediately, discard the unspoken remainder, and treat what they said as the next turn. Record what the agent had actually said aloud rather than what it intended to say. Trap: Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. Done when: Speaking over the agent stops its speech within a fraction of a second. The transcript records only what was actually spoken, marked as interrupted. The interrupted turn does not confuse the following turn's context. Interrupting during a holding phrase also works. Do not build yet: No tools or outcomes yet. ### Phase 4 · One tool, and knowing when to stop Build: Give the agent one tool that queries something real. Require it to say it does not know rather than guessing when the tool returns nothing. Cap the number of tool calls per turn and per call. Trap: An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. Done when: A question answerable from the tool is answered correctly with the tool call logged. A question the tool cannot answer produces an explicit statement that it does not know, verified with a question deliberately outside the data. Tool calls are capped per turn and per call. Every tool call is recorded with its arguments and duration. Do not build yet: No handoff or retention yet. ### Phase 5 · Handing off to a person Build: Transfer on explicit request, on repeated failure to understand, and on any topic you have listed as requiring a person. Pass a short summary to the receiving human. Record the handoff reason. Trap: An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. Done when: Asking for a person transfers on the first request without negotiation. Two consecutive misunderstandings trigger a transfer. The receiving person gets a summary of what was discussed. Every handoff records its reason. A transfer failing because nobody is available says so and offers a callback rather than looping. Do not build yet: Retention and deployment come last. ### Phase 6 · Retention, deletion and the record you must keep Build: Encrypt recordings at rest with a retention period per jurisdiction. Delete automatically and record that the run happened. Handle a request for a copy or for deletion. Keep the disclosure and consent record separately from the recording. Trap: Deleting the recording but keeping the transcript, or the reverse, satisfies neither the retention policy nor the caller's expectation. Decide what each retention period covers, apply it to every artefact, and keep the consent record longer than the recording because it is the evidence that you were entitled to make it. Done when: Recordings past their retention period are deleted automatically and each run records what it removed. A deletion request removes the recording, the transcript and derived summaries together, verified by searching afterwards. The consent record survives the recording deletion. Recordings are encrypted at rest, verified by inspecting storage. Do not build yet: Nothing after this. ### Out of scope (and why) - Outbound campaigns, which bring their own rules about calling times, do-not-call registers and consent to be contacted, and are a substantially larger legal surface than answering a number. - Production operations at volume: concurrency, carrier relationships, number provisioning across countries and the reliability people expect from a phone number. - Legal advice. The consent and disclosure position differs by jurisdiction and is changing, and confirming yours is a prerequisite rather than a detail. - The agent quality of a tuned commercial platform, which comes from many thousands of real calls you do not have. ### README must contain - The consent and disclosure position you confirmed, with the date and the jurisdictions covered - That recording begins only after affirmative consent and refusal is a supported path - The measured latency budget per stage and what happens when it is breached - That the transcript records what was spoken rather than what was generated - The retention period per artefact and proof that automatic deletion runs ===== AGENTS.md ===== # Agent instructions · Thrumble indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A provider offering a media stream over a socket, Streaming transcription with interim results, A model with a short system prompt and one or two tools, Streaming synthesis started on the first sentence, PostgreSQL for calls and outcomes, with recordings encrypted and expiring. 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 - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. ===== BUILD_PLAN.md ===== # Build plan · Thrumble One inbound number answered by a speech to model to speech loop that discloses what it is, obtains consent before recording, keeps the response latency inside the budget a conversation tolerates, hands off to a person when it should, 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 · Answer, disclose, and ask before recording The number answers, says what it is, and does not record until it has permission. ### Steps 1. Answer and stream media A provider that gives you the audio stream. Confirm you can receive and send audio before anything else. Files: `src/telephony.ts` 2. Play disclosure and capture the response Automated system, who for, recording, why, how to object. Configuration, not a string in code. Files: `config/disclosure.json` 3. Gate recording on consent Nothing to disk before consent. Refusal is a supported path with the call continuing. Files: `src/consent.ts` ### Done when - [ ] Disclosure plays before anything reaches disk - [ ] Refusing consent continues the call with no recording - [ ] Consent state and disclosure time are recorded - [ ] Disclosure text is configurable per jurisdiction ### Watch out - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. ## Phase 2 · The latency budget, measured The agent replies fast enough that the caller does not think the line dropped. ### Steps 1. Stream transcription with interim results Use them to detect end of speech rather than waiting for a final result. Files: `src/stt.ts` 2. Start speaking on the first sentence Synthesis begins before generation finishes. This is where most of the perceived latency goes. Files: `src/tts.ts` 3. Record per-stage timings Transcription, generation, synthesis and total, per turn. You cannot tune what you have not measured. Files: `src/metrics.ts` ### Done when - [ ] Median response gap is measured per turn - [ ] Budget breaches are logged rather than invisible - [ ] Stages overlap rather than run in sequence - [ ] A slow tool speaks a holding phrase rather than going silent ### Watch out - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. ## Phase 3 · Interruption, which is how people talk A caller can interrupt the agent and the agent stops. ### Steps 1. Detect speech during playback Voice activity on the inbound stream while outbound audio is playing. Files: `src/barge.ts` 2. Stop and discard immediately Cancel synthesis and drop the queued audio. A half second of continued speech feels rude. Files: `src/tts.ts` 3. Log what was spoken, not generated Truncated at the interruption and marked. The transcript must match the conversation. Files: `src/turns.ts` ### Done when - [ ] Speaking over the agent stops it quickly - [ ] The transcript records only spoken text, marked interrupted - [ ] The following turn is not confused by the interruption - [ ] Interrupting a holding phrase works ### Watch out - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. ## Phase 4 · One tool, and knowing when to stop The agent can look one thing up, and it does not invent an answer when it cannot. ### Steps 1. Wire one real tool Something genuinely useful to a caller. One is enough to prove the pattern. Files: `src/tools.ts` 2. Constrain factual answers to tool output In the prompt and in a check. Test with a question outside the data. Files: `config/prompt.md` 3. Cap tool calls Per turn and per call. An agent looping on tools produces a long silence and a large bill. Files: `src/agent.ts` ### Done when - [ ] A tool-answerable question is answered with the call logged - [ ] A question outside the data produces an explicit unknown - [ ] Tool calls are capped per turn and per call - [ ] Every tool call records arguments and duration ### Watch out - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. ## Phase 5 · Handing off to a person The agent transfers when it should, and the caller does not have to repeat themselves. ### Steps 1. Transfer on first request No negotiation, no retention attempt. This is the behaviour that earns trust. Files: `src/handoff.ts` 2. Detect repeated failure Two consecutive low-confidence turns is a transfer condition rather than a third try. Files: `src/handoff.ts` 3. Pass a summary So the caller does not start again. This is most of the value of the handoff. Files: `src/summary.ts` ### Done when - [ ] An explicit request transfers immediately - [ ] Two misunderstandings trigger a transfer - [ ] The receiving person gets a summary - [ ] An unavailable destination offers a callback rather than looping ### Watch out - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. ## Not in this build - Outbound campaigns and their separate legal surface. - Production telephony operations at volume. - Legal advice about your consent position. - The agent quality of a platform tuned on many thousands of calls. ## After v1, if you want it - A second language, which mostly means a second disclosure and a different speech pipeline - Post-call summaries written into whatever system your team already uses - Sentiment or outcome tagging, once you have enough calls for it to mean anything ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Calls, turns and consent records. Personal data with retention obligations. DATABASE_URL=postgres://user:pass@localhost:5432/calls # Required. Encrypted at rest, with lifecycle expiry set as a second line of defence. RECORDINGS_BUCKET=call-recordings # Required. What the agent says first, per jurisdiction. Configuration so it can change without a deploy. DISCLOSURE_CONFIG=./config/disclosure.json # Required. Target gap from the caller finishing to the agent speaking. Breaches are logged. RESPONSE_BUDGET_MS=900 # Required. Cap, so a looping agent cannot produce a long silence and a large bill. MAX_TOOL_CALLS_PER_CALL=6 # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, because it is the evidence you were entitled to make it. CONSENT_RETENTION_DAYS=2555
You are building a lean indie version of Thrumble. 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 ===== # Thrumble · indie build One inbound number answered by a speech to model to speech loop that discloses what it is, obtains consent before recording, keeps the response latency inside the budget a conversation tolerates, hands off to a person when it should, 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 | | --- | --- | --- | | Telephony | A provider offering a media stream over a socket | you need the raw audio in and out, which not every telephony product exposes | | Speech in | Streaming transcription with interim results | waiting for a final transcript before thinking adds a second the caller feels as a pause | | Reasoning | A model with a short system prompt and one or two tools | an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable | | Speech out | Streaming synthesis started on the first sentence | generating the whole reply before speaking wastes the time you just spent saving | | Storage | PostgreSQL for calls and outcomes, with recordings encrypted and expiring | a call recording is personal data with a retention obligation attached | ## 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 media pipeline and the agent loop. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your confirmed consent and disclosure position** · an hour of advice, and it is the cheapest part of getting this wrong - Why: It determines the first sentence the agent says and whether you may record at all. Consent rules differ by jurisdiction and disclosure of automation is required in some places, so this decides the build rather than following it. - Get it: Identify every jurisdiction a caller might be in, take the strictest applicable rule, and confirm your position with someone qualified if this is commercial. Record what you concluded and when. - Verify: A written note names the jurisdictions, the rule applied and the date confirmed - [ ] **A telephony provider exposing a media stream** · a number is typically a few US dollars a month plus per-minute charges - Why: You need the raw audio both ways. Many products give you call control without the media, which cannot support this. - Get it: Choose a provider that offers a bidirectional media stream over a socket, and take a number in the country you will answer in. Confirm media streaming is available on your plan before building. - Verify: Audio arrives from a real call and audio you send is heard - [ ] **Streaming transcription, a model and streaming synthesis** · priced per minute and per token; a three minute call is typically a few cents and adds up quickly under load - Why: All three must stream. Any one of them batching adds a second the caller feels. - Get it: Providers for each, chosen for streaming support rather than accuracy alone. Set spend limits when you create the keys. - Verify: Each streams partial output rather than returning at the end - [ ] **A phone you can call from, in a quiet room and a noisy one** · free - Why: Latency, interruption and transcription accuracy all behave differently on a real call than in a local test, and background noise is the normal case. - Get it: Your own phone. Test from a car or a street as well as from a desk. - Verify: Ten real calls have been made and their timings recorded - [ ] **A retention policy per artefact** · free - Why: Recordings, transcripts, summaries and consent records may have different periods, and automatic deletion has to be built rather than remembered. - Get it: Write it before launch: what is kept, for how long, and why. Consent records usually outlive recordings because they are the evidence you were entitled to record. - 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: - Outbound campaigns and their separate legal surface. - Production telephony operations at volume. - Legal advice about your consent position. - The agent quality of a platform tuned on many thousands of calls. - hosted agent builder, knowledge bases, tools, and transfer destinations - outbound campaign dialer and contact workflows - managed phone numbers, concurrency, and telephony debugging - call logs, recordings, post-call extraction, and analytics - uptime, support, and multi-tenant workspace polish If one of those is essential to you, that is the reason to keep paying for Thrumble, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Thrumble 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 voice agent that answers one phone number, does something useful and logs the outcome to replace Thrumble. 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 Settle the legal position before writing any code, because it decides what the agent must say in its first sentence. As checked in September 2026: in the United States 38 states and the District of Columbia require one party's consent to record while 12 require all parties, including California, Illinois, Florida, Massachusetts, Pennsylvania and Washington. Under the General Data Protection Regulation recording is data processing and consent must be freely given, specific, informed and unambiguous, with silence not counting; callers must be told who is recording, why, for how long and how to withdraw. In the United Kingdom the Lawful Business Practice regulations permit recording for training, quality and dispute purposes, and UK data protection still requires a lawful basis, a recorded announcement, encryption and a defined retention period with automatic deletion. Separately, disclosure that the caller is speaking to an automated system is now required in some jurisdictions, California among them, and the direction of travel elsewhere is the same. Build the agent so it discloses and obtains consent first, every time, and take the strictest rule that could apply rather than the one where you happen to sit. This is not legal advice; confirm your own position. ### Stack (fixed, do not substitute) - Telephony: A provider offering a media stream over a socket · you need the raw audio in and out, which not every telephony product exposes - Speech in: Streaming transcription with interim results · waiting for a final transcript before thinking adds a second the caller feels as a pause - Reasoning: A model with a short system prompt and one or two tools · an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable - Speech out: Streaming synthesis started on the first sentence · generating the whole reply before speaking wastes the time you just spent saving - Storage: PostgreSQL for calls and outcomes, with recordings encrypted and expiring · a call recording is personal data with a retention obligation attached ### Data model (create this before Phase 1) - calls(id, direction, from_hash, to_number, started_at, ended_at, outcome, disclosed_at, consent_state, jurisdiction) - turns(id, call_id, ordinal, speaker, text, started_ms, ended_ms, latency_ms) · speaker is caller or agent - recordings(call_id, storage_key, encrypted, expires_at, deleted_at) · nullable, because consent may be refused - tool_calls(id, call_id, turn_id, name, arguments_json, result_summary, duration_ms) - handoffs(call_id, at, reason, destination) - retention_runs(at, deleted_count) · proof that automatic deletion actually ran - A call with consent refused has no recording row. The absence is the record, and the transcript retention is a separate decision. ### Phase 1 · Answer, disclose, and ask before recording Build: Answer an inbound call and play a disclosure that states this is an automated system, who is calling on behalf of, that the call may be recorded, why, and how to object. Capture the response. Only start recording after affirmative consent, and continue the call without recording if it is refused. Trap: Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. Done when: An inbound call is answered and the disclosure plays before anything is written to disk. Refusing consent continues the call with no recording created, verified by checking storage. The consent state and the disclosure timestamp are recorded on the call. The disclosure text is configuration rather than code, so it can be changed per jurisdiction. Do not build yet: No transcription, reasoning or tools. ### Phase 2 · The latency budget, measured Build: Stream transcription with interim results, start the model as soon as the caller stops, and start speaking on the first sentence rather than the whole reply. Measure each stage and record the total per turn. Trap: Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. Done when: Median time from the caller finishing to the agent starting to speak is measured and recorded per turn. A test conversation of ten turns has no turn where the gap exceeds the configured budget without being logged as a breach. The pipeline overlaps stages rather than running them in sequence, verified by the per-stage timings. A slow tool call speaks a holding phrase rather than leaving silence. Do not build yet: No tools or handoff yet. ### Phase 3 · Interruption, which is how people talk Build: Detect caller speech while the agent is speaking, stop synthesis immediately, discard the unspoken remainder, and treat what they said as the next turn. Record what the agent had actually said aloud rather than what it intended to say. Trap: Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. Done when: Speaking over the agent stops its speech within a fraction of a second. The transcript records only what was actually spoken, marked as interrupted. The interrupted turn does not confuse the following turn's context. Interrupting during a holding phrase also works. Do not build yet: No tools or outcomes yet. ### Phase 4 · One tool, and knowing when to stop Build: Give the agent one tool that queries something real. Require it to say it does not know rather than guessing when the tool returns nothing. Cap the number of tool calls per turn and per call. Trap: An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. Done when: A question answerable from the tool is answered correctly with the tool call logged. A question the tool cannot answer produces an explicit statement that it does not know, verified with a question deliberately outside the data. Tool calls are capped per turn and per call. Every tool call is recorded with its arguments and duration. Do not build yet: No handoff or retention yet. ### Phase 5 · Handing off to a person Build: Transfer on explicit request, on repeated failure to understand, and on any topic you have listed as requiring a person. Pass a short summary to the receiving human. Record the handoff reason. Trap: An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. Done when: Asking for a person transfers on the first request without negotiation. Two consecutive misunderstandings trigger a transfer. The receiving person gets a summary of what was discussed. Every handoff records its reason. A transfer failing because nobody is available says so and offers a callback rather than looping. Do not build yet: Retention and deployment come last. ### Phase 6 · Retention, deletion and the record you must keep Build: Encrypt recordings at rest with a retention period per jurisdiction. Delete automatically and record that the run happened. Handle a request for a copy or for deletion. Keep the disclosure and consent record separately from the recording. Trap: Deleting the recording but keeping the transcript, or the reverse, satisfies neither the retention policy nor the caller's expectation. Decide what each retention period covers, apply it to every artefact, and keep the consent record longer than the recording because it is the evidence that you were entitled to make it. Done when: Recordings past their retention period are deleted automatically and each run records what it removed. A deletion request removes the recording, the transcript and derived summaries together, verified by searching afterwards. The consent record survives the recording deletion. Recordings are encrypted at rest, verified by inspecting storage. Do not build yet: Nothing after this. ### Out of scope (and why) - Outbound campaigns, which bring their own rules about calling times, do-not-call registers and consent to be contacted, and are a substantially larger legal surface than answering a number. - Production operations at volume: concurrency, carrier relationships, number provisioning across countries and the reliability people expect from a phone number. - Legal advice. The consent and disclosure position differs by jurisdiction and is changing, and confirming yours is a prerequisite rather than a detail. - The agent quality of a tuned commercial platform, which comes from many thousands of real calls you do not have. ### README must contain - The consent and disclosure position you confirmed, with the date and the jurisdictions covered - That recording begins only after affirmative consent and refusal is a supported path - The measured latency budget per stage and what happens when it is breached - That the transcript records what was spoken rather than what was generated - The retention period per artefact and proof that automatic deletion runs ===== AGENTS.md ===== # Agent instructions · Thrumble indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A provider offering a media stream over a socket, Streaming transcription with interim results, A model with a short system prompt and one or two tools, Streaming synthesis started on the first sentence, PostgreSQL for calls and outcomes, with recordings encrypted and expiring. 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 - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. ===== BUILD_PLAN.md ===== # Build plan · Thrumble One inbound number answered by a speech to model to speech loop that discloses what it is, obtains consent before recording, keeps the response latency inside the budget a conversation tolerates, hands off to a person when it should, 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 · Answer, disclose, and ask before recording The number answers, says what it is, and does not record until it has permission. ### Steps 1. Answer and stream media A provider that gives you the audio stream. Confirm you can receive and send audio before anything else. Files: `src/telephony.ts` 2. Play disclosure and capture the response Automated system, who for, recording, why, how to object. Configuration, not a string in code. Files: `config/disclosure.json` 3. Gate recording on consent Nothing to disk before consent. Refusal is a supported path with the call continuing. Files: `src/consent.ts` ### Done when - [ ] Disclosure plays before anything reaches disk - [ ] Refusing consent continues the call with no recording - [ ] Consent state and disclosure time are recorded - [ ] Disclosure text is configurable per jurisdiction ### Watch out - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. ## Phase 2 · The latency budget, measured The agent replies fast enough that the caller does not think the line dropped. ### Steps 1. Stream transcription with interim results Use them to detect end of speech rather than waiting for a final result. Files: `src/stt.ts` 2. Start speaking on the first sentence Synthesis begins before generation finishes. This is where most of the perceived latency goes. Files: `src/tts.ts` 3. Record per-stage timings Transcription, generation, synthesis and total, per turn. You cannot tune what you have not measured. Files: `src/metrics.ts` ### Done when - [ ] Median response gap is measured per turn - [ ] Budget breaches are logged rather than invisible - [ ] Stages overlap rather than run in sequence - [ ] A slow tool speaks a holding phrase rather than going silent ### Watch out - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. ## Phase 3 · Interruption, which is how people talk A caller can interrupt the agent and the agent stops. ### Steps 1. Detect speech during playback Voice activity on the inbound stream while outbound audio is playing. Files: `src/barge.ts` 2. Stop and discard immediately Cancel synthesis and drop the queued audio. A half second of continued speech feels rude. Files: `src/tts.ts` 3. Log what was spoken, not generated Truncated at the interruption and marked. The transcript must match the conversation. Files: `src/turns.ts` ### Done when - [ ] Speaking over the agent stops it quickly - [ ] The transcript records only spoken text, marked interrupted - [ ] The following turn is not confused by the interruption - [ ] Interrupting a holding phrase works ### Watch out - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. ## Phase 4 · One tool, and knowing when to stop The agent can look one thing up, and it does not invent an answer when it cannot. ### Steps 1. Wire one real tool Something genuinely useful to a caller. One is enough to prove the pattern. Files: `src/tools.ts` 2. Constrain factual answers to tool output In the prompt and in a check. Test with a question outside the data. Files: `config/prompt.md` 3. Cap tool calls Per turn and per call. An agent looping on tools produces a long silence and a large bill. Files: `src/agent.ts` ### Done when - [ ] A tool-answerable question is answered with the call logged - [ ] A question outside the data produces an explicit unknown - [ ] Tool calls are capped per turn and per call - [ ] Every tool call records arguments and duration ### Watch out - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. ## Phase 5 · Handing off to a person The agent transfers when it should, and the caller does not have to repeat themselves. ### Steps 1. Transfer on first request No negotiation, no retention attempt. This is the behaviour that earns trust. Files: `src/handoff.ts` 2. Detect repeated failure Two consecutive low-confidence turns is a transfer condition rather than a third try. Files: `src/handoff.ts` 3. Pass a summary So the caller does not start again. This is most of the value of the handoff. Files: `src/summary.ts` ### Done when - [ ] An explicit request transfers immediately - [ ] Two misunderstandings trigger a transfer - [ ] The receiving person gets a summary - [ ] An unavailable destination offers a callback rather than looping ### Watch out - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. ## Not in this build - Outbound campaigns and their separate legal surface. - Production telephony operations at volume. - Legal advice about your consent position. - The agent quality of a platform tuned on many thousands of calls. ## After v1, if you want it - A second language, which mostly means a second disclosure and a different speech pipeline - Post-call summaries written into whatever system your team already uses - Sentiment or outcome tagging, once you have enough calls for it to mean anything ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Calls, turns and consent records. Personal data with retention obligations. DATABASE_URL=postgres://user:pass@localhost:5432/calls # Required. Encrypted at rest, with lifecycle expiry set as a second line of defence. RECORDINGS_BUCKET=call-recordings # Required. What the agent says first, per jurisdiction. Configuration so it can change without a deploy. DISCLOSURE_CONFIG=./config/disclosure.json # Required. Target gap from the caller finishing to the agent speaking. Breaches are logged. RESPONSE_BUDGET_MS=900 # Required. Cap, so a looping agent cannot produce a long silence and a large bill. MAX_TOOL_CALLS_PER_CALL=6 # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, because it is the evidence you were entitled to make it. CONSENT_RETENTION_DAYS=2555
You are building a production product version of Thrumble. 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 ===== # Thrumble · product brief ## Problem A single inbound receptionist agent is a credible weekend build with LiveKit Agents or Pipecat plus a SIP trunk. Thrumble as a product is larger: agent config UI, knowledge bases, tools, call transfer, outbound campaigns, phone numbers, transcripts, and production telephony. You can DIY the core conversation loop; you still own carriers, media ports, failed calls, and ops. ## Product outcome A phone number that answers, says what it is, records only with permission, replies fast enough to feel like a conversation, stops when interrupted, transfers when asked, 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 - public VM with TLS and correctly opened SIP / WebRTC / RTP ports - SIP trunk and phone number (e.g. Telnyx) - STT, LLM, and TTS API keys - consent and recording rules for your country before real traffic ## Explicit non-goals for v1 - Outbound campaigns and their separate legal surface. - Production telephony operations at volume. - Legal advice about your consent position. - The agent quality of a platform tuned on many thousands of calls. - hosted agent builder, knowledge bases, tools, and transfer destinations - outbound campaign dialer and contact workflows - managed phone numbers, concurrency, and telephony debugging - call logs, recordings, post-call extraction, and analytics - uptime, support, and multi-tenant workspace polish ## Success criteria - Disclosure is confirmed to play before anything reaches disk - Refusing consent is confirmed to leave no recording - Median response latency is within budget across ten real calls - Interruption is confirmed to stop speech and truncate the transcript - The retention job is confirmed to delete and to log the run ===== BRIEF.md ===== # Build brief · Thrumble 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 voice agent that answers one phone number, does something useful and logs the outcome to replace Thrumble. 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 Settle the legal position before writing any code, because it decides what the agent must say in its first sentence. As checked in September 2026: in the United States 38 states and the District of Columbia require one party's consent to record while 12 require all parties, including California, Illinois, Florida, Massachusetts, Pennsylvania and Washington. Under the General Data Protection Regulation recording is data processing and consent must be freely given, specific, informed and unambiguous, with silence not counting; callers must be told who is recording, why, for how long and how to withdraw. In the United Kingdom the Lawful Business Practice regulations permit recording for training, quality and dispute purposes, and UK data protection still requires a lawful basis, a recorded announcement, encryption and a defined retention period with automatic deletion. Separately, disclosure that the caller is speaking to an automated system is now required in some jurisdictions, California among them, and the direction of travel elsewhere is the same. Build the agent so it discloses and obtains consent first, every time, and take the strictest rule that could apply rather than the one where you happen to sit. This is not legal advice; confirm your own position. ### Stack (fixed, do not substitute) - Telephony: A provider offering a media stream over a socket · you need the raw audio in and out, which not every telephony product exposes - Speech in: Streaming transcription with interim results · waiting for a final transcript before thinking adds a second the caller feels as a pause - Reasoning: A model with a short system prompt and one or two tools · an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable - Speech out: Streaming synthesis started on the first sentence · generating the whole reply before speaking wastes the time you just spent saving - Storage: PostgreSQL for calls and outcomes, with recordings encrypted and expiring · a call recording is personal data with a retention obligation attached ### Data model (create this before Phase 1) - calls(id, direction, from_hash, to_number, started_at, ended_at, outcome, disclosed_at, consent_state, jurisdiction) - turns(id, call_id, ordinal, speaker, text, started_ms, ended_ms, latency_ms) · speaker is caller or agent - recordings(call_id, storage_key, encrypted, expires_at, deleted_at) · nullable, because consent may be refused - tool_calls(id, call_id, turn_id, name, arguments_json, result_summary, duration_ms) - handoffs(call_id, at, reason, destination) - retention_runs(at, deleted_count) · proof that automatic deletion actually ran - A call with consent refused has no recording row. The absence is the record, and the transcript retention is a separate decision. ### Phase 1 · Answer, disclose, and ask before recording Build: Answer an inbound call and play a disclosure that states this is an automated system, who is calling on behalf of, that the call may be recorded, why, and how to object. Capture the response. Only start recording after affirmative consent, and continue the call without recording if it is refused. Trap: Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. Done when: An inbound call is answered and the disclosure plays before anything is written to disk. Refusing consent continues the call with no recording created, verified by checking storage. The consent state and the disclosure timestamp are recorded on the call. The disclosure text is configuration rather than code, so it can be changed per jurisdiction. Do not build yet: No transcription, reasoning or tools. ### Phase 2 · The latency budget, measured Build: Stream transcription with interim results, start the model as soon as the caller stops, and start speaking on the first sentence rather than the whole reply. Measure each stage and record the total per turn. Trap: Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. Done when: Median time from the caller finishing to the agent starting to speak is measured and recorded per turn. A test conversation of ten turns has no turn where the gap exceeds the configured budget without being logged as a breach. The pipeline overlaps stages rather than running them in sequence, verified by the per-stage timings. A slow tool call speaks a holding phrase rather than leaving silence. Do not build yet: No tools or handoff yet. ### Phase 3 · Interruption, which is how people talk Build: Detect caller speech while the agent is speaking, stop synthesis immediately, discard the unspoken remainder, and treat what they said as the next turn. Record what the agent had actually said aloud rather than what it intended to say. Trap: Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. Done when: Speaking over the agent stops its speech within a fraction of a second. The transcript records only what was actually spoken, marked as interrupted. The interrupted turn does not confuse the following turn's context. Interrupting during a holding phrase also works. Do not build yet: No tools or outcomes yet. ### Phase 4 · One tool, and knowing when to stop Build: Give the agent one tool that queries something real. Require it to say it does not know rather than guessing when the tool returns nothing. Cap the number of tool calls per turn and per call. Trap: An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. Done when: A question answerable from the tool is answered correctly with the tool call logged. A question the tool cannot answer produces an explicit statement that it does not know, verified with a question deliberately outside the data. Tool calls are capped per turn and per call. Every tool call is recorded with its arguments and duration. Do not build yet: No handoff or retention yet. ### Phase 5 · Handing off to a person Build: Transfer on explicit request, on repeated failure to understand, and on any topic you have listed as requiring a person. Pass a short summary to the receiving human. Record the handoff reason. Trap: An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. Done when: Asking for a person transfers on the first request without negotiation. Two consecutive misunderstandings trigger a transfer. The receiving person gets a summary of what was discussed. Every handoff records its reason. A transfer failing because nobody is available says so and offers a callback rather than looping. Do not build yet: Retention and deployment come last. ### Phase 6 · Retention, deletion and the record you must keep Build: Encrypt recordings at rest with a retention period per jurisdiction. Delete automatically and record that the run happened. Handle a request for a copy or for deletion. Keep the disclosure and consent record separately from the recording. Trap: Deleting the recording but keeping the transcript, or the reverse, satisfies neither the retention policy nor the caller's expectation. Decide what each retention period covers, apply it to every artefact, and keep the consent record longer than the recording because it is the evidence that you were entitled to make it. Done when: Recordings past their retention period are deleted automatically and each run records what it removed. A deletion request removes the recording, the transcript and derived summaries together, verified by searching afterwards. The consent record survives the recording deletion. Recordings are encrypted at rest, verified by inspecting storage. Do not build yet: Nothing after this. ### Out of scope (and why) - Outbound campaigns, which bring their own rules about calling times, do-not-call registers and consent to be contacted, and are a substantially larger legal surface than answering a number. - Production operations at volume: concurrency, carrier relationships, number provisioning across countries and the reliability people expect from a phone number. - Legal advice. The consent and disclosure position differs by jurisdiction and is changing, and confirming yours is a prerequisite rather than a detail. - The agent quality of a tuned commercial platform, which comes from many thousands of real calls you do not have. ### README must contain - The consent and disclosure position you confirmed, with the date and the jurisdictions covered - That recording begins only after affirmative consent and refusal is a supported path - The measured latency budget per stage and what happens when it is breached - That the transcript records what was spoken rather than what was generated - The retention period per artefact and proof that automatic deletion runs ===== ARCHITECTURE.md ===== # Architecture · Thrumble ## Stack | Part | Choice | Why | | --- | --- | --- | | Telephony | A provider offering a media stream over a socket | you need the raw audio in and out, which not every telephony product exposes | | Speech in | Streaming transcription with interim results | waiting for a final transcript before thinking adds a second the caller feels as a pause | | Reasoning | A model with a short system prompt and one or two tools | an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable | | Speech out | Streaming synthesis started on the first sentence | generating the whole reply before speaking wastes the time you just spent saving | | Storage | PostgreSQL for calls and outcomes, with recordings encrypted and expiring | a call recording is personal data with a retention obligation attached | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Telephony | The media stream both ways. | Providers differ; requiring a raw media stream is the constraint that eliminates most of them. | | Consent | Disclosure, permission and what may be stored. | Not swappable for a policy people follow. It gates the recording path structurally. | | Pipeline | Overlapped transcription, generation and synthesis. | Any provider that streams. Sequential processing is the thing being avoided. | | Agent | Reasoning, tools and refusing to guess. | Any model. The constraint that factual answers come from tool output must survive. | | Retention | Automatic deletion and its proof. | 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 · Calls, turns and consent records. Personal data with retention obligations. - `RECORDINGS_BUCKET` · required · Encrypted at rest, with lifecycle expiry set as a second line of defence. - `DISCLOSURE_CONFIG` · required · What the agent says first, per jurisdiction. Configuration so it can change without a deploy. - `RESPONSE_BUDGET_MS` · required · Target gap from the caller finishing to the agent speaking. Breaches are logged. - `MAX_TOOL_CALLS_PER_CALL` · required · Cap, so a looping agent cannot produce a long silence and a large bill. - `RECORDING_RETENTION_DAYS` · required · Per your written policy. Deletion is automatic and logged. - `CONSENT_RETENTION_DAYS` · required · Usually longer than the recording, because it is the evidence you were entitled to make it. ## 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 · Thrumble product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A provider offering a media stream over a socket, Streaming transcription with interim results, A model with a short system prompt and one or two tools, Streaming synthesis started on the first sentence, PostgreSQL for calls and outcomes, with recordings encrypted and expiring. - 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 - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. - Deleting the recording but keeping the transcript, or the reverse, satisfies neither the retention policy nor the caller's expectation. Decide what each retention period covers, apply it to every artefact, and keep the consent record longer than the recording because it is the evidence that you were entitled to make it. ===== MILESTONES.md ===== # Delivery milestones · Thrumble Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Answer, disclose, and ask before recording The number answers, says what it is, and does not record until it has permission. ### Steps 1. Answer and stream media A provider that gives you the audio stream. Confirm you can receive and send audio before anything else. Files: `src/telephony.ts` 2. Play disclosure and capture the response Automated system, who for, recording, why, how to object. Configuration, not a string in code. Files: `config/disclosure.json` 3. Gate recording on consent Nothing to disk before consent. Refusal is a supported path with the call continuing. Files: `src/consent.ts` ### Done when - [ ] Disclosure plays before anything reaches disk - [ ] Refusing consent continues the call with no recording - [ ] Consent state and disclosure time are recorded - [ ] Disclosure text is configurable per jurisdiction ### Watch out - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. ## M2 · The latency budget, measured The agent replies fast enough that the caller does not think the line dropped. ### Steps 1. Stream transcription with interim results Use them to detect end of speech rather than waiting for a final result. Files: `src/stt.ts` 2. Start speaking on the first sentence Synthesis begins before generation finishes. This is where most of the perceived latency goes. Files: `src/tts.ts` 3. Record per-stage timings Transcription, generation, synthesis and total, per turn. You cannot tune what you have not measured. Files: `src/metrics.ts` ### Done when - [ ] Median response gap is measured per turn - [ ] Budget breaches are logged rather than invisible - [ ] Stages overlap rather than run in sequence - [ ] A slow tool speaks a holding phrase rather than going silent ### Watch out - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. ## M3 · Interruption, which is how people talk A caller can interrupt the agent and the agent stops. ### Steps 1. Detect speech during playback Voice activity on the inbound stream while outbound audio is playing. Files: `src/barge.ts` 2. Stop and discard immediately Cancel synthesis and drop the queued audio. A half second of continued speech feels rude. Files: `src/tts.ts` 3. Log what was spoken, not generated Truncated at the interruption and marked. The transcript must match the conversation. Files: `src/turns.ts` ### Done when - [ ] Speaking over the agent stops it quickly - [ ] The transcript records only spoken text, marked interrupted - [ ] The following turn is not confused by the interruption - [ ] Interrupting a holding phrase works ### Watch out - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. ## M4 · One tool, and knowing when to stop The agent can look one thing up, and it does not invent an answer when it cannot. ### Steps 1. Wire one real tool Something genuinely useful to a caller. One is enough to prove the pattern. Files: `src/tools.ts` 2. Constrain factual answers to tool output In the prompt and in a check. Test with a question outside the data. Files: `config/prompt.md` 3. Cap tool calls Per turn and per call. An agent looping on tools produces a long silence and a large bill. Files: `src/agent.ts` ### Done when - [ ] A tool-answerable question is answered with the call logged - [ ] A question outside the data produces an explicit unknown - [ ] Tool calls are capped per turn and per call - [ ] Every tool call records arguments and duration ### Watch out - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. ## M5 · Handing off to a person The agent transfers when it should, and the caller does not have to repeat themselves. ### Steps 1. Transfer on first request No negotiation, no retention attempt. This is the behaviour that earns trust. Files: `src/handoff.ts` 2. Detect repeated failure Two consecutive low-confidence turns is a transfer condition rather than a third try. Files: `src/handoff.ts` 3. Pass a summary So the caller does not start again. This is most of the value of the handoff. Files: `src/summary.ts` ### Done when - [ ] An explicit request transfers immediately - [ ] Two misunderstandings trigger a transfer - [ ] The receiving person gets a summary - [ ] An unavailable destination offers a callback rather than looping ### Watch out - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. ## M6 · Retention, deletion and the record you must keep (production only) Recordings expire automatically and you can prove they did. ### Steps 1. Encrypt and set expiry per jurisdiction The period comes from your written policy, which comes from the position you settled in rule zero. Files: `src/retention.ts`, `docs/retention.md` 2. Delete automatically and log the run A policy that relies on someone remembering is not automatic deletion. Files: `src/retention.ts` 3. Handle access and deletion requests Covering every artefact. Keep the consent record, which is your evidence. Files: `src/requests.ts` ### Done when - [ ] Aged recordings are deleted automatically with a logged run - [ ] A deletion request removes recording, transcript and summaries - [ ] The consent record survives the recording deletion - [ ] Recordings are encrypted at rest ### Watch out - Deleting the recording but keeping the transcript, or the reverse, satisfies neither the retention policy nor the caller's expectation. Decide what each retention period covers, apply it to every artefact, and keep the consent record longer than the recording because it is the evidence that you were entitled to make it. ===== OPERATIONS.md ===== # Operations · Thrumble ## Backup Consent records and call outcomes are the durable material and are small. Recordings are large and expiring by design, so backing them up would work against the retention policy, which is worth stating explicitly. ## Restore Restore the database and confirm that consent records survived and that the retention job resumes. A restore that reinstates recordings past their expiry 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 Response latency percentiles, transfer rate, tool failure rate and retention run success. A rising transfer rate is information about the agent rather than a fault, and a missed retention run is a compliance problem rather than an outage. ## Incident checklist If the agent said something wrong, the transcript of what was actually spoken plus the tool calls is the record. If a recording was made without consent, that is the most serious failure here and the consent gate is what should have prevented it. 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 - [ ] Disclosure is confirmed to play before anything reaches disk - [ ] Refusing consent is confirmed to leave no recording - [ ] Median response latency is within budget across ten real calls - [ ] Interruption is confirmed to stop speech and truncate the transcript - [ ] The retention job is confirmed to delete and to log the run ## Launch constraint Do not market omitted Thrumble 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. Calls, turns and consent records. Personal data with retention obligations. DATABASE_URL=postgres://user:pass@localhost:5432/calls # Required. Encrypted at rest, with lifecycle expiry set as a second line of defence. RECORDINGS_BUCKET=call-recordings # Required. What the agent says first, per jurisdiction. Configuration so it can change without a deploy. DISCLOSURE_CONFIG=./config/disclosure.json # Required. Target gap from the caller finishing to the agent speaking. Breaches are logged. RESPONSE_BUDGET_MS=900 # Required. Cap, so a looping agent cannot produce a long silence and a large bill. MAX_TOOL_CALLS_PER_CALL=6 # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, because it is the evidence you were entitled to make it. CONSENT_RETENTION_DAYS=2555
# Thrumble · indie build One inbound number answered by a speech to model to speech loop that discloses what it is, obtains consent before recording, keeps the response latency inside the budget a conversation tolerates, hands off to a person when it should, 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 | | --- | --- | --- | | Telephony | A provider offering a media stream over a socket | you need the raw audio in and out, which not every telephony product exposes | | Speech in | Streaming transcription with interim results | waiting for a final transcript before thinking adds a second the caller feels as a pause | | Reasoning | A model with a short system prompt and one or two tools | an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable | | Speech out | Streaming synthesis started on the first sentence | generating the whole reply before speaking wastes the time you just spent saving | | Storage | PostgreSQL for calls and outcomes, with recordings encrypted and expiring | a call recording is personal data with a retention obligation attached | ## 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 media pipeline and the agent loop. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your confirmed consent and disclosure position** · an hour of advice, and it is the cheapest part of getting this wrong - Why: It determines the first sentence the agent says and whether you may record at all. Consent rules differ by jurisdiction and disclosure of automation is required in some places, so this decides the build rather than following it. - Get it: Identify every jurisdiction a caller might be in, take the strictest applicable rule, and confirm your position with someone qualified if this is commercial. Record what you concluded and when. - Verify: A written note names the jurisdictions, the rule applied and the date confirmed - [ ] **A telephony provider exposing a media stream** · a number is typically a few US dollars a month plus per-minute charges - Why: You need the raw audio both ways. Many products give you call control without the media, which cannot support this. - Get it: Choose a provider that offers a bidirectional media stream over a socket, and take a number in the country you will answer in. Confirm media streaming is available on your plan before building. - Verify: Audio arrives from a real call and audio you send is heard - [ ] **Streaming transcription, a model and streaming synthesis** · priced per minute and per token; a three minute call is typically a few cents and adds up quickly under load - Why: All three must stream. Any one of them batching adds a second the caller feels. - Get it: Providers for each, chosen for streaming support rather than accuracy alone. Set spend limits when you create the keys. - Verify: Each streams partial output rather than returning at the end - [ ] **A phone you can call from, in a quiet room and a noisy one** · free - Why: Latency, interruption and transcription accuracy all behave differently on a real call than in a local test, and background noise is the normal case. - Get it: Your own phone. Test from a car or a street as well as from a desk. - Verify: Ten real calls have been made and their timings recorded - [ ] **A retention policy per artefact** · free - Why: Recordings, transcripts, summaries and consent records may have different periods, and automatic deletion has to be built rather than remembered. - Get it: Write it before launch: what is kept, for how long, and why. Consent records usually outlive recordings because they are the evidence you were entitled to record. - 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: - Outbound campaigns and their separate legal surface. - Production telephony operations at volume. - Legal advice about your consent position. - The agent quality of a platform tuned on many thousands of calls. - hosted agent builder, knowledge bases, tools, and transfer destinations - outbound campaign dialer and contact workflows - managed phone numbers, concurrency, and telephony debugging - call logs, recordings, post-call extraction, and analytics - uptime, support, and multi-tenant workspace polish If one of those is essential to you, that is the reason to keep paying for Thrumble, and the README should say so rather than pretend.
# Build brief · Thrumble 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 voice agent that answers one phone number, does something useful and logs the outcome to replace Thrumble. 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 Settle the legal position before writing any code, because it decides what the agent must say in its first sentence. As checked in September 2026: in the United States 38 states and the District of Columbia require one party's consent to record while 12 require all parties, including California, Illinois, Florida, Massachusetts, Pennsylvania and Washington. Under the General Data Protection Regulation recording is data processing and consent must be freely given, specific, informed and unambiguous, with silence not counting; callers must be told who is recording, why, for how long and how to withdraw. In the United Kingdom the Lawful Business Practice regulations permit recording for training, quality and dispute purposes, and UK data protection still requires a lawful basis, a recorded announcement, encryption and a defined retention period with automatic deletion. Separately, disclosure that the caller is speaking to an automated system is now required in some jurisdictions, California among them, and the direction of travel elsewhere is the same. Build the agent so it discloses and obtains consent first, every time, and take the strictest rule that could apply rather than the one where you happen to sit. This is not legal advice; confirm your own position. ### Stack (fixed, do not substitute) - Telephony: A provider offering a media stream over a socket · you need the raw audio in and out, which not every telephony product exposes - Speech in: Streaming transcription with interim results · waiting for a final transcript before thinking adds a second the caller feels as a pause - Reasoning: A model with a short system prompt and one or two tools · an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable - Speech out: Streaming synthesis started on the first sentence · generating the whole reply before speaking wastes the time you just spent saving - Storage: PostgreSQL for calls and outcomes, with recordings encrypted and expiring · a call recording is personal data with a retention obligation attached ### Data model (create this before Phase 1) - calls(id, direction, from_hash, to_number, started_at, ended_at, outcome, disclosed_at, consent_state, jurisdiction) - turns(id, call_id, ordinal, speaker, text, started_ms, ended_ms, latency_ms) · speaker is caller or agent - recordings(call_id, storage_key, encrypted, expires_at, deleted_at) · nullable, because consent may be refused - tool_calls(id, call_id, turn_id, name, arguments_json, result_summary, duration_ms) - handoffs(call_id, at, reason, destination) - retention_runs(at, deleted_count) · proof that automatic deletion actually ran - A call with consent refused has no recording row. The absence is the record, and the transcript retention is a separate decision. ### Phase 1 · Answer, disclose, and ask before recording Build: Answer an inbound call and play a disclosure that states this is an automated system, who is calling on behalf of, that the call may be recorded, why, and how to object. Capture the response. Only start recording after affirmative consent, and continue the call without recording if it is refused. Trap: Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. Done when: An inbound call is answered and the disclosure plays before anything is written to disk. Refusing consent continues the call with no recording created, verified by checking storage. The consent state and the disclosure timestamp are recorded on the call. The disclosure text is configuration rather than code, so it can be changed per jurisdiction. Do not build yet: No transcription, reasoning or tools. ### Phase 2 · The latency budget, measured Build: Stream transcription with interim results, start the model as soon as the caller stops, and start speaking on the first sentence rather than the whole reply. Measure each stage and record the total per turn. Trap: Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. Done when: Median time from the caller finishing to the agent starting to speak is measured and recorded per turn. A test conversation of ten turns has no turn where the gap exceeds the configured budget without being logged as a breach. The pipeline overlaps stages rather than running them in sequence, verified by the per-stage timings. A slow tool call speaks a holding phrase rather than leaving silence. Do not build yet: No tools or handoff yet. ### Phase 3 · Interruption, which is how people talk Build: Detect caller speech while the agent is speaking, stop synthesis immediately, discard the unspoken remainder, and treat what they said as the next turn. Record what the agent had actually said aloud rather than what it intended to say. Trap: Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. Done when: Speaking over the agent stops its speech within a fraction of a second. The transcript records only what was actually spoken, marked as interrupted. The interrupted turn does not confuse the following turn's context. Interrupting during a holding phrase also works. Do not build yet: No tools or outcomes yet. ### Phase 4 · One tool, and knowing when to stop Build: Give the agent one tool that queries something real. Require it to say it does not know rather than guessing when the tool returns nothing. Cap the number of tool calls per turn and per call. Trap: An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. Done when: A question answerable from the tool is answered correctly with the tool call logged. A question the tool cannot answer produces an explicit statement that it does not know, verified with a question deliberately outside the data. Tool calls are capped per turn and per call. Every tool call is recorded with its arguments and duration. Do not build yet: No handoff or retention yet. ### Phase 5 · Handing off to a person Build: Transfer on explicit request, on repeated failure to understand, and on any topic you have listed as requiring a person. Pass a short summary to the receiving human. Record the handoff reason. Trap: An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. Done when: Asking for a person transfers on the first request without negotiation. Two consecutive misunderstandings trigger a transfer. The receiving person gets a summary of what was discussed. Every handoff records its reason. A transfer failing because nobody is available says so and offers a callback rather than looping. Do not build yet: Retention and deployment come last. ### Phase 6 · Retention, deletion and the record you must keep Build: Encrypt recordings at rest with a retention period per jurisdiction. Delete automatically and record that the run happened. Handle a request for a copy or for deletion. Keep the disclosure and consent record separately from the recording. Trap: Deleting the recording but keeping the transcript, or the reverse, satisfies neither the retention policy nor the caller's expectation. Decide what each retention period covers, apply it to every artefact, and keep the consent record longer than the recording because it is the evidence that you were entitled to make it. Done when: Recordings past their retention period are deleted automatically and each run records what it removed. A deletion request removes the recording, the transcript and derived summaries together, verified by searching afterwards. The consent record survives the recording deletion. Recordings are encrypted at rest, verified by inspecting storage. Do not build yet: Nothing after this. ### Out of scope (and why) - Outbound campaigns, which bring their own rules about calling times, do-not-call registers and consent to be contacted, and are a substantially larger legal surface than answering a number. - Production operations at volume: concurrency, carrier relationships, number provisioning across countries and the reliability people expect from a phone number. - Legal advice. The consent and disclosure position differs by jurisdiction and is changing, and confirming yours is a prerequisite rather than a detail. - The agent quality of a tuned commercial platform, which comes from many thousands of real calls you do not have. ### README must contain - The consent and disclosure position you confirmed, with the date and the jurisdictions covered - That recording begins only after affirmative consent and refusal is a supported path - The measured latency budget per stage and what happens when it is breached - That the transcript records what was spoken rather than what was generated - The retention period per artefact and proof that automatic deletion runs
# Agent instructions · Thrumble indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A provider offering a media stream over a socket, Streaming transcription with interim results, A model with a short system prompt and one or two tools, Streaming synthesis started on the first sentence, PostgreSQL for calls and outcomes, with recordings encrypted and expiring. 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 - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt.
# Build plan · Thrumble One inbound number answered by a speech to model to speech loop that discloses what it is, obtains consent before recording, keeps the response latency inside the budget a conversation tolerates, hands off to a person when it should, 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 · Answer, disclose, and ask before recording The number answers, says what it is, and does not record until it has permission. ### Steps 1. Answer and stream media A provider that gives you the audio stream. Confirm you can receive and send audio before anything else. Files: `src/telephony.ts` 2. Play disclosure and capture the response Automated system, who for, recording, why, how to object. Configuration, not a string in code. Files: `config/disclosure.json` 3. Gate recording on consent Nothing to disk before consent. Refusal is a supported path with the call continuing. Files: `src/consent.ts` ### Done when - [ ] Disclosure plays before anything reaches disk - [ ] Refusing consent continues the call with no recording - [ ] Consent state and disclosure time are recorded - [ ] Disclosure text is configurable per jurisdiction ### Watch out - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. ## Phase 2 · The latency budget, measured The agent replies fast enough that the caller does not think the line dropped. ### Steps 1. Stream transcription with interim results Use them to detect end of speech rather than waiting for a final result. Files: `src/stt.ts` 2. Start speaking on the first sentence Synthesis begins before generation finishes. This is where most of the perceived latency goes. Files: `src/tts.ts` 3. Record per-stage timings Transcription, generation, synthesis and total, per turn. You cannot tune what you have not measured. Files: `src/metrics.ts` ### Done when - [ ] Median response gap is measured per turn - [ ] Budget breaches are logged rather than invisible - [ ] Stages overlap rather than run in sequence - [ ] A slow tool speaks a holding phrase rather than going silent ### Watch out - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. ## Phase 3 · Interruption, which is how people talk A caller can interrupt the agent and the agent stops. ### Steps 1. Detect speech during playback Voice activity on the inbound stream while outbound audio is playing. Files: `src/barge.ts` 2. Stop and discard immediately Cancel synthesis and drop the queued audio. A half second of continued speech feels rude. Files: `src/tts.ts` 3. Log what was spoken, not generated Truncated at the interruption and marked. The transcript must match the conversation. Files: `src/turns.ts` ### Done when - [ ] Speaking over the agent stops it quickly - [ ] The transcript records only spoken text, marked interrupted - [ ] The following turn is not confused by the interruption - [ ] Interrupting a holding phrase works ### Watch out - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. ## Phase 4 · One tool, and knowing when to stop The agent can look one thing up, and it does not invent an answer when it cannot. ### Steps 1. Wire one real tool Something genuinely useful to a caller. One is enough to prove the pattern. Files: `src/tools.ts` 2. Constrain factual answers to tool output In the prompt and in a check. Test with a question outside the data. Files: `config/prompt.md` 3. Cap tool calls Per turn and per call. An agent looping on tools produces a long silence and a large bill. Files: `src/agent.ts` ### Done when - [ ] A tool-answerable question is answered with the call logged - [ ] A question outside the data produces an explicit unknown - [ ] Tool calls are capped per turn and per call - [ ] Every tool call records arguments and duration ### Watch out - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. ## Phase 5 · Handing off to a person The agent transfers when it should, and the caller does not have to repeat themselves. ### Steps 1. Transfer on first request No negotiation, no retention attempt. This is the behaviour that earns trust. Files: `src/handoff.ts` 2. Detect repeated failure Two consecutive low-confidence turns is a transfer condition rather than a third try. Files: `src/handoff.ts` 3. Pass a summary So the caller does not start again. This is most of the value of the handoff. Files: `src/summary.ts` ### Done when - [ ] An explicit request transfers immediately - [ ] Two misunderstandings trigger a transfer - [ ] The receiving person gets a summary - [ ] An unavailable destination offers a callback rather than looping ### Watch out - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. ## Not in this build - Outbound campaigns and their separate legal surface. - Production telephony operations at volume. - Legal advice about your consent position. - The agent quality of a platform tuned on many thousands of calls. ## After v1, if you want it - A second language, which mostly means a second disclosure and a different speech pipeline - Post-call summaries written into whatever system your team already uses - Sentiment or outcome tagging, once you have enough calls for it to mean anything
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Calls, turns and consent records. Personal data with retention obligations. DATABASE_URL=postgres://user:pass@localhost:5432/calls # Required. Encrypted at rest, with lifecycle expiry set as a second line of defence. RECORDINGS_BUCKET=call-recordings # Required. What the agent says first, per jurisdiction. Configuration so it can change without a deploy. DISCLOSURE_CONFIG=./config/disclosure.json # Required. Target gap from the caller finishing to the agent speaking. Breaches are logged. RESPONSE_BUDGET_MS=900 # Required. Cap, so a looping agent cannot produce a long silence and a large bill. MAX_TOOL_CALLS_PER_CALL=6 # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, because it is the evidence you were entitled to make it. CONSENT_RETENTION_DAYS=2555
# Thrumble · product brief ## Problem A single inbound receptionist agent is a credible weekend build with LiveKit Agents or Pipecat plus a SIP trunk. Thrumble as a product is larger: agent config UI, knowledge bases, tools, call transfer, outbound campaigns, phone numbers, transcripts, and production telephony. You can DIY the core conversation loop; you still own carriers, media ports, failed calls, and ops. ## Product outcome A phone number that answers, says what it is, records only with permission, replies fast enough to feel like a conversation, stops when interrupted, transfers when asked, 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 - public VM with TLS and correctly opened SIP / WebRTC / RTP ports - SIP trunk and phone number (e.g. Telnyx) - STT, LLM, and TTS API keys - consent and recording rules for your country before real traffic ## Explicit non-goals for v1 - Outbound campaigns and their separate legal surface. - Production telephony operations at volume. - Legal advice about your consent position. - The agent quality of a platform tuned on many thousands of calls. - hosted agent builder, knowledge bases, tools, and transfer destinations - outbound campaign dialer and contact workflows - managed phone numbers, concurrency, and telephony debugging - call logs, recordings, post-call extraction, and analytics - uptime, support, and multi-tenant workspace polish ## Success criteria - Disclosure is confirmed to play before anything reaches disk - Refusing consent is confirmed to leave no recording - Median response latency is within budget across ten real calls - Interruption is confirmed to stop speech and truncate the transcript - The retention job is confirmed to delete and to log the run
# Build brief · Thrumble 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 voice agent that answers one phone number, does something useful and logs the outcome to replace Thrumble. 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 Settle the legal position before writing any code, because it decides what the agent must say in its first sentence. As checked in September 2026: in the United States 38 states and the District of Columbia require one party's consent to record while 12 require all parties, including California, Illinois, Florida, Massachusetts, Pennsylvania and Washington. Under the General Data Protection Regulation recording is data processing and consent must be freely given, specific, informed and unambiguous, with silence not counting; callers must be told who is recording, why, for how long and how to withdraw. In the United Kingdom the Lawful Business Practice regulations permit recording for training, quality and dispute purposes, and UK data protection still requires a lawful basis, a recorded announcement, encryption and a defined retention period with automatic deletion. Separately, disclosure that the caller is speaking to an automated system is now required in some jurisdictions, California among them, and the direction of travel elsewhere is the same. Build the agent so it discloses and obtains consent first, every time, and take the strictest rule that could apply rather than the one where you happen to sit. This is not legal advice; confirm your own position. ### Stack (fixed, do not substitute) - Telephony: A provider offering a media stream over a socket · you need the raw audio in and out, which not every telephony product exposes - Speech in: Streaming transcription with interim results · waiting for a final transcript before thinking adds a second the caller feels as a pause - Reasoning: A model with a short system prompt and one or two tools · an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable - Speech out: Streaming synthesis started on the first sentence · generating the whole reply before speaking wastes the time you just spent saving - Storage: PostgreSQL for calls and outcomes, with recordings encrypted and expiring · a call recording is personal data with a retention obligation attached ### Data model (create this before Phase 1) - calls(id, direction, from_hash, to_number, started_at, ended_at, outcome, disclosed_at, consent_state, jurisdiction) - turns(id, call_id, ordinal, speaker, text, started_ms, ended_ms, latency_ms) · speaker is caller or agent - recordings(call_id, storage_key, encrypted, expires_at, deleted_at) · nullable, because consent may be refused - tool_calls(id, call_id, turn_id, name, arguments_json, result_summary, duration_ms) - handoffs(call_id, at, reason, destination) - retention_runs(at, deleted_count) · proof that automatic deletion actually ran - A call with consent refused has no recording row. The absence is the record, and the transcript retention is a separate decision. ### Phase 1 · Answer, disclose, and ask before recording Build: Answer an inbound call and play a disclosure that states this is an automated system, who is calling on behalf of, that the call may be recorded, why, and how to object. Capture the response. Only start recording after affirmative consent, and continue the call without recording if it is refused. Trap: Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. Done when: An inbound call is answered and the disclosure plays before anything is written to disk. Refusing consent continues the call with no recording created, verified by checking storage. The consent state and the disclosure timestamp are recorded on the call. The disclosure text is configuration rather than code, so it can be changed per jurisdiction. Do not build yet: No transcription, reasoning or tools. ### Phase 2 · The latency budget, measured Build: Stream transcription with interim results, start the model as soon as the caller stops, and start speaking on the first sentence rather than the whole reply. Measure each stage and record the total per turn. Trap: Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. Done when: Median time from the caller finishing to the agent starting to speak is measured and recorded per turn. A test conversation of ten turns has no turn where the gap exceeds the configured budget without being logged as a breach. The pipeline overlaps stages rather than running them in sequence, verified by the per-stage timings. A slow tool call speaks a holding phrase rather than leaving silence. Do not build yet: No tools or handoff yet. ### Phase 3 · Interruption, which is how people talk Build: Detect caller speech while the agent is speaking, stop synthesis immediately, discard the unspoken remainder, and treat what they said as the next turn. Record what the agent had actually said aloud rather than what it intended to say. Trap: Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. Done when: Speaking over the agent stops its speech within a fraction of a second. The transcript records only what was actually spoken, marked as interrupted. The interrupted turn does not confuse the following turn's context. Interrupting during a holding phrase also works. Do not build yet: No tools or outcomes yet. ### Phase 4 · One tool, and knowing when to stop Build: Give the agent one tool that queries something real. Require it to say it does not know rather than guessing when the tool returns nothing. Cap the number of tool calls per turn and per call. Trap: An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. Done when: A question answerable from the tool is answered correctly with the tool call logged. A question the tool cannot answer produces an explicit statement that it does not know, verified with a question deliberately outside the data. Tool calls are capped per turn and per call. Every tool call is recorded with its arguments and duration. Do not build yet: No handoff or retention yet. ### Phase 5 · Handing off to a person Build: Transfer on explicit request, on repeated failure to understand, and on any topic you have listed as requiring a person. Pass a short summary to the receiving human. Record the handoff reason. Trap: An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. Done when: Asking for a person transfers on the first request without negotiation. Two consecutive misunderstandings trigger a transfer. The receiving person gets a summary of what was discussed. Every handoff records its reason. A transfer failing because nobody is available says so and offers a callback rather than looping. Do not build yet: Retention and deployment come last. ### Phase 6 · Retention, deletion and the record you must keep Build: Encrypt recordings at rest with a retention period per jurisdiction. Delete automatically and record that the run happened. Handle a request for a copy or for deletion. Keep the disclosure and consent record separately from the recording. Trap: Deleting the recording but keeping the transcript, or the reverse, satisfies neither the retention policy nor the caller's expectation. Decide what each retention period covers, apply it to every artefact, and keep the consent record longer than the recording because it is the evidence that you were entitled to make it. Done when: Recordings past their retention period are deleted automatically and each run records what it removed. A deletion request removes the recording, the transcript and derived summaries together, verified by searching afterwards. The consent record survives the recording deletion. Recordings are encrypted at rest, verified by inspecting storage. Do not build yet: Nothing after this. ### Out of scope (and why) - Outbound campaigns, which bring their own rules about calling times, do-not-call registers and consent to be contacted, and are a substantially larger legal surface than answering a number. - Production operations at volume: concurrency, carrier relationships, number provisioning across countries and the reliability people expect from a phone number. - Legal advice. The consent and disclosure position differs by jurisdiction and is changing, and confirming yours is a prerequisite rather than a detail. - The agent quality of a tuned commercial platform, which comes from many thousands of real calls you do not have. ### README must contain - The consent and disclosure position you confirmed, with the date and the jurisdictions covered - That recording begins only after affirmative consent and refusal is a supported path - The measured latency budget per stage and what happens when it is breached - That the transcript records what was spoken rather than what was generated - The retention period per artefact and proof that automatic deletion runs
# Architecture · Thrumble ## Stack | Part | Choice | Why | | --- | --- | --- | | Telephony | A provider offering a media stream over a socket | you need the raw audio in and out, which not every telephony product exposes | | Speech in | Streaming transcription with interim results | waiting for a final transcript before thinking adds a second the caller feels as a pause | | Reasoning | A model with a short system prompt and one or two tools | an agent that can look one thing up is useful; an agent that can do ten is slow and unpredictable | | Speech out | Streaming synthesis started on the first sentence | generating the whole reply before speaking wastes the time you just spent saving | | Storage | PostgreSQL for calls and outcomes, with recordings encrypted and expiring | a call recording is personal data with a retention obligation attached | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Telephony | The media stream both ways. | Providers differ; requiring a raw media stream is the constraint that eliminates most of them. | | Consent | Disclosure, permission and what may be stored. | Not swappable for a policy people follow. It gates the recording path structurally. | | Pipeline | Overlapped transcription, generation and synthesis. | Any provider that streams. Sequential processing is the thing being avoided. | | Agent | Reasoning, tools and refusing to guess. | Any model. The constraint that factual answers come from tool output must survive. | | Retention | Automatic deletion and its proof. | 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 · Calls, turns and consent records. Personal data with retention obligations. - `RECORDINGS_BUCKET` · required · Encrypted at rest, with lifecycle expiry set as a second line of defence. - `DISCLOSURE_CONFIG` · required · What the agent says first, per jurisdiction. Configuration so it can change without a deploy. - `RESPONSE_BUDGET_MS` · required · Target gap from the caller finishing to the agent speaking. Breaches are logged. - `MAX_TOOL_CALLS_PER_CALL` · required · Cap, so a looping agent cannot produce a long silence and a large bill. - `RECORDING_RETENTION_DAYS` · required · Per your written policy. Deletion is automatic and logged. - `CONSENT_RETENTION_DAYS` · required · Usually longer than the recording, because it is the evidence you were entitled to make it. ## 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 · Thrumble product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A provider offering a media stream over a socket, Streaming transcription with interim results, A model with a short system prompt and one or two tools, Streaming synthesis started on the first sentence, PostgreSQL for calls and outcomes, with recordings encrypted and expiring. - 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 - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. - Deleting the recording but keeping the transcript, or the reverse, satisfies neither the retention policy nor the caller's expectation. Decide what each retention period covers, apply it to every artefact, and keep the consent record longer than the recording because it is the evidence that you were entitled to make it.
# Delivery milestones · Thrumble Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Answer, disclose, and ask before recording The number answers, says what it is, and does not record until it has permission. ### Steps 1. Answer and stream media A provider that gives you the audio stream. Confirm you can receive and send audio before anything else. Files: `src/telephony.ts` 2. Play disclosure and capture the response Automated system, who for, recording, why, how to object. Configuration, not a string in code. Files: `config/disclosure.json` 3. Gate recording on consent Nothing to disk before consent. Refusal is a supported path with the call continuing. Files: `src/consent.ts` ### Done when - [ ] Disclosure plays before anything reaches disk - [ ] Refusing consent continues the call with no recording - [ ] Consent state and disclosure time are recorded - [ ] Disclosure text is configurable per jurisdiction ### Watch out - Starting the recording at pickup and disclosing afterwards means you recorded before consent, which is the thing the rules exist to prevent. Buffer nothing to disk until consent is given, and make refusal a supported path rather than a failure, because a caller who says no still deserves service. ## M2 · The latency budget, measured The agent replies fast enough that the caller does not think the line dropped. ### Steps 1. Stream transcription with interim results Use them to detect end of speech rather than waiting for a final result. Files: `src/stt.ts` 2. Start speaking on the first sentence Synthesis begins before generation finishes. This is where most of the perceived latency goes. Files: `src/tts.ts` 3. Record per-stage timings Transcription, generation, synthesis and total, per turn. You cannot tune what you have not measured. Files: `src/metrics.ts` ### Done when - [ ] Median response gap is measured per turn - [ ] Budget breaches are logged rather than invisible - [ ] Stages overlap rather than run in sequence - [ ] A slow tool speaks a holding phrase rather than going silent ### Watch out - Waiting for a final transcript, then a full model response, then a full synthesis before speaking produces several seconds of silence, and callers hang up or start talking again. Overlap the stages: interim transcription while they speak, generation as soon as they stop, speech as soon as the first sentence exists. ## M3 · Interruption, which is how people talk A caller can interrupt the agent and the agent stops. ### Steps 1. Detect speech during playback Voice activity on the inbound stream while outbound audio is playing. Files: `src/barge.ts` 2. Stop and discard immediately Cancel synthesis and drop the queued audio. A half second of continued speech feels rude. Files: `src/tts.ts` 3. Log what was spoken, not generated Truncated at the interruption and marked. The transcript must match the conversation. Files: `src/turns.ts` ### Done when - [ ] Speaking over the agent stops it quickly - [ ] The transcript records only spoken text, marked interrupted - [ ] The following turn is not confused by the interruption - [ ] Interrupting a holding phrase works ### Watch out - Recording the full generated reply in the transcript when the caller interrupted halfway makes the transcript disagree with the conversation, and any summary built from it describes things that were never said. Log what was actually spoken, and mark it as interrupted. ## M4 · One tool, and knowing when to stop The agent can look one thing up, and it does not invent an answer when it cannot. ### Steps 1. Wire one real tool Something genuinely useful to a caller. One is enough to prove the pattern. Files: `src/tools.ts` 2. Constrain factual answers to tool output In the prompt and in a check. Test with a question outside the data. Files: `config/prompt.md` 3. Cap tool calls Per turn and per call. An agent looping on tools produces a long silence and a large bill. Files: `src/agent.ts` ### Done when - [ ] A tool-answerable question is answered with the call logged - [ ] A question outside the data produces an explicit unknown - [ ] Tool calls are capped per turn and per call - [ ] Every tool call records arguments and duration ### Watch out - An agent that answers a factual question without the tool because it sounds plausible will tell a caller something wrong in your organisation's voice, and they will act on it. Constrain the prompt to answer only from tool results for anything factual, and test with a question whose answer is not in the data. ## M5 · Handing off to a person The agent transfers when it should, and the caller does not have to repeat themselves. ### Steps 1. Transfer on first request No negotiation, no retention attempt. This is the behaviour that earns trust. Files: `src/handoff.ts` 2. Detect repeated failure Two consecutive low-confidence turns is a transfer condition rather than a third try. Files: `src/handoff.ts` 3. Pass a summary So the caller does not start again. This is most of the value of the handoff. Files: `src/summary.ts` ### Done when - [ ] An explicit request transfers immediately - [ ] Two misunderstandings trigger a transfer - [ ] The receiving person gets a summary - [ ] An unavailable destination offers a callback rather than looping ### Watch out - An agent that will not transfer traps the caller in a loop, which is the experience that makes people hate these systems. Transfer on the first explicit request without argument, and treat two consecutive failures to understand as a transfer condition rather than a third attempt. ## M6 · Retention, deletion and the record you must keep (production only) Recordings expire automatically and you can prove they did. ### Steps 1. Encrypt and set expiry per jurisdiction The period comes from your written policy, which comes from the position you settled in rule zero. Files: `src/retention.ts`, `docs/retention.md` 2. Delete automatically and log the run A policy that relies on someone remembering is not automatic deletion. Files: `src/retention.ts` 3. Handle access and deletion requests Covering every artefact. Keep the consent record, which is your evidence. Files: `src/requests.ts` ### Done when - [ ] Aged recordings are deleted automatically with a logged run - [ ] A deletion request removes recording, transcript and summaries - [ ] The consent record survives the recording deletion - [ ] Recordings are encrypted at rest ### Watch out - Deleting the recording but keeping the transcript, or the reverse, satisfies neither the retention policy nor the caller's expectation. Decide what each retention period covers, apply it to every artefact, and keep the consent record longer than the recording because it is the evidence that you were entitled to make it.
# Operations · Thrumble ## Backup Consent records and call outcomes are the durable material and are small. Recordings are large and expiring by design, so backing them up would work against the retention policy, which is worth stating explicitly. ## Restore Restore the database and confirm that consent records survived and that the retention job resumes. A restore that reinstates recordings past their expiry 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 Response latency percentiles, transfer rate, tool failure rate and retention run success. A rising transfer rate is information about the agent rather than a fault, and a missed retention run is a compliance problem rather than an outage. ## Incident checklist If the agent said something wrong, the transcript of what was actually spoken plus the tool calls is the record. If a recording was made without consent, that is the most serious failure here and the consent gate is what should have prevented it. 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 - [ ] Disclosure is confirmed to play before anything reaches disk - [ ] Refusing consent is confirmed to leave no recording - [ ] Median response latency is within budget across ten real calls - [ ] Interruption is confirmed to stop speech and truncate the transcript - [ ] The retention job is confirmed to delete and to log the run ## Launch constraint Do not market omitted Thrumble 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. Calls, turns and consent records. Personal data with retention obligations. DATABASE_URL=postgres://user:pass@localhost:5432/calls # Required. Encrypted at rest, with lifecycle expiry set as a second line of defence. RECORDINGS_BUCKET=call-recordings # Required. What the agent says first, per jurisdiction. Configuration so it can change without a deploy. DISCLOSURE_CONFIG=./config/disclosure.json # Required. Target gap from the caller finishing to the agent speaking. Breaches are logged. RESPONSE_BUDGET_MS=900 # Required. Cap, so a looping agent cannot produce a long silence and a large bill. MAX_TOOL_CALLS_PER_CALL=6 # Required. Per your written policy. Deletion is automatic and logged. RECORDING_RETENTION_DAYS=30 # Required. Usually longer than the recording, because it is the evidence you were entitled to make it. CONSENT_RETENTION_DAYS=2555
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay to go from a demo voice loop to dependable inbound and outbound calling without running SIP, media, agent workers, numbers, campaigns, and monitoring themselves.
xhosted agent builder, knowledge bases, tools, and transfer destinations
xoutbound campaign dialer and contact workflows
xmanaged phone numbers, concurrency, and telephony debugging
xcall logs, recordings, post-call extraction, and analytics
xuptime, support, and multi-tenant workspace polish
Vibecode Thrumble
Kinda. The core of Thrumble is buildable in a weekend with the prompt on this page, but there are real gaps: hosted agent builder, knowledge bases, tools, and transfer destinations, outbound campaign dialer and contact workflows. Read the honest list above before committing.
How much does Thrumble cost?
Thrumble costs about $99/month (Starter, checked 2026-08-05), which is $1188 per year.
What do I lose by replacing Thrumble?
Honestly: hosted agent builder, knowledge bases, tools, and transfer destinations; outbound campaign dialer and contact workflows; managed phone numbers, concurrency, and telephony debugging; call logs, recordings, post-call extraction, and analytics; uptime, support, and multi-tenant workspace polish. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Thrumble?
Yes: LiveKit Agents + SIP (Realtime agent framework; pair with LiveKit Server + SIP for a self-hosted phone stack.), Pipecat (Composable voice pipelines with telephony integrations; you still own deploy and carriers.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.