Build Thrumble

KINDAreplaces $99/mosaves $1,188/yrback to the verdict

0%0 of 21 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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-daythe files for this build are in the project pack

TelephonyA provider offering a media stream over a socketSpeech inStreaming transcription with interim resultsReasoningA model with a short system prompt and one or two toolsSpeech outStreaming synthesis started on the first sentenceStoragePostgreSQL for calls and outcomes, with recordings encrypted and expiring

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the media pipeline and the agent loop.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. decidean 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

  3. API keya 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

  4. API keypriced 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

  5. have readyfree

    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

  6. decidefree

    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

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- 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.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://user:pass@localhost:5432/callsCalls, turns and consent records. Personal data with retention obligations.
RECORDINGS_BUCKETrequiredcall-recordingsEncrypted at rest, with lifecycle expiry set as a second line of defence.
DISCLOSURE_CONFIGrequired./config/disclosure.jsonWhat the agent says first, per jurisdiction. Configuration so it can change without a deploy.
RESPONSE_BUDGET_MSrequired900Target gap from the caller finishing to the agent speaking. Breaches are logged.
MAX_TOOL_CALLS_PER_CALLrequired6Cap, so a looping agent cannot produce a long silence and a large bill.
RECORDING_RETENTION_DAYSrequired30Per your written policy. Deletion is automatic and logged.
CONSENT_RETENTION_DAYSrequired2555Usually longer than the recording, because it is the evidence you were entitled to make it.

The build, in order

  1. Answer, disclose, and ask before recording

    The number answers, says what it is, and does not record until it has permission.

    1. A provider that gives you the audio stream. Confirm you can receive and send audio before anything else.

      Files src/telephony.ts

    2. Automated system, who for, recording, why, how to object. Configuration, not a string in code.

      Files config/disclosure.json

    3. Nothing to disk before consent. Refusal is a supported path with the call continuing.

      Files src/consent.ts

    done when · tick each as it passes
    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.
  2. The latency budget, measured

    The agent replies fast enough that the caller does not think the line dropped.

    1. Use them to detect end of speech rather than waiting for a final result.

      Files src/stt.ts

    2. Synthesis begins before generation finishes. This is where most of the perceived latency goes.

      Files src/tts.ts

    3. Transcription, generation, synthesis and total, per turn. You cannot tune what you have not measured.

      Files src/metrics.ts

    done when · tick each as it passes
    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.
  3. Interruption, which is how people talk

    A caller can interrupt the agent and the agent stops.

    1. Voice activity on the inbound stream while outbound audio is playing.

      Files src/barge.ts

    2. Cancel synthesis and drop the queued audio. A half second of continued speech feels rude.

      Files src/tts.ts

    3. Truncated at the interruption and marked. The transcript must match the conversation.

      Files src/turns.ts

    done when · tick each as it passes
    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.
  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.

    1. Something genuinely useful to a caller. One is enough to prove the pattern.

      Files src/tools.ts

    2. In the prompt and in a check. Test with a question outside the data.

      Files config/prompt.md

    3. Per turn and per call. An agent looping on tools produces a long silence and a large bill.

      Files src/agent.ts

    done when · tick each as it passes
    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.
  5. Handing off to a person

    The agent transfers when it should, and the caller does not have to repeat themselves.

    1. No negotiation, no retention attempt. This is the behaviour that earns trust.

      Files src/handoff.ts

    2. Two consecutive low-confidence turns is a transfer condition rather than a third try.

      Files src/handoff.ts

    3. So the caller does not start again. This is most of the value of the handoff.

      Files src/summary.ts

    done when · tick each as it passes
    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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more voice ai.