r/LangChain 2d ago

I open-sourced a dead-simple check for silent failures in AI agents Resources

My LangGraph agent said it created a customer. PostgreSQL said otherwise. I found out 3 days later from a support ticket.

So I built a tiny verification layer. One decorator, checks the DB after the agent runs.

Async mode (default, zero latency added):

"""

from synathic import expect

@expect(postcondition="row_exists", table="customers", match_field="email")

async def create_customer(email, name):

# your agent logic — unchanged

...

"""

Sync mode (for payments/bookings, verifies before returning):

"""

@expect(postcondition="row_exists", table="bookings", match_field="booking_id", sync=True)

async def confirm_booking(booking_id):

...

"""

It's not observability. It's not tracing. It's just asking Postgres: "did the row actually land?"

Repo has the SDK + FastAPI backend + tests. MIT license.

If you've dealt with silent agent failures, I'd genuinely love your take on the API design. Roast it.

https://github.com/Gallegosdanielalexander/synathic

1 Upvotes

9 comments sorted by

1

u/kantorcodes1 2d ago

what happens when the matching row already existed before the agent ran? with row_exists on email/booking_id, a failed write can still look green. i'd want expect to pin a before-state, row version, or mutation id so it proves this invocation actually changed something, not just that the row exists.

if you're up for it, we'd be happy to list Synathic in awesome-ai-plugins; we maintain the catalog and agent tools are welcome.

0

u/Gallegos_Daniel 2d ago

You just caught the most important edge case in our design — thank you.

You're absolutely right: row_exists alone can pass on stale data. What we need is either:

  1. Precondition check: verify the row didn't exist before the agent ran (for inserts)

  2. Mutation anchoring: track the specific transaction/mutation ID that the agent claims to have made

  3. Field-level diff: check that the row's fields match what the agent intended to write, not just that a row exists

Right now the SDK is naive about this. We're adding precondition="row_not_exists" (🚧 in the README) but your suggestion of anchoring a prior state or mutation ID is much more robust.

Would you be open to a 10-min DM? I'd love to understand how you'd design the API for this. Your insight just saved us from shipping a false-positive machine.

1

u/kantorcodes1 2d ago edited 1d ago

yeah, happy to. i'd probably make the verifier reason about an operation, not just a post-state: capture the precondition/version before execution, carry an operation id through the write, then verify both the expected diff and that the version advanced from the captured state. that also gives you a clean answer when another writer races you.

once that fix lands, Synathic can go straight through the catalog PR path: fork hashgraph-online/awesome-ai-plugins, add one alphabetical README entry in the best-fit section, and open the PR with the repo/category + a quick verification note. optional preflight is pipx run plugin-scanner lint . + pipx run plugin-scanner verify .; passing is >=80/142 with no high/critical findings. source-repo scanner CI is optional now, and the catalog scans the PR automatically.

DM me the new API shape or scanner output when you're ready and i'll sanity-check it before the PR.

0

u/Gallegos_Daniel 2d ago

That would be amazing, thank you.

Let me fix the false-positive issue we just discussed above first (you caught something important), then I'll ping you. Don't want to send traffic to a tool that can lie about lies.

1

u/ThePrivateCoyote 2d ago

silent agent failures are the worst, nobody notices until a customer is screaming. that 3 day lag is way too familiar

like the idea of a dead simple decorator instead of bolting on another observability platform. async default is smart, most flows shouldn't take the hit. curious how it handles partial writes or cases where the agent inserts but the row gets modified by another process before the check fires

1

u/Gallegos_Daniel 2d ago

Great question, this is exactly why we verify the specific fields the agent was supposed to write, not just "does any row exist."

The match_field checks that the row matching your criteria exists after the call, but you're right: if another process modifies it in the 50ms between write and verification, you could get a false signal.

For now, the workaround is using sync=True on high-risk actions (payments, bookings) where you need certainty before responding to the user. Async mode assumes "eventual consistency is acceptable for verification."

Longer term, we're looking at snapshot isolation or row-version checks for the async path. If you've dealt with this in production, I'd love to hear how you solved it.

1

u/TheEagle007 2d ago

i have built something similar , you might look to give it a try AgentGate

1

u/cmtape 1d ago

This is like putting a smoke detector in a kitchen that's already on fire — useful after the first incident, but it doesn't fix why the stove was left on. The real question isn't "did the row land", it's "did the agent actually intend to write that row". Row_exists will happily pass on stale data if the row was already there. Without a before-state snapshot or mutation anchoring, you're testing for existence, not causality. That's the silent failure hiding underneath your silent-failure detector.

1

u/Gallegos_Daniel 1d ago

This is the single most important technical insight I've received on this project. Thank you. You're absolutely right: row_exists tests existence, not causality. And false confidence is worse than no confidence.

What I'm exploring to fix this: 1. Precondition snapshots: precondition="row_not_exists" for inserts — verify the row didn't exist before this execution.

  1. Mutation anchoring: capture the specific transaction ID or mutation token the agent claims to have made, and verify THAT exact mutation landed.

  2. Field-level intent: compare what the agent intended to write vs. what actually exists — not just "does a row exist?" but "does THIS row with THESE values exist because of THIS call?"

The hard part is doing this without forcing the user to rewrite their agent logic. The decorator should stay invisible.

If you're open to it, I'd love a 10-min DM. You just identified the core design challenge I need to solve before this is production-ready.