⚖️ About the project

A calendar reminder cannot cancel a contract.

Exhibit A: a contract costs $540 per month. Section 3 says it renews for another year unless written notice arrives at least 90 days before the term ends. Miss the window by one day and a forgotten admin task becomes a $6,480 mistake.

Nobody intends to miss that deadline. The failure is almost always the same: a reminder fires, a person is busy, and the actual work—reading the clause, calculating the date, drafting the notice, sending it through the required channel, saving proof, and checking the next invoice—never gets finished.

Reaper turns that loose reminder into a closed, auditable workflow:

READ → GATE → SLEEP → WAKE → SIGN → SERVE → HEAR → VERIFY → DISPUTE

HEAR is the step most tools have no answer for. Cancellations get confirmed on a phone call and denied on the next invoice, so Reaper transcribes the recording with gemini-3.5-transcribe and stores the audio's SHA-256 beside the transcript — masking anything spoken that should not be written down. A promise made out loud becomes something you can produce later.

Reaper also remembers how similar clauses resolved before. Each gated clause is embedded with gemini-embedding-001 and matched in BigQuery against prior clause shapes, gate outcomes, delivery requirements, and billing results. That memory is deliberately advisory: retrieval happens only after the deterministic gate has fixed the current verdict, so precedent can warn the human but can never decide the contract.

It is not a chatbot that tells you how to cancel. It is a long-running contract-obligation agent that carries the cancellation to completion, asks for one human signature at the moment authority is required, and keeps the evidence needed if the vendor bills anyway.

The idea behind Reaper is simple: deadlines need execution, and execution needs proof.

💀 Inspiration

Most renewal products stop at the easiest part: reminding a human. But an auto-renewal deadline is not an event on a calendar; it is a task with a verb.

Someone still has to:

  1. find the controlling renewal clause;
  2. calculate the notice deadline correctly;
  3. wait—sometimes for months—without losing state;
  4. draft and authorize a valid notice;
  5. send it by the method the contract actually requires;
  6. preserve proof of what was sent and when; and
  7. inspect the next invoice to confirm the cancellation was honored.

Two principles shaped Reaper:

  • An AI acting on a contract must not be trusted on confidence alone. One incorrect date can cost an entire renewal term, so the model may propose a deadline but cannot schedule it.
  • The real deliverable is not the email. It is the evidence. If a vendor charges again, "we cancelled" is an assertion. A timestamped delivery record inside a verified chain is a case.

That is why Reaper behaves less like a reminder app and more like a notary carrying a scythe: Gemini handles language; deterministic code controls consequences.

🔄 What it does

📥 1. It finds the obligation

Reaper accepts text, PDFs, scans, and photographed contracts. It can also monitor a dedicated Reaper-owned mailbox using a headers-first admission pipeline. Gemma 4 performs lightweight renewal triage so irrelevant documents never reach the larger agent.

🛡️ 2. It redacts before it reasons

Every document crosses one privacy choke point before any model sees it. Card numbers, Aadhaar numbers, PANs, GSTINs, IFSC codes, IBANs, passport numbers, and phone numbers are masked deterministically. Luhn and Verhoeff checks reduce false positives, and the ledger records what kinds of identifiers were removed without storing the values themselves.

⚖️ 3. It makes the model prove its date

Gemini 3.5 Flash quotes the renewal clause verbatim and proposes a deadline. A separate date engine—plain regex plus calendar arithmetic, with no LLM inside—independently derives the deadline from that same clause.

  • MATCH → the obligation is scheduled.
  • MISMATCH → scheduling is blocked.
  • AMBIGUOUS → scheduling is blocked and sent for human review.

The demo includes a deliberately hostile clause that says "sixty (90) days." Reaper does not choose the answer that sounds plausible. It refuses to gamble.

😴 4. It sleeps without pretending to work

Once scheduled, Reaper parks the obligation until the notice window opens. The wait consumes no model tokens. When calendar time reaches the event, the agent wakes itself and writes a WOKE receipt—proof that the next action was triggered by time, not by a person clicking "continue."

✍️ 5. It asks for authority once—and survives the wait

Reaper drafts the formal non-renewal notice, then pauses at the only consequential human boundary: Sign and send.

The approval arrives on a phone through Telegram. Tokens are random, single-use, bound to the obligation, and accepted only from the enrolled chat. The contract text never leaves the app in the Telegram message.

This is a real durable pause, not a polling loop. In the demo, the server is killed while approval is pending. A new process starts 2.4 seconds later, reconstructs the same ADK invocation from persistent state, and continues waiting for the same signature.

✉️ 6. It serves the notice—and tells the truth about delivery

With SMTP configured, Reaper sends the real notice and records the email Message-ID inside the next SHA-256-linked receipt.

A deterministic delivery classifier also checks whether email is contractually sufficient. If the clause requires registered post or certified mail, Reaper labels the email COURTESY_COPY_ONLY and tells the human that the printable pack must still be dispatched through the required channel. It never converts "email sent" into "notice legally served" by wishful thinking.

📞 7. It hears the one channel that leaves no paper

Cancellations get confirmed on the phone and denied on the invoice. Every other channel in this workflow produces a document; a call produces a memory, and a memory is exactly what a dispute discounts.

So a recording is treated like everything else here. Upload it and gemini-3.5-transcribe writes down what was said — verbatim, never summarised. The audio's own SHA-256 is stored beside the transcript, which is what makes the pair evidence rather than a note: the transcript can be re-derived from the file, and the file can be shown to be the one that was read.

The redaction choke point applies first. In the demo the vendor asks the customer to read back the card on file, and the ledger stores [redacted:1111]two identifiers spoken aloud, zero written down, with the count on the receipt. It heard a card number and refused to record it.

The model is asked to transcribe and nothing else. It does not judge whether the vendor agreed or decide anything: what was said is a fact, what it means is for the deterministic checks and the human.

Engineering note: this is a dedicated speech model, not an instruct model. Asked for speaker labels it returns the same unlabelled paragraph either way — so the receipt stores an unlabelled verbatim transcript and records the direction of the call separately, rather than printing labels the model never assigned.

🔍 8. It checks whether the vendor obeyed

At the next billing event, Reaper wakes again and reads the invoice as a document—the form in which invoices actually arrive. Gemini Vision extracts the printed amount; deterministic arithmetic compares billed with expected.

  • VERIFIED → billing stopped.
  • REFUTED → the vendor charged again.

On the villain path, Reaper automatically creates the dispute record and packages the original delivery receipt and its hash as supporting evidence. The demo accelerates calendar time and uses a scripted vendor test world to generate the next invoice; the document reading, arithmetic verdict, agent actions, and ledger writes remain live.

🧾 9. It produces evidence, not activity theater

Every material action is appended to a per-obligation SHA-256 hash chain in Firestore: extraction, both deadline readings, the gate verdict, autonomous wakes, approval, delivery, invoice verification, and dispute creation.

One click exports a printable evidence pack containing the verbatim clause, the model's proposed deadline, the engine's independent derivation, the full chain of custody, timestamps, receipt hashes, and a fresh integrity verdict recomputed from the genesis record.

Reaper applies the same standard to its own access. Chain 0 is the mailbox access ledger: header scans, opened messages, declined messages, and denials are all recorded. In the demo, Reaper admits five messages and opens one. The four refusals are evidence too. An agent cannot quietly read more than it admits without breaking the same verification mechanism on which its dispute evidence depends.

🧠 10. It remembers precedent—without letting precedent rule

After the deterministic gate has fixed the current verdict, Reaper embeds the clause with gemini-embedding-001 and searches a BigQuery precedent store using native VECTOR_SEARCH.

The store contains both labelled fixture clauses and ledger-backed obligations. Each row preserves its provenance and operational outcome: gate verdict, notice method, final status, whether billing stopped, whether a dispute opened, and the terminal receipt hash when one exists.

This allows Reaper to surface warnings such as:

  • a near-identical clause was blocked because its words and numerals disagreed;
  • a similar contract required registered post rather than email; or
  • the vendor billed again after receiving a valid cancellation.

Precedent is advisory only. It is retrieved after the current gate rules, and a deterministic test proves that even a 97%-similar blocked precedent cannot change a clean clause from MATCH to BLOCKED.

Every lookup becomes a hash-chained PRECEDENT_CONSULTED receipt. Reaper distinguishes "no matching precedent" from "memory was unavailable," and a BigQuery or embedding failure never changes the contractual verdict.

🏗️ How I built it

Agent orchestration

Reaper uses Google ADK 2.6.3 as one resumable root LlmAgent. The human approval is implemented with LongRunningFunctionTool, ResumabilityConfig, and a SQL-backed DatabaseSessionService. This exact topology lets an invocation pause mid-plan and resume after a full process restart.

Models

  • Gemini 3.5 Flash reads contracts, extracts renewal obligations, drafts notices, and interprets invoice documents.
  • Gemma 4 is the lightweight mailbox/document triage layer that decides whether renewal language is present before the main workflow runs.
  • gemini-3.5-transcribe turns call recordings into verbatim transcripts that enter the evidence chain beside the audio's own hash.
  • gemini-embedding-001 turns every clause into a 768-dimension vector for the BigQuery precedent store — retrieval runs on a separate quota from generation, so memory never starves the agent.

Deterministic control plane

Four critical decisions live outside the model:

  • notice-date derivation and LLM-vs-engine gating;
  • delivery-method classification;
  • invoice arithmetic; and
  • privacy redaction.

The model handles ambiguity and language. Code controls dates, money, permissions, and state transitions.

State and evidence

  • Google Cloud Firestore stores obligations, the append-only receipt chains, mailbox access records, activity, resume pointers, and the living demo clock.
  • Google Cloud BigQuery (sandbox) holds the precedent store: clause shapes, gate outcomes, dispute history, and their embeddings, matched with native VECTOR_SEARCH — writes travel as load jobs because the sandbox forbids DML, which suits an append-only memory fine.
  • Google Cloud SQL (Postgres 15) holds the ADK session store through DatabaseSessionService. Two stores, because they do different jobs: a document store fits an append-only receipt chain, while the paused invocation is relational and has to outlive the container it started in.
  • Every receipt links previous hash + event type + canonical payload + timestamp through SHA-256 and is reverified on read.

Interfaces and delivery

  • FastAPI provides contract intake, obligation state, the live ledger, evidence-pack export, an iCalendar feed, and the accelerated demo clock.
  • Telegram long-polling delivers phone approvals without an inbound webhook. An approval offered in the app leaves the same receipt, so the ledger records that a human was asked whichever channel carried it.
  • SMTP/IMAP provide real outbound notice delivery and a dedicated, least-privilege mailbox path when configured.
  • Cloud Run serves the deployed container, built from this repo. Cloud Scheduler calls POST /tick once a minute so the agent keeps waking itself on a service that scales to zero — the same _tick_once() body the local loop runs, deliberately not a second implementation that could drift from the one the demo exercises. The endpoint is guarded by a shared secret, because an open heartbeat on a public URL is a way for a stranger to spend the project's model quota.

Reliability

Direct Gemini helper calls use retry, backoff, and key rotation; ADK runs can advance through a configured model ladder after quota failures. If the models are unavailable, Reaper records that it could not read a document; it never turns an API failure into a contractual verdict.

The project currently passes 60 deterministic unit tests across deadline derivation, delivery rules, privacy redaction, mailbox admission, and ledger integrity. It also includes an adk eval exam scored with deterministic ROUGE rather than an LLM judge: the clean clause must schedule, and the contradictory clause must be blocked.

Full architecture diagrams, trust boundaries, lifecycle states, and reproducible setup instructions are in ARCHITECTURE.md and README.md.

🔌 Data sources and integrations

Reaper works with user-supplied contract text, PDFs, scans, photographs, invoice documents, and messages from a dedicated IMAP mailbox. The repository includes synthetic sample contracts and a scripted vendor test world for reproducible demonstrations; it does not rely on a proprietary dataset. It also accepts call recordings (wav, mp3, m4a), which are transcribed and hash-stamped into the same evidence chain. External integrations in the live path are the Gemini API, Cloud Run, Firestore, Cloud SQL, BigQuery, Cloud Scheduler, Telegram, SMTP, and IMAP.

🧗 Challenges I ran into

Making a human pause genuinely durable

The obvious human-in-the-loop paths were not interchangeable once resumability and a database-backed session service were involved. Confirmation APIs, streaming, nested agents, and long-running tools each had different resume failure modes. I narrowed the architecture to the topology that survived failure: one root agent, non-streaming execution, a long-running function tool, resumability enabled, and a persisted resume pointer cleared only after a successful continuation.

Then I tested the claim the rude way: by killing the process on camera.

Teaching code to distrust legal English

A validator is useless if it merely repeats the model. The date engine had to independently distinguish notice periods from renewal-term lengths, recognize different deadline anchors, support days, weeks, and months, and reject contradictions such as written words disagreeing with numerals. The hardest outcome to design well was not MATCH; it was an honest AMBIGUOUS.

Building evidence that includes restraint

Logging what an agent opened is easy. Proving what it deliberately refused to open is harder. The headers-first mailbox flow, chain-0 access ledger, reason-coded admissions, and read receipts turn least privilege from a promise into inspectable state.

Demonstrating months of agency in four minutes

The production workflow is defined by long waits, while the demo must show the whole arc quickly. I built a persistent accelerated clock and a self-narrating cinema mode so the video could show real state transitions, Gemini calls, a process death, a human signature, SMTP delivery, Firestore writes, a transcribed call, invoice reading, and dispute evidence in one continuous run. The simulated time and vendor behavior are visible test fixtures; the agent execution is not prerecorded.

Giving the agent memory without giving memory authority

Retrieving similar clauses was straightforward. Defining their permissions was not.

If precedent reaches the decision path too early, a previously blocked clause can anchor the interpretation of a clean one. I therefore placed recall after the deterministic gate, preserved the source of every row, stored outcomes alongside embeddings, and made lookup failures visible without allowing them to block the workflow.

The key test is adversarial: a 97%-similar blocked precedent is returned for a clean current clause, and the current clause must still schedule. Memory may warn. It never votes.

Keeping an agent autonomous on a service that scales to zero

Cloud Run stops the container between requests. An in-process heartbeat therefore cannot be the only one: an agent whose autonomy depends on a container happening to still be alive is not autonomous.

I extracted the loop body into _tick_once() and exposed it as POST /tick for Cloud Scheduler, so the same code path serves both the local demo and production. The alternative — paying for an always-on instance — would have cost real money to preserve an illusion the architecture did not need.

Choosing the Gemini API over Vertex AI, with evidence

Vertex was tempting: the runtime service account authenticates, so there is no key to store or rotate. I wired it up and then measured what it actually served. In us-central1 this project got gemini-2.5-flash and a 404 for the 3.x Flash line, for Gemma, and for the transcription model.

Vertex would have cost two of the four Google models the project depends on. So the support ships and is documented, but the Gemini API stays primary — the decision is in the repo with the reason attached, rather than a preference asserted.

🏆 Accomplishments that I am proud of

  • The workflow closes the loop. Reaper does not stop after extracting a clause or sending a reminder. It returns after the notice, checks the next bill, and prepares the evidence-backed response if the vendor failed to comply.
  • The kill scene is a product test. The process dies during approval; a different process returns 2.4 seconds later with the same pending ADK invocation intact.
  • The refusal path is first-class. A contradictory clause becomes BLOCKED, and messages the agent declines to read become hash-chained records.
  • The gate caught the model live, on the hosted instance. Filing a clean contract there, Gemini proposed a notice deadline of 2026-11-02; the date engine independently derived 2026-11-01 (2026-12-31 - 60 days) and the obligation was BLOCKED with both readings recorded in the receipt. The model was wrong by one day, and one day past a notice window is another year of fees. Nothing about that was staged - it is receipt no. 2 on obligation 3.
  • It refuses to record what it hears. The transcription path runs through the same redaction choke point as everything else: in the demo a card number is read aloud on the call and reaches the ledger as [redacted:1111] — two identifiers heard, none written down, and the count on the receipt.
  • The demo produced unscripted evidence. A real SMTP delivery bounce arrived during filming and Reaper captured it as a VENDOR_REPLY, threaded to the notice's own Message-ID.
  • The trust thesis is executable. Sixty tests and a deterministic agent eval guard the exact boundaries Reaper claims to enforce.

🔎 What is real, and what is simulated

A demo that hides its seams is not evidence of anything, so here are mine.

Real. The agent and its durable pause. The gate. The process kill and the resurrection. The hash chain in Firestore. BigQuery precedent recall via VECTOR_SEARCH. The SMTP notice and the Message-ID stamped into the chain. The IMAP scanning, including every message it declined to open. The transcription, the redaction, and every receipt. The Cloud Run service, its logs, and the Cloud Scheduler heartbeat.

Simulated. The counterparty. DataVault Pro is not a company: it lives at @datavaultpro.test, its replies come from a stub, and check_invoice seeds the invoice it then goes on to read. The vendor's phone call in the video is two text-to-speech voices, not a recorded person — the video says so on screen while it plays. What the model hears in that audio, and what the ledger does with it, is not simulated.

The line that matters: nothing in the reasoning, the arithmetic, or the evidence is staged. The other party is.

💡 What I learned

Agents become trustworthy when you can state precisely what the model is not allowed to decide.

Reaper improved every time I moved a consequence out of the prompt and into a boundary: date math into the gate, money into arithmetic, private identifiers into the redaction choke point, authorization into a durable human signature, and history into the hash chain.

The deepest lesson was that determinism is not the opposite of agency. Determinism is what makes agency defensible.

🚀 What's next for Reaper

  • Compliant physical delivery: integrate certified-mail and registered-post APIs so contracts that prohibit email can be completed end to end with carrier receipts.
  • Close the loop where people actually reply: vendors answer on WhatsApp, SMS and RCS far more reliably than on email. Routing the notice and the vendor's response through a messaging layer would let the agent thread replies into the same evidence chain instead of waiting on a mailbox.
  • A real billing and dispute connector layer: ingest live vendor invoices and send disputes through email, ticketing, and accounts-payable systems instead of the reproducible vendor test world.
  • Vendor-response resolution: classify replies, request missing evidence, track deadlines, and continue until the dispute is closed.
  • Obligation portfolio: give teams one risk-ranked calendar of every renewal, termination right, notice window, and evidence pack across all vendors.

Built With

Share this project:

Updates