Volunteers run on their inbox. Nobody has time for it.

Small organizations (food pantries, shelters, parish offices) lose hours to the same emails: are you open Thursday, can I donate furniture, how do I start volunteering. The usual chatbot answer is to hand the inbox to an LLM and hope. Verger does the work, but a human holds the send button.

What it does

Verger is a front-desk agent that reads the organization inbox end to end. For every message it drafts a reply and queues it for the trustee's explicit yes, declines politely, or stays silent. Every autonomous action lands in a hash-chained receipt ledger a human can audit.

The gate. A Strands BeforeToolCallEvent hook checks every tool call against the allowlist and raises an interrupt before anything leaves the agent. Approving sends bit-for-bit what was reviewed; denying retires the paused session. Nothing is sent without the trustee. Watch it happen in the demo video.

The receipts. An append-only sha256 hash chain, self-verifying on every append. The desk shows the ledger's own verdict and never re-derives hashes client-side.

The desk. One screen for the trustee: a decision bell that swings when a send is waiting, one-click approve or deny, and the full chain verdict in view.

How we built it

  • Strands Agents SDK (TypeScript, @strands-agents/sdk): the Agent class, tool() helpers, and interrupt-based human-in-the-loop as enforcement, not decoration.
  • Serverless rounds: one inbox message per function invocation, chained by the desk client, with a single-flight lock so an unattended cron tick and the trustee can never double-hold the same message.
  • Netlify Blobs holds all state: mailbox, sent folder, receipt ledger, pending decisions. No server, no disk.
  • gpt-oss-20b on Groq's OpenAI-compatible endpoint as the demo brain.

Try it

The live desk ships with a seeded demo mailbox: run a round, the agent works the inbox, the bell rings, you decide. Source is on GitHub.

Challenges

  • Fresh agent per chunk: shared conversation state made the model answer the previous sender. Zero cross-chunk state fixed it.
  • Every "just let the model decide" shortcut failed the trust bar. The gate is real because the trustee's decision executes deterministically.
  • Status chrome must never lie: the receipt chain verdict comes from the writer, never from a stale cache re-derivation.

Built With

  • groq
  • netlify
  • netlify-blobs
  • netlify-functions
  • nextjs
  • sha256
  • strands-agents-sdk
  • typescript
Share this project:

Updates

Submission history