Show HN: SafeAgent – exactly-once execution guard for AI agent side effects
One issue we ran into while experimenting with agent workflows is that retries can trigger irreversible actions multiple times:
agent calls tool ↓ network timeout ↓ agent retries ↓ side effect happens twice
Examples:
• duplicate payment • duplicate email • duplicate ticket • duplicate trade
Most systems solve this ad-hoc using idempotency keys scattered around different services.
SafeAgent centralizes this into a small execution guard.
The idea is simple:
1. every tool execution gets a request_id 2. SafeAgent records the execution receipt 3. retries return the original receipt instead of running the side effect again
Example:
FIRST CALL REAL SIDE EFFECT: sending email
SECOND CALL WITH SAME request_id SafeAgent returns the original execution receipt (no second side effect)
The project is early but includes examples for:
• OpenAI tool calls • LangChain style tools • CrewAI actions
PyPI: https://pypi.org/project/safeagent-exec-guard/
GitHub: https://github.com/azender1/SafeAgent
Curious how other people are handling retry safety for agent side effects.
While experimenting with LLM agents calling tools, I ran into a reliability problem: retries can easily trigger irreversible actions more than once.
For example:
agent → call tool network timeout → retry agent retries tool call side effect runs twice
That can mean:
duplicate payment
duplicate email
duplicate ticket
duplicate trade
Most systems solve this locally with idempotency keys, but in agent workflows the retries can come from multiple layers (agent loops, orchestration frameworks, API retries, etc.).
SafeAgent is a small execution guard that sits between the agent and the side effect. Every tool call gets a request_id, and SafeAgent records a durable execution receipt. If the same request is replayed, it returns the original receipt instead of executing again.
What you described — (request_id, tool, input_hash, response) with a unique constraint — is basically the same core idea: treat the tool execution as a durable receipt and return the stored result on retries.
While experimenting with agents I kept seeing retries coming from multiple layers (agent loops, HTTP retries, queue workers), so the goal was to wrap that execution guard into a small library instead of re-implementing the pattern each time.
The tricky part you mentioned about error states is exactly what I ran into as well. If the execution fails you don’t want to record it in a way that blocks legitimate retries but also don’t want to silently replay a failure as if it succeeded.
Curious how you ended up modeling that in Postgres — did you separate failure states from successful receipts or just include a status flag in the same row?