Inspiration
Getting one service visit done — a car, a furnace, a delivery slot — is not one decision but eight, and today a person makes all of them every time: pull up the context (what the thing needs, what was done before), check the history (is this already booked?), judge the provider's qualification, compare the price, find a slot that fits the availability, give the approval, absorb the cancellation when the shop calls, and re-enter the whole loop for the replacement. An assistant that asks before each of those eight steps has only turned the errand into a form you keep signing. An assistant that never asks is not something anyone wants near money, safety or anything irreversible.
We wanted the missing third thing: say the boundary once — routine care up to ¥10,000, at most ¥30,000 a month, safety work always asks me — and let the assistant carry the routine steps for days, across conversations, restarts and a garage that cancels, while the risky steps still arrive as questions with the exact quote attached. Car maintenance is the demonstration because everyone recognises both failure modes there. The pattern is the product.
What it does
Steward is a self-hosted MCP server plus a deterministic authority engine that lets a language model act autonomously inside a human-set boundary. Before: you approve every action. After: you approve the boundary — once. The first screen answers four questions in three seconds: what the AI may do alone (AUTO), what always pauses (ALWAYS ASK), what is never by the AI (approve · pay · change or revoke the rule — no MCP tool exists for them), and what it just did and why it stopped (the latest server decision on a story strip, one spoken sentence labelled as the Alexa+ experience simulation it is, and an AI tool calls ribbon of every MCP call with the server's answer). All of it is derived from server records, never from what the model said.
- The AI does the routine steps. It discovers the five MCP tools at runtime, reads context, history, qualification, price and availability, evaluates each plan against the rule and books what the rule allows: an ¥8,000 oil change is AUTO. An ¥18,000 brake job is PAUSED — the server will not book it.
- Approval is a data structure, not a message. You approve that exact quote (vehicle, work, provider, slot, total, versions, expiry). It is hashed, expires, and is used once; any change invalidates it. Then the booking goes through as APPROVED.
- The delegation survives events. Next day the garage cancels. The confirmed cancellation releases the ¥8,000 hold — only the hold — and the AI books an ¥8,500 replacement inside the original rule: RECOVERED, no new question. Committed ¥26,500, remaining ¥3,500, paid ¥0.
- When the model tries anyway, the server stops it — on record, with a live model. On 2026-09-15 a live GLM 5 on Amazon Bedrock read that the delegation had been revoked and still submitted
book_within_authority; over the real Streamable HTTP MCP path the engine answeredstop: rule_revoked. Later that day, with ¥26,000 of the ¥30,000 month committed, the same model submitted a ¥5,000 filter and the engine answeredstop: monthly_budget. Nothing was booked, reserved or dispatched either time. - The car is only the example. The same engine runs a home-maintenance delegation — furnace filter AUTO, gas-line repair PAUSED, cancelled visit RECOVERED, touch-up paint BLOCKED — from data alone (
tests/generalisation.test.ts).
The Alexa+ conversational shell and the Alexa+ client in our simulation are labelled simulations; the MCP server, the model's tool selection, the policy decisions and the server's refusals of the live model are real.
How we built it
- Amazon Bedrock, Converse API, client-side tool use — the agent loop.
toolConfigis generated from the MCP server'stools/list(closed JSON-Schema dialect; the booking retry key is application-owned and projected out of the model's schema). Verified withzai.glm-5inap-northeast-1: ten ledgered requests, nine AWS request IDs, US$0.035; two further owner allowances of three requests paid for the two live refusals. - Self-hosted MCP server — official TypeScript SDK 1.30, Streamable HTTP, MCP 2025-11-25 (a client asking for another version is answered with 2025-11-25, as the specification's negotiation rule says), Origin/Host validation, closed input and output schemas on all five tools, readable error results. No approve, confirm, pause or revoke tool exists.
- Alexa+ readiness (remote profile) — written line by line against the Alexa+ MCP Toolkit pages:
401withoutWWW-Authenticate, RFC 9728 Protected Resource Metadata, authorization-server metadata withS256, OAuth 2.1 bearer validation bound to the canonicalresource,client_credentialsfor registration andtools/list,authorization_code+ PKCE for the linked customer,403on the first user tool so Alexa+ can start account linking, JWKS for an external issuer, rate limit, timeouts,/ready, structured logs, fail-closed composition. A labelled simulated client replays the documented runtime flow against the real server (23/23); the image is container-tested; the manifest candidate and its media pass every documented constraint. Not deployed, not registered. - Deterministic authority engine — TypeScript, Node 24
node:sqlite,BEGIN IMMEDIATEtransactions: rule, integer-JPY budget (committed = paid + held), safety category, exact-approval fingerprint with TTL and one-time use, version binding, idempotency, reserve-before-dispatch and recheck-before-commit, unknown outcomes keep their hold, cancellation releases exactly one hold. - Evidence — a durable ledger reserves a slot before every Bedrock send; receipts carry the model-input digest, so the proof export correlates model requests, MCP executions and AWS request IDs;
REAL AI — VERIFIEDis computed from that evidence, never typed. - Adversarial proof —
npm run adversarialdrives hostile model adapters through the official MCP client into the real server: eight boundaries, 16/16 runs held, every refusal in the audit trail; eight malformed outputs never reach MCP. The same runner sent the live model's requests for the two refusals above. - Hosting boundary — an AgentCore Runtime release candidate: the
linux/arm64image runs the whole campaign as a container on a mounted session store; CloudFormation, a least-privilege execution role and a one-command deploy/verify/teardown exist. Two hosted attempts reached ECR and IAM;CreateAgentRuntimewas refused with a reason-less AccessDenied underBedrockAgentCoreFullAccessplus our deployer policy, and a later one-call diagnosis under a temporaryAdministratorAccesspassed authorization (stopped by design at a non-existent role; nothing created) — the missing permission or condition is not yet identified and is with AWS Support; everything was torn down. Not deployed. - UI — Next.js 16 / React 19, bilingual, keyboard-first, tested at six widths with zero console errors.
Challenges we ran into
Access, not code — three times. Model access: on a new account every current Claude model was agreement-gated, and each prerequisite (agreement write, Marketplace subscribe, account verification, "not available for this account") surfaced one at a time; a read-only discovery script found 42 callable models and the owner chose GLM 5 — the swap touched adapter configuration only, which is why src/core and src/mcp are model-free. Hosting: CreateAgentRuntime was refused with a message that names no reason, under three policy versions and the AWS managed full-access policy alike, yet authorized under a temporary AdministratorAccess in a one-call diagnosis that created nothing — so it is not a gate we can name: the missing permission or condition is not yet identified, and an AWS Support case is open. Alexa+: the toolkit's requirements can only be validated at alexa-ai deploy against a public HTTPS URL with a full store-listing media set, so we built the server side to every written line and replay the documented client flow locally, labelled as a simulation. Details in the friction log.
Accomplishments that we're proud of
- A real model on Bedrock completed the whole delegation — AUTO, PAUSED, human exact approval, cancellation, RECOVERED — choosing MCP tools itself, and in its final turn quoted an injected IGNORE ALL PREVIOUS RULES provider text and stated it grants no authority.
- The server never depends on that good behaviour, and we proved it twice with the same live model:
stop rule_revokedafter revocation,stop monthly_budgetat the limit — audited asbooking_stopped, nothing booked. Hostile adapters got the same answer on all eight boundaries (16/16 runs over the real MCP path). 209 tests, 46/46 guard mutations detected, eight forbidden inputs rejected over real HTTP, restart/SIGKILL/parallel-writer recovery, the campaign re-run through the AgentCore invocation contract inside the built container, and the Alexa+ checklist met on the server side in a container-tested image. - An honesty model judges can check: computed verification label, untrusted-text framing, simulated days and simulated clients labelled, a cost ledger with the failed request kept, and the inconclusive first refusal attempt kept on record next to the successful one.
What we learned
Approval has to be a data structure, not a message: once "approved" is a hash over everything that gives an action its meaning — bound to versions, expiring, consumed once — long-running autonomy stops being scary. Keep the model out of the authority path entirely: the cheapest defence against prompt injection is a server that cannot be told anything. And read the vendor's contract as a test suite: the Alexa+ toolkit's checklist became conformance tests the same afternoon, which is how we found that its documented client negotiates an older protocol version than the toolkit names.
What's next
- Register the add-on: host the remote profile behind HTTPS,
alexa-ai new mcp … --mcp-server-url,configure-account-linking,deploy, the web simulator — the owner's steps are written (docs/alexa/ALEXA_PLUS_READINESS.md§3) and unexecuted. - Deploy the AgentCore Runtime release candidate once AWS Support names the permission or condition behind the
CreateAgentRuntimerefusal (docs/phase6/RESUME_AB.md). - Live refusals for the other six adversarial boundaries and a second model; multi-customer routing (one delegation per linked account).
Built With
- amazon-bedrock
- aws-sdk-for-javascript-v3
- docker
- model-context-protocol
- next.js-16
- node.js-24
- oauth-2.1
- playwright
- react-19
- typescript
- zod
Log in or sign up for Devpost to join the conversation.