first-turn disclosure that doesn't sound like a disclosure
Every CallingScout call starts with a sentence the recipient is legally entitled to hear: that they are talking to an AI. That sentence is the boundary between a useful product and a regulator's exhibit A. The temptation, if you've ever shipped a voice agent before, is to drop the disclosure into the system prompt and trust the model to say it. Don't.
Models drift. They get clever. A future fine-tune decides the disclosure 'kills the rapport' and it gracefully omits the line. A clever prompt-injection attack convinces the agent that the disclosure has already been spoken. Whatever the failure mode, the result is a TCPA exposure that scales with your call volume.
Where the disclosure actually lives
We inject the disclosure at the pipeline factory — the function that wires Pipecat's frame processors into a runnable graph. Before any STT frame, before any LLM token, we queue a TTSSpeakFrame with the disclosure text and append_to_context=False. The bot speaks it on connect. The LLM never sees the line; it cannot remove it; the recipient hears it before the conversation begins.
# pipeline.py — abridged
if first_message:
await task.queue_frames([
TTSSpeakFrame(text=first_message, append_to_context=False),
])append_to_context=False is critical. If the disclosure ends up in the LLM's running context, the model will sometimes 'reference' it on later turns ('as I mentioned, I'm an AI'), which sounds robotic and uncanny. Keeping it out of context lets the disclosure exist purely as audio.
The hard part isn't the injection. It's the warmth.
We A/B'd six disclosure phrasings. The clinical one ("This is an AI calling on behalf of...") had a 14% drop-off in the first six seconds. The casual one ("Hey, just a heads up — I'm an AI calling for...") had a 3% drop-off. Same legal weight, very different first impression. The drop-off correlated almost perfectly with whether the recipient stayed on long enough to hear the offer.
The line we landed on, for the public preview at /play: "You're talking to an AI agent built by CallingScout. This is a 10-minute browser preview. Nothing is stored." Three sentences, conversational cadence, and the second sentence sets the expectation so the recipient knows what they're walking into.
What changed in the model after we moved out
Pulling the disclosure out of the system prompt freed roughly 200 tokens. We spent them on something that actually needs to live in the LLM's worldview: the agent's character. Tone notes, refusal patterns, brand-voice instructions. Things that genuinely require model intelligence to apply, instead of things the LLM should never have been responsible for in the first place.
The general principle
If failure to follow an instruction causes data loss, legal liability, or budget overrun, that instruction must be enforced in code, not in a prompt. Prompt instructions are for behavior that degrades gracefully if skipped. Disclosure isn't graceful. Neither is the DNC check, the calling-hour gate, or the budget cap. Every one of those lives in a function. Every one of them is the only path. There is no 'skip compliance' flag in the dashboard because there is no skip compliance code path in the runtime.