Why We Evaluated jambonz for SIP Voice Agents
We wanted to answer a procurement question, not win a framework argument: how much infrastructure does a telephone-based voice agent need before orchestration becomes the expensive part?
At tens of concurrent calls, SDK coverage and implementation speed can dominate the decision. At hundreds, process count, audio conversion, scheduling, and operational overhead deserve their own budget line. That is where jambonz becomes interesting.
Underneath its application interface, jambonz separates SIP signaling, media handling, and call orchestration. Drachtio handles signaling; the SBC tier relays media; the feature server executes application instructions. The shared compiled media-server architecture differs materially from the per-job agent subprocess architecture evaluated on the LiveKit side.
Our review has an important boundary: we inspected the supplied source material and designed the reproduction workflow; we did not run jambonz, LiveKit, or Docker in this review environment. There are no independent Effloow capacity measurements below. We will not convert a vendor result into a fictional lab observation.
The result we are evaluating comes from the jambonz benchmark whitepaper: approximately 250 simultaneous voice-agent sessions versus 25 on identical 8-vCPU allocations.
That is compelling enough to investigate. It is not sufficient to base an infrastructure purchase on.
The tested workload matters: real SIP and RTP, G.711 audio, an eight-turn conversation lasting roughly three minutes, and deterministic replacements for speech and language-model vendors. A session had to complete the expected conversation. Sustainable capacity required both:
- Fewer than 0.5% failed calls.
- P95 turn latency no more than 25% above that platform’s unloaded baseline.
We therefore treat “10x” as a specific vendor-run telephony-agent result—not a general ranking of media platforms, and not a prediction for every production voice application.
Proposed Setup and SIPp Smoke-Test Workflow
Our first reproduction gate is hardware identity. “Eight vCPUs” is not a hardware specification. We need CPU model, topology, RAM, storage, kernel, virtualization, container versions, and service placement.
In our review, we could verify the matched vCPU allocations but not the complete hardware specifications. We therefore cannot supply an identical-hardware shopping list.
We would start by inspecting the published benchmark repository, then pinning its revision:
git clone https://github.com/jambonz/jambonz-livekit-load-testing-benchmarks.git cd jambonz-livekit-load-testing-benchmarks git rev-parse HEAD find . -maxdepth 4 -type f \ \( -iname '*compose*.yml' -o -iname '*compose*.yaml' \ -o -iname '*.env*' -o -iname '*readme*' \) -print These are inspection commands, not an assertion that a particular Compose file exists. The reviewed material does not include a verified Docker installation procedure, image tags, complete configuration, or concrete environment-variable contract.
That distinction prevents a common failure in infrastructure articles: presenting plausible YAML as a working distribution.
The stack we need to verify
For the requested Docker reproduction, our inventory would include:
| Component | Purpose | Configuration we must establish |
|---|---|---|
| Drachtio | SIP signaling | Listen address, advertised address, control authentication |
| SBC applications | Routing and admission | Carrier matching, application routing, service discovery |
| rtpengine | SBC media relay | Interfaces, advertised addresses, control endpoint, UDP allocation |
| FreeSWITCH with required jambonz modules | Feature-side media processing | SIP profile, media addresses, RTP allocation, control credentials |
| Feature server | Call-flow orchestration | Media-server access, database/cache access, application endpoint |
| MySQL and Redis | Configuration and ephemeral state | Schema initialization, credentials, persistence |
| Application and vendor mocks | Conversation behavior | Protocol adapters, audio format, deterministic turn timing |
We could map the logical architecture, but could not verify the exact FreeSWITCH/rtpengine image combination or environment-variable names for a current Compose deployment. We would extract those from the pinned deployment artifacts rather than invent variables such as a generic PUBLIC_IP and assume every service consumes them.
Once we select an authoritative Compose file, our installation sequence would be:
- Create its environment file using that revision’s template.
- Configure signaling and media addresses separately.
- Allocate non-overlapping media-port ranges where services share a host.
- Set database, Redis, signaling-control, and media-control credentials.
- Configure application routing and the protocol-compatible mock endpoints.
- Validate the rendered configuration before starting services.
The command pattern is straightforward:
# Run from the verified deployment directory after editing its environment file. docker compose --env-file .env config --quiet docker compose --env-file .env pull docker compose --env-file .env up -d docker compose --env-file .env ps docker compose --env-file .env logs --tail=100 This is deliberately not a turnkey jambonz installer. Without the verified deployment files, it cannot be one.
We also identified two commercial prerequisites in our self-hosting review: a required license key and a self-hosted trial limited to ten sessions. Before planning a 250-call test, we would obtain authorization for that capacity. Local mocks remove speech-vendor credentials; they do not eliminate platform licensing.
SIPp UAC/UAS smoke test
Before connecting a platform, we would verify the load generator independently. SIPp includes built-in UAC and UAS scenarios, so this Linux example needs no invented XML fixtures:
#!/usr/bin/env bash set -euo pipefail command -v sipp >/dev/null sipp -v mkdir -p artifacts # Start a local answering endpoint using SIPp's built-in UAS scenario. sipp -sn uas -i 127.0.0.1 -p 5090 \ >artifacts/uas.log 2>&1 & uas_pid=$! trap 'kill "$uas_pid" 2>/dev/null || true' EXIT sleep 1 # Generate up to ten simultaneous calls, holding each for five seconds. sipp 127.0.0.1:5090 \ -sn uac -i 127.0.0.1 -p 5080 \ -r 2 -rp 1000 -l 10 -m 20 -d 5000 \ -timeout 30s -timeout_error \ -trace_err -trace_stat \ -stf artifacts/uac-stats.csv \ >artifacts/uac.log 2>&1 printf 'SIPp signaling smoke test finished; inspect artifacts/.\n' Illustrative output—not an observed run:
SIPp signaling smoke test finished; inspect artifacts/. Here, -r controls call arrival rate, -l caps simultaneous calls, and -m caps total calls. Twenty calls can check wiring; they cannot establish a production failure rate.
More importantly, this is signaling, not the eight-turn media benchmark. A 200 OK and a clean BYE do not prove intelligible bidirectional audio.
For a platform test, the UAC must address the configured SBC route. A separate UAS is useful for an outbound termination test; it is not automatically part of an inbound agent conversation. We would then add validated RTP fixtures, receive-side audio checks, and a turn-level observer. The reviewed evidence does not establish that the vendor’s generator is SIPp, so replacing it with SIPp creates a new harness requiring equivalence checks.
Our capacity ladder would preserve the conversation, codec, baseline definition, and failure criteria. We would separate call-arrival stress from steady concurrency and run the generator and mocks outside the measured platform allocation—or explicitly account for them.
What We Could Not Verify and What We Would Test
The immediate roadblock in our review was reproducibility, not a demonstrated runtime crash. We lacked the complete pinned deployment, hardware manifest, and raw configuration needed to execute an equivalent test.
We also found several places where a superficially successful deployment could produce a misleading benchmark. These are acceptance checks we would apply, not incidents we observed.
Docker networking can pass SIP while losing audio. SIP messages advertise media destinations through SDP. A reachable signaling port does not make an internal container address reachable by the caller. We would inspect SDP and packet captures at both sides before increasing concurrency.
STUN is not a universal repair for this. Discovering an external address does not automatically configure drachtio, FreeSWITCH, rtpengine, firewall rules, and container publishing consistently. Our preferred first baseline would use native Linux with an explicit networking model. Host networking can simplify address translation, but introduces port conflicts and reduces isolation.
RTP allocation must be explicit. There is no verified port range in the reviewed configuration. We would select ranges in the actual media services, permit the corresponding UDP traffic, and reserve headroom for RTP/RTCP behavior, multiple legs, and renegotiation. We would not calculate capacity by assuming “one port per call.”
CPU pinning is an experiment, not free performance. We would compare an unpinned baseline with controlled CPU placement for media services. Pinning the whole shared media process to one core could impose a bottleneck rather than remove one. CPU quotas, interrupt placement, SMT siblings, and competing services all belong in the run manifest.
Memory needs a slope and a recovery test. We have no measured per-call RSS figure. Our plan is to sample idle, steady-state, and post-drain memory across several concurrency levels. For a multi-process architecture, summing RSS can double-count shared pages; PSS and container-level memory provide useful cross-checks. A rising post-drain floor deserves investigation before we call it a leak.
Codec choice changes the work. The supplied result uses G.711. We would keep PCMU/PCMA choice, packetization interval, and conversion paths fixed. A WebRTC/Opus or transcoding-heavy deployment is a different workload.
Recording and real streaming remain unproven extensions. The benchmark already includes mocked speech-service interactions; it is not simply an idle-call test. However, mocks cannot establish real-provider backpressure, network variation, reconnect behavior, or quota handling.
We cannot identify a measured point where the 10x ratio stops holding. Instead, we would run paired tests with protocol-compatible streaming mocks, real speech services, and recording enabled. Recording adds encoding, buffering, disk or upload work, and retention obligations. Comparing recorded jambonz calls with unrecorded LiveKit calls would invalidate the comparison.
Finally, we would instrument the application webhook/WebSocket tier. In our benchmark review, we identified an important deployment lesson: the small Node application became a bottleneck before the platform did. A fast media server cannot rescue a blocked application event loop.
Scale, Latency & Cost vs. Alternatives
We use the following numbers as vendor benchmark results, not Effloow measurements:
| Configuration | Allocated vCPUs | Sustainable sessions | Sessions per vCPU |
|---|---|---|---|
| jambonz, single node | 8 | Approximately 250 | 31.3 |
| LiveKit, single node | 8 | Approximately 25 | 3.1 |
| jambonz, medium reference cluster | 16 | Approximately 450 | 28.1 |
| LiveKit, two agent servers/reference topology | 16 | Approximately 35 | 2.2 |
| LiveKit, four agent servers | 24 | Approximately 70 | 2.9 |
The denominator includes allocated orchestration, database, and cache capacity, not just busy media cores. We would preserve that accounting when comparing our own deployments.
In our review of the benchmark’s CPU sampling, we found support for an architectural explanation: approximately 0.014 cores per session in jambonz’s shared media process versus 0.11 in LiveKit’s per-call agent processes. It does not imply that dividing eight cores by 0.014 predicts whole-system capacity. Other services, scheduling, and the latency threshold constrain the deployment.
For latency, we found an approximately 96 ms lower median for jambonz in our review of the benchmark’s matched-load comparison with identical off-box turn detection. Capacity, however, uses p95 relative to each platform’s own baseline. Passing that threshold does not prove either platform satisfies our absolute product latency target.
We also would not compare against an untuned straw man. In our review, we checked that the benchmark included a fix for a LiveKit configuration initially limited to roughly 14 sessions, along with tests of multiple variants. That improves confidence in the experiment’s intent without making it independent replication.
A break-even calculation we can actually use
Consider a hypothetical 100-concurrent-call workload and an 8-vCPU node costing $0.40 per hour. Applying the published single-node capacities, with no redundancy or safety margin:
- jambonz: one node, or $0.40/hour.
- LiveKit: four nodes, or $1.60/hour.
- Infrastructure difference: $1.20/hour, or $876 across 730 continuously provisioned hours.
Those are illustrative prices and arithmetic, not deployment quotes.
At full stated capacity, the infrastructure-only cost per occupied call-minute would be:
jambonz: $0.40 / (250 × 60) = $0.0000267 LiveKit: $0.40 / (25 × 60) = $0.0002667 Those tiny numbers explain why a dramatic density ratio can still be a modest business saving. Licensing, engineering time, spare capacity, monitoring, storage, and incident response can outweigh compute.
Our break-even condition is:
avoided platform compute cost > incremental licensing + operations + amortized migration cost For CPaaS replacement, we would compare against avoidable platform charges—not the entire telephone bill. SIP trunks, numbers, carrier minutes, speech models, and compliance work do not disappear through self-hosting.
For browser/mobile SDKs, video, or multiparty rooms, we would keep LiveKit in the evaluation. This benchmark measures telephone-originated voice-agent density, not the value of its broader platform.
Our Final Verdict: When to Deploy, When to Skip
Our verdict: shortlist jambonz for high-concurrency SIP voice agents, but do not base a budget on the 10x headline until we reproduce the result with the target workload.
We would deploy after a successful pilot if:
- Telephony is the primary entry point.
- Sustained concurrency makes media-plane efficiency financially meaningful.
- The team can operate SIP routing, RTP networking, and application services.
- Licensing permits the required capacity.
- Recording, streaming speech, and failover pass the same quality gates.
- Measured savings survive redundancy and operational overhead.
We would hold off if:
- The requirement is merely to make a handful of calls quickly.
- Browser/mobile media features dominate the roadmap.
- Nobody owns one-way-audio diagnosis and carrier interoperability.
- The business case assumes self-hosting removes carrier or model charges.
- The purchase depends on treating mocked-provider results as production guarantees.
The next useful artifact is not another concurrency headline. It is a pinned deployment, hardware manifest, media-validating harness, raw turn timestamps, and an operations budget.
We would use the published benchmark repository as the starting point, keep alternative infrastructure options in our tools collection, and turn the unresolved items into a scoped architecture review. That is how we would move this from an interesting architecture result to a deployment decision.

