PWCOM — Office Telephony

The Office Line

A decades-old key-telephone system, kept alive underneath a modern Asterisk PBX and a self-hosted AI agent that screens every unknown caller before anyone picks up.

Scroll

One line, forked two ways

The office runs on a single carrier line with one published number. The carrier's gateway forks every inbound call before it reaches either system — into the original key-telephone system, and into Asterisk. Whichever answers first wins. The number itself never goes down.

The Hitachi

Installed decades ago

  • Still wired to every desk phone and the fax line
  • Needs no power from the PBX, no network, no software
  • Answers the moment Asterisk can’t

Asterisk

Added this project

  • Runs on a small always-on PC, not a server rack
  • Screening, the auto-attendant and the AI lookup all live here
  • If it’s off, unplugged, or mid-upgrade — the Hitachi quietly takes over

The fork is the disaster recovery plan: no failover script, no alert to respond to at 2am. The old hardware simply never stopped being wired in.

Before anyone gets through

Every inbound call is screened against a short set of rules before it ever reaches the attendant or rings a desk phone.

A voice that always knows what day it is

The attendant picks its greeting from the calendar and the clock. Every message is synthesized by a locally-hosted text-to-speech voice and can be rewritten from the dashboard — save new wording, and the next caller hears it. No studio, no re-recording session.

Business hours

The main menu — sales, or the operator

Lunch

A recorded message, weekdays only

After hours

A recorded message, outside the work day

Holiday

National holidays, refreshed every New Year’s

Changing the wording of any message is a save on the dashboard, not a deploy.

Who's calling?

A small AI agent finds out — before anyone picks up

pbx:~$ incoming call — unknown number

no employee, no known caller, not blocked — lookup starts

lookup:~$ run caller-lookup.py

› searching the web for a matching listing

› checking the name against the listing’s own page title

› label: 営業 (sales) — verified, not guessed

✓ staff see the verified name before they answer

The evaluation

41 / 42 correct

Verdicts right across a 42-number evaluation set — real callers, real spam.

Zero false spam

No real caller was ever mislabeled as spam in the eval.

~2 seconds, a fraction of a cent

Fast and cheap enough to run on every unknown number, every time.

Benchmarked, not assumed

Compared against Gemini variants, a self-hosted Gemma 4 with web search, and Claude — same numbers, same task.

A name is only shown if it comes from the listing's own page title — the model can't just assert one. Staff can still overrule any label by hand, and a flagged number is never searched again.

One dashboard, built for the people who answer the phone

A small Flask app staff use directly — desktop-first, no account per person, nothing to install. Every page maps onto a real daily task, not an abstraction of one.

Calls

Incoming calls by day, colour-coded by caller — play back or relabel any recording

Numbers

Address books for regulars, staff, and blocked numbers

Extensions

Register or rename a desk phone, see who’s online

Prompts

Rewrite any IVR message — live the moment it’s saved

Voices

Pick and tune the synthesized voice callers hear

Holidays

National holidays plus company closures, kept current automatically

Mail

Where recording notices get emailed, and which account sends them

Test Call

Place a fake inbound call with any caller ID and watch the outcome

Don't rip out what works. Bolt modern tools onto it, carefully.

The Hitachi never left. Asterisk, the AI lookup, and the dashboard are additions — each one failing safely back to the hardware that was answering this line long before any of this was written.

Legacy hardware stays

Never removed, never depended on being removed

Asterisk takes the first swing

Screening, the attendant, the AI lookup

AI screens the unknown

Verified against a real source, not guessed

Staff keep the final say

Every label and every message is editable, live