Inspiration

A deaf father can't call the pharmacy to check whether his daughter's prescription is ready before driving across town. The business has no website and no online refill. His options today are a 711 relay operator, where a stranger hears the whole call and businesses often hang up on the relay protocol, or asking a hearing person for a favour. Relay services are mandated and they work, but they are synchronous, they put a human in the loop for private calls, and they hand back live speech instead of an answer. We wanted a text-first option that removes the operator and returns a clear result, for the everyday voice-only calls that deaf, hard-of-hearing, and non-speaking people still get forced onto the phone for.

What it does

You type a task, like "Is Jordan Rivera's prescription ready for pickup?" Skia places one phone call through CALL-E, discloses up front that it is an AI calling on someone's behalf, has the conversation, and hands it back to you as a readable text thread plus a structured result: the answer, the outcome, and a summary you can act on. It is asynchronous: state the goal once, read the result when it is done.

And it fails closed. If the answer isn't grounded in what the other person actually said, or the call never disclosed it was AI, it routes to a human instead of reporting a guess. A call tells you what someone said, not that it is true, and the code enforces that.

How we built it

Skia is a CALL-E Agent Skill (skills/relayme/) plus a runnable Python demo (apps/python/relayme/). The skill defines the call goal, the mandatory AI disclosure, the allowed follow-up questions, and a fail-closed result schema. The app wires two live integration paths behind the same classifier and thread builder:

  • OAuth CLI path: calle call plan -> run -> status
  • REST path: GET /v1/goals preflight, POST /v1/calls with the documented recipients[] schema, GET /v1/calls/{id} poll

A reserve-before-dial journal makes one authorization exactly one call. A thread model turns a call into the deaf-user text thread with phone masking and PII redaction. Everything runs mock-by-default from five hard-case fixtures (answered, unsure, refused, voicemail, wrong number), covered by 38 no-network tests, with an accessible single-file dark web view (Atkinson Hyperlegible, one amber accent, AA contrast).

Challenges we ran into

The two hardest were external, and we documented them honestly rather than hide them.

  1. The OAuth token exchange returned 502 Bad Gateway on every attempt over ~20 minutes even though the session showed AUTHORIZED, so the CLI/MCP path could not authenticate.
  2. The only phone number we had access to was in Kenya (+254), which the account's API rejected with call_not_ready ("English calling for Kenya is not currently supported"), and we had no US, SG, or other international number.

So we could not place a completed live call. We also reverse-engineered the REST create schema from 422 validation errors because there was no OpenAPI endpoint; the intuitive {phone_number, task} guess is rejected, and the real shape is task + recipients[{phones, region, locale}].

Accomplishments that we're proud of

We built a complete, reviewable contribution that runs end to end with zero credentials and spends no calls, so a judge can evaluate all of it offline. The fail-closed design is real, not a slogan: five hard cases each resolve to a safe outcome, verified by tests. We verified the live REST API up to the point it would have dialed (authentication, the GET /v1/goals preflight returning 200, the create schema, request validation, and the fail-closed rejection path) without spending any credit. And we shipped it as an accessible product from the start, with a captioned demo, a high-contrast interface, and a font built for low vision.

What we learned

That a phone call is a claim, not a fact, and an accessibility tool that quietly converts "I think so" into "yes" is worse than no tool. The fail-closed classifier came directly from that. We also learned how much the developer experience gap matters: the api_key works only against the REST API, not the CLI, the two use different base URLs, and without an OpenAPI spec most of our early time went to schema guesswork. We wrote all of this up as feedback for CALL-E.

What's next for Skia

  • Place a completed live call once a supported-region number is available, to exercise the completed-transcript mapping end to end (it is currently taken from CALL-E's documented example, not a live call).
  • Add more languages and locales so the callee hears their own language.
  • Support a callback/inbound leg so a business can reach the user back through the same text thread.
  • Harden the reserve-before-dial store for multi-user deployment.

Built With

Share this project:

Updates

Submission history