Inspiration

Every household transaction carries a promise. A four-hour delivery window. A five-minute ETA. A refund "within 5–7 days". A punctuality guarantee. These get broken constantly, and the businesses making them price in the fact that almost nobody claims. Unclaimed money is a budgeted line item.

It isn't laziness. Claiming costs more than any single promise is worth — find the confirmation, find the policy, prove what happened, wait on hold — so the money quietly stays with the merchant.

Meanwhile Alexa+ is becoming a place where transactions happen, and every add-on shipped for it so far exists so a brand can reach a customer. The store is a distribution channel. We wanted the inversion: an add-on whose principal is the household, sitting on the customer's side of every transaction those brands run through Alexa. Amazon built Wallet so you can pay. Owed is the mirror image — the layer that collects when the other side doesn't deliver.

What it does

"Alexa, what am I owed?" "You've recovered forty-seven dollars this week, and eight dollars is still open. Two promises were kept. There's one I'm not claiming, because I only watched twenty percent of the window. There's one more I can file."

Owed records every promise a business makes your household, notices when one is broken, and collects — agent to agent, while you do nothing.

  • Captures the promise where it is made. A rule-based extractor reads receipts, confirmations and ETAs into typed promises across seven kinds — delivery windows, ETAs, refund SLAs, appointment slots, guarantees, price matches, warranties. Every promise keeps the sentence it was read out of, and the evidence card quotes it back under "they wrote", so you check the merchant's own words rather than trusting Owed's reading.
  • Watches with evidence the home already produces. Timestamps, carrier scans, doorbell events, snapshots, observed refunds and prices.
  • Refuses to overclaim. A breach needs two gates, not one: confidence ≥ 0.85 and coverage ≥ 0.7 of the promised window actually observed. Fail either and the claim is held back, not filed — and the card names the gate that refused and the stretches nobody watched.
  • Files and settles with the merchant's agent. An open, MCP-shaped recourse protocol: CLAIM → OFFER → COUNTER → SETTLE, every counter citing the merchant's own published clause. In the demo: asked $10.00, they offered $3.00, Owed countered $8.00 citing Delivery Guarantee 4.1, settled at $8.00 in two rounds.
  • Keeps an event-sourced ledger you can query by voice, replayable to any instant — so a timeline scrubber through the week is truthful rather than cosmetic: every tool answers for the clock, not for now.
  • Speaks first when a claim window is closing, instead of waiting to be asked.
  • Works with no screen at all. Every answer stands on its own on an Echo Dot.

The feature we are proudest of is the one that doesn't claim. An agent that collects money on your behalf is only worth having if it tells you when it isn't sure.

How we built it

A real MCP server, not a mock. TypeScript on the MCP SDK 2.1.0 over Streamable HTTP, protocol revision 2025-11-25 — six tools, three ui:// MCP Apps views in sandboxed opaque-origin iframes, elicitation. Account linking runs against our own OAuth 2.1 + PKCE authorization server, because the SDK ships none: RFC 9728 and 8414 discovery, RFC 8707 audience binding, RFC 8252 loopback redirects, static clients and no dynamic registration (Alexa+ doesn't support it).

Core engines. A rules extractor with no model and no network. A breach engine with coverage algebra over six breach kinds. A negotiator that takes its reservation value from a policy library of the merchants' own published remedies. An append-only, event-sourced ledger behind one port, running over memory, Node's built-in SQLite, or DynamoDB — where the entire append-only guarantee is one conditional write (attribute_not_exists(sk)) plus an atomic sequence counter, with seq rather than time as the sort key so a replay is identical across all three adapters.

Four merchant agents run in their own process and are argued with over HTTP, never called in-process — and the protocol inspector shows that traffic on the wire, in causal order.

Measured, not asserted. Three hypotheses pre-registered before the code that answers them, six eval harnesses, nine verification gates, 58 test files and 569 test cases. CI re-derives every committed number on a different platform and runs the whole suite a second time under a timezone on the far side of the date line. One gate opens the archive pnpm pack would publish and reads what's inside it; another recomputes the H1–H3 verdicts from the result files and fails if the README disagrees with them.

And the part worth saying plainly: Alexa+ for Builders is partner-gated, so none of this has run against the real Alexa+ client — not one tool call, not one card. Every green check is against the documented contract, a community conformance CLI, and a third party's Alexa+ client. The orchestrator, the voice pipeline, proactive delivery and the household's evidence are simulated, and labelled as such wherever they appear.

Challenges we ran into

Twenty of them are written up with reproductions in docs/friction-log.md. The ones that changed the product:

The SDK ships no authorization server, and the docs don't say what to do instead. So we wrote one — discovery, PKCE S256, audience binding, consent, refresh.

Elicitation cannot happen on the revision Alexa+ documents. On 2025-11-25 it's a server-to-client request that needs a session, and per-request serving can't make one. So claim_file proposes in words and waits for an explicit confirm, and the elicited round trip is exercised on a modern-era connection instead. Both paths are tested, including decline — after we shipped the SDK's own worked example and found it loops forever when the user says no.

There is no documented way for an add-on to speak first — and Owed exists to notice what the household didn't ask about. A parcel that never came. A refund that never landed. We could not find a proactive channel in the published docs, so we wrote the one we needed as a zod schema (CommitmentEvent, with expires_at, urgency and offer) and badged every announcement simulated proactive. Later we were told the public docs omit a good deal here — so what we can evidence is a documentation gap, and it still had a product-shaped consequence.

A sandboxed view was permitted to call every tool, claim_file included. MCP Apps' tool visibility defaults to the permissive value and the server helper never says so. This was invisible from inside our own repo — our host builds its bridge with a null client, so it never wires view-to-tool calls at all. We only found it by running a third party's Alexa+ client against the server and asking it which tools it would let a view call.

Three of the four things that cost us the most time were checks that could not fail. sha256sum -c warned about a SHA-512 line and exited 0, so a supply-chain check passed without checking. systemd-analyze verify exited 0 on a directive it couldn't parse. A /health route with no explicit handler fell through to the static site and returned index.html with a 200, so a monitor would have reported healthy forever regardless of the database.

Our headline number didn't survive its own robustness test — see below.

Accomplishments that we're proud of

All three pre-registered hypotheses are published met or not met, with the numbers.

  • H2 — breach detection: met. 100% precision and 100% recall on every one of the six breach kinds across 60 pre-registered timelines, with a 0% false-positive rate on promises that were kept. And the number the product actually rests on came out clean: every case where a promise was broken but the camera had been starved was held back rather than claimed.
  • H1 — extraction: met on the corpus it was tuned against, and missed badly on data it had never seen. 100% precision / 99% recall on 120 labelled messages; 73.7% / 58.3% on 40 held-out ones. That gap is the most useful number in the repository, because the corpus figure was reached by changing the extractor three times against the very messages it is scored on — which is exactly the process that makes a benchmark meaningless. The held-out set was written after tuning finished, run once, and will not be fixed against.
  • H3 — negotiation: met on one of three criteria. Across 1,620 negotiations on a 270-policy grid, arguing recovers 33.9% of what merchants' own policies say they owe against 30.8% for accepting the first offer — a 1.10× lift at a median of two rounds. With priors learned from the ledger it reaches 44.1%, a 1.43× lift, and stops arguing with merchants who punish it. The pre-registered bar was ≥60% and ≥1.5×. Not met, and reported as not met.

The result we published even though it hurt. Re-running the grid with merchant parameters drawn from a seeded source instead of taken exactly, the 1.10× lift becomes 0.95× at 10% wobble and 0.90× at 25%: once a merchant's acceptance threshold is uncertain, arguing loses to taking the first offer. The sign of our headline result flips. We then ran the fix we had predicted for it — and the prediction was wrong; conceding trades about one for one and never gets back above 1.0×. The sentence that predicted otherwise was replaced by the table that refutes it.

Documentation that cannot drift from the code. A gate recomputes the hypothesis verdicts from the result files, compares every quoted figure, checks every relative link, and compares the words a presenter is told to say against what the app actually says. It exists because this README once spent eleven days telling a reader to announce "breach detection: not met" after a fix had made it met.

Open source that is actually liftable. Apache-2.0, repository public, with three packages — the recourse protocol, the policy library and the merchant agents — self-contained enough to lift out as they stand, each shipping its own licence and NOTICE inside the archive. The protocol's published conformance checker runs against all four reference agents over real HTTP.

What we learned

The failure mode that survives a test suite is the one where the check itself cannot fail. That was the lesson of the whole build, and it kept arriving in different costumes: an exit code that means nothing, a gate written and wired to no caller, a licence field checked while the archive it described shipped no licence at all. We started proving a check could fail by mutating the code and watching it go red.

A benchmark you tune against stops being a measurement. Three extractor changes took the corpus to 100% precision. Forty messages written afterwards took it to 73.7%. Both numbers are published, in that order, with the explanation of why the second one is the real one.

Don't trust the write-up over the code — including this one. A detector defect lived in our README as one problem for two iterations; reading the two failing cases rather than the sentence summarising them showed it was two problems, with a third hiding underneath.

Owning both ends of a protocol gives you exactly one reading of the spec. Every bug that mattered most was found by an implementation we didn't write — a community conformance CLI, and then a third party's Alexa+ client, which earned its keep within an hour.

Write down what you could not verify, at the moment you could not verify it. Half of what makes this submission readable is a sentence from the week it was true.

What's next for Owed

  • Run Bedrock for real. A model-backed extractor is written and tested either side of the model call and at it — against a local Converse endpoint through a real SDK client — and no model has ever been invoked. The IAM policy for it exists and is attached to nothing, deliberately. The first real number to get is whether a model beats 73.7% on the held-out set.
  • Proactive, for real. If Alexa+ exposes a channel for an add-on to speak first, CommitmentEvent is ready to meet it, and the badge comes off.
  • Live evidence instead of seeded. Ring Partner API certification for doorbell events, and a live inbox with on-device pre-filtering so no raw mail leaves the home.
  • Fix the knife-edge. The negotiator wins against a merchant who honours its published figure exactly and loses the moment they don't. Conceding isn't the answer — we measured it. Learning a merchant's threshold rather than assuming it is the next thing to try.
  • Publish the protocol. The three packages to npm, plus a merchant SDK, so a merchant agent can implement recourse without reading our source.
  • Per-person ledgers once Alexa+ exposes speaker identity, Amazon Wallet settlement so credits land as balance, opt-in auto-file policies per merchant, and household scorecards — which merchants actually keep their promises.

Built With

Share this project:

Updates

Submission history