# Tel-Agent tags v0.1.1 while pyproject still reads 0.1.0, and its v1 model stack is three cloud services

> Tel-Agent is a self-hosted gateway that sits between a phone line and an AI agent, routing SIP calls to a human, to a block, or to the agent. The engineering notes in pyproject.toml are the most informative part of the repository: dependency caps written after a Starlette release broke main, Argon2id chosen because passlib has been dormant since 2020, and credentials split between a plaintext env file and encrypted database columns. What the self-hosted label does not cover is the model layer, where v1 still points at Deepgram, one cloud LLM and ElevenLabs.

**Dpro-at/Tel-Agent** — AI phone assistant | open-source 

- Repository: https://github.com/Dpro-at/Tel-Agent
- Website: https://Tel-Agent.com
- Stars: 1,065 · Forks: 219
- Language: Python
- License: AGPL-3.0
- Published: 2026-09-14 · Updated: 2026-09-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/dpro-at-tel-agent

## The newest tag is v0.1.1 and the manifest still says 0.1.0

Both releases carry the same date. v0.1.0 was tagged on 2026-09-05 at 12:52 and v0.1.1 a little over three hours later at 16:01. `pyproject.toml` declares `version = "0.1.0"`, so the version bump for the second tag is not in the manifest. The project labels itself alpha in the status section, which is consistent with two tags and a manifest that has not caught up, but it does mean anything that resolves the package version from the source tree sees the older number than anything that reads the release list. The rest of the project header is more settled: `requires-python = ">=3.11"`, `license = { text = "AGPL-3.0-or-later" }`, and the author listed as Dpro GmbH, which the page also credits as the maintainer in Vienna. Three compose files sit at the top level, one for development, one for release and one default, and an import-linter configuration suggests the layering is enforced rather than merely intended.

## An empty encryption key is refused at startup on purpose

The compose file will not start without a key, and the mechanism is a shell parameter expansion rather than a runtime check. The environment block reads `ENCRYPTION_KEY: "${ENCRYPTION_KEY:?set ENCRYPTION_KEY in .env - generate one with openssl rand -hex 32}"`, so an unset value stops `docker compose up` before any container is created. The comment gives the reasoning in full: an installation without a key stores every user-entered credential nowhere, and finding that out at the first save is worse than finding out at startup. That is the right place for the failure. The key is not optional anywhere else either. Both SQL drivers ship as dependencies, and the manifest comment explains that the file-based one is what the install wizard offers first while Postgres is what an existing server already runs, which is a different reason to include both than convenience.

## Both published ports bind loopback, so remote access is something you build

The dashboard is on 38471 and the API on 38472, and the compose comment states that both are published on loopback only, tied to a numbered decision in their own documentation. The consequence is spelled out: reaching an installation from anywhere else goes through a private network, a VPN, or a reverse proxy terminating TLS. Two environment defaults reinforce it. `TRUSTED_HOSTS` defaults to `localhost,127.0.0.1`, and `CORS_ORIGINS` is derived from the web origin variable, defaulting to the dashboard address. Reaching beyond loopback therefore means editing both the binding and the host list rather than flipping a single flag. Two optional profiles sit beside the default stack: one brings up Postgres instead of SQLite, and one adds a bundled n8n on port 5678 that talks to the API like any other machine client. A compose default bound to loopback and a bundled automation server on a second port is a reasonable alpha posture, and both are opt-in.

## Installation secrets live in a plaintext file, user credentials live in the database

The env template is unusually explicit about a distinction that most projects leave implicit, and it is tied to the same numbered decision as the ports. The file itself holds installation secrets, one set per installation, set once and changed with a restart: the database URL, the Redis URL, the encryption key and the LiveKit keys. The database holds the user-entered credentials, encrypted at rest: provider API keys, per-number SIP credentials and messaging channel tokens, typed into the interface, effective without a restart, and many of each. The argument against putting them in the env file is made outright. It is plaintext on disk, so one file read exposes every credential, while encrypted columns force an attacker to obtain both the database and the key. There is an acknowledged exception for the earliest milestone, where there is no database yet and the provider keys live in the file, and it ends at a named later milestone. The template also states a rule for future work: never write a settings screen that edits that file.

## FastAPI is capped below 0.142 and Starlette is pinned in its own right

The dependency comments record a specific breakage. Both FastAPI and Starlette carry upper bounds, with the reason given: FastAPI is pre-1.0, so the minor position is the breaking one. The cited incident is Starlette 1.6 putting a new router object into the application routes list and turning main red with nothing in this repository having changed. The more useful half is why Starlette appears as a direct dependency instead of being left to arrive through FastAPI. The answer is in the tree and the constraints: an errors module and four middlewares import Starlette directly, and FastAPI only asks for a lower bound on it, so capping FastAPI alone would leave the same hole open. That is a dependency cap written by someone who read their own imports. Elsewhere in the same list, AES-GCM for credentials at rest comes from the cryptography wheel, with a note that nothing in the repository implements a cipher.

## Password hashing is pinned to argon2-cffi because passlib has been dormant since 2020

One line in the dependency list carries the sharpest reasoning in the repository. Argon2id comes from `argon2-cffi` directly rather than through passlib, and the comment explains that passlib has not had a release since 2020 and that this is the one choice which cannot be corrected later without every user resetting their password. The reasoning generalises to the rest of the list. Anything you can swap out without a migration is free to move; a password hash is a permanent commitment once users exist. The same section notes that the WebSocket clients for the Discord gateway and Slack Socket Mode are already present through the server package but are declared anyway, because an import this repository makes is a dependency it should own. Two compose profiles and a LiveKit path complete the picture of a project where the declared dependency list is doing real work rather than filling a lockfile.

## v1 is self-hosted only at the server, with three cloud providers in the voice path

The architecture table is a list of choices: Python with LiveKit Agents for voice, Python with FastAPI for the API, Next.js and React for the frontend, SQLite by default with PostgreSQL supported, Redis for cache and queue, and Caddy in front for automatic HTTPS. The voice stack is where the self-hosting stops. Providers for the first release are named as Deepgram for speech recognition, one cloud language model, and ElevenLabs for speech synthesis. Local models, with Ollama, Whisper and Piper named, follow in a later version and are not the default, with the reason given: they need a GPU to hold a natural conversation. So an installation on your own hardware still sends audio and text to three outside services unless you wait. The telephony side is more self-contained. The LiveKit path connects outward only, with no inbound ports, no NAT traversal and no RTP range to open, and the project is explicit that it is not a PBX replacement, connecting to 3CX, Asterisk or FreePBX as an extension, and not analog-capable without an ATA.

## Twenty-four channels is ten wired plus fourteen open issues, and the badge rows are not a count

The stated scope is 24 channels including the phone. It reconciles exactly. Ten are wired today: the phone, web chat, WhatsApp, Telegram, Messenger, Instagram, Discord, Slack, email and SMS. Fourteen more are open issues under one label: Microsoft Teams, Signal, Viber, Google Chat, Mattermost, Matrix, IRC, LINE, WeChat, WeCom, QQ, DingTalk, Feishu and Lark, and iMessage. The reason the second wave can be counted is that the issues share one declarative setup contract, numbered in the page, so each new channel is a definition plus a transport rather than a fork of the last one. The badge strip at the top of the page carries a comment explaining that its rows are about recognition, not arithmetic, which is a useful thing for a README to say about its own logos. The taxonomy is also explicit about scope. A channel is a route a customer uses to reach you; an integration is a system the agent acts on, and those are reached through webhooks and a generic HTTP tool rather than being claimed.

## Conclusion

Tel-Agent is for someone who already runs a PBX and wants an AI answerpoint on their own hardware rather than a hosted receptionist. Check three things first. v1 is not model-independent: the speech stack is Deepgram, ElevenLabs and one cloud LLM, and local models are deferred to v1.1 because they need a GPU. The installers are unsigned until code signing is configured, so the operating system will prompt on first run. And both default ports bind loopback only, which is a good default and also means remote access has to be built deliberately through a VPN or a TLS-terminating proxy. It is alpha, under AGPL-3.0-or-later, with two tags three hours apart and a manifest that was not bumped for the second.

## FAQ

### What is Tel-Agent and does it replace a PBX?

It is a self-hosted gateway between a phone line and an AI agent. It is not a PBX replacement: it connects to an existing 3CX, Asterisk or FreePBX as an extension, and it only speaks SIP, so an analog line needs an ATA.

### Do I need a cloud AI service to run Tel-Agent v1?

Yes. The named providers for the first release are Deepgram for speech recognition, one cloud language model, and ElevenLabs for speech synthesis. Local models with Ollama, Whisper and Piper are deferred to a later version because they need a GPU to hold a natural conversation.

### Why will docker compose up refuse to start for Tel-Agent?

The compose file requires `ENCRYPTION_KEY` with a shell parameter expansion that fails when it is unset. Generate one with `openssl rand -hex 32` and put it in `.env`. The reasoning given is that an installation without a key stores user-entered credentials nowhere.

### Which ports does Tel-Agent publish and can I reach them from another machine?

The dashboard is on 38471 and the API on 38472, both bound to loopback only, with `TRUSTED_HOSTS` defaulting to localhost and 127.0.0.1. Remote access is meant to go through a private network, a VPN, or a reverse proxy terminating TLS.

### How many channels does Tel-Agent support today?

The stated scope is 24 including the phone. Ten are wired now, covering the phone, web chat, WhatsApp, Telegram, Messenger, Instagram, Discord, Slack, email and SMS, and fourteen more are open issues sharing one declarative setup contract.

## Sources

- [Dpro-at/Tel-Agent on GitHub](https://github.com/Dpro-at/Tel-Agent)
- [License: AGPL-3.0](https://github.com/Dpro-at/Tel-Agent/blob/main/LICENSE)
- [Project website](https://Tel-Agent.com)
- [README](https://github.com/Dpro-at/Tel-Agent/blob/main/README.md)
- [Releases](https://github.com/Dpro-at/Tel-Agent/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dpro-at-tel-agent
