<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tahir Almas</title>
    <description>The latest articles on DEV Community by Tahir Almas (@tahiralmas).</description>
    <link>https://dev.to/tahiralmas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3911307%2F77d2f227-47d5-42ea-87e2-5d4abeb9b2cd.png</url>
      <title>DEV Community: Tahir Almas</title>
      <link>https://dev.to/tahiralmas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tahiralmas"/>
    <language>en</language>
    <item>
      <title>How to Benchmark Your Own Telephony Voice Agent in Ten Minutes</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sun, 06 Sep 2026 10:31:27 +0000</pubDate>
      <link>https://dev.to/tahiralmas/how-to-benchmark-your-own-telephony-voice-agent-in-ten-minutes-12kl</link>
      <guid>https://dev.to/tahiralmas/how-to-benchmark-your-own-telephony-voice-agent-in-ten-minutes-12kl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictinnovations.com/benchmark-telephony-voice-agent-guide/" rel="noopener noreferrer"&gt;ictinnovations.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can benchmark a telephony voice agent in about ten minutes without touching its code. Install the harness, start your agent, point the harness at its AudioSocket port, and read four numbers. This guide covers what each command does, what the output means, and which part of your pipeline to open when a number comes back wrong.&lt;/p&gt;

&lt;p&gt;The tool is &lt;a href="https://github.com/ictinnovations/telephony-voice-agent-benchmark" rel="noopener noreferrer"&gt;tvbench&lt;/a&gt;, MIT licensed and installable from PyPI. If you want the reasoning behind the design first, we wrote that up separately in &lt;a href="https://ictinnovations.com/caller-side-voice-agent-benchmark-open-source/" rel="noopener noreferrer"&gt;why we measure from the caller's seat&lt;/a&gt;. This one is the practical half.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need before you start
&lt;/h2&gt;

&lt;p&gt;A Linux machine and a voice agent that accepts AudioSocket connections. That's the whole list. You do not need Asterisk running, you do not need a SIP trunk, and you do not need to instrument your agent, because the harness plays the role Asterisk would play.&lt;/p&gt;

&lt;p&gt;The Linux requirement is not a preference. On Windows, &lt;code&gt;asyncio.sleep(0.02)&lt;/code&gt; takes roughly 31 ms rather than 20, so the harness cannot pace caller audio accurately and every number it produces drifts. The tool runs there and warns you with a realtime ratio well under 1.0, but do not report those figures anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four steps
&lt;/h2&gt;

&lt;p&gt;The whole run. Measure the reference agent on the same machine first so you know what your hardware can do.&lt;/p&gt;

&lt;p&gt;Install it into a virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="sb"&gt;`&lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;tvbench&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start your agent however you normally start it, listening on its usual port. Then run the two scenarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tvbench run &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 9092 &lt;span class="nt"&gt;--scenario&lt;/span&gt; greeting &lt;span class="nt"&gt;--runs&lt;/span&gt; 5
tvbench run &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 9092 &lt;span class="nt"&gt;--scenario&lt;/span&gt; bargein &lt;span class="nt"&gt;--runs&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five runs is the default for a reason. Timing measurements on a shared machine vary, and a single run tells you very little. The report gives you a median and a range so you can see whether a number is stable or whether you caught one unlucky scheduling moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure something known-good first
&lt;/h2&gt;

&lt;p&gt;Before you draw any conclusion about your own agent, find out what your machine is capable of. The tool ships a reference agent that paces correctly, and running against it establishes your floor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="sb"&gt;`&lt;/span&gt;tvbench reference &lt;span class="nt"&gt;--mode&lt;/span&gt; paced &lt;span class="nt"&gt;--port&lt;/span&gt; 9092&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a Linux box with eight cores we get a worst burst of 2 frames and a realtime ratio of 1.00 from that. If your machine gives you 1.15 on the reference agent, then 1.15 from your own agent means nothing is wrong with your agent. This step takes thirty seconds and it prevents the most common way people misread a benchmark.&lt;/p&gt;

&lt;p&gt;The same command takes &lt;code&gt;--mode burst&lt;/code&gt;, &lt;code&gt;--mode gappy&lt;/code&gt; and &lt;code&gt;--mode deaf&lt;/code&gt;. Running each of those once is worth the two minutes, because you get to see the shape of each failure before you go looking for it in your own numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  If your agent has a call allowlist
&lt;/h2&gt;

&lt;p&gt;Plenty of production agents refuse connections for calls they have not been told about. Ours does. That's correct behaviour and you should not turn it off to run a test, because then you are measuring a code path that no real call takes.&lt;/p&gt;

&lt;p&gt;Pass the registration endpoint instead. The harness posts the call id to it before connecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tvbench run &lt;span class="nt"&gt;--port&lt;/span&gt; 9092 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--register-url&lt;/span&gt; http://127.0.0.1:9091/register &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scenario&lt;/span&gt; bargein &lt;span class="nt"&gt;--runs&lt;/span&gt; 5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--label&lt;/span&gt; &lt;span class="s2"&gt;"my-agent 1.2.0"&lt;/span&gt; &lt;span class="nt"&gt;--out&lt;/span&gt; results/my-agent.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--label&lt;/code&gt; is free text that lands in the output file, and it is worth filling in properly. Six months later, a result file that says which version and which machine produced it is evidence. One that says nothing is a curiosity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the output
&lt;/h2&gt;

&lt;p&gt;Each run writes a JSON file containing the summary metrics and the raw arrival timestamps for every frame. The raw data is in there deliberately, so that anyone who disagrees with how a metric is computed can recompute it from the same recording rather than argue about method.&lt;/p&gt;

&lt;p&gt;Four things are worth your attention, and each one points at a fairly small set of causes.&lt;/p&gt;

&lt;p&gt;Work down whichever column moved. The metrics are designed to move independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Realtime ratio well above 1
&lt;/h3&gt;

&lt;p&gt;This is the common one and the expensive one. A ratio of 2764 means you delivered forty-six minutes of audio in a second of wall clock, which is what happens when synthesis output goes straight to the socket with no clock in the write path. The far end keeps a few frames and discards the rest, so the caller hears a fragment of the end of the sentence.&lt;/p&gt;

&lt;p&gt;The fix is a deadline per frame. Write 320 bytes, add 20 ms to the deadline, sleep until it, repeat. The part people get wrong is what to do after a stall: if synthesis blocks for half a second and you compute the next deadline from where you should have been, your writer bursts to catch up and you're back to dropping audio. Snap the deadline to the current time instead. Lateness cannot be recovered, but burst loss can be avoided.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ratio near 1 with occasional spikes
&lt;/h3&gt;

&lt;p&gt;Two usual suspects. The first is Nagle's algorithm coalescing your small writes, which is exactly the pattern one 320-byte write every 20 ms triggers. Turn it off on the accepted socket and the spikes often disappear.&lt;/p&gt;

&lt;p&gt;The second is blocking work on the thread that owns the frame clock. Speech synthesis, model calls and file reads all belong somewhere else. A pacer that gets descheduled for 80 ms produces a burst when it wakes up, and it will look intermittent because it depends on what else the box is doing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Worst hole in the hundreds of milliseconds
&lt;/h3&gt;

&lt;p&gt;Pacing is fine and the gap is inside the agent's own turn, which points at how you chunk text for synthesis. If you render one sentence, play it, then go quiet while the next one renders, the caller hears that silence and reads it as the agent having finished.&lt;/p&gt;

&lt;p&gt;Split the model's streamed text on sentence boundaries and start rendering the next chunk while the current one is still playing out. In our own agent we also fall back to clause boundaries once a sentence runs past about 120 characters, because a long sentence with no full stop otherwise stalls the pipeline waiting for punctuation that never arrives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Barge-in cut over roughly 300 milliseconds
&lt;/h3&gt;

&lt;p&gt;Anything above a few hundred milliseconds is noticeable and anything in seconds makes the agent feel deaf. The number covers four stages: detecting the caller's speech, deciding the turn is interrupted, dropping queued audio, and whatever was already handed to the far end and cannot be recalled.&lt;/p&gt;

&lt;p&gt;Most implementations stop producing new audio and let the existing queue drain, which is the wrong instinct. Discard the queue. Cancel in-flight synthesis rather than letting it finish and throw the result away. And keep the queue shallow in the first place, because its depth is the floor on how fast you can possibly stop talking.&lt;/p&gt;

&lt;h3&gt;
  
  
  First audible over a second
&lt;/h3&gt;

&lt;p&gt;If everything else looks correct and only the opening is slow, you are generating the greeting on demand. It's fixed text, so there is nothing to generate live. Render it once at startup, keep the audio, and play it from memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers cannot tell you
&lt;/h2&gt;

&lt;p&gt;The caller signal is band-limited noise modulated at roughly syllable rate. Voice activity detectors treat it as speech, which is all a timing test requires, but it is not speech. tvbench will not tell you anything about transcription accuracy and does not try. If you want a word error rate, use a real speech corpus and a tool built for that job.&lt;/p&gt;

&lt;p&gt;Loopback is also not a network. Every number you get on one machine is a lower bound, because you have removed jitter, loss and the variable delay of a real path. If you care about behaviour under those conditions, add them yourself with a traffic shaper and rerun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it in continuous integration
&lt;/h2&gt;

&lt;p&gt;The most useful thing you can do with this is stop treating it as a one-off. Timing regressions are easy to introduce and almost impossible to notice by ear, because the failure is usually intermittent and always sounds like something else.&lt;/p&gt;

&lt;p&gt;Start your agent, run both scenarios, and fail the build if the realtime ratio leaves a sensible band or the barge-in cut exceeds your threshold. Pick thresholds from your own reference run rather than from ours, since they are hardware dependent. Our benchmark repository does the same thing to itself: eight assertions run on every commit, checking both that each injected defect still moves its metric and that the correct reference agent still passes cleanly. The second half matters more than it sounds, because a tool that flags healthy agents gets switched off within a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing a result
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;results/&lt;/code&gt; directory in the repository takes pull requests. If you maintain a voice agent and you think our figures are wrong, unflattering, or measured badly, a run of your own with the command line attached settles it faster than a discussion will.&lt;/p&gt;

&lt;p&gt;Releases are archived on Zenodo with a DOI, so cite the version you ran. A concept DOI always resolves to the newest release, and a version DOI pins the exact one your number came from, which keeps a published result meaningful after the tool moves on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need Asterisk installed to run the benchmark?
&lt;/h3&gt;

&lt;p&gt;No. The harness plays Asterisk's part in the conversation, so it connects to your agent directly over AudioSocket. No PBX, no SIP trunk and no dialplan are involved. That is also why a run takes seconds rather than requiring a lab.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I benchmark an agent written in Node, Go or Java?
&lt;/h3&gt;

&lt;p&gt;Yes. The harness only speaks the AudioSocket protocol and knows nothing about what is behind the socket. If your agent accepts the connection and sends audio frames back, it can be measured, whatever it is written in.&lt;/p&gt;

&lt;h3&gt;
  
  
  My realtime ratio is 0.85. Is that bad?
&lt;/h3&gt;

&lt;p&gt;It means you delivered audio slower than real time, so the caller heard gaps. Check the worst hole figure from the same run: if the hole is large, you have a synthesis stall rather than a pacing bug. If pacing is even but consistently slow, your frame clock is drifting, usually because something blocking shares its thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  What barge-in figure should I aim for?
&lt;/h3&gt;

&lt;p&gt;Under about 200 ms feels natural to a caller. Our reference agent that discards queued audio on interruption measures 97 ms, and the same agent with interruption handling removed measures 4,974 ms, so the range between good and bad here is enormous. Set your own threshold from a reference run on your own hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why five runs instead of one?
&lt;/h3&gt;

&lt;p&gt;A single timing measurement on a shared machine is an anecdote. Five gives you a median and a range, which is enough to tell a real regression from one unlucky scheduling moment. Report the median and say how many runs it came from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this work with chan_websocket instead of AudioSocket?
&lt;/h3&gt;

&lt;p&gt;Not currently. The harness implements AudioSocket only. On the newer WebSocket channel driver Asterisk owns the playout clock and re-times frames for you, so several of the pacing failures this tool catches are not reachable there. We wrote about that difference in our &lt;a href="https://ictinnovations.com/asterisk-ai-voice-agent-lessons-audiosocket-barge-in/" rel="noopener noreferrer"&gt;voice agent engineering log&lt;/a&gt;, alongside the rest of our &lt;a href="https://ictinnovations.com/projects/" rel="noopener noreferrer"&gt;open source telephony projects&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>We Built an Open Source Benchmark That Measures a Voice Agent From the Caller's Seat</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sun, 06 Sep 2026 10:28:11 +0000</pubDate>
      <link>https://dev.to/tahiralmas/we-built-an-open-source-benchmark-that-measures-a-voice-agent-from-the-callers-seat-58db</link>
      <guid>https://dev.to/tahiralmas/we-built-an-open-source-benchmark-that-measures-a-voice-agent-from-the-callers-seat-58db</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictinnovations.com/caller-side-voice-agent-benchmark-open-source/" rel="noopener noreferrer"&gt;ictinnovations.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A telephony voice agent can produce perfect audio and still be unusable on a phone call. What decides it is timing: whether frames arrive evenly, whether the sentence has holes in it, and how fast the agent shuts up when you talk over it. None of that is visible from inside the agent, so we built an open source benchmark that measures it from the caller's end of the wire.&lt;/p&gt;

&lt;p&gt;The tool is called &lt;a href="https://github.com/ictinnovations/telephony-voice-agent-benchmark" rel="noopener noreferrer"&gt;tvbench&lt;/a&gt;. It's MIT licensed, it runs against any agent that speaks AudioSocket, and every number it reports comes from arrival timestamps rather than from anything the agent says about itself. This article is about why we ended up there, because the reasoning matters more than the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that started it
&lt;/h2&gt;

&lt;p&gt;We had an Asterisk voice agent that worked on our desks and sounded broken on real calls. Callers reported hearing the tail end of sentences. Sometimes they heard nothing at all and then a fragment. Our logs were clean. Every turn showed the right text, the right byte count, the right duration. Nothing anywhere said a word had gone missing.&lt;/p&gt;

&lt;p&gt;The fault turned out to be a single line of synthesis code that wrote a whole utterance to the socket in one call. That is a perfectly reasonable thing to write if you're used to files or HTTP. On a call it destroys the audio, because &lt;code&gt;app_audiosocket&lt;/code&gt; hands every frame it receives straight to the channel and does not buffer on your behalf. Two and a half seconds of speech delivered in eight milliseconds means the jitter buffer at the far end keeps a handful of frames and drops the rest.&lt;/p&gt;

&lt;p&gt;We wrote up that fix and several others in our &lt;a href="https://ictinnovations.com/asterisk-ai-voice-agent-lessons-audiosocket-barge-in/" rel="noopener noreferrer"&gt;engineering log for the voice agent&lt;/a&gt;. What stayed with us afterwards wasn't the bug. It was how long it took to find something that obvious, and the reason it took so long: we had no instrument pointed at the right place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Byte counts are not evidence
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable thing about that failure. Every measurement we had said the turn was fine, and every one of them was correct. The agent really did produce 120 frames. It really did write all of them. The duration really was 2.4 seconds of audio. The problem was that those facts have nothing to do with what a caller hears.&lt;/p&gt;

&lt;p&gt;A phone call is a real-time system with a fixed clock. Audio is worth something only if it arrives at the rate it will be played. Deliver it faster and the surplus is thrown away. Deliver it slower and the caller hears silence in the middle of a word. Both failures are invisible to anything that counts bytes, and both are trivially visible if you record when each frame showed up.&lt;/p&gt;

&lt;p&gt;So we stopped instrumenting the agent and started instrumenting the socket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standing where Asterisk stands
&lt;/h2&gt;

&lt;p&gt;tvbench takes Asterisk's place in the conversation. It opens the AudioSocket connection, paces caller audio at one 320-byte frame every 20 ms on a deadline it re-clamps after every frame, and timestamps every frame that comes back. It knows nothing about the agent behind the socket and needs to know nothing, which is why it works against agents written in any language.&lt;/p&gt;

&lt;p&gt;The harness only speaks the protocol. Everything it reports is derived from when audio arrived.&lt;/p&gt;

&lt;p&gt;That position is the whole design. An agent reporting its own latency is reporting when it decided to do something, not when the result reached anyone. The gap between those two things is where the interesting failures live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four numbers, and what each is for
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Opening latency&lt;/strong&gt; is how long the caller waited before hearing anything. Callers are unforgiving about this one and it is the easiest to measure honestly, because an agent's greeting runs without a transcript or a model round trip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pacing&lt;/strong&gt; is two numbers that answer the same question from different sides. The worst burst is the most 20 ms frames that landed inside a single 20 ms window. The realtime ratio is audio delivered divided by wall clock. For both of them, one is the correct answer. Anything much above one means you're overrunning the far end's buffer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuity&lt;/strong&gt; is the longest hole inside the agent's own speech, measured between its first and last audible frame. This catches an agent that synthesises one sentence at a time and stalls between them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Barge-in cut&lt;/strong&gt; is how long the agent kept talking after the caller started. It's timed from the first frame of caller speech the harness puts on the wire to the last frame of agent audio that comes back, so it covers voice detection, the decision to stop, dropping whatever was queued, and anything already handed over that can't be recalled.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure that scores well
&lt;/h2&gt;

&lt;p&gt;Put the correct agent and the bursting agent side by side and you get a result that should make anyone nervous about single-number benchmarks.&lt;/p&gt;

&lt;p&gt;Identical audio, two delivery patterns. The buffer keeps a few frames from the burst and discards the rest.&lt;/p&gt;

&lt;p&gt;The bursting agent scores a &lt;em&gt;better&lt;/em&gt; continuity number than the correct one. Zero milliseconds of hole against 21. It has to, because frames that arrive at the same instant leave no gaps between them. If you ranked agents on continuity alone, the broken one would win.&lt;/p&gt;

&lt;p&gt;That's not a flaw we papered over. It's the reason the pacing columns exist, and it's the argument against reducing a voice agent to one score. Timing failures pull the metrics in different directions and you need to read them together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the test
&lt;/h2&gt;

&lt;p&gt;A benchmark that has only ever seen well-behaved agents proves nothing. You have no idea whether it would notice a fault, because it has never been shown one.&lt;/p&gt;

&lt;p&gt;So tvbench ships a reference agent with switchable defects, and a validation script that runs the whole matrix. Each defect is designed to move exactly one metric. If a defect stops moving its metric, the build fails, because that means the tool has gone blind to something it's supposed to catch.&lt;/p&gt;

&lt;p&gt;Five runs per row on Linux. Read the columns down: each defect moves its own metric and leaves the rest near the correct row.&lt;/p&gt;

&lt;p&gt;Two rows are worth dwelling on. The agent that drops queued audio on interruption stops in 97 ms. The one that ignores the caller runs 4,974 ms. That's a factor of fifty on the single number a caller actually notices, and no amount of model quality compensates for it.&lt;/p&gt;

&lt;p&gt;The other row worth dwelling on is the burst one, for the reason above. We published it because it undercuts a simple reading of our own tool, and a benchmark you cannot argue with is not measuring anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two scenarios, and why only those two
&lt;/h2&gt;

&lt;p&gt;We were tempted to build a scenario library. We ended up with two, because two is what we could measure honestly.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;greeting&lt;/strong&gt; run answers the call and stays silent. That sounds like a weak test until you notice that an agent's opening line is the one part of the pipeline that runs without a transcript and without a model round trip. Nothing upstream can vary, so anything wrong in the numbers is a delivery problem and nothing else. It's the cleanest look at pacing available, and it happens to match what a real caller does at that moment, which is wait.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;barge-in&lt;/strong&gt; run lets the agent get about a second into its turn, then talks over it. This is the scenario that separates demos from products. An agent that can't be interrupted is exhausting to talk to, and the delay is cumulative, because every stage adds to it and the caller experiences the total.&lt;/p&gt;

&lt;p&gt;The clock covers four stages. A number measured inside the agent usually covers the first two.&lt;/p&gt;

&lt;p&gt;We time the whole window on purpose. Voice detection has to decide the caller is speaking and not coughing. The turn has to be marked interrupted and every producer told to stop. Queued audio has to be dropped instead of played out. Whatever was already handed to the far end is simply gone, which means your queue depth sets a floor on how fast you can possibly stop. A figure covering only the first two stages will look excellent and mislead you, because stages three and four are where most of the delay lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it against agents that expect a real call
&lt;/h2&gt;

&lt;p&gt;Most production agents won't accept an anonymous connection. Ours doesn't. It expects the call to have been registered first, which is sensible behaviour and awkward for a benchmark.&lt;/p&gt;

&lt;p&gt;Rather than ask people to weaken their own security to run a test, the harness takes a registration URL and posts the call id to it before connecting. That keeps the allowlist working and keeps the measurement honest, because it exercises the same path a real call takes instead of a special test path built alongside it. If your agent has no allowlist, you leave the flag off.&lt;/p&gt;

&lt;p&gt;Every run writes a JSON file with the raw arrival timestamps, not just the summary. We wanted the underlying data in the artifact so anyone who thinks a metric is computed wrongly can recompute it without rerunning anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it will not tell you
&lt;/h2&gt;

&lt;p&gt;The caller signal is band-limited noise, amplitude modulated at roughly syllable rate and generated from a fixed seed. Voice activity detectors accept it as speech, which is all a timing test needs.&lt;/p&gt;

&lt;p&gt;It is not speech. tvbench cannot tell you anything about transcription accuracy and doesn't try, because a word error rate computed from synthetic noise would be a fabrication. Use a real speech corpus for that question and use this for the timing one.&lt;/p&gt;

&lt;p&gt;Two more limits we state in the README rather than bury. Run it on Linux, because &lt;code&gt;asyncio.sleep(0.02)&lt;/code&gt; takes about 31 ms on Windows and the harness can't pace accurately there. And loopback is not a network, so if you want to know how your agent behaves under jitter and loss, you have to add jitter and loss yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we made it open source
&lt;/h2&gt;

&lt;p&gt;We build &lt;a href="https://ictinnovations.com/projects/" rel="noopener noreferrer"&gt;open source VoIP software&lt;/a&gt; for service providers, and this is a measurement problem the whole field has rather than a problem we have. Every team wiring a model into a call has to solve frame pacing and barge-in, and most of them are doing it without an instrument, the same way we were.&lt;/p&gt;

&lt;p&gt;There's a self-interested reason too. If our own agent's figures are wrong, the fastest way for anyone to prove it is to run the same tool and post a different result. The &lt;code&gt;results/&lt;/code&gt; directory takes pull requests for exactly that. Releases are archived on Zenodo with a DOI, so a number measured against a given version stays reproducible even if the repository moves.&lt;/p&gt;

&lt;p&gt;Disagreement backed by a reproducible number is the point of the exercise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does tvbench only work with Asterisk voice agents?
&lt;/h3&gt;

&lt;p&gt;It works with anything that accepts an AudioSocket connection. The harness speaks that protocol and nothing else, so it doesn't know or care whether Asterisk, a custom gateway, or nothing at all sits behind the socket. The agent can be written in any language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why measure from outside the agent instead of adding timing logs inside it?
&lt;/h3&gt;

&lt;p&gt;Internal timing tells you when the agent decided to send audio. It can't tell you when that audio arrived, whether a jitter buffer dropped it, or how much was already committed when the caller interrupted. Those are the parts that go wrong, and they're only visible from the far end.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a good realtime ratio?
&lt;/h3&gt;

&lt;p&gt;One. A ratio of one means you delivered a second of audio in a second of wall clock, which is what a phone call needs. Well above one means you're bursting and the far end is discarding audio. Below one means you're delivering slower than real time and the caller hears gaps.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does a benchmark run take?
&lt;/h3&gt;

&lt;p&gt;A single scenario run takes a few seconds. The default is five runs so you get a median and a range rather than an anecdote, and the full validation matrix across every reference defect finishes in about a minute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use these numbers to compare commercial voice agent platforms?
&lt;/h3&gt;

&lt;p&gt;Only where the platform exposes an AudioSocket endpoint you can point the harness at. Where it does, the comparison is fair, because the harness treats every agent identically and measures the same thing from the same place. Publish the command line alongside any number you report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where does this fit with your other open source telephony work?
&lt;/h3&gt;

&lt;p&gt;It came out of building &lt;a href="https://ictinnovations.com/asterisk-ai-voice-agent-audiosocket-open-source/" rel="noopener noreferrer"&gt;our open source Asterisk AI voice agent&lt;/a&gt; and the AudioSocket library under it. The benchmark is the instrument we wish we'd had first, and it now runs in that project's own test cycle.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Most Fax Breaches Are a Wrong Number. Most Fines Are a Missing Risk Analysis</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sun, 06 Sep 2026 07:19:06 +0000</pubDate>
      <link>https://dev.to/tahiralmas/most-fax-breaches-are-a-wrong-number-most-fines-are-a-missing-risk-analysis-1kel</link>
      <guid>https://dev.to/tahiralmas/most-fax-breaches-are-a-wrong-number-most-fines-are-a-missing-risk-analysis-1kel</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ictfax.com/fax-server-software-wrong-number-risk-analysis/" rel="noopener noreferrer"&gt;ictfax.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two things are true about fax and HIPAA, and they point in different directions. Almost no fax incident starts with someone intercepting a transmission. It starts with a wrong number. And almost no enforcement action turns on the wrong number itself. It turns on the risk analysis nobody wrote.&lt;/p&gt;

&lt;h2&gt;
  
  
  The threat you prepared for is not the one that happens
&lt;/h2&gt;

&lt;p&gt;Ask most teams how they secure fax and you'll hear about encryption. TLS on the transport, AES at rest, a signed business associate agreement with whoever carries the traffic. Good answers, all of them, and we've written the full checklist in &lt;a href="https://www.ictfax.com/hipaa-compliant-fax-2026-encryption-baa-audit-trails/" rel="noopener noreferrer"&gt;HIPAA-compliant fax in 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now look at what actually goes wrong. A records clerk fixes a referral for a patient waiting on the phone, types eleven digits, transposes two of them, and sends a discharge summary to a tyre shop. A cover sheet from last week gets reused with last week's destination still on it. A directory entry lists a practice that moved offices in 2023 and the number now belongs to somebody's mother.&lt;/p&gt;

&lt;p&gt;None of those are attacks. All of them are breaches. And an encrypted transmission to the wrong recipient is encrypted right up to the moment it lands, in perfect condition, in the hands of a stranger.&lt;/p&gt;

&lt;p&gt;That's an uncomfortable thing to sit with if you've spent your budget on the transport layer. It's also the most tractable problem in healthcare compliance, because unlike ransomware, the failure mode is predictable and happens in software you control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The finding that shows up in settlement after settlement
&lt;/h2&gt;

&lt;p&gt;Here's the second half, and it catches people who did nothing wrong on the day.&lt;/p&gt;

&lt;p&gt;When the Office for Civil Rights investigates, the incident is the reason they showed up. It's frequently not the reason for the penalty. Read a run of HIPAA settlements and the same finding appears with tedious regularity: the organisation had never conducted an accurate and thorough assessment of the risks to the electronic protected health information it holds. Not a missing firewall. Missing paperwork about what could go wrong and where.&lt;/p&gt;

&lt;p&gt;OCR has been explicit that risk analysis is an enforcement priority, and the settlements bear it out across hospitals, business associates, software vendors and employer health plans of every size. The incident varies. The finding barely does.&lt;/p&gt;

&lt;p&gt;Fax gets caught here more than it should, because risk analyses tend to cover the systems people think of as systems. The EHR, obviously. The mail server, usually. The fax path moves exactly the same records, sits on a server somebody configured in 2019, and appears in no document anywhere. So when the assessor asks where else ePHI lives, the honest answer is a shrug, and the shrug is what gets written down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five places to stop a wrong number
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pick, don't type.&lt;/strong&gt; Every number typed by hand is a chance to get it wrong, and the fix is to make the typed path the exception rather than the default. Send to directory entries that have an owner and a review date. The value isn't only accuracy. It's that a directory can be audited and a keypad cannot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirm against a name, not digits.&lt;/strong&gt; This is the cheapest control on the list and the one most often skipped. Before the fax goes, show the practice name the number belongs to. People proof-read names automatically. Nobody proof-reads an eleven digit string at the end of a shift, and asking them to is a control that exists on paper only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Put rules on the number itself.&lt;/strong&gt; Block the ranges you have no business faxing, premium and international among them. Flag a destination that nobody in this department has ever sent to before, and let the sender confirm rather than blocking outright. First-time destinations are where transposition errors surface, because a wrong number that somebody faxes daily gets noticed within a day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send less on the front page.&lt;/strong&gt; If the fax does go astray, the damage is set by what's readable without opening anything. A cover page with a patient name and a diagnosis on it makes a misdial into a reportable disclosure of clinical detail. A cover page with a reference number and a callback line makes the same misdial into an embarrassment. Same error, different Monday.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat the receipt as a record.&lt;/strong&gt; Delivery confirmation is not a courtesy feature. It's how you establish that a document reached the machine you meant, and it's what you'll be asked for when somebody claims they never received a referral. Keep sender, destination, result, timestamps, retries, page count, and who opened the document afterwards, for longer than the employment of the person who sent it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Those five controls are most of the risk analysis you owe
&lt;/h2&gt;

&lt;p&gt;This is the part I find genuinely useful, and it's why the two halves of this post belong together.&lt;/p&gt;

&lt;p&gt;A risk analysis is not a mystical document. At its core it answers three questions for every place ePHI lives: what could go wrong here, how bad would it be, and what have you done about it. If you've implemented the five controls above, you've already answered the third question for the fax path. Writing it down is the cheap part.&lt;/p&gt;

&lt;p&gt;Start by naming the places. In a fax path that's more than most people expect: the spool directory where a document waits, the queue, the archive, the mail relay if you route inbound faxes to email, and the carrier holding the transmission in transit. Each one holds ePHI, each one has a different failure mode, and each one belongs in the document by name.&lt;/p&gt;

&lt;p&gt;Then answer the question everyone gets wrong: who can read a finished fax today, and does that list still match the people who work here? Access lists rot faster than anything else in a fax deployment, because inbound routing tends to be set up once, by someone who has since left, using a group that made sense at the time. Our post on &lt;a href="https://www.ictfax.com/what-makes-a-fax-server-hipaa-compliant/" rel="noopener noreferrer"&gt;what actually makes a fax server HIPAA compliant&lt;/a&gt; goes deeper on the access side.&lt;/p&gt;

&lt;p&gt;And if the only place any of this is recorded is the memory of whoever set the server up, you don't have a risk analysis. You have a person, and people leave.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why self-hosting makes this cheaper rather than harder
&lt;/h2&gt;

&lt;p&gt;There's a fair argument that cloud fax offloads compliance work, and for some organisations it does. I'd push back on one specific point though, which is evidence.&lt;/p&gt;

&lt;p&gt;When the spool, the logs and the archive sit on hardware you control, answering "where does ePHI live and who touched it" is a query. When they sit with a vendor, it's a support ticket, a wait, and whatever export format they decided to offer. That difference is invisible for years and then becomes the whole job for two weeks. Retention is the same story: your retention policy is a setting you own, rather than a tier you pay for.&lt;/p&gt;

&lt;p&gt;The proposed tightening of the HIPAA Security Rule pushes in the same direction, with encryption, multi-factor authentication and regular vulnerability scanning moving from addressable to required. We covered what that means for fax in &lt;a href="https://www.ictfax.com/hipaa-security-rule-encryption-fax-server-software/" rel="noopener noreferrer"&gt;the proposed Security Rule post&lt;/a&gt;. Every one of those requirements is easier to demonstrate on a system whose configuration you can show somebody.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start on Monday
&lt;/h2&gt;

&lt;p&gt;If you do one thing, add the destination confirmation showing a name rather than digits. It takes an afternoon, it addresses the failure that actually happens, and staff stop resenting it within a week because it catches real mistakes.&lt;/p&gt;

&lt;p&gt;If you do two things, open a document and list every place ePHI sits in your fax path. Not the polished assessment, just the list. Most teams discover a spool directory or an email relay they'd forgotten, and finding that on your own terms is considerably better than finding it during an investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is a misdirected fax a reportable HIPAA breach?
&lt;/h3&gt;

&lt;p&gt;Usually yes. Sending protected health information to someone not authorised to receive it is an impermissible disclosure, and it's presumed to be a breach unless a risk assessment shows a low probability that the information was compromised. The content of the pages, and particularly what's readable on the cover sheet, drives that assessment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does encryption protect us if the fax goes to the wrong number?
&lt;/h3&gt;

&lt;p&gt;No. Encryption protects the transmission, not the addressing. A document encrypted in transit and at rest arrives perfectly intact at whatever destination you dialled, which is exactly the problem when the destination is wrong. Addressing controls and transport security solve different problems and you need both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does the risk analysis matter more than the incident?
&lt;/h3&gt;

&lt;p&gt;Because it's the finding regulators can make regardless of how the incident went. An organisation that suffered a breach but can show a thorough, current assessment and the controls that followed from it is in a very different position from one that cannot produce the document at all. The second case is where the larger settlements cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does our risk analysis have to name the fax server specifically?
&lt;/h3&gt;

&lt;p&gt;It has to cover every system that creates, receives, maintains or transmits ePHI, and a fax server does all four. Naming it, along with the spool, queue, archive and any mail relay in the path, is the straightforward way to show that coverage rather than arguing about it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long should we keep fax transmission logs?
&lt;/h3&gt;

&lt;p&gt;Long enough to answer a question about a transmission years after the person who sent it has left, which in practice means aligning fax log retention with your wider HIPAA documentation retention rather than with a mailbox quota. Keep the delivery result, not just the attempt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can fax server software prevent all wrong numbers?
&lt;/h3&gt;

&lt;p&gt;No, and any vendor claiming otherwise is selling something. What it can do is remove the most common causes: hand-typed destinations, unreviewed directory entries, silent first-time sends, and cover pages carrying more clinical detail than they need. That turns a frequent incident into a rare one, and gives you the record to show what happened when it does.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Federal AI Call Rule Never Landed. Your Auto Dialer Software Answers to the States Now</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sun, 06 Sep 2026 07:16:11 +0000</pubDate>
      <link>https://dev.to/tahiralmas/the-federal-ai-call-rule-never-landed-your-auto-dialer-software-answers-to-the-states-now-jod</link>
      <guid>https://dev.to/tahiralmas/the-federal-ai-call-rule-never-landed-your-auto-dialer-software-answers-to-the-states-now-jod</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ictdialer.com/ai-voice-disclosure-state-rules-auto-dialer-software/" rel="noopener noreferrer"&gt;ictdialer.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're running AI voice on outbound calls and waiting for the FCC to tell you exactly what to say, stop waiting. The federal disclosure rule has been a proposal since August 2024. California has required a spoken disclosure since January 2025, Utah requires one whenever someone asks, and both are enforceable today.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened, and what didn't
&lt;/h2&gt;

&lt;p&gt;Two things get confused constantly, so let's separate them.&lt;/p&gt;

&lt;p&gt;The first is settled. In February 2024 the FCC ruled that a voice generated or cloned by AI counts as an "artificial voice" under the TCPA. That single sentence pulled every AI voice agent into the existing robocall regime. Prior express consent, identification, opt-out handling, calling windows: all of it applies to your AI calls exactly as it applies to a recorded message, and the damages are the same $500 per call, or $1,500 if a court finds the violation willful. We went through the mechanics of that in our &lt;a href="https://www.ictdialer.com/2026-tcpa-rules-auto-dialers-consent-dnc-ai-voice/" rel="noopener noreferrer"&gt;2026 TCPA rules for auto dialers&lt;/a&gt;, and none of it has softened since.&lt;/p&gt;

&lt;p&gt;The second thing is not settled. In August 2024 the Commission proposed going further: a specific requirement that you announce, in plain language at the start of the call, that the voice is AI-generated. That proposal is still a proposal. Two years on, with a Commission that has signalled a lighter regulatory posture, nobody sensible is holding their breath.&lt;/p&gt;

&lt;p&gt;So there's a gap between "AI voice is regulated" (yes, heavily) and "there is a federal script you must read" (no, not yet). And states have filled that gap in their own ways, which is the part that actually shapes how you build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two states, two completely different triggers
&lt;/h2&gt;

&lt;p&gt;California took the simple road. AB 2905 has been in force since 1 January 2025, and it says that when a prerecorded message uses an artificial voice, including one generated by AI, the announcement has to tell the person that. No trigger, no exception for friendly calls, no waiting to be asked. Every call, in the opening. Up to $500 per violation.&lt;/p&gt;

&lt;p&gt;Utah went the other way. Its AI Policy Act, narrowed by amendment in 2025, requires you to disclose that a person is dealing with generative AI rather than a human when they ask. On top of that, disclosure has to be up front for what the statute treats as higher-stakes territory: health, financial and biometric matters, and anything that amounts to advice on financial, legal or medical questions. The Division of Consumer Protection can go to $2,500 per violation.&lt;/p&gt;

&lt;p&gt;Read those two side by side and you'll notice they're not stricter and looser versions of the same rule. They're different mechanisms. California's is a broadcast obligation you satisfy with audio. Utah's is a conversational obligation you satisfy with behaviour, because a rule that fires "when the person asks" cannot be met by a recording that plays before they've said anything.&lt;/p&gt;

&lt;p&gt;That distinction is the whole engineering problem, and it's why bolting a disclosure sentence onto the front of every campaign feels like compliance without actually being it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the disclosure goes in the call
&lt;/h2&gt;

&lt;p&gt;For a voice broadcast, this is easy and you should just do it. The message is prerecorded, so prepend a disclosure clip to the audio and attach it at the campaign level, chosen by the destination state. One clip, recorded once, applied by rule. If your dialer can't select a different audio prefix per state on the same campaign, that's the feature to ask for, because the alternative is duplicating campaigns per state and that falls apart at the first content change.&lt;/p&gt;

&lt;p&gt;For a conversational agent, the opening turn does the same job: identify who's calling and say the voice is AI, before you ask for anything. I'd argue you should do this even where no state requires it, and not for legal reasons. People work out that they're talking to a machine within a few seconds anyway, and the ones who feel they were tricked into that realisation are the ones who complain.&lt;/p&gt;

&lt;p&gt;Then there's the branch most teams miss entirely. Somewhere in the middle of a perfectly normal conversation, the person asks "wait, am I talking to a real person?" Under Utah's rule that question is the trigger, and your agent has to answer it honestly, in that turn, without deflecting into the script.&lt;/p&gt;

&lt;p&gt;That is not a line you can put in a prompt and forget. It's an intent your agent has to recognise across the dozen ways people actually phrase it. Are you a robot. Is this a recording. Am I speaking with a human. Are you real. You want that intent handled explicitly, answered plainly, and logged with the turn index where it fired, because "the model probably handled it" is not something you want to say to a regulator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build it once, key it to the number you're dialling
&lt;/h2&gt;

&lt;p&gt;Here's the part that trips people up: the rule that applies follows the person you're calling, not the office you're calling from. A dialer in Karachi calling a Los Angeles number is in California's world for that call. Your configuration has to hang off the destination, and area code is a rough proxy at best given how many people keep a number after moving.&lt;/p&gt;

&lt;p&gt;Practically, that means three things in the dialer.&lt;/p&gt;

&lt;p&gt;A disclosure policy per campaign, resolved per call, that picks the audio prefix or the agent opening based on the destination. A default that errs toward disclosing, because the cost of announcing AI on a call that didn't require it is a slightly longer intro, and the cost of not announcing it on a call that did is $500 to $2,500 plus the complaint. And an intent handler in the agent for the "are you human" question that works regardless of which state rule you think applies.&lt;/p&gt;

&lt;p&gt;The same logic already applies to consent and opt-out, which is why this is less new work than it sounds. If you built per-destination rules for the revoke-all handling we covered in &lt;a href="https://www.ictdialer.com/fcc-revoke-all-consent-auto-dialer-opt-out/" rel="noopener noreferrer"&gt;the FCC's revoke-all waiver&lt;/a&gt;, the AI disclosure is another column in the same table rather than a separate system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to log, and why the recording isn't enough
&lt;/h2&gt;

&lt;p&gt;Most teams assume the call recording is the evidence. It is, right up until you need to answer a question about ten thousand calls rather than one, and nobody is going to listen to ten thousand recordings.&lt;/p&gt;

&lt;p&gt;What you actually want, per call, is a small set of fields that let you answer a complaint in minutes: which disclosure policy resolved for this call and why, which audio or script version played, whether the person asked about AI mid-call and at what point, what the agent said back, and the consent record that got you there in the first place. Version your disclosure text so "we changed the wording in March" is a queryable fact rather than an argument.&lt;/p&gt;

&lt;p&gt;That evidence trail pays for itself the first time somebody claims they never got a disclosure. It's the same reason we bang on about scrubbing reassigned numbers, where the cost of a bad assumption showed up as &lt;a href="https://www.ictdialer.com/reassigned-numbers-auto-dialer-tcpa-2026/" rel="noopener noreferrer"&gt;a $3 million wrong number&lt;/a&gt;. The pattern repeats: the call itself is cheap, the inability to prove what happened on it is expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'd do if we were starting this week
&lt;/h2&gt;

&lt;p&gt;Turn the disclosure on everywhere first, then optimise. Get a single clear sentence into the opening of every AI-voiced campaign regardless of destination, measure what it does to your answer and completion rates for a fortnight, and only then decide whether it's worth varying by state. My honest guess is that you'll find the drop smaller than you feared, and mostly concentrated in the calls that were never going to convert.&lt;/p&gt;

&lt;p&gt;Then handle the mid-call question properly, because that's the one that separates a script from a system.&lt;/p&gt;

&lt;p&gt;Then write down what you did. Not for us, and not really for the regulator either, but for the version of your team that inherits this in eighteen months when a rule changes and somebody has to work out which of five campaign templates carries the old wording.&lt;/p&gt;

&lt;p&gt;ICTDialer runs AI voice agents on outbound campaigns today, and the disclosure question is one of the first things new operators raise. It's a fair question to lead with. The teams that handle it deliberately are the ones still calling in two years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does the FCC require an AI disclosure on outbound calls right now?
&lt;/h3&gt;

&lt;p&gt;No, not as a separate spoken disclosure. The FCC proposed one in August 2024 and has not finalised it. What does apply today is the February 2024 ruling that an AI-generated voice is an artificial voice under the TCPA, which brings consent, identification and opt-out obligations with damages of $500 to $1,500 per call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which states currently require an AI voice disclosure on calls?
&lt;/h3&gt;

&lt;p&gt;California requires it in the announcement of any prerecorded message using an artificial voice, under AB 2905, in force since January 2025. Utah requires disclosure when the person asks, and up front for health, financial and biometric matters or advice on financial, legal and medical questions. Several other states have moved in the same direction, so treat this as a list that grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the rule follow my location or the number I'm calling?
&lt;/h3&gt;

&lt;p&gt;The number you're calling. A dialer operating anywhere in the world is subject to the destination state's rule for that call, which is why the disclosure policy belongs on the per-call routing decision rather than in a global setting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where should the disclosure sit in a conversational AI call?
&lt;/h3&gt;

&lt;p&gt;In the opening turn, alongside identifying who's calling and why, before you ask the person for anything. You also need to handle the mid-call version, because someone asking whether they're talking to a human is a trigger in its own right under Utah's rule and simply good manners everywhere else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will disclosing that the voice is AI hurt my answer rates?
&lt;/h3&gt;

&lt;p&gt;Some hang-ups go up, and that effect is real. It is also cheaper than the alternative, and in our experience the calls lost are weighted toward people who were never going to engage. Measure it on your own traffic for two weeks before you decide the disclosure is the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  What has to be logged to prove a disclosure happened?
&lt;/h3&gt;

&lt;p&gt;Per call: the policy that resolved, the disclosure version that played, whether the person raised the AI question and where in the call, the agent's response, and the underlying consent record. Recordings alone do not scale to answering a regulator's question about a whole campaign.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>We Put an AI Assistant in Front of Asterisk and FreeSWITCH. Here Is What Building pbx-mcp Taught Us About PBX Software Safety</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sat, 05 Sep 2026 05:55:44 +0000</pubDate>
      <link>https://dev.to/tahiralmas/we-put-an-ai-assistant-in-front-of-asterisk-and-freeswitch-here-is-what-building-pbx-mcp-taught-us-5fge</link>
      <guid>https://dev.to/tahiralmas/we-put-an-ai-assistant-in-front-of-asterisk-and-freeswitch-here-is-what-building-pbx-mcp-taught-us-5fge</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictpbx.com/pbx-mcp-ai-assistant-asterisk-freeswitch-lessons/" rel="noopener noreferrer"&gt;ictpbx.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The question your PBX software gets asked most isn't technical. It's "is the trunk down?" and "who's on a call right now?", from people who'll never open a CLI. &lt;a href="https://github.com/ictinnovations/pbx-mcp" rel="noopener noreferrer"&gt;pbx-mcp&lt;/a&gt; is our open source answer: an MCP server that hands live Asterisk and FreeSWITCH state to an AI assistant, read-only unless you say otherwise. Four weeks in, the code was the easy part. Here's what the safety model taught us.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an MCP server for PBX software actually does
&lt;/h2&gt;

&lt;p&gt;MCP, the Model Context Protocol, is the way assistants like Claude Desktop or Cursor call tools. pbx-mcp registers a set of tools, each described well enough that the model picks the right one without being told, and answers them by talking to Asterisk over the Manager Interface and to FreeSWITCH over the Event Socket. Ask "why is my SIP trunk not registering?" and the assistant runs &lt;code&gt;freeswitch_sofia_status&lt;/code&gt;, reads the gateway table, and tells you which one is stuck in TRYING. No dashboard, no copy-pasting from a terminal.&lt;/p&gt;

&lt;p&gt;It runs with &lt;code&gt;npx -y pbx-mcp&lt;/code&gt; or as a container, speaks stdio so nothing listens on a port, and the only runtime dependencies are the MCP SDK and Zod. The AMI and ESL clients underneath are hand rolled and published separately, which matters later. We validated the gap before writing it: the one "asterisk" package on npm belongs to a code security company that shares the name, and nothing at all existed for FreeSWITCH.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson one: gate at registration, not at runtime
&lt;/h2&gt;

&lt;p&gt;Pointing a language model at a live switch is a bad idea if you do it lazily. Our first design decision, and the one every reviewer since has singled out, is that the write tools are never registered with the model unless &lt;code&gt;PBX_MCP_ALLOW_WRITE=true&lt;/code&gt; is set. &lt;code&gt;asterisk_originate&lt;/code&gt;, &lt;code&gt;asterisk_hangup&lt;/code&gt; and their FreeSWITCH twins simply don't exist as far as the assistant can see.&lt;/p&gt;

&lt;p&gt;That's different from registering them and refusing at call time, and the difference is bigger than it sounds. A visible but refusing tool still burns context on its schema, the model will cheerfully retry it, and a determined one will try to route around the refusal. A tool it can't see is a tool it can't want. One commenter put it better than I had: the default doesn't protect the operator who flips the flag on day one, because they've made a conscious call. It protects the person wiring the server into an autonomous loop who never read past the quickstart and is one hallucinated argument away from hanging up a live channel. They never flip the flag because they never knew it existed. That's the whole win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson two: read-only is only as true as the allow list
&lt;/h2&gt;

&lt;p&gt;Two of the read-only tools are raw pass-throughs, &lt;code&gt;asterisk_cli&lt;/code&gt; and &lt;code&gt;freeswitch_api&lt;/code&gt;, filtered by an allow list. The same reviewer called that spot "a write in a trenchcoat the moment the allow list has a gap", and then found the gap from the README alone. &lt;code&gt;conference&lt;/code&gt; was on the FreeSWITCH read list because &lt;code&gt;conference 3001 list&lt;/code&gt; is genuinely useful. The policy checked the first word against the list, then only rejected the line if some later word matched a list of scary verbs. &lt;code&gt;kick&lt;/code&gt; wasn't on it. So &lt;code&gt;conference 3001 kick all&lt;/code&gt; sailed through read-only mode and dropped every caller in the room.&lt;/p&gt;

&lt;p&gt;Before changing anything, I compiled the old policy and ran commands through it, partly so I wasn't fixing a bug I'd only imagined. Five got through: the one he found, plus &lt;code&gt;hup all&lt;/code&gt;, &lt;code&gt;mute 1&lt;/code&gt;, a &lt;code&gt;record&lt;/code&gt; that writes a file to disk, and &lt;code&gt;db insert&lt;/code&gt;, which writes to the switch database. The last two I hadn't spotted when I first replied, so it was worse than I'd made it sound, and I said so in the thread. A deny list of scary words only catches what you thought of. It's the wrong shape for the job.&lt;/p&gt;

&lt;p&gt;Release 0.1.2 switched FreeSWITCH to a prefix allow list matched against the whole command, the way the Asterisk side already worked. &lt;code&gt;sofia status&lt;/code&gt; reads. &lt;code&gt;sofia profile internal restart&lt;/code&gt; isn't a listed prefix, so it's refused. &lt;code&gt;conference&lt;/code&gt; got its own positional rule because the room name sits between the verb and the subcommand. &lt;code&gt;fsctl&lt;/code&gt; came off the read list entirely. The five holes are regression tests now, CI runs them on Node 18, 20 and 22, and I verified the fix against the published npm tarball rather than the local build before telling anyone it was done. The list stays hardcoded on purpose. An environment variable that extends it has the same failure mode as the write flag: someone pastes it out of a blog post and the list means nothing. If a command belongs there, it belongs there for everyone, and that's a pull request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson three: the injection nobody thinks about is in the caller ID
&lt;/h2&gt;

&lt;p&gt;Asterisk's Manager Interface is newline delimited. A caller ID field with a carriage return and line feed inside it is otherwise a free command, because the next line is parsed as a new header. It's about four lines of code to reject, and it bothers me how rarely it's there. The same goes for shell metacharacters in anything that reaches a CLI: we reject them rather than escape them, because escaping is where the clever bugs live. And output is clamped at 20,000 characters, since &lt;code&gt;show channels&lt;/code&gt; on a busy box will happily eat an assistant's whole context window and leave it unable to answer the question it was asked. The commenter who found the allow-list hole said the CRLF guard was what made him trust the rest of the design. I'd argue that's the right instinct: check the boring input paths first, and the flashy features earn trust by association.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson four: a dry run is worth more than a warning
&lt;/h2&gt;

&lt;p&gt;The idea came from the same thread, from someone who ships a memory server where the dangerous verb is "forget" rather than "hangup". Add a mode on the destructive tools that returns what would happen without doing it. So 0.1.2 also shipped &lt;code&gt;asterisk_hangup_preview&lt;/code&gt; and &lt;code&gt;freeswitch_hangup_preview&lt;/code&gt;, registered even in read-only mode. They show which live channels a hangup would match and warn when the matched channel is bridged, since the far end drops with it. Read-only by contract, but they exercise the write path's planning logic, so a cautious operator can see the blast radius before ever setting the flag. If I were building any PBX software integration that can touch a live call, I'd copy this pattern before copying anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson five: what shipping it taught us about being found
&lt;/h2&gt;

&lt;p&gt;The thread that produced all of the above is on r/mcp: &lt;a href="https://old.reddit.com/r/mcp/comments/1vkatsb/" rel="noopener noreferrer"&gt;pbx-mcp, an MCP server for Asterisk and FreeSWITCH, read-only by default&lt;/a&gt;. It worked because it led with the problem and the safety model, kept the links to the end, and closed with a real question about whether read-only defaults survive contact with users. Every correction got a reply, the fix shipped in two days, and the follow-up named the commit. One commitment took longer: someone asked for a demo video, and what eventually went up is a narrated walkthrough rather than the live screen capture I'd promised. I said so in the thread instead of pretending a slideshow was the same thing. Credibility on those forums is a balance, and overclaiming costs more than the gap it covers.&lt;/p&gt;

&lt;p&gt;Distribution had its own lessons. The official MCP Registry wouldn't accept a publish under our organisation's namespace from an interactive login; only GitHub Actions with an OIDC token from the org-owned repository could claim &lt;code&gt;io.github.ictinnovations/pbx-mcp&lt;/code&gt;. Glama's quality score sat at 17 percent until we cut a Glama release, which is a different thing from a GitHub release. And the honest number: the seven product-specific MCP servers we published afterwards, for ICTPBX, ICTFax and the rest, show zero npm downloads, because their users must already run the matching product and developer registries can't reach them. pbx-mcp, which works against any Asterisk or FreeSWITCH, is the one people find. Build the generic thing first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits with ICTPBX
&lt;/h2&gt;

&lt;p&gt;pbx-mcp talks to the switches underneath, so it works against a stock Asterisk or FreeSWITCH as much as against the FreeSWITCH inside ICTPBX. For the PBX layer itself there's a separate read-only server, &lt;a href="https://github.com/ictinnovations/ictpbx-mcp" rel="noopener noreferrer"&gt;ictpbx-mcp&lt;/a&gt;, which reads extensions, DIDs, SIP trunks, tenants and statistics through the &lt;a href="https://ictpbx.com/ictpbx-rest-api/" rel="noopener noreferrer"&gt;ICTPBX REST API&lt;/a&gt;. Enterprise Edition also ships an &lt;a href="https://ictpbx.com/manage-pbx-by-voice-ai-agent/" rel="noopener noreferrer"&gt;AI voice agent for managing the PBX by phone&lt;/a&gt;, which is where the "a misheard word on a phone line is easier than a typo in a terminal" argument for confirm steps came from. And if you'd rather own the whole stack, &lt;a href="https://github.com/ictinnovations/ictpbx-community-edition" rel="noopener noreferrer"&gt;ICTPBX Community Edition&lt;/a&gt; is the open source multi-tenant PBX on FreeSWITCH, FusionPBX and ICTCore under MPL 2.0, with an &lt;a href="https://github.com/ictinnovations/ansible-role-ictpbx" rel="noopener noreferrer"&gt;Ansible role&lt;/a&gt; that installs it in one play. The allow-list thinking above applies just as much to the &lt;a href="https://ictpbx.com/pbx-toll-fraud-sip-security-2026/" rel="noopener noreferrer"&gt;toll fraud problem&lt;/a&gt;: the commands you didn't think to deny are the ones that cost you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The open source pieces around it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ictinnovations/asterisk-ami-node" rel="noopener noreferrer"&gt;asterisk-ami-node&lt;/a&gt; and &lt;a href="https://github.com/ictinnovations/freeswitch-esl-node" rel="noopener noreferrer"&gt;freeswitch-esl-node&lt;/a&gt;: the zero dependency protocol clients pbx-mcp is built on, with the CRLF guard included, if you want the protocol layer without the MCP part.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ictinnovations/asterisk-ai-voice-agent" rel="noopener noreferrer"&gt;asterisk-ai-voice-agent&lt;/a&gt;: a self-hosted AI voice agent for Asterisk calls, and the &lt;a href="https://github.com/ictinnovations/asterisk-audiosocket" rel="noopener noreferrer"&gt;AudioSocket library&lt;/a&gt; underneath it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The other product servers: &lt;a href="https://github.com/ictinnovations/ictfax-mcp" rel="noopener noreferrer"&gt;ictfax-mcp&lt;/a&gt;, &lt;a href="https://github.com/ictinnovations/ictcontact-mcp" rel="noopener noreferrer"&gt;ictcontact-mcp&lt;/a&gt;, &lt;a href="https://github.com/ictinnovations/ictbroadcast-mcp" rel="noopener noreferrer"&gt;ictbroadcast-mcp&lt;/a&gt;, &lt;a href="https://github.com/ictinnovations/ictdialer-mcp" rel="noopener noreferrer"&gt;ictdialer-mcp&lt;/a&gt;, &lt;a href="https://github.com/ictinnovations/ictcrm-mcp" rel="noopener noreferrer"&gt;ictcrm-mcp&lt;/a&gt; and &lt;a href="https://github.com/ictinnovations/ictexam-mcp" rel="noopener noreferrer"&gt;ictexam-mcp&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Everything else, including the LangChain toolkits, n8n nodes and the explainer videos, is indexed on the &lt;a href="https://ictinnovations.com/projects/" rel="noopener noreferrer"&gt;ICT Innovations open source page&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is it safe to connect an AI assistant to production PBX software?
&lt;/h3&gt;

&lt;p&gt;With the right defaults, yes. pbx-mcp starts read-only, never registers the call control tools unless you set a flag, filters raw commands through a hardcoded allow list, rejects shell metacharacters and AMI header injection, and clamps output. Treat the write flag like root and give the AMI user only the permissions the read tools need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Asterisk and FreeSWITCH versions does pbx-mcp support?
&lt;/h3&gt;

&lt;p&gt;Any Asterisk with the Manager Interface, with PJSIP endpoint tools falling back to chan_sip peers on older installs, and any FreeSWITCH with the Event Socket enabled. You need Node 18 or newer to run it, or use the container and keep Node off the PBX host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it work with ICTPBX?
&lt;/h3&gt;

&lt;p&gt;Yes, at two levels. pbx-mcp reads the FreeSWITCH underneath ICTPBX directly. ictpbx-mcp reads the PBX layer itself, tenants, extensions, DIDs and trunks, through the ICTPBX REST API, and is read-only.&lt;/p&gt;

&lt;h3&gt;
  
  
  What did the read-only allow list get wrong originally?
&lt;/h3&gt;

&lt;p&gt;It allowed a command by its first word and then denied it only if a later word looked dangerous. Five FreeSWITCH commands passed that check in read-only mode, including a conference kick and a database insert. Version 0.1.2 replaced it with a prefix allow list against the whole command and added those five as regression tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I extend the allowed commands for my own site?
&lt;/h3&gt;

&lt;p&gt;Not through configuration, and that's deliberate. An environment variable that widens the allow list would be copied out of a blog post the same way the write flag is. If a command is safe to read, it's safe for everyone, so it goes in as a pull request with a test.&lt;/p&gt;

&lt;p&gt;The code, the tests, the changelog and the user guide are all in the &lt;a href="https://github.com/ictinnovations/pbx-mcp" rel="noopener noreferrer"&gt;pbx-mcp repository&lt;/a&gt;. If you find a command that gets past the list, open an issue. The best review this project has had came from someone who'd never run it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building an Asterisk AI Voice Agent: What Six Releases and Two Reddit Threads Taught Us</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sat, 05 Sep 2026 05:48:01 +0000</pubDate>
      <link>https://dev.to/tahiralmas/building-an-asterisk-ai-voice-agent-what-six-releases-and-two-reddit-threads-taught-us-1nn0</link>
      <guid>https://dev.to/tahiralmas/building-an-asterisk-ai-voice-agent-what-six-releases-and-two-reddit-threads-taught-us-1nn0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictinnovations.com/asterisk-ai-voice-agent-lessons-audiosocket-barge-in/" rel="noopener noreferrer"&gt;ictinnovations.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're wiring an LLM into an Asterisk call, the hard part isn't the model. It's the 20 ms frame clock, the five separate things a barge-in has to cancel, and a voice engine that isn't thread safe. We open sourced &lt;a href="https://github.com/ictinnovations/asterisk-ai-voice-agent" rel="noopener noreferrer"&gt;asterisk-ai-voice-agent&lt;/a&gt; four weeks ago, shipped six releases since, and got most of the corrections from strangers on Reddit. This is the engineering log.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Asterisk AI voice agent is, in one paragraph
&lt;/h2&gt;

&lt;p&gt;It's a Python sidecar. Asterisk bridges a call into it over AudioSocket, or since 0.1.4 over chan_websocket, and from there it runs the loop you'd expect: voice activity detection on every 20 ms frame, speech to text through Whisper or ElevenLabs Scribe, a streaming turn with Claude that can call tools on your own webhook, and speech out through Piper locally or ElevenLabs in the cloud. It came out of the AI voice agent that ships inside &lt;a href="https://www.ictcontact.com/" rel="noopener noreferrer"&gt;ICTContact&lt;/a&gt;, stripped back to the parts that don't depend on our product. MIT, on PyPI and Docker Hub. We wrote up the release itself &lt;a href="https://ictinnovations.com/asterisk-ai-voice-agent-audiosocket-open-source/" rel="noopener noreferrer"&gt;back in August&lt;/a&gt;, so this post skips the pitch and goes straight to what went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson one: decide who owns the playout clock before you write a line
&lt;/h2&gt;

&lt;p&gt;The bug that cost us the most time looked like nothing. Logs were clean, no errors, and the caller heard the last word of every sentence and nothing before it. The cause is that &lt;code&gt;app_audiosocket&lt;/code&gt; forwards each frame to the channel the moment it arrives. Hand it 2.4 seconds of speech in one write and all 120 frames hit the far end in a few milliseconds. The jitter buffer keeps a handful and throws the rest away.&lt;/p&gt;

&lt;p&gt;The fix everyone knows is to write one 320 byte frame and sleep until the next 20 ms deadline. The fix fewer people know is to re-clamp that deadline on every frame. When synthesis stalls for half a second, a pacer that computes the next deadline from where it thinks it should be will burst to catch up, and you're dropping audio again, just later. Snap the deadline to now instead. Lateness you can't recover; burst loss you can avoid.&lt;/p&gt;

&lt;p&gt;Then a commenter on our first thread pointed out something we'd never checked. Python's asyncio leaves Nagle's algorithm on by default, and one small write every 20 ms is exactly the pattern Nagle exists to coalesce. Our pacer was correct and the kernel was re-bunching the frames underneath it. Setting &lt;code&gt;TCP_NODELAY&lt;/code&gt; on the accepted socket fixed it, and we added a test that reads the option back off a real accepted socket, then ran it once with the fix removed to make sure the test wasn't lying. It came back zero. Nagle had been on the whole time.&lt;/p&gt;

&lt;p&gt;Here's my honest recommendation after all that. If your Asterisk is 20.18 or 22.8 or newer, use chan_websocket and let Asterisk own the clock. You hand it audio ahead of real time, it re-frames and re-times, and &lt;code&gt;FLUSH_MEDIA&lt;/code&gt; takes queued audio back when the caller interrupts. We measured that last claim rather than trusting it: queued 18 seconds of audio on 22.10.1, flushed after two, and not one flushed byte reached the caller. Replacement audio came back about 10 ms later. Six runs, ulaw and slin, same result every time. AudioSocket is still the right choice for the 18 and 20 boxes most sites actually run, and the protocol is small enough to read in one sitting. Just know that under AudioSocket, queue depth is your barge-in floor, because audio you've handed over is gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson two: barge-in is five cuts, not one
&lt;/h2&gt;

&lt;p&gt;Barge-in "worked" from the first release, in the sense that playback stopped when the caller spoke. It took an engineer from Exotel, who works on the streaming side of a voice API, to point out what was still running after playback stopped. Once text to speech streams into a pacer, a flush on its own leaves the synthesizer rendering a tail nobody will hear. With API priced voices that's real money. With a local voice it's worse, and I'll get to why.&lt;/p&gt;

&lt;p&gt;So we went looking, and found that our stop flag was only polled between frames. Synthesis sat one &lt;code&gt;await&lt;/code&gt; earlier. A caller who interrupted while we were still rendering had no effect at all: the whole sentence was built, then playback stopped at frame zero. Release 0.1.4 passed the stop check into synthesis itself. On the ElevenLabs path that closes the HTTP stream, so an interrupted sentence stops being billed.&lt;/p&gt;

&lt;p&gt;The Piper path is where it gets interesting. Piper phonemizes through espeak-ng, and that C API is not thread safe, so every render on the box goes through one process wide lock. Our first instinct in 0.1.5 was to cancel the synthesis task outright on barge-in. That cancel lands on the await inside the lock's context, which releases the lock while espeak-ng is still running in its worker thread. The next render, from this call or any other, walks straight in. We proved it in the shipped Docker image with the real voice: barge in five frames into a turn while sentence two is rendering, close the turn, then render from a second call. The 0.1.5 code showed two concurrent Piper renders and the second caller waited 2.14 seconds. Letting the in-flight render finish and discarding it, which is what 0.1.6 does, showed one render and 1.32 seconds. The wait is just the remainder of a render that was already happening, and nothing about the interrupt itself changes.&lt;/p&gt;

&lt;p&gt;The fifth cut is the one I'd never have found without reading our own code against the thread. Breaking out of the LLM stream skipped the step that records the assistant's message, so the model never learned it had said anything. And if the model had already asked for a tool before the caller cut in, the tool still ran and its result was filed against a tool call that was never recorded. The API rejects that request, and every request after it, so one badly timed interruption left the caller hearing only the apology line for the rest of the call. Now the record holds the sentences that actually reached the transport, tool calls from an interrupted turn are dropped, and the abandoned stream is closed immediately rather than whenever the garbage collector gets to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson three: measure before you optimise, especially the obvious fix
&lt;/h2&gt;

&lt;p&gt;Someone opened an issue saying the process wide Piper lock caps concurrent calls, and I agreed with them. Obviously a pool of worker processes, each with its own voice, would let four callers render at once. We built it. Then we measured it in the shipped image with a real voice, four concurrent calls, five trials each, because single runs had disagreed with each other at 0.82x, 0.93x and 1.31x, which is noise.&lt;/p&gt;

&lt;p&gt;The pool was worse on every axis. Total time 2.30 seconds against 1.99 for the lock. The first caller waited 1.89 seconds instead of 0.48. ONNX Runtime already parallelises a single render across every core, so the lock was serialising work that was already saturating the CPU, and the pool only added contention, inter-process copying, and the loss of first in, first out ordering. Capping each worker to one thread was worse again, 5.41 seconds. The ceiling was CPU, not the lock. We reverted it, closed the issue with the numbers, and I'd argue the numbers on that issue are worth more than the feature would have been.&lt;/p&gt;

&lt;p&gt;The measurement that did pay off was overlapping synthesis with playback. Speaking one sentence at a time meant sentence N+1 didn't start rendering until the caller had finished hearing N, so every sentence boundary carried a gap the length of the next render. With a real Asterisk echoing our audio back so we could time arrival rather than departure, a three sentence turn went from 2.65 seconds with two 500 ms holes to 1.88 seconds with a 120 ms worst gap. When synthesis is faster than playback, which it usually is, the gap closes completely. Overlap can't create throughput though. Where a sentence costs more to render than to play, the residual gap is the deficit and no queue fixes that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson four: the small text handling bugs are the ones callers notice
&lt;/h2&gt;

&lt;p&gt;Two of them. First, the splitter that hands sentences to the voice while the model is still streaming fired on every comma, semicolon and colon, and on any full stop before whitespace. So "Dr. Smith" was two utterances, "1. Restart the phone" became a spoken "one." followed by a fragment, and every clause played as its own choppy piece with the voice losing the run of the sentence at each cut. It now splits at sentence ends only, knows about abbreviations, initials and list numbers, and falls back to a clause break only once a sentence runs past 120 characters.&lt;/p&gt;

&lt;p&gt;Second, the first word after a barge-in kept getting clipped. The voice detector only calls a frame voiced once the talk spurt has enough energy, so a quiet onset was already gone by the time the utterance opened. A commenter suggested a lookback buffer and I initially said we'd gone another way. He was right and I was wrong. Release 0.1.3 holds a 300 ms rolling window of pre-onset frames and prepends them when an utterance opens, one ring buffer per call so it stays at 4.8 kB however long the call runs. The part I didn't expect: that prepended audio had to stay out of the voiced duration count, because that number feeds a word density check for Whisper hallucinations, and padding it made short answers look like garbage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson five: post the war story, not the announcement
&lt;/h2&gt;

&lt;p&gt;Almost every fix above came from two threads on r/Asterisk. The first, &lt;a href="https://old.reddit.com/r/Asterisk/comments/1vjttmd/" rel="noopener noreferrer"&gt;why writing a whole TTS sentence to AudioSocket means the caller only hears the end of it&lt;/a&gt;, brought in an Asterisk maintainer who corrected me on chan_websocket availability (it landed in 20, not just 22), pointed at the mark mechanism for playout confirmation, and confirmed how the drain feedback works. It brought the Exotel engineer with the chunk size floor argument that reframed barge-in latency for me as a question of what unit you can't cancel once you've handed it over. It brought the Nagle catch, an external media comparison from someone running it in production, and a correction on format naming: in Asterisk terms slin is 8 kHz and slin16 is 16 kHz, so 320 bytes per 20 ms is plain slin. Our docs said slin16 for a month. The second thread, &lt;a href="https://old.reddit.com/r/Asterisk/comments/1vnihem/" rel="noopener noreferrer"&gt;fixing dead air and clipped words when driving Piper into AudioSocket&lt;/a&gt;, produced the lookback buffer.&lt;/p&gt;

&lt;p&gt;What made those threads work, as far as I can tell, was structure. Five paragraphs of the actual bug told as a story, the GitHub links only in paragraph six, and a closing question that gave people somewhere to go. Every correction got a reply within a day that either shipped the fix with a commit link or explained honestly why not. Nothing about that is clever. It's just the opposite of a launch post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's still open in the voice agent
&lt;/h2&gt;

&lt;p&gt;The listening side is the biggest remaining latency. Speech to text is cloud only and not streaming: the agent waits for 550 ms of silence, wraps the utterance as a WAV, uploads it, and waits for one final result before the model can even start. A streaming engine with partial words and its own endpointing would cut the fixed wait and the round trip together. Deepgram is the least code for the cloud path and sherpa-onnx is the local answer; neither is implemented today, so don't read the config as if they were. On the voice side, Kokoro is the obvious upgrade from Piper for anyone who wants a more natural voice without a GPU. And chan_websocket play currently returns on a wall clock estimate rather than Asterisk's own drain notification, which is exactly the feedback the maintainer told us to use. All three are on the list, and the lab that measures them, real Asterisk 22 with an echo dialplan and the real Piper image, already exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of the open source set this came from
&lt;/h2&gt;

&lt;p&gt;The voice agent is one of a family, and the pieces underneath it are published on their own so you can take just the part you need.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ictinnovations/asterisk-audiosocket" rel="noopener noreferrer"&gt;asterisk-audiosocket&lt;/a&gt;: the AudioSocket protocol for Node.js and TypeScript, zero dependencies, both server and client sides.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ictinnovations/piper-tts-server" rel="noopener noreferrer"&gt;piper-tts-server&lt;/a&gt;: the voice cache, synthesis lock and paced writer from this project as a standalone library and server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ictinnovations/pbx-mcp" rel="noopener noreferrer"&gt;pbx-mcp&lt;/a&gt;: an MCP server that lets an AI assistant inspect channels, registrations, trunks and dialplan on Asterisk or FreeSWITCH, read only unless you flip a flag.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seven product MCP servers, for &lt;a href="https://github.com/ictinnovations/ictfax-mcp" rel="noopener noreferrer"&gt;ICTFax&lt;/a&gt;, &lt;a href="https://github.com/ictinnovations/ictpbx-mcp" rel="noopener noreferrer"&gt;ICTPBX&lt;/a&gt;, &lt;a href="https://github.com/ictinnovations/ictcontact-mcp" rel="noopener noreferrer"&gt;ICTContact&lt;/a&gt;, &lt;a href="https://github.com/ictinnovations/ictbroadcast-mcp" rel="noopener noreferrer"&gt;ICTBroadcast&lt;/a&gt;, &lt;a href="https://github.com/ictinnovations/ictdialer-mcp" rel="noopener noreferrer"&gt;ICTDialer&lt;/a&gt;, &lt;a href="https://github.com/ictinnovations/ictcrm-mcp" rel="noopener noreferrer"&gt;ICTCRM&lt;/a&gt; and &lt;a href="https://github.com/ictinnovations/ictexam-mcp" rel="noopener noreferrer"&gt;ICTExam&lt;/a&gt;. The &lt;a href="https://ictinnovations.com/mcp-servers-telephony-stack/" rel="noopener noreferrer"&gt;overview of all eight&lt;/a&gt; explains what each one exposes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ictinnovations/asterisk-ami-node" rel="noopener noreferrer"&gt;asterisk-ami-node&lt;/a&gt; and &lt;a href="https://github.com/ictinnovations/freeswitch-esl-node" rel="noopener noreferrer"&gt;freeswitch-esl-node&lt;/a&gt;: zero dependency clients for the two control protocols.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ictinnovations/ictpbx-community-edition" rel="noopener noreferrer"&gt;ICTPBX Community Edition&lt;/a&gt;: a multi-tenant IP PBX on FreeSWITCH, FusionPBX and ICTCore under MPL 2.0, with an &lt;a href="https://github.com/ictinnovations/ansible-role-ictpbx" rel="noopener noreferrer"&gt;Ansible role&lt;/a&gt; that installs it in one play.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;LangChain toolkits and n8n community nodes for the same products, all indexed on our &lt;a href="https://ictinnovations.com/projects/" rel="noopener noreferrer"&gt;open source projects page&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only take one thing from this post, take the lab. A real Asterisk in a container with an &lt;code&gt;Echo()&lt;/code&gt; dialplan returns every byte you play, so the arrival times of the echoed stream are the caller's actual experience. Three of the bugs above were invisible to unit tests with fake peers and obvious within minutes against that echo. Build it before you build the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does an Asterisk AI voice agent need chan_websocket, or does AudioSocket work?
&lt;/h3&gt;

&lt;p&gt;Both work. AudioSocket runs on every supported Asterisk and the protocol is tiny, but your application owns the playout clock and has to pace one 320 byte frame every 20 ms with a re-clamped deadline and TCP_NODELAY set. chan_websocket, on 20.18, 22.8 and newer, lets Asterisk pace and gives you FLUSH_MEDIA for barge-in. If your version allows it, chan_websocket is less to get wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why did the caller only hear the last word of each sentence?
&lt;/h3&gt;

&lt;p&gt;Because the whole sentence was written to AudioSocket at once. Asterisk forwards frames as they arrive, so the burst overran the far end's jitter buffer and everything but the tail was discarded. Pacing fixes it, and so does leaving pacing to chan_websocket.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Piper handle several concurrent calls on one server?
&lt;/h3&gt;

&lt;p&gt;Yes, behind one process wide lock, and that lock isn't the bottleneck. We measured a worker pool against it with four concurrent calls and the pool was slower in total and made the first caller wait four times longer. ONNX Runtime already uses every core for a single render, so the CPU is the ceiling. Add cores or add boxes; don't add processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What has to happen on barge-in besides stopping playback?
&lt;/h3&gt;

&lt;p&gt;Drop the frames in your own queue, stop or discard the sentence still being synthesised without breaking the voice engine's lock, close the LLM stream, record only what the caller actually heard, and drop any tool calls from the interrupted turn so the conversation history stays valid for the next request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the voice agent tied to ICTContact or any other product?
&lt;/h3&gt;

&lt;p&gt;No. It came out of the AI voice agent inside ICTContact, but the open source sidecar runs against a plain Asterisk with your own API keys for the model and voices. The product specific pieces live in the separate MCP servers and LangChain packages, which do need the matching product.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which speech to text engine should I use with it?
&lt;/h3&gt;

&lt;p&gt;Today it supports OpenAI Whisper and ElevenLabs Scribe, both cloud and both non-streaming. That's the largest remaining latency item. A streaming engine with partial transcripts and endpointing is the next change we'd make, and we'd measure it in the same lab before recommending one.&lt;/p&gt;

&lt;p&gt;The code, the tests that time frames rather than count them, and the changelog with every measurement above are in the &lt;a href="https://github.com/ictinnovations/asterisk-ai-voice-agent" rel="noopener noreferrer"&gt;asterisk-ai-voice-agent repository&lt;/a&gt;. Issues and corrections are welcome; the best ones so far came from people who'd never seen the code.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Your Logo On It Is Not Multi-Tenancy. Ask What Actually Separates</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Fri, 04 Sep 2026 04:15:43 +0000</pubDate>
      <link>https://dev.to/tahiralmas/your-logo-on-it-is-not-multi-tenancy-ask-what-actually-separates-2225</link>
      <guid>https://dev.to/tahiralmas/your-logo-on-it-is-not-multi-tenancy-ask-what-actually-separates-2225</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ict.vision/white-label-ict-software-tenant-isolation-not-rebranding" rel="noopener noreferrer"&gt;ict.vision&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every white label ICT software demo ends the same way. Your logo appears in the corner, your colours load, your domain resolves, and everyone nods. That part took the vendor an afternoon. It tells you nothing about whether one of your resellers can read another one's customer list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebranding and isolation are different products
&lt;/h2&gt;

&lt;p&gt;The confusion is understandable, because vendors sell them as one thing. They aren't. Rebranding is a presentation concern: logo, palette, domain, login page, invoice header. Isolation is an architecture concern, and it decides who carries the loss when something leaks.&lt;/p&gt;

&lt;p&gt;A platform can be excellent at the first and hopeless at the second. Plenty are.&lt;/p&gt;

&lt;p&gt;The left column is what gets demoed. The right column is what gets litigated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four things that actually have to be separate
&lt;/h2&gt;

&lt;p&gt;Ask about these specifically, and ask to be shown rather than told.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data.&lt;/strong&gt; Not just "tenants have their own accounts" but: what stops a query written for tenant A returning tenant B's rows? If the answer is "the application adds a filter", ask what happens when a developer forgets. That's not a hypothetical failure mode, it's the common one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration and policy.&lt;/strong&gt; Retention periods, consent handling, do-not-call lists and data residency are not global settings in a serious multi-tenant platform. One reseller selling into healthcare and another selling into retail need different answers, and a shared setting means the strictest customer sets everyone's policy or, worse, doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workload.&lt;/strong&gt; If one reseller fires a large campaign, does everybody else's queue slow down? Per-tenant rate limits are boring until the Monday your biggest partner blames you for their missed SLA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visibility.&lt;/strong&gt; Logs, metrics, and your own support desk. When your engineer opens a ticket for reseller A, can they see reseller B's call records? Most platforms fail this one quietly, because admin tooling is built last and scoped loosely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate databases or row-level security
&lt;/h2&gt;

&lt;p&gt;There are two credible answers, and vendors will tell you theirs is obviously correct. Both work. They fail differently, which is the part worth understanding before you sign.&lt;/p&gt;

&lt;p&gt;Neither model is wrong. They just put the risk in different places.&lt;/p&gt;

&lt;p&gt;A database per tenant gives you the strongest wall. A mistaken query can't cross it, restoring one customer's data doesn't touch anyone else, and "where does our data live" has a simple answer. You pay in operations: every migration runs once per tenant, costs climb with each one, and any report spanning the fleet turns into a project.&lt;/p&gt;

&lt;p&gt;Row-level security in one shared database is cheaper and far easier to run. Onboarding a reseller is a row rather than a build. The risk concentrates: one missing policy on one table exposes every tenant simultaneously, and deleting a single customer's data properly takes real care.&lt;/p&gt;

&lt;p&gt;For most white label ICT platforms I'd start with row-level security and treat cross-tenant read tests as non-negotiable, then move the customers who contractually demand it onto their own database. What I wouldn't accept is a vendor who can't tell you which model they use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five questions that get past the demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Show me the query that scopes a tenant. Where is that enforced, in the app or the database?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What happens to everyone else when one tenant sends a million messages this afternoon?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can your support staff see my resellers' data? Prove the scoping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How do I delete one reseller completely, and how long does it take?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which settings are global, and which are per tenant? Show me the list.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answers matter less than whether they arrive quickly. A vendor who has thought about isolation answers these in a minute. One who hasn't will offer to show you the branding again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this lands on you, not the vendor
&lt;/h2&gt;

&lt;p&gt;We've written before about how &lt;a href="https://www.ict.vision/white-label-ict-software-reseller-ai-support-liability/" rel="noopener noreferrer"&gt;white label vendors inherit their resellers' mistakes&lt;/a&gt;. The reverse is also true, and it's the direction that catches resellers out: when a shared platform leaks, your customer's contract is with you. Your name is on the login page. That was the whole point of white labelling, and it cuts both ways.&lt;/p&gt;

&lt;p&gt;This is also why the platform's programmability matters as much as its feature list, something we covered in &lt;a href="https://www.ict.vision/programmable-white-label-platform-resellers/" rel="noopener noreferrer"&gt;why programmable white-label platforms win&lt;/a&gt;. A platform you can't inspect is one you're trusting on faith.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Isn't multi-tenant just a marketing term?
&lt;/h3&gt;

&lt;p&gt;It's used as one, often. Treat it as a claim to be tested rather than a category. The useful question is never "is it multi-tenant" but "what specifically is separated, and enforced where".&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a database per tenant always safer?
&lt;/h3&gt;

&lt;p&gt;It's a stronger wall against query mistakes, yes. It's also more to run, and an under-maintained per-tenant estate with skipped migrations isn't safer than a well-tested shared one. Safety comes from the discipline around the model as much as the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our vendor says row-level security handles it. Is that enough?
&lt;/h3&gt;

&lt;p&gt;It can be, if the policy is enforced by the database rather than remembered by developers, and if there are tests that actively try to read across tenants. Ask to see those tests. If they don't exist, the control is a convention, not a control.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about branding, is that not worth anything?
&lt;/h3&gt;

&lt;p&gt;It's worth plenty commercially. It just isn't evidence of isolation, and it's routinely presented as though it were. Buy the branding, verify the separation.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I check this without an engineering team?
&lt;/h3&gt;

&lt;p&gt;Ask the five questions above and watch how the vendor responds. You don't need to audit the code to notice whether someone has thought about the problem or is hearing it for the first time.&lt;/p&gt;

&lt;p&gt;Your logo on the login page is table stakes and takes an afternoon. What separates your resellers from each other is the thing you're actually buying, so make the vendor show you that part.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop Counting Deflection. Start Counting Resolution</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:31:19 +0000</pubDate>
      <link>https://dev.to/tahiralmas/stop-counting-deflection-start-counting-resolution-id1</link>
      <guid>https://dev.to/tahiralmas/stop-counting-deflection-start-counting-resolution-id1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictdesk.net/blog/ai-support-deflection-vs-resolution-2026/" rel="noopener noreferrer"&gt;ictdesk.net&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the trap a lot of support teams are walking into with AI this year. Deflection rate looks great on a dashboard, ticket volume drops, the bot handled it, everyone claps. But deflection only counts the conversations a human did not touch. It says nothing about whether the customer's problem got solved. Optimise for the wrong number and you can cut volume while your satisfaction score quietly falls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two numbers are not the same
&lt;/h2&gt;

&lt;p&gt;Deflection counts a ticket a human skipped. Resolution counts a problem that got solved. A bot can deflect a conversation by answering confidently and wrongly, and the customer gives up rather than pushing through, which shows up as a win and a lost customer at the same time.&lt;/p&gt;

&lt;p&gt;This is not a small risk. Forrester expects about three in ten firms to actually damage their customer experience in 2026 through poorly built AI self-service, and surveys keep finding that most customers wish companies would stop bolting AI onto support. The technology is not the problem. Measuring the wrong thing is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes AI actually resolve a ticket
&lt;/h2&gt;

&lt;p&gt;The teams reporting the highest real resolution rates all describe the same handful of things, and none of them is a cleverer way to avoid a human.&lt;/p&gt;

&lt;p&gt;First, the agent is &lt;strong&gt;grounded in your knowledge&lt;/strong&gt;, so answers come from your docs and past tickets rather than a guess. Second, it can &lt;strong&gt;take action&lt;/strong&gt;, look up an order, reset a setting, change a status, instead of only describing the steps. Third, it does a &lt;strong&gt;clean handoff&lt;/strong&gt; when it is stuck, passing the full context to a person early rather than looping the customer. And fourth, you &lt;strong&gt;measure resolution and satisfaction&lt;/strong&gt;, not deflection, so a bad answer cannot hide as a ticket the bot technically handled.&lt;/p&gt;

&lt;h2&gt;
  
  
  What good looks like
&lt;/h2&gt;

&lt;p&gt;Set expectations honestly. Early AI deployments land around thirty to fifty percent resolution, mature workflows reach fifty to seventy, and deeply integrated agents that can take action hit seventy to eighty-five on well-scoped issues. Those are resolution numbers, not deflection numbers, and they only get there when the AI is wired into your systems and your &lt;a href="https://ictdesk.net/" rel="noopener noreferrer"&gt;help desk&lt;/a&gt; is treated as a knowledge and process problem, not just a software purchase.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is deflection a useless metric?&lt;/strong&gt; Not useless, just incomplete. It tells you volume moved off humans. Pair it with resolution and CSAT so you know the volume moved because problems got solved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does chasing deflection hurt CSAT?&lt;/strong&gt; Because it rewards answering rather than solving. A confident wrong answer counts as a deflection and leaves a frustrated customer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a realistic AI resolution rate?&lt;/strong&gt; Thirty to fifty percent early on, up to seventy to eighty-five for integrated, action-taking agents on well-defined problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does an AI agent replace my team?&lt;/strong&gt; No. It resolves the well-scoped, repetitive tickets and hands the rest to people with full context, which is where the good numbers come from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I start measuring resolution?&lt;/strong&gt; Tag whether the customer's issue was actually solved, tie it to a follow-up satisfaction signal, and stop treating an untouched ticket as a solved one.&lt;/p&gt;

&lt;p&gt;Deflection is easy to celebrate and easy to fake. Resolution is the one your customers actually feel, so it is the one worth building around.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Toll Fraud Can Drain a Weekend. Here Is How to Lock Down Your PBX</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:31:04 +0000</pubDate>
      <link>https://dev.to/tahiralmas/toll-fraud-can-drain-a-weekend-here-is-how-to-lock-down-your-pbx-4k5a</link>
      <guid>https://dev.to/tahiralmas/toll-fraud-can-drain-a-weekend-here-is-how-to-lock-down-your-pbx-4k5a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictpbx.com/pbx-toll-fraud-sip-security-2026/" rel="noopener noreferrer"&gt;ictpbx.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The most expensive thing that can happen to a PBX is not downtime, it is a phone bill you did not make. Toll fraud is automated, it runs while your office is closed, and a single quiet weekend can cost five figures. The good news is that the attack is predictable, so the defense can be too. Here is how it works and the layers that stop it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the attack works
&lt;/h2&gt;

&lt;p&gt;None of this is clever. Bots sweep the internet looking for SIP on port 5060, brute-force weak extension passwords, register as a legitimate extension once they get in, and then dial premium-rate and international numbers they profit from. The chain runs in minutes and repeats all night.&lt;/p&gt;

&lt;p&gt;The numbers are worth sitting with. Telecom fraud losses run to roughly thirty-nine billion dollars a year, and many victims only find out when an automated carrier alert fires after the damage is done. If your PBX faces the public internet with default settings, you are already on the list that gets scanned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six layers that stop it
&lt;/h2&gt;

&lt;p&gt;No single setting is enough, but stacked together they turn a cracked password into a dead end instead of an open account.&lt;/p&gt;

&lt;p&gt;Start with &lt;strong&gt;strong SIP passwords&lt;/strong&gt;, long and random, one per extension, with no defaults and no using the extension number as the password. Then &lt;strong&gt;close port 5060&lt;/strong&gt; to the open internet: firewall it, allow-list the IPs that need it, or put a session border controller in front. &lt;strong&gt;Require authentication on outbound calls&lt;/strong&gt; so the system fails closed rather than open. Add &lt;strong&gt;per-tenant spend caps and rate limits&lt;/strong&gt; so a compromise cannot run up an unbounded bill. &lt;strong&gt;Block high-risk destinations&lt;/strong&gt; by default, the premium-rate ranges and countries you never call. And &lt;strong&gt;watch for spikes&lt;/strong&gt;, odd volume, off-hours calling, or a brand new destination, with the ability to auto-suspend an extension that starts behaving like a bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-tenant raises the stakes
&lt;/h2&gt;

&lt;p&gt;If you run a &lt;a href="https://ictpbx.com/" rel="noopener noreferrer"&gt;multi-tenant PBX&lt;/a&gt;, one weak tenant should never be able to spend the whole platform's credit. Caps, route rules and monitoring belong at the tenant level, not just the system level, so a single careless customer cannot become everyone's problem. This is exactly the kind of control a white-label platform needs to make its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does STIR/SHAKEN stop toll fraud?&lt;/strong&gt; No. It targets caller ID spoofing on the network, not fraudulent outbound calls from a compromised PBX. You still need the layers above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How fast can a weak password be cracked?&lt;/strong&gt; Automated tools test thousands of combinations, and a default or weak password often falls within minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the single most important step?&lt;/strong&gt; Getting SIP off the open internet. Closing or filtering port 5060 removes most of the automated attack surface at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do spend caps matter if I have strong passwords?&lt;/strong&gt; Because they bound the damage if anything else slips. A cap turns a five-figure weekend into a small, contained alert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How would I even know it is happening?&lt;/strong&gt; Only if you watch for it. Spike alerts on volume and destination are what turn a silent bill into an early warning.&lt;/p&gt;

&lt;p&gt;Toll fraud is a numbers game the attackers play at scale. Make your PBX a bad bet, and they move on to an easier one.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>We built an open source MCP server for ICTFax</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Wed, 02 Sep 2026 09:25:19 +0000</pubDate>
      <link>https://dev.to/tahiralmas/we-built-an-open-source-mcp-server-for-ictfax-2ogk</link>
      <guid>https://dev.to/tahiralmas/we-built-an-open-source-mcp-server-for-ictfax-2ogk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ictfax.org/ictfax-mcp-open-source-mcp-server/" rel="noopener noreferrer"&gt;ictfax.org&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can now hand your fax server to an AI assistant. We published &lt;strong&gt;ictfax-mcp&lt;/strong&gt;, a small open source server that speaks the Model Context Protocol, so a tool like Claude can list your fax transmissions, check whether one went through, and, when you let it, send a fax from a document on your machine. It is on npm and GitHub today, and you run it with one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;An MCP server hands an AI assistant a set of tools it can call on its own. ictfax-mcp gives it four. Two are read-only and always on: list the fax transmissions on your server, and get the status of a single one so you can see if it is processing, delivered, or failed. The other two change things, so they stay off until you turn them on: upload a document, and send a fax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending a fax, in one call
&lt;/h2&gt;

&lt;p&gt;Ask the assistant to fax a PDF to a number and the server handles the whole chain for you. It uploads the document and gets an id back, creates the recipient, builds the transmission, and sends it. Then you can poll the status tool to watch it deliver. ICTFax does the hard part underneath, converting the file and pushing it over T.38.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read-only until you decide
&lt;/h2&gt;

&lt;p&gt;Sending a fax dials a real number and can cost money, so the server ships read-only. The two write tools, upload and send, are not even registered unless you set one environment variable, &lt;code&gt;ICTFAX_MCP_ALLOW_WRITE=true&lt;/code&gt;. Reading fax status and history needs no switch and never dials anyone. It signs in with your own ICTFax account, using the same JWT the web app uses, and your password never leaves the machine you run it on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;If you have Node 18 or newer, you do not need to install anything first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; ictfax-mcp&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point it at your server with three settings, &lt;code&gt;ICTFAX_BASE_URL&lt;/code&gt;, &lt;code&gt;ICTFAX_USERNAME&lt;/code&gt; and &lt;code&gt;ICTFAX_PASSWORD&lt;/code&gt;, then add it to your MCP client. The README on GitHub has a config block you can paste in. New to the server itself? The &lt;a href="https://www.ictfax.org/" rel="noopener noreferrer"&gt;ICTFax&lt;/a&gt; project is free and open source, and the &lt;a href="https://www.ictfax.org/ictfax-installation-guide/" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; or the Docker image will get you running in a few minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is it really free?&lt;/strong&gt; Yes. MIT licensed, source on GitHub, package on npm and listed in the MCP registry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which assistants work with it?&lt;/strong&gt; Any MCP client. We test with Claude, but the protocol is open, so others work the same way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can it send a fax without me knowing?&lt;/strong&gt; No. Sending is opt-in through a single environment variable. Leave it off and the server can only read fax status and history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What formats can it fax?&lt;/strong&gt; PDF is the common one. You upload a local file and ICTFax converts it for transmission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it work with the Docker image?&lt;/strong&gt; Yes. Point it at any reachable ICTFax install, including the official Docker one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do I report a bug?&lt;/strong&gt; Open an issue on the GitHub repo. We read them.&lt;/p&gt;

&lt;p&gt;ictfax-mcp is one of several MCP servers we are opening up across our stack, from PBX to contact center to exams. If you run other ICT products, more are already out.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AI Mental Health Chatbots Now Have Rules. Here Is What They Ask For</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Tue, 01 Sep 2026 18:04:51 +0000</pubDate>
      <link>https://dev.to/tahiralmas/ai-mental-health-chatbots-now-have-rules-here-is-what-they-ask-for-2ajm</link>
      <guid>https://dev.to/tahiralmas/ai-mental-health-chatbots-now-have-rules-here-is-what-they-ask-for-2ajm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://roshni.online/new-ai-mental-health-chatbot-rules-2026/" rel="noopener noreferrer"&gt;roshni.online&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI mental health chatbots now have rules. In the first part of 2026, dozens of states introduced more than seventy bills covering them, and the common threads are clear: a chatbot has to tell you it is AI, it must not pretend to be a licensed professional, it needs to spot a crisis and point you to real help, and it has to take extra care with young people. If you use one of these tools, that is good news, and here is what it means in plain terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the rules arrived now
&lt;/h2&gt;

&lt;p&gt;Support chatbots grew fast because they answer at any hour and cost nothing to open. That reach is genuinely useful for everyday stress, low mood, and learning coping skills. The worry is what happens at the edges. Studies through 2026 found that general and specialist models both handle ordinary conversations reasonably well, yet respond unevenly when someone raises self-harm. Lawmakers looked at that gap and wrote it into rules. One example, New York's S 3008, requires systems that simulate human-like conversation to detect signs of suicidal thinking, offer crisis resources, and remind people they are talking to AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the new rules ask for
&lt;/h2&gt;

&lt;p&gt;The details vary by bill, but four requirements keep coming up. First, disclosure: the tool has to say it is a program, not a person, and repeat that during longer chats. Second, no false credentials: it must not present itself as a therapist, doctor, or lawyer, or imply a professional relationship it cannot hold. Third, crisis handling: it has to recognise signs of self-harm or suicidal thinking and steer the person to crisis resources and human help. Fourth, care for minors: added protections when the user may be a child, from clearer disclosure to firmer limits on what the tool will do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What good crisis handling looks like
&lt;/h2&gt;

&lt;p&gt;The most important rule is the crisis one, because that is where a wrong answer does the most harm. A responsible tool checks each message for risk before it replies, and it changes course the moment something serious shows up.&lt;/p&gt;

&lt;p&gt;When there is a sign of crisis, safety comes first. The tool surfaces crisis lines and local resources, offers a warm handoff to a person, and stays honest that it is AI rather than a clinician. When there is no such sign, it can listen, suggest coping steps and next actions, and keep reminding you that a human is available whenever you want one. The point is not to replace a therapist. It is to be safe, honest, and always one step away from real help.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this shapes what we build
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://roshni.online/" rel="noopener noreferrer"&gt;Roshni&lt;/a&gt;, this is how the AI support is meant to work day to day. It is clear that you are speaking with an assistant, it does not claim to be a licensed clinician, and it keeps a human path open for anything heavy. The same care runs through the &lt;a href="https://roshni.online/" rel="noopener noreferrer"&gt;legal guidance&lt;/a&gt; side, where the assistant offers general information and points you to a qualified professional for advice on your specific situation. Rules like these are not a burden on tools built this way. They describe what they already try to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you are in crisis right now
&lt;/h2&gt;

&lt;p&gt;If you or someone you know is in immediate danger, contact your local emergency services or a crisis line in your area straight away. An AI tool, ours included, is a starting point and a bridge to help, not a substitute for emergency care or a trained professional.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Are AI mental health chatbots safe to use?&lt;/strong&gt; For everyday support like coping skills and low-risk moments they can help, especially because they are available any time. For a crisis, they should hand you to human help, and the new rules push them to do exactly that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will an AI tool tell me it is not a therapist?&lt;/strong&gt; Under the new rules it has to. A responsible tool discloses that it is AI and does not present itself as a licensed professional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should a chatbot do if I mention self-harm?&lt;/strong&gt; Recognise it, put safety first, show crisis resources, and offer a path to a real person, rather than trying to counsel you through it alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do the rules cover young users?&lt;/strong&gt; Yes. Many bills add specific protections for minors, including clearer disclosure and tighter limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a chatbot give me legal advice?&lt;/strong&gt; It can share general information, but for advice on your own situation you should speak with a qualified professional. The disclosure rules exist so tools are honest about that line.&lt;/p&gt;

&lt;p&gt;The through line across all of these bills is simple. Be honest about what you are, be careful when it matters most, and keep a human within reach. That is a fair bar, and it is the bar worth holding any AI support tool to.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Your Calls Pass STIR/SHAKEN and Still Say Spam Likely. Here Is Why</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Tue, 01 Sep 2026 18:04:03 +0000</pubDate>
      <link>https://dev.to/tahiralmas/your-calls-pass-stirshaken-and-still-say-spam-likely-here-is-why-1ll9</link>
      <guid>https://dev.to/tahiralmas/your-calls-pass-stirshaken-and-still-say-spam-likely-here-is-why-1ll9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ictbroadcast.com/stir-shaken-still-spam-likely-number-reputation-2026/" rel="noopener noreferrer"&gt;ictbroadcast.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your outbound numbers pass STIR/SHAKEN with A-attestation and still show up as "Spam Likely," you are not doing anything wrong with the standard. Attestation proves the number is really yours. It says nothing about how you dial it. The label comes from a separate carrier reputation model, and that model watches your call patterns, not your signature. Fix the patterns and the label follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attestation and reputation are two different things
&lt;/h2&gt;

&lt;p&gt;A lot of teams treat STIR/SHAKEN as a spam-label fix. It isn't one. The framework lets a carrier vouch that the caller ID hasn't been spoofed. A-attestation means your provider knows you and confirmed you own the number. That's real value, and you want it. But the phone that rings is a mobile handset running its own screening, fed by the carrier's analytics engine, and that engine scores every number on behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the carrier model actually looks at
&lt;/h2&gt;

&lt;p&gt;Every carrier keeps its own recipe, but the signals overlap. High call volume from one number in a short window reads like a spam run. A large share of very short calls, the kind that end in a second or two, looks like people declining a robocall. Complaint reports weigh heavily, and so do repeated hits against numbers that have gone quiet or landed on do-not-call lists. Roll those together and you get a rolling reputation score. Cross a threshold and the label flips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six habits that keep numbers clean
&lt;/h2&gt;

&lt;p&gt;Reputation is earned by how you run campaigns, day after day. These are the levers that matter, and good &lt;a href="https://www.ictbroadcast.com/" rel="noopener noreferrer"&gt;call center software&lt;/a&gt; should make each one easy rather than manual.&lt;/p&gt;

&lt;p&gt;Start with pacing. Keep abandonment low and close the silent gap between an answer and an agent, because that gap is the single clearest robocall tell. Scrub your lists against do-not-call and known-litigator data before the campaign runs, not after a complaint arrives. Add branded caller ID through Rich Call Data so your name and logo show on the screen, which lifts answer rates and shortens the guessing that leads to declines. Spread volume across numbers sensibly instead of burning one line. Track spam-label and complaint rates per number so you can pull a number before it drags the rest down. And respect calling windows and per-day contact caps by region.&lt;/p&gt;

&lt;p&gt;None of this replaces STIR/SHAKEN. It sits alongside it. The standard gets you in the door with the carrier, and clean dialing keeps you welcome. For the mechanics of pacing and abandonment specifically, our guide on &lt;a href="https://www.ictbroadcast.com/how-predictive-dialers-work-2026-pacing-abandonment-compliance/" rel="noopener noreferrer"&gt;how predictive dialers work in 2026&lt;/a&gt; goes deeper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovering a flagged number
&lt;/h2&gt;

&lt;p&gt;If a number is already labeled, rest it. Move active campaigns to healthier lines, stop the behavior that triggered the score, and let the rolling average recover, which usually takes weeks rather than days. Some carriers and analytics providers accept remediation or registration requests, and branded caller ID registration can help re-establish trust. What does not help is rotating to a fresh number and repeating the same pattern, because you'll just train the model again.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does A-attestation guarantee my calls connect?&lt;/strong&gt; No. It confirms the number is yours and unspoofed. The carrier's reputation model still decides the label based on your calling behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do short calls hurt me?&lt;/strong&gt; A high ratio of one or two second calls looks like people rejecting a robocall, which is one of the strongest spam signals a carrier tracks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is branded caller ID the same as STIR/SHAKEN?&lt;/strong&gt; No. STIR/SHAKEN authenticates the number. Branded caller ID, delivered through Rich Call Data, displays your name and logo. They work best together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long until a flagged number recovers?&lt;/strong&gt; Reputation is a rolling average, so plan on weeks of clean, low-volume dialing rather than an overnight reset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I just keep buying new numbers?&lt;/strong&gt; No. If the dialing pattern doesn't change, new numbers get flagged too. Fix the pattern first.&lt;/p&gt;

&lt;p&gt;The short version: STIR/SHAKEN is table stakes, not a shield. Your numbers stay clean when your dialing looks like a business talking to people who want to hear from it.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
