Ringbolt is a phone-call agent for on-call engineering: it rings the engineer on call, talks the incident through, and runs the fix they authorize out loud. It is built around what a phone call costs when it goes wrong. One create that timed out became 23 billed call tasks in half an hour, so Ringbolt now refuses the fourth call in ten minutes.

The problem

Every on-call tool can already phone you. None of them can have a conversation. PagerDuty's own docs describe its phone alert: a text-to-speech recording, then press 4 to acknowledge, 6 to resolve, 8 to escalate. At three in the morning that call tells you something broke and hands you a laptop-shaped problem. You still get up, find a screen, log in, and read a dashboard before you can do the one thing the alert needed.

The timing makes it worse. Grafana OnCall OSS was archived in March 2026 and its phone notifications went with it. Atlassian stopped selling Opsgenie and support ends in April 2027. Cheap phone alerting for small teams is disappearing while the pagers keep going off.

What Ringbolt does

Ringbolt phones the engineer on call, talks the incident through with them, and carries out the fix they authorize out loud.

Any monitor posts a plain JSON alert to an intake endpoint (a curl works). Policy decides whether this is worth waking somebody: the service's severity threshold, its quiet hours, whether this exact problem already rang a phone. Ringbolt then places a call through CALL-E to whoever is on the rota, carrying the incident facts and a result schema that forces a structured decision back instead of a paragraph of prose. On the call you ask what broke, what else is affected, what the last deploy was, and you get answers from the incident facts. Then you say what to do. The decision is verified, the runbook action executes, and the whole exchange lands in one audit record: transcript, decision, who authorized it, what ran, and the system state before and after. If nobody answers, the next person on the rota gets called on a timer.

The rules that make it safe to give a phone call hands

This product executes real changes on real systems, so the interesting part is what it refuses.

  • A webhook cannot authorize anything. CALL-E delivers its terminal events unsigned, so Ringbolt re-fetches every call from the CALL-E API under its own key and acts on that snapshot. The type system enforces it: the authorization code accepts only a value the re-fetch can produce.
  • A guess cannot authorize anything. An action runs only when the decision validates against the schema, clears a confidence floor, names an action that was actually offered on that call and is still permitted by that service's policy, and, for anything destructive, the exact confirmation phrase appears as whole words in a transcript turn attributed to the responder. "Roll it back" has to have been said and transcribed, and "don't roll it back" does not count.
  • Silence cannot authorize anything. A call where the responder was never heard is refused outright, whatever the confidence score claims. Voice is a lossy channel and Ringbolt never acts on a guess about what somebody said.
  • One broken thing is one phone call. Repeats collapse into the open incident, a flapping service gets muted after its call allowance, and quiet hours hold alerts instead of dropping them.
  • Spending is bounded. A live build spends nothing until it is told what it may spend, and it places at most three real calls in ten minutes no matter what is on fire.

The deck

The dashboard is built like an instrument cluster with one primary instrument: a ring counting the seconds somebody has been on the phone. The sentence that granted permission is joined by a red rule to the action it allowed, so the authorization and the change cannot be read as two separate events. Everything drawn is data Ringbolt genuinely holds. There is no fake waveform and no invented error chart.

Break it yourself

The hosted demo has a service named dockside that exists to be broken. Press the button, watch the alert arrive, the call go out, the transcript land, and the rollback run. The demo deployment is enforced read-only in the worker itself: no write except the demo controls, no action allowed any host, phone numbers withheld from every read, and it refuses to even start in live mode. So nothing you press there can ring a telephone.

If you would rather not open anything, the same walkthrough is written out in the repository at examples/demo-transcript.md and reads in about a minute.

How it is built

Cloudflare Workers, with one Durable Object per incident owning the state machine and the escalation timer, D1 for storage, Hono for routing, React and Vite for the deck. The CALL-E Developer API through its TypeScript SDK, with result schemas, stable idempotency keys, webhook ingestion, and a reconciliation sweep for the webhook that never arrives. Development runs against a local stand-in of the CALL-E API that is deliberately awkward (async calls, thin webhooks, state that must be fetched back), and one contract suite runs every case against both the stand-in and the real adapter, so the two cannot drift apart. 555 tests. Accessibility is measured, not asserted: a real browser drives every screen in both themes and contrast is measured from the painted pixels. Retention windows are enforced by a sweep, erasing one person is a single request, and every call opens by disclosing that an automated system is speaking.

What building it taught me

A create that times out is not a create that did not happen. On 22 August this build created 23 separate call tasks in half an hour, and every task is billed whether or not it connects. Two rules came out of that half hour. A failed create is sent once more under the SAME idempotency key, because CALL-E bills a repeat under a fresh key as a second call to the same person. And there is a ceiling on the rate, at most three real calls in ten minutes, because each of those 23 was a different logical call, so no per-call check could have refused any of them. Spending is budgeted in money rather than call counts, and nothing is spent that nobody approved.

The second lesson: transcription is lossy in both directions. The transcript check refuses some calls a responder genuinely authorized, and that trade is taken deliberately, because it refuses toward the rotation (the next person gets called) rather than toward an unauthorized change.

The telephone, honestly

The telephony in the video is shown honestly. Ringbolt sends real call tasks to CALL-E and they are accepted, but since 8 September the provider's planner refuses to dial the Netherlands in English ("Calls to the Netherlands in English are not supported for this call setup"), although their own region table lists the Netherlands with English, and other entrants reported the same for Spain and Indonesia. CALL-E support confirmed the block in writing and could not promise it would lift before the deadline. On the calls that did connect before that, the return audio path recorded my side as silence. Both are failure modes the product is built to survive: a call that never connected, or one where the responder was never heard, cannot authorize anything. The video shows the loop on the public demo's stand-in and says so on screen. The full evidence trail, with call ids, is in docs/two-way-audio.md in the repo.

I filed it in CALL-E's own tracker the day it happened, with the request, the rejection and the region table side by side: https://github.com/CALLE-AI/call-e-integrations/issues/121 (open since 8 September 2026).

What is next

Real accounts instead of one shared admin token (the tenancy ADR already prices what that costs), a one-request subject access export to match the one-request erasure, and typed intake adapters for the common monitor payloads on top of the generic JSON intake.

Disclosures

I used AI assistance for research, drafting, code navigation, and implementation support. I reviewed the final work myself and take responsibility for the submission.

Six shared quality-gate scripts under scripts/vendor/ were not written for Ringbolt. I reuse them across projects to test the product, and they add no product behavior. Everything in src/, test/, migrations/ and docs/ was written inside the submission period.

Built With

  • call-e
  • cloudflare-workers
  • d1
  • durable-objects
  • fast-check
  • hono
  • puppeteer
  • react
  • stryker
  • typescript
  • vite
  • zod
Share this project:

Updates

Submission history