REPORT 03 :: CALL-E, AI PHONE CALLS
CALL-E lets an agent pick up the phone and make a call for you. I built on it for a hackathon and kept hitting the same question: what actually happened on that call? The API has an answer. Quite often it is the wrong one, and it says it with high confidence.
§01
There are three ways into CALL-E and each one reports how a call ended in its own words. One can say voicemail and busy. One has no word for either. The third has a failure code with no published list of values, and their own errors guide says not to branch on it. So I wrote a small library that reads all three onto the same scale, and every reading says where it came from: quoted from a field, worked out from other fields, or simply not there.
3
ways in: API, goal runs, MCP
3
vocabularies for how it ended
0
tables saying which is which
Not there is the important one. A mapping that cannot say a fact is missing will make one up.
§02
Early on I dialled my own phone and it went to voicemail. The agent asked its question into the beep three times and hung up. The API came back completed, task_completed true, confidence high. Sitting in the same object, the result field said the answer was unknown. The payload disagreed with itself and the field everybody branches on picked the wrong side.
Nothing in my mapping was wrong. Each field was read faithfully. The missing rule was about the pair: a job marked done on a call where nobody established a person was there.
§03
So I needed a call with a known right answer and nobody bothered by it. The US speaking clock is a recorded line that exists to be called, and what it says is checkable to the second. The agent heard the time and got it right. That real success and the voicemail box that answered nothing scored two hundredths apart, under the same label. There is no threshold you can set between them. Anyone gating on the score is gating on noise.
calle-docs #44, fixed a week later§04
Read a finished call straight away and the attempt times are fine: a timezone, fractions of a second. Read the same call a minute later and they have been rewritten. The zone is gone, the time has moved four hours, and the fractions are rounded off. After that it stays wrong. A billing check or a support ticket always reads it later, so it always gets the bad copy.
Another builder had already reported failed calls showing zero duration and blamed the failure path. Their issue was tagged as needing validation. I posted a reproduction that disagreed with their theory: it is about when you read, not whether the call failed. I also said plainly which part of their report my data could not explain. The event stream never moved in any read, so the tool treats events as the clock and the attempt fields as a rumour.
the validation, on their p1 issue that asked for it§05
My library read an automated phone menu as a human picking up. The screen said a person answered and it was safe to act on. That one switched off the exact safety rule the whole thing exists for, and a live call caught it, not my hundred tests. The reviewer caught the rest. My demo would let anyone who guessed a call id read that call. I shipped two real phone numbers after ticking the box that said all numbers were fictional. My first pull request carried payloads from real calls, and I closed it myself.
Every one of those is fixed and written up in the thread where it happened. The reviewer asked for four rounds of changes and was right every time.
§06
While building I kept a defect log and filed it in the right repos: the docs, the two SDKs, the CLI and plugins. A maintainer fixed five of them in one night and closed each one with a merged change. Two of them he rated top priority.
The one I am proudest of shows up three times. The TypeScript SDK, the Python SDK and the n8n node all stop waiting the moment a call's status says finished, and all three do the opposite for goal runs. Three codebases, written separately, with the same bug. That is not three careless authors. It is one sentence in the docs, and they all believed it.
| WHERE | WHAT | STATE |
|---|---|---|
| calle-docs #39 | the auth example returns 405 for a good key and a bad one | FIXED |
| calle-docs #43 | the spec calls a field machine readable, the guide says never branch on it | FIXED |
| calle-docs #44 | task_completed is named for one question and defined as another | FIXED |
| integrations #107 | the key placeholder uses a prefix real keys do not have | FIXED |
| integrations #108 | agents are never told how to recover a call, so they place a second one | FIXED |
| integrations #109 | two packages install the same command and the skills call the wrong one | FIXED |
| calle-docs #40 | a finished status can arrive before the result does | OPEN |
| calle-docs #41 | a result comes back filled in on a call nobody spoke on | OPEN |
| calle-docs #42 | attempt timestamps lose their timezone | OPEN |
| ts sdk #17 | calls stop waiting at the status, goals wait for the result | OPEN |
| python sdk #30 | the same split, plus a poll interval of zero that hammers the API | OPEN |
| n8n node #3 | the same split, a third time | OPEN |
| n8n node #4 | the phone masker shows the whole number at 8 or 9 digits | OPEN |
| dify plugin #2 | the plugin points at the test docs host | OPEN |
§07
The contribution is a skill that teaches an agent to read what actually happened to a call, plus a small app that shows it. It flags calls that are stuck, calls that are replays, retries that would ring someone twice, and durations that cannot be trusted. Each flag maps to an issue somebody else had already filed and nobody had built against.
feat(call-state-reconciler): read what actually happened to a call (#337)
THE HONEST PART
This repo merges a lot, so a merge here is not rare. The part worth reading is the issue table and the reviews.
Eight of the fourteen issues are still open and have not been touched since I filed them. The library is on npm as asheard and the hosted app still places real calls to the speaking clock, so you can watch the API say done on a recording yourself.
REPORT 01 :: DATAHUB, TWO MERGED→