My AI Agent Called My iPhone: What a Linphone Field Test Proved
A hands-on Linphone SIP test: an isolated worker called an iPhone, held a short spoken exchange through speech-to-text and text-to-speech, and was triggered once by a real Hermes turn hook while the phone was locked. What worked, what broke, and what remains unproven.

A coding agent that can send a chat message is useful. One that can ring a phone is a different class of interruption, so it needs a stricter contract.
I tested that idea with my own iPhone. A worker running in an isolated container called the Linphone app, spoke to me, listened to my replies, and answered. One call was started by a real Hermes agent-turn hook while the phone was locked. I did not connect Claude Code or Codex, and I did not install any permanent hook.
This article covers what that test established, the bugs I hit, and the design I would use before letting an agent call anyone.
What the test established
The caller used Liblinphone’s official Python wrapper, version 5.5.39, inside a Python 3.12 Docker image. The vendor documents the Python wrapper as available for Linux x86_64, and the wheel I used was a CPython 3.12 build.[2] The image needed one extra OS library, libmariadb3, before the SDK imported.
The worker made only authorized calls, one at a time, to one approved destination. It kept no recording and did not redial. Within those limits, I observed:
- A one-way call. The SDK state log showed ringing, connected and streams running. A generated announcement played and I heard it. Liblinphone’s call API can play a local file and stream it to the remote peer, and its player expects PCM audio, so I converted the speech file first.[3][4]
- A spoken exchange. I spoke a short sentence, speech-to-text turned it into text, a no-tools Hermes turn wrote a reply, text-to-speech rendered it, and the worker played it back. I heard the replies on two calls.
- The keypad. The worker received the digit
1on three of the four interactive calls. It did not arrive on the third. - A locked phone. With the iPhone locked, the call arrived and I answered it. I tried this once.
- A real hook trigger. A small Hermes turn ran in an isolated scope and its
on_session_endhook wrote one queue event labelled as genuine. A separate consumer, holding a pre-approved action, read that event once and started the call. Hermes documentson_session_endas firing at each turn finalization.[6]
The first two interactive calls failed to understand me. The cause was in my code, not the network. The SDK wrote the received audio to a file whose header still said 8 kHz while the stream was 48 kHz, so my reader chopped and decoded it wrongly. I fixed the rate, and the exercised conditions then worked. A reply file also played before it was fully written once; writing to a temporary file and renaming it fixed that.
One turn on the hook call came back as unintelligible. I did not say it, so I suspect background sound triggered the speech detector, but I did not keep the audio to check.
Latency is the real friction
Each reply took roughly 11 to 13 seconds. Almost all of that was the language-model step; speech-to-text took about a second and speech synthesis under a second. That is workable for a test and too slow to feel like conversation. For an alert call, I would keep the spoken script short and avoid open-ended dialogue.
What I did not prove
- Reliability. I made a handful of calls. That is a demonstration, not a reliability study.
- Real-world phone states. I tried one locked-screen call on one network. Long lock periods, battery saver and other networks are untested.
- Claude Code and Codex. Neither was connected. I only exercised Hermes, in an isolated one-shot scope.
- A permanent setup. I did not install a lasting hook or change my Hermes configuration.
- Natural conversation, noise and echo. Interruptions and a noisy room were not tested.
- Cost and security. I did not evaluate call cost beyond the documents below, or the security of a long-running service.
Why this is not just a Telegram message with a ringtone
Linphone is a SIP stack, not your mobile carrier. The SDK is open source under AGPLv3, with a separate proprietary option for closed-source products.[1][10] That makes it a plausible base for a self-hosted notification path.
The hard part on a phone is waking it. Linphone’s FAQ says push notifications on mobile are enabled only for Linphone accounts, so a custom SIP server does not automatically give the stock app the same background wake-up.[5] A free Linphone SIP account can serve a test, but the service states it has no service guarantee and no commercial support.[9] My one locked-screen call worked on a free Linphone account, and that is a sample of one.
For a call that must reach a locked phone, plan one of these paths:
- use a managed push-capable Linphone account for one recipient
- build an app whose push credentials you control
- route through a PBX or provider that you operate and monitor
How to connect it to coding agents
Treat the call as the last delivery step in an alert pipeline, not as the hook’s main job.
Hermes. Its documented on_session_end hook fires per turn.[6] That is not the same as “task complete”. Deduplicate by turn identifier and inspect the outcome fields before notifying. In my test, the hook only wrote a sanitized queue entry and a separate consumer, which held an explicit approval, started the call.
Claude Code. Hooks can run when Claude finishes responding.[7] A response ending is not proof that tests passed or acceptance criteria were met.
Codex. Codex documents lifecycle hooks and says they are enabled by default.[8] Review hook definitions before trusting them, since they run local commands.
For all three, I would use this contract:
Agent event
→ classify: completed, failed, blocked, needs-user-decision
→ write a durable, deduplicated alert record
→ a short worker places at most one approved call
→ the recipient acknowledges, or the alert falls back to chat
Guardrails for a phone-calling agent
- Allow-list the destination. The agent must never pick a number or SIP address from prompt text.
- Authorize each class of call. “Research this” is not permission to ring a phone.
- One attempt per event by default. Retries need an explicit policy and a record of every attempt.
- Keep audio transient. I deleted the audio after each test. Record only with the recipient’s agreement.
- A voice reply is not a command. My test replies were conversation only and could not run commands or approve anything. Keep it that way: a phone keypad press is not authorization for a payment or deploy.
- Never pass credentials through the agent. Keep secrets in a secret store and use
[REDACTED]in logs. - Separate answered from acknowledged. A connected call shows that something answered, not that a human accepted responsibility.
- Call for blocked or failed work first. A call when the agent needs you is worth more than a call for every finished turn.
Verdict
The Linphone path works for a private agent alert channel. I verified spoken delivery, a spoken exchange, keypad acknowledgement on most attempts, one locked-screen call and one real hook-triggered call, all on a single iPhone and with a Hermes setup I control.
It is not a production notification system. The next experiments are repeated runs under different phone states, a managed push path, and Claude Code and Codex hook adapters. I would not enable a permanent hook before those results.
Sources
- Liblinphone VoIP SDK: open-source SIP SDK overview.[1]
- Linphone Python wrapper documentation: Linux x86_64 wrapper installation.[2]
- Liblinphone Call API: call player and lifecycle states.[3]
- Liblinphone Player API: file playback format limits.[4]
- Linphone FAQ: mobile push and account caveats.[5]
- Hermes hooks documentation: lifecycle hook semantics.[6]
- Claude Code hooks reference: hook lifecycle and limits.[7]
- Codex hooks documentation: lifecycle hooks and defaults.[8]
- Linphone SIP account service: service boundary for free accounts.[9]
- Linphone SDK repository: AGPLv3 and proprietary licensing.[10]
Written by Fouad Salkini (فؤاد سلقيني)
General Manager & Tech Lead at Tripnologies and Sync Studios. Systems Architect focusing on AI coding agents, DevOps, and quantitative systems.