A missing layer for safe side effects in agent systems.
Retries can duplicate irreversible actions: payments, emails, trades, and external API mutations.
Execution Guard ensures a side effect runs exactly once — even under retries.
Even if your system runs it twice, it executes once.
Run the same request twice — it executes once.
Install:
pip install safeagent-exec-guardMinimal local example (SQLite):
from safeagent_exec_guard.sqlite_store import SQLiteExecutionStore
store = SQLiteExecutionStore("safeagent.db")
store.init_db()
def send_payment(request_id: str):
action = "send_payment"
if store.insert_if_not_exists(request_id, action):
print("executing side effect")
result = {"status": "sent", "receipt_id": "rcpt_12345"}
store.complete(request_id, result)
print("result:", result)
else:
print("duplicate blocked")
send_payment("req_123")
send_payment("req_123")Expected output:
executing side effect
result: {'status': 'sent', 'receipt_id': 'rcpt_12345'}
duplicate blocked
A retry without protection can execute the same irreversible action twice.
Execution Guard guarantees the side effect runs once — even if the agent retries.
Without an execution boundary, retries can re-run irreversible side effects.
With Execution Guard, the second attempt returns the original receipt instead of executing again.
SafeAgent is a reference implementation of the Execution Guard pattern.
pip install safeagent-exec-guardUse SQLite for local development, demos, and single-process workflows.
from safeagent_exec_guard.sqlite_store import SQLiteExecutionStore
store = SQLiteExecutionStore("safeagent.db")
store.init_db()
request_id = "payment_123"
action = "send_payment"
def do_side_effect():
print("Executing payment...")
return {"status": "sent", "receipt_id": "rcpt_12345"}
if store.insert_if_not_exists(request_id, action):
result = do_side_effect()
store.complete(request_id, result)
print("Executed:", result)
else:
print("Duplicate request detected — execution blocked")See also: Wrap a side effect
Use Postgres for distributed workers, production services, and multi-instance agent systems.
docker run --name safeagent-postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_USER=postgres \
-e POSTGRES_DB=postgres \
-p 5432:5432 \
-d postgres:16import os
from safeagent_exec_guard.postgres_store import PostgresExecutionStore
dsn = os.getenv(
"POSTGRES_DSN",
"postgresql://postgres:postgres@localhost:5432/postgres"
)
store = PostgresExecutionStore(dsn)
store.init_db()
request_id = "payment_123"
action = "send_payment"
def do_side_effect():
print("Executing payment...")
return {"status": "sent", "receipt_id": "rcpt_12345"}
if store.insert_if_not_exists(request_id, action):
result = do_side_effect()
store.complete(request_id, result)
print("Executed:", result)
else:
print("Duplicate request detected — execution blocked")docker exec -it safeagent-postgres psql -U postgres -d postgres -c "TRUNCATE TABLE execution_requests;"if store.insert_if_not_exists(request_id, action):
result = do_side_effect()
store.complete(request_id, result)
else:
print("Duplicate request detected — execution blocked")A request is identified once. Every retry resolves against that same execution record.
Without SafeAgent:
- Request is sent.
- Timeout or uncertainty happens.
- The system retries.
- The side effect runs again.
With SafeAgent:
- Request is sent with a
request_id. - Execution is recorded durably.
- The side effect runs once.
- Retries are blocked for the same request.
- Payments
- Trading systems
- Background jobs
- Webhooks
- External API mutations
- AI agent tool calls
- Ticketing and messaging workflows
Any system where running the same side effect twice is unacceptable.
Retries do not mean “nothing happened.”
They mean “we do not know what happened.”
Most systems retry anyway.
That is fine for reads.
It is dangerous for side effects.
That is how you get duplicate payments, duplicate emails, duplicate trades, and repeated external mutations.
Execution Guard adds a safety boundary at the side-effect layer:
- record the execution attempt
- execute once
- resolve retries safely
- SQLite → local / single process
- Postgres → distributed / production
Apache-2.0
