Metadata-Version: 2.4
Name: hermes-plivo-phone-gateway
Version: 0.2.0
Summary: Real-time voice channel plugin for Hermes Agent over Plivo Audio Streaming. Phone conversations with the agent, inbound and outbound, with barge-in and keypad input.
Author: Plivo
License: MIT
Project-URL: Homepage, https://github.com/plivo-dev/hermes-plivo-phone-gateway
Project-URL: Repository, https://github.com/plivo-dev/hermes-plivo-phone-gateway
Project-URL: Issues, https://github.com/plivo-dev/hermes-plivo-phone-gateway/issues
Project-URL: Changelog, https://github.com/plivo-dev/hermes-plivo-phone-gateway/blob/main/CHANGELOG.md
Project-URL: Plivo Voice API, https://www.plivo.com/docs/voice/
Project-URL: Hermes Agent, https://github.com/NousResearch/hermes-agent
Keywords: plivo,hermes,hermes-agent,voice-agent,voice-ai,telephony,phone-agent,audio-streaming,speech-to-text,text-to-speech,barge-in,websocket
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Telephony
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.14.1
Requires-Dist: hermes-plivo-tools>=0.1.3
Requires-Dist: audioop-lts>=0.2.1; python_version >= "3.13"
Requires-Dist: numpy>=1.24
Requires-Dist: onnxruntime>=1.17
Dynamic: license-file

# hermes-plivo-phone-gateway

Plivo voice channel for [Hermes Agent](https://github.com/NousResearch/hermes-agent). Real-time
phone conversations with the agent over
[Plivo](https://cx.plivo.com/?utm_source=github&utm_medium=oss&utm_campaign=hermes-plivo-phone-gateway),
inbound and outbound.

| Capability | Detail |
|---|---|
| Inbound calls | Any Hermes agent becomes reachable on a phone number |
| Outbound calls | The agent dials out and talks to whoever answers |
| Barge-in | The agent stops speaking the moment it is interrupted |
| Keypad input | DTMF digits reach the agent as text |
| Call end | Silence, a call-length limit, a hangup, or the agent ending the call |
| Provisioning | Plivo is configured automatically on every start, including a public URL |

Speech-to-text, the model turn and text-to-speech are Hermes settings, shared with every other
channel. The plugin handles telephony and barge-in.

## Requirements

| | |
|---|---|
| Hermes Agent | With the gateway and its `voice` extra, which carries the speech-detection model. Python 3.11 or later |
| Plivo | An account and a voice-enabled number |
| `ffmpeg` | Only when `PLIVO_VOICE_STREAMING_TTS` is `false`, which is the default |

`cloudflared` is fetched automatically when no public URL is configured.

## Installation

### 1. Install

```bash
pip install hermes-plivo-phone-gateway
```

`hermes-plivo-tools` is installed alongside it and provides outbound calling.

### 2. Enable

```bash
hermes plugins enable plivo_voice-platform
hermes plugins list
```

The name includes the `-platform` suffix. Hermes strips it to derive the channel name
`plivo_voice`, and rejects the short form.

### 3. Add credentials

In `~/.hermes/.env`:

```dotenv
PLIVO_AUTH_ID=MAxxxxxxxxxxxxxxxxxx
PLIVO_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PLIVO_PHONE_NUMBER=+14155550100
```

`hermes gateway setup` lists these three. Additional optional settings live in the same file
and are listed under [Configuration](#configuration). A pip-installed plugin does not appear in
the `hermes config` screens.

### 4. Start the gateway

```bash
hermes gateway run          # foreground
hermes gateway install      # or as a supervised service
```

The number can be reached once the log reads:

```
[plivo_voice] READY. Call +141****0100 to talk to the agent.
```

A temporary tunnel can take up to 2 minutes. Setting `PLIVO_VOICE_PUBLIC_URL` removes the
delay. Numbers are masked in Hermes logs.

### 5. Verify

```bash
python -m hermes_plivo_voice.doctor
```

Checks credentials, the Plivo API, the registered answer URL, text-to-speech, speech detection
and the caller allowlist, and reports which of them would stop a call working.

## Outbound calls

`hermes-plivo-tools` is installed as a dependency and only needs enabling:

```bash
hermes plugins enable plivo-tools
```

Enabling adds `make_call`, `send_sms` and `lookup_number`. The plugin answers the outbound
call itself, and whoever picks up talks to the agent exactly as an inbound caller would.

```
Call +14155550100 and ask what time the meeting starts.
```

A call is refused rather than placed when the plugin's answer URL is not responding, or when
Plivo rejects the credentials.

## Configuration

Read from `~/.hermes/.env`. Speech providers are configured in `~/.hermes/config.yaml` under
`stt` and `tts`, documented in Hermes'
[voice mode guide](https://hermes-agent.nousresearch.com/docs/user-guide/features/voice-mode#configuration-reference).

**Required**

| Variable | Purpose |
|---|---|
| `PLIVO_AUTH_ID` | Plivo auth ID |
| `PLIVO_AUTH_TOKEN` | Plivo auth token. Also verifies callback signatures |
| `PLIVO_PHONE_NUMBER` | The answering number, E.164 |

**Optional**

| Variable | Default | Purpose |
|---|---|---|
| `PLIVO_VOICE_PUBLIC_URL` | unset | Public HTTPS base Plivo reaches the plugin on, and it must forward WebSocket upgrades as well as HTTP. Required for deployment. A temporary tunnel is opened when unset |
| `PLIVO_VOICE_STREAMING_TTS` | `false` | `true` speaks the reply as it is generated. Reduces time to first audio from 27 seconds to about 3, and removes the `ffmpeg` requirement. Needs a streaming-capable provider |
| `PLIVO_VOICE_ALLOWED_USERS` | empty | Comma-separated numbers allowed to call the agent, digits only. Empty admits every caller. Inbound only, and it does not grant outbound |
| `PLIVO_VOICE_ALLOWED_DESTINATIONS` | empty | Comma-separated numbers the agent is allowed to call, digits only. Empty refuses every outbound call. Independent of the inbound list |
| `PLIVO_VOICE_IDLE_TIMEOUT` | `60` | Seconds of caller silence before hangup. `0` disables |
| `PLIVO_VOICE_MAX_CALL_SECONDS` | `600` | Maximum call length. `0` disables |
| `PLIVO_VOICE_MAX_CONCURRENT_CALLS` | `20` | Simultaneous calls. `0` disables. Callers beyond the limit hear a short message |
| `PLIVO_VOICE_AUTO_WIRE` | `true` | `false` leaves the Plivo application and number untouched, to be configured by hand. The log prints the answer URL to use |
| `PLIVO_VOICE_WS_PORT` | `8791` | Local port for the HTTP and WebSocket server |
| `PLIVO_VOICE_SYSTEM_PROMPT` | built-in | Replaces the per-turn instruction keeping replies speakable |
| `PLIVO_VOICE_SILERO_MODEL` | auto | Path to a Silero ONNX model, when the bundled copy is unreachable |
| `PLIVO_VOICE_INSECURE_NO_SIGNATURE` | `false` | `true` accepts unverified callbacks. Development only |
| `PLIVO_VOICE_RAW_DUMP` | unset | Path to append raw inbound audio to. Unencrypted |

Audio rates, speech-detection thresholds and interruption sensitivity are not configurable.

## Tool scope

`platform_toolsets` in `~/.hermes/config.yaml` limits the toolsets a caller can reach. The
key is `plivo_voice`, the plugin name without the `-platform` suffix.

```yaml
platform_toolsets:
  plivo_voice:
    - plivo_send_sms
    - plivo_make_call
    - plivo_lookup_number
    - web
    - memory
```

Without an entry a caller reaches every toolset except the ones Hermes ships switched off
and anything under `agent.disabled_toolsets`. A plugin's toolsets are enabled by default, so
the Plivo tools are reachable either way. Saving a toolset list for a platform through
`hermes tools` reverses that for plugin toolsets, because from then on one absent from the
saved list counts as switched off. The startup log names the toolsets a caller can reach.

Exclude any tool the agent can call but cannot complete. It calls the tool regardless, and the
failure and retry are dead air on the line.

## Security

- Answer callbacks are signature-verified against every signature header Plivo sends
- Each call mints a short-lived token carried in the audio WebSocket URL
- Plivo applications and numbers the plugin did not create are never modified
- `PLIVO_VOICE_ALLOWED_USERS` filters who may call the agent, and
  `PLIVO_VOICE_ALLOWED_DESTINATIONS` filters who the agent may call. The two lists are
  independent, so a number in one is not permitted by the other
- An empty inbound list admits every caller, while an empty outbound list refuses every
  call. Outbound spends money and rings a stranger, where an unexpected inbound call
  only spends tokens
- The caller's number is not verified, so anyone who knows an allowed number can dial in
  as that number
- `PLIVO_VOICE_INSECURE_NO_SIGNATURE` and `PLIVO_VOICE_RAW_DUMP` both weaken the above

## Troubleshooting

Run `python -m hermes_plivo_voice.doctor` first.

| Symptom | Cause |
|---|---|
| `Plugin 'plivo_voice' is not installed or bundled` | The enable name needs the `-platform` suffix |
| No Plivo settings in `hermes config` | Expected for a pip-installed plugin. Edit `~/.hermes/.env` |
| `READY` never appears | DNS propagation on a fresh tunnel, up to 3 minutes. A warning instead means the hostname does not resolve locally, which does not affect Plivo |
| Agent will not call a number | The number is absent from `PLIVO_VOICE_ALLOWED_DESTINATIONS`, which is empty by default and is not granted by the inbound list |
| Agent has no `make_call` | The tools plugin is not enabled as `plivo-tools` |
| Outbound call refused before Plivo is contacted | The plugin's answer URL is not responding, or Plivo rejected the credentials |
| Long spoken replies | Reply length is requested in the prompt, not enforced. Interrupt by speaking |

## Uninstalling

Plivo keeps sending calls to a number until it is detached. Undo the Plivo side before
removing the plugin:

```bash
python -m hermes_plivo_voice.unwire
pip uninstall hermes-plivo-phone-gateway
```

## Tests

```bash
python -m unittest discover -s tests
ruff check .
```

`tests/` also holds scripts driving a synthetic call through a live gateway. See
[tests/README.md](tests/README.md).

## Limitations

- One Plivo number answers through one application, leaving the voice and SMS plugins unable
  to share a number
- Reply length is requested in the per-turn prompt, not enforced
- Speech providers are global Hermes settings, and changing one affects every channel

## License

MIT
