An agent licensed to act on money-mail. It reads a live Gmail inbox, reconciles invoices in a Daytona sandbox, and does not create a Gmail draft until you stamp it.
No mocks. No simulated inbox. No fake send.
Stamp reads your Gmail for vendor invoices, checks each one against a ledger of what you have already paid, and produces a decision table — duplicate, new, mismatch, or unknown. For each actionable invoice it drafts a reply (dispute or pay-confirm). Then it stops and waits.
Nothing leaves your account until you click Allow in TrueForge. If you click Deny, no draft is created. The analysis stays on screen so you can adjust and try again.
The full flow in one sentence: read mail → check books in a sandbox → show table → wait for human stamp → create draft.
The problem: Vendor reminders look identical to new invoices. A "friendly nudge" on an invoice you already paid is indistinguishable from a new bill. Chat-based AI tools will happily draft a payment confirmation from the email text alone — without ever checking your records. If the agent can also send, that draft becomes a real duplicate payment or a commitment you cannot unsay.
The fix Stamp provides: It separates reading and reasoning (which the model does) from sending (which only you do). The reconciliation runs as real Python code in a Daytona sandbox — not as model prose — so amounts and invoice IDs are compared as integers, not guessed. TrueForge's @write approval gate is the licence: the model is licensed to act on money-mail up to the point of sending, and no further.
Who it is for: Solo operators, freelancers, and small finance teams who receive vendor invoices by email and want an automated first-pass books check without handing an AI the ability to send payments on their behalf.
Three Acme invoices in a real Gmail mailbox:
| Books | Stamp does | |
|---|---|---|
| Invoice #4412 · $4,200 | already paid | draft dispute → wait for stamp |
| Reminder #4412 · $4,200 | same row | one dispute, not two |
| Invoice #4419 · $890 | not on ledger | draft pay-confirm → wait for stamp |
Prompt: Process my Acme invoices.
After you click Allow, open Gmail → Drafts. If the draft is not there, it did not work.
| Piece | What |
|---|---|
| Runtime | TrueForge (bundled chat). Not a custom UI. |
| Model | OpenAI |
| Inbox | Google Gmail MCP (gmailmcp.googleapis.com) |
| Write gate | TrueForge @write approval — pauses before create_draft |
| Sandbox | Daytona — runs the reconcile Python, not the laptop |
| Books | demo/ledger.csv — read in sandbox, not typed into prompt |
| Skill | skills/stamp/SKILL.md |
Before you start, make sure you have:
- Node 22+ —
node --version - OpenAI API key — from platform.openai.com
- Daytona API key — from app.daytona.io (needs Sandboxes + Snapshots write)
- Google Cloud project with Gmail API enabled and an OAuth 2.0 client (see step 3 below)
# Clone
git clone https://github.com/fozagtx/stamp.git
cd stamp
# Copy env defaults (no secrets in this file)
cp .env.example .env
# Start TrueForge
npx @truefoundry/trueforge@latest --port 8790Open http://localhost:8790 — you should see the TrueForge chat UI.
Settings → Models → OpenAI → Add key
Paste your OPENAI_API_KEY. Temperature 0.2 is set in the agent spec.
Settings → Sandbox Providers → Daytona → Add key
Paste your DAYTONA_API_KEY. The reconcile Python runs here — not on your machine.
Settings → Connectors → Add MCP Server
| Field | Value |
|---|---|
| Name | gmail |
| URL | https://gmailmcp.googleapis.com/mcp/v1 |
| Transport | Streamable HTTP |
Click Connect → Google OAuth popup → sign in with the Gmail account Stamp should read.
Google Cloud setup (one-time):
- console.cloud.google.com → your project → Enable Gmail API
- APIs & Services → Credentials → Create OAuth 2.0 Client ID (Web application)
- Authorized JavaScript origins:
http://localhost:8790- Authorized redirect URIs:
http://localhost:8790/auth/callback- Paste Client ID + Secret when TrueForge prompts during Connect
Settings → Skills → Import from GitHub → fozagtx/stamp
TrueForge finds skills/stamp/SKILL.md automatically.
Agents → Import → upload agent/stamp.spec.json from this repo.
This wires everything: Gmail connector, stamp skill, Daytona sandbox, @write approval policy.
Follow demo/INBOX.md — send three real emails into the connected Gmail account:
- Invoice #4412 · $4,200 from Acme
- A reminder for #4412 (same amount)
- Invoice #4419 · $890 from Acme
These must be real emails. The agent searches Gmail live — there are no fixtures in this repo.
In TrueForge chat, type:
Process my Acme invoices.
Watch it:
- Gmail search + read — ungated, no approval prompt
- Daytona sandbox — reconcile runs as Python, not prose
- Table —
#4412 duplicate_paid,#4419 new_unpaid - Pause — TrueForge shows
create_drafttool call, asks Allow / Deny
Try Deny first → check Gmail Drafts → empty ✅
Try Allow → check Gmail Drafts → draft appears ✅
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python -m pytest -q
# 12 passedRequired for submission. After the first reviewed merge, replace this paragraph with:
- Link to the merged PR
- What Qodo surfaced and what changed or was dismissed
- Note that a follow-up review ran on the final code
Direct pushes to main do not count.
Stamp is a TrueForge agent. MCP reaches Gmail via Google's official Gmail MCP (gmailmcp.googleapis.com). The sandbox runs extract + reconcile. Skills hold the procedure. Subagents split mail vs numbers. TrueForge pauses on create_draft. The session is TrueForge's. We did not wrap a chat model in a custom app.
