v0.49.10
thevoiceguy/siphon-aiv0.49.10Aug 22, 2026by github-actions[bot]
Upgrading?
AI Summary
Fixes the inbound call path to return `503 Service Unavailable` instead of `500 Server Internal Error` when the RTP port pool is exhausted. Adds a new metric to track capacity rejections separately and completes load test documentation.
Key Highlights
- RTP port pool exhaustion now returns `503` with `Retry-After` instead of `500`.
- New metric label `siphon_ai_invites_total{result="rejected_capacity"}` added.
- Documentation for outbound origination, mixed-direction load, and soak tests is now complete.
Full Release Notes
One fix to the **inbound** call path: a full RTP port pool is a capacity
condition, and it now answers like one. Relevant to any node that can run
out of ports — most sharply to a node that also originates, since both
directions draw from the same pool.
**No config change, no protocol change** (still `1`), CDR still v8. Adds
one metric label. Wants a restart to take effect.
### Fixed
- **An exhausted RTP port pool now answers `503 Service Unavailable` +
`Retry-After`, not `500 Server Internal Error`** (#554). A full pool is a
capacity condition — the daemon is working exactly as configured and is
simply out of ports — but `500` tells the peer we have a defect. RFC 3261
§21.5.4 reserves `503` for temporary overload, a proxy that receives one
SHOULD try the next server in its target set, and carrier SBCs treat the
two very differently. It was also inconsistent with this daemon's own
overload signalling: `[sip.admission]` and the drain path already answer
`503` + `Retry-After`, so alerting built on "503 means siphon-ai is full"
got an internal-error page instead.
* `forge-core` has always distinguished `ForgeError::ResourceLimit`;
`media-glue` was flattening every forge error into one stringly
`SetupError::Session`, so the acceptor could not tell capacity from a
fault. The type now survives the conversion.
* The delayed-offer path hardcoded `500` and the flat `rejected` label
instead of consulting the shared status table, so a delayed-offer
caller got a different answer to the same condition. It routes through
the table now.
* New metric label **`siphon_ai_invites_total{result="rejected_capacity"}`**,
separately alertable from `rejected`. On a node that also originates the
pool is shared with outbound, so this counter is the only inbound-side
signal that a surge in the other direction is refusing calls — see
`test-harness/load/RESULTS-0.49.9-mixed-and-soak.md` §2.1. That pool
has no reservation between the two directions, which is tracked
separately as #556; this release changes only the answer given, not
who gets a port.
### Documentation
- **The load test plan is complete — outbound origination, a live-carrier
tier, both directions at once, and a churning soak** (#553). Everything
through §11 loaded *inbound* only, which is how a per-originated-call
dialog leak (#548) reached production unnoticed; the plan now has a §12
outbound phase, §13 mixed-direction phase, and the tier-3 live run its
three tiers always promised. Three results documents land with it:
- `RESULTS-0.49.9-outbound.md` — origination under load. `fds = 13 + 3N`
and two RTP ports per originated leg; a 60-minute churning soak
(2,995 placed / 2,995 completed / 0 failed / 0 WARN) whose RSS growth
is **arena, not a leak** — the same drained process absorbed 750 more
calls at 1.9 KB each where a leak needed ~16 MB.
- `RESULTS-0.49.9-tier3.md` — 60 sequential calls over a live Twilio
trunk (TLS + SRTP), out through the carrier and back in to the same
node. MOS p50 **4.435**, jitter p50 **1.94 ms**, loss **0.001 %**,
setup p95 **460 ms**. It also proves the tier-2 netem model
*pessimistic*: live jitter 1.94 ms against netem's 3.35, live loss
0.001 % against 0.495 %.
- `RESULTS-0.49.9-mixed-and-soak.md` — the two directions compose
linearly (`fds = 13 + 3N`, `udp_sockets = 2N + 1`, N the *total*), and
the shared RTP pool is first-come-first-served with **no reservation
between them** (#556). Running the exhaustion test in the reverse
order is what found #554, fixed above.
---
**Artifacts**
- Static musl binaries for `x86_64` and `aarch64` (`.tar.gz`).
- Debian packages (`.deb`) for `amd64` and `arm64` — `apt install ./siphon-ai_*_amd64.deb`.
- `SHA256SUMS` — verify with `sha256sum -c SHA256SUMS`.
- `*-sbom.cdx.json` — CycloneDX SBOM.
- `SHA256SUMS.cosign.bundle` — cosign keyless signature over the checksums.
- Container: `ghcr.io/thevoiceguy/siphon-ai:v0.49.10` (multi-arch, cosign-signed).
Verify the checksums signature:
```
cosign verify-blob --bundle SHA256SUMS.cosign.bundle \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp 'release.yml@' SHA256SUMS
```
Related Repositories
A cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build & Hermes Agent. Only official website: ccswitch.io
An open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer.
Build smaller, faster, and more secure desktop and mobile applications with a web frontend.
π RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video.
Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one