DEV Community

Anthony Zender
Anthony Zender

Posted on

My Trading Bot Tried to Execute the Same Trade Twice. That Became SafeAgent.

GitHub “Finish-Up-A-Thon” Challenge Submission

This is a submission for the GitHub Finish-Up-A-Thon Challenge

The Bug That Doubled Real Trades

On May 21, my live trading bot generated six duplicate execution attempts in one session.

SafeAgent blocked all six.

Without the guard:

  • one duplicated a $1,350 sell
  • another doubled a TQQQ position
  • total duplicate transaction exposure: $3,653

That session changed how I think about AI agents, retries, and execution guarantees.

What I Built

SafeAgent is an exactly-once execution guard for AI agents and SaaS applications. It prevents duplicate payments, emails, trades, and webhook processing when retries fire after a timeout or crash.

The Comeback Story

How it actually started

Six months ago I was building two things at once: PeerPlay — a patented P2P wagering exchange for skill-based video game tournaments (USPTO provisional 63/914,036) — and a live QQQ/TQQQ momentum trading bot running on Alpaca Markets.

Both hit the same bug. Contest verification agent times out, retries, settlement fires twice. Bot order fills, confirmation drops, retry fires, doubled position. Same failure mode. Different domain.

Different models pushed me toward very different architectures during development. Some were fast but overconfident. The most useful moments came when a model explained why an approach was broken before I implemented it.

That's part of why SafeAgent sat unfinished. Not just time — wrong turns that burned momentum.

Why local idempotency fails

Early versions used a local SQLite guard. It worked until it didn't:

  • workers restart and the in-memory state is gone
  • containers reschedule and replay from the last checkpoint
  • retries land on a different machine entirely

Exactly-once semantics require a durable coordination boundary outside the worker itself. That's what the hosted /claim endpoint provides — the claim lives on the server, not in the process.

Where the project was

I published the original article in April after extracting the pattern from both projects. That was the before state:

  • Local SQLite guard only
  • Basic /claim endpoint
  • Trading bot integration example
  • /audit marked "coming soon"
  • No SaaS coverage
  • No external integrations

What I finished

1. /audit endpoint — now live

Was implemented in code, never deployed, never documented. Now it's live:

curl "https://safeagent-production.up.railway.app/audit?agent_id=bot-1&status=COMMITTED"
Enter fullscreen mode Exit fullscreen mode

Full claim history, filterable by agent_id, action, status, and timestamp range. Every claim, every SKIP, every duplicate blocked — with timestamps.

2. SaaS coverage — Stripe, webhooks, email

The original README read like a trading tool. SafeAgent solves the same problem for any SaaS. Stripe, GitHub, and Twilio all guarantee at-least-once webhook delivery. SafeAgent turns at-least-once into exactly-once:

def handle_stripe_webhook(event):
    r = requests.post(
        "https://safeagent-production.up.railway.app/claim",
        json={
            "agent_id": "saas-webhooks",
            "action_type": "stripe_event",
            "scope": event["id"]
        }
    )
    if r.json()["status"] == "SKIP":
        return {"ok": True}
    provision_subscription(event)
Enter fullscreen mode Exit fullscreen mode

3. WisePick integration

WisePick shipped a full adapter, replay demo, and integration docs. The integration splits routing from execution — WisePick answers what and which provider, SafeAgent answers whether this already ran. The decision_id is intentionally excluded from the request_id derivation so retries that mint a new routing decision still hit the same execution slot and return SKIP.

4. CrewAI hosted backend

PR crewAIInc/crewAI#5822 adds pluggable idempotency backends. I shipped a hosted SafeAgentCacheBackend that implements the interface — cross-machine, crash-safe, no local SQLite required.

5. Production proof — May 21

Time Event Blocked
0942 ET duplicate buy TQQQ qty=6 $452
0947 ET duplicate add TQQQ qty=6 $452
0949 ET duplicate sell TQQQ qty=12 $902
1000 ET duplicate entry TQQQ qty=6 $454
1014 ET duplicate sell TQQQ qty=18 $1,350
1106 ET duplicate SQQQ add $43

The session also surfaced a gap: the exit side has no guard. When a SQQQ exit failed with 422 Unprocessable Entity, the bot logged ENTRY BLOCKED for three hours from a phantom position that didn't exist. That failure mode is now documented and is the next spec item — not buried, in the README.

Demo

My Experience with AI

Claude has been in every meaningful step: the two-phase claim architecture, the SaaS integration examples, the WisePick README section, every GitHub PR comment before I posted it.

The most useful thing it does isn't writing code — it's telling me when an approach is wrong before I build it.

The model that's most confident isn't always the most correct. The one that says "this approach is broken because X" is worth more than the one that says "here's how to build the broken thing faster."

Top comments (2)

Collapse
 
azender1 profile image
Anthony Zender

The exit-side gap mentioned at the end now has a fault-injection test suite thanks to w2jmoe — github.com/w2jmoe/WisePick/blob/ma.... Building the production implementation against it this week.

Collapse
 
jay_carson_fe76a7ba89578d profile image
Jay Wu

Great write-up! Integrating SafeAgent with WisePick was seamless and has significantly hardened our agentic workflows. Your approach to splitting routing from execution is spot on! 🤗✨️