Inspiration

Most agents that touch money gate it at the wrong layer - a single on/off "financial" permission, or a ">$200" rule the language model is merely asked to honor. A jailbroken or overconfident model spends or sends anyway. A family will only hand an agent real money and real consent if the rules are enforced in code the model cannot talk its way around. So we built the authority ledger first, not the chatbot.

How we built it

  • Strands Agents SDK (strands-agents 1.54.0, bidi extra): six agents in a Strands Graph — intake, matcher, planner, authority, executor, briefer — with a critic-to-planner revision edge.
  • Authority decided three times in code: the check_authority tool result the model may only echo, an AuthorityGuard that recomputes every decision after the run, and a BeforeToolCallEvent / BidiBeforeToolCallEvent hook that cancels any rail call the ledger did not allow. The same hook covers text and voice.
  • Amazon Bedrock: Nova Pro for document vision, Nova 2 Lite for reasoning, Nova 2 Sonic for a live voice conversation over a WebSocket.
  • Amazon Bedrock AgentCore Runtime: the whole graph is deployed to a runtime (ARM64 image, scoped IAM with no wildcards) and verified with a live invocation.
  • Honest rails: Amazon SES email, an internal double-entry ledger, a Stripe test-mode intent, official-form rendering, and read-only public lookups (CPSC recalls).
  • A guardrail harness replays 50+ adversarial and in-scope requests and generates the scorecard into the README.

Challenges we ran into

  • The live number was not zero. Running the guardrail sweep against a real Nova 2 Lite executed 6 of 28 out-of-scope requests — every one a case where the model reframed a forbidden request into something the ledger genuinely permits (an in-limit slice of a split payment, a parent's own allowance transfer nudged by a document injection). We chose to publish both numbers rather than the flattering one: code never executed anything the ledger forbids, but "0 out-of-scope" is not true of a capable live model, because it can reframe.
  • Consumer money has no APIs, so "completed" is bounded honestly: a real email, an internal ledger, a rendered form, a read-only lookup - never a fabricated bank transfer.
  • A stale field name (fixture_id vs request_id) hid a bug in the remote runtime path until the first live AgentCore call.

Accomplishments we're proud of

  • Human-in-the-loop the way the track asks for it. Kith acts autonomously on what the ledger allows and surfaces everything else to the responsible adult in the app queue for one tap (PIN-gated, minors excluded), and emails them the documents it prepares as a real Amazon SES send; a prepared insurance claim is rendered for the human to file, never submitted for them.
  • A per-member authority model that neither reference project has: adult self, parent-for-minor, and scoped spouse grants, all enforced in code with a published adversarial metric.
  • The same code-level gate holds identically over text, voice, and (foundation laid) any channel.
  • Live proof on AWS: Nova Pro read a statement photo 18/18 fields, Nova 2 Sonic held a voice turn, and the graph runs on a deployed AgentCore runtime.

What we learned

A deterministic harness proves the code path is wired; only a live model proves what the code path actually permits. Keeping both numbers is what makes a safety claim survive a skeptical reader.

What's next

Reach families where they already are - a shared identity seam and per-channel trust policy are built so Kith can answer from a family group chat (Telegram), SMS, and Alexa+ via a Streamable-HTTP MCP server, with money always escalating to an authenticated approval. A warmer, mascot-led interface so a seven-year-old and a parent approving a payment share one screen.

Built With

  • amazon-bedrock-(nova-pro
  • amazon-bedrock-agentcore-runtime
  • amazon-ecr
  • amazon-ses
  • aws-iam
  • docker
  • fastapi
  • nova-2-lite
  • nova-2-sonic)
  • playwright
  • pydantic-v2
  • python-3.12
  • strands-agents
Share this project:

Updates

Submission history