|
| 1 | +# asheard |
| 2 | + |
| 3 | +Read what actually happened to a CALL-E call, and see which field said so. |
| 4 | + |
| 5 | +A finished call hands you a status word, a `task_completed` boolean and a |
| 6 | +confidence score. That comes back saying the job is done for a voicemail box: |
| 7 | +the recording picks up, the agent asks its question into the tone, and the call |
| 8 | +ends `completed` with a schema-valid result and a high score. |
| 9 | + |
| 10 | +Nothing in that payload is wrong. There is just no field in it that says a |
| 11 | +person was ever on the line. |
| 12 | + |
| 13 | +This app reads a call as three separate answers instead of one, and puts the |
| 14 | +field it read next to every sentence. |
| 15 | + |
| 16 | +## Try it without placing a call |
| 17 | + |
| 18 | +No credentials, no phone, nothing stored. |
| 19 | + |
| 20 | +```bash |
| 21 | +npm install |
| 22 | +npm run dev |
| 23 | +``` |
| 24 | + |
| 25 | +Then: |
| 26 | + |
| 27 | +- `/read` takes a payload you paste and reads it. Four synthetic sample payloads |
| 28 | + are loaded for you. |
| 29 | +- `/briefing` shows a batch of calls in the order somebody should deal with |
| 30 | + them, from fixtures, with the boring ones collapsed into a line. |
| 31 | +- `/matrix` prints which endings each surface can actually express, generated |
| 32 | + from the mappers rather than written by hand. |
| 33 | + |
| 34 | +```bash |
| 35 | +npm test |
| 36 | +``` |
| 37 | + |
| 38 | +No network and no credentials. The suite runs against the package's own |
| 39 | +synthetic fixtures and a stubbed `fetch`, so it exercises what the app actually |
| 40 | +imports rather than a copy of it. |
| 41 | + |
| 42 | +## The pages that need a key or a token |
| 43 | + |
| 44 | +`/key` reads one call by id with your own key. The key travels in a header, gets |
| 45 | +used for one request and is written nowhere. It lives in React state and dies |
| 46 | +with the tab. What comes back is a masked projection of the call, never the raw |
| 47 | +payload. |
| 48 | + |
| 49 | +`/hook` is a webhook inbox. CALL-E deliveries are unsigned, so the inbox never |
| 50 | +believes one. It takes the call id out of the delivery, fetches that call from |
| 51 | +CALL-E with the server's key, and keeps only that. The posted body is never |
| 52 | +stored or shown. The inbox address carries a post token only the operator can |
| 53 | +mint, and reading the inbox back needs a second token. |
| 54 | + |
| 55 | +`/live` places a real call, and it's the only thing here that can. It needs an |
| 56 | +operator token minted for the exact number being dialled, so a browser can't |
| 57 | +make one up and a token for one number won't dial another. The built-in |
| 58 | +destinations are all in the 555-0100 range, held back for fiction and assigned |
| 59 | +to nobody. Other test lines go in `ASHEARD_EXTRA_DESTINATIONS` on your own |
| 60 | +deployment. |
| 61 | + |
| 62 | +## Configuration |
| 63 | + |
| 64 | +```bash |
| 65 | +CALLE_API_KEY=... # server only. Without it nothing reaches CALL-E |
| 66 | +ASHEARD_OPERATOR_SECRET=... # 32+ characters. Without it /live and /hook are off |
| 67 | +KV_REST_API_URL=... # Redis, for the budget, the intents and the inbox |
| 68 | +KV_REST_API_TOKEN=... |
| 69 | +``` |
| 70 | + |
| 71 | +Mint tokens on the machine that holds the secret: |
| 72 | + |
| 73 | +```bash |
| 74 | +npm run operator-token -- dial +13035550100 |
| 75 | +npm run operator-token -- inbox |
| 76 | +``` |
| 77 | + |
| 78 | +Every response from a route that touches CALL-E goes through a deep mask on the |
| 79 | +way out. A phone number that turns up anywhere in it, an error message or a |
| 80 | +structured result quoting one back, gets masked. Timestamps, scores and ids are |
| 81 | +left alone. |
| 82 | + |
| 83 | +## Side effects |
| 84 | + |
| 85 | +`/live` places real phone calls and spends credit on the configured account, |
| 86 | +and only with an operator token. Everything else in this app is read-only. |
| 87 | + |
| 88 | +Nothing here schedules anything or creates a recurring job, so there is nothing |
| 89 | +to cancel. Close the tab. |
| 90 | + |
| 91 | +Each press of the button gets an id from the page, and that id is for |
| 92 | +idempotency, not permission. The server writes the intent down before dialling |
| 93 | +and derives the idempotency key from it, so a retry after a lost response finds |
| 94 | +the call that already exists. If a request goes out and the answer never comes |
| 95 | +back, the record says the call may have gone out and stops there. Retrying a |
| 96 | +submission you can't see the outcome of is how somebody gets rung twice. |
| 97 | + |
| 98 | +## What it will not do |
| 99 | + |
| 100 | +It does not interpret a transcript, score anything, or ask a model what it |
| 101 | +thinks. Every line it produces traces to a field and says the same thing every |
| 102 | +time. |
| 103 | + |
| 104 | +It never resolves the business outcome for you. Where a reading rests on |
| 105 | +inference it says so and routes to a person, and where no field carries the fact |
| 106 | +it says that instead of filling the gap. |
| 107 | + |
| 108 | +## The engine |
| 109 | + |
| 110 | +The reading itself lives in [`asheard`](https://www.npmjs.com/package/asheard) on |
| 111 | +npm, published from CI with provenance, and this app is a front end over it. |
| 112 | + |
| 113 | +- `disposition/` maps a payload from any of the three surfaces onto three axes, |
| 114 | + each carrying whether it was quoted from a field, derived from other fields, |
| 115 | + or absent because nothing carries it. |
| 116 | +- `reconciler/` builds one evidence document per call, runs four checks over it, |
| 117 | + ranks a batch by what costs you something if ignored, and writes the sentences. |
| 118 | +- `ledger/` records what was authorized before a call goes out, with the |
| 119 | + idempotency key derived from the authorization rather than the attempt. |
| 120 | + |
| 121 | +The paired skill is [`skills/call-state-reconciler`](../../../skills/call-state-reconciler/). |
| 122 | +Its `references/observed-shapes.md` shows each platform behaviour the reading |
| 123 | +relies on, with invented values, and how to check it yourself. |
0 commit comments