Quickstart: Protect your first AI agent
In this tutorial, you build a simple AI agent that searches a knowledge base and creates user accounts. You protect each operation with the right UndoLog tier so that mistakes are caught, side effects are reversible, and dangerous actions require human sign-off.
What you will build
A single-file agent with three tool tiers:
| Tool | Tier | Behaviour |
|---|---|---|
search_knowledge_base |
SAFE | Executes immediately, no logging |
create_user |
COMPENSABLE | Logged; a delete_user compensation is registered before execution |
process_payout |
IRREVERSIBLE | Logged; requires human approval, the session suspends until resolved |
Install the SDK
pip install undolog-sdk
Make sure the UndoLog stack is running (see Installation).
Create the agent
Save the following as agent.py:
import asyncio
from undolog_sdk import (
undolog_tool,
ToolTier,
CompensationDescriptor,
UndoLogSession,
AwaitingApprovalError,
)
# ── SAFE: read-only, no logging needed ──────────────────────────────────────
@undolog_tool(tier=ToolTier.SAFE)
async def search_knowledge_base(query: str) -> list[dict]:
"""Search the internal knowledge base."""
# In production this would hit your vector DB.
results = [
{"title": "Onboarding workflow", "score": 0.95},
{"title": "Account creation guide", "score": 0.88},
]
return [r for r in results if query.lower() in r["title"].lower()]
# ── COMPENSABLE: logged, undo registered before execution ───────────────────
@undolog_tool(
tier=ToolTier.COMPENSABLE,
compensation=CompensationDescriptor.new(
"delete_user",
args={"soft": True},
),
)
async def create_user(email: str, name: str) -> dict:
"""Create a new user account."""
# In production this would call your identity provider.
user_id = f"usr_{hash(email) & 0xFFFF}"
print(f" [create_user] Created {name} <{email}> → {user_id}")
return {"user_id": user_id, "email": email, "name": name}
# ── IRREVERSIBLE: logged, human approval required before execution ──────────
@undolog_tool(tier=ToolTier.IRREVERSIBLE)
async def process_payout(user_id: str, amount: float) -> dict:
"""Send a payout to the user."""
print(f" [process_payout] Paying ${amount:.2f} to {user_id}")
return {"status": "paid", "user_id": user_id, "amount": amount}
# ── Main agent loop ─────────────────────────────────────────────────────────
async def main():
session = UndoLogSession(org_id="org-acme")
async with session:
# Step 1. SAFE call: executes immediately, no log entry.
print(">>> Searching knowledge base...")
docs = await search_knowledge_base(query="onboarding", _session=session)
print(f" Found {len(docs)} document(s)")
# Step 2. COMPENSABLE call: the proxy records an effect and pushes a
# compensation onto the undo stack *before* the function runs.
print(">>> Creating user...")
user = await create_user(
email="alice@example.com",
name="Alice",
_session=session,
)
print(f" Created: {user}")
# Step 3. IRREVERSIBLE call: the proxy records the effect, creates an
# approval request, and raises AwaitingApprovalError.
print(">>> Processing payout...")
try:
result = await process_payout(
user_id=user["user_id"],
amount=49.99,
_session=session,
)
print(f" Payout result: {result}")
except AwaitingApprovalError as err:
print(f" ! Need approval: {err.approval_id}")
print(" The session is suspended. Approve or reject via the")
print(" dashboard at http://localhost:3000")
if __name__ == "__main__":
asyncio.run(main())
Run the agent
python agent.py
With the Docker stack running you see output similar to:
>>> Searching knowledge base...
Found 1 document(s)
>>> Creating user...
[create_user] Created Alice <alice@example.com> → usr_1234
Created: {'user_id': 'usr_1234', 'email': 'alice@example.com', 'name': 'Alice'}
>>> Processing payout...
! Need approval: apr_abc123
The session is suspended. Approve or reject via the
dashboard at http://localhost:3000
Open http://localhost:3000 in your browser. The dashboard shows one active
session with a pending approval for process_payout. You can inspect the
arguments (user_id and amount) and approve or reject the request.
What you built
You wired a three-tier safety net into a real agent in about 30 lines of decorator annotations:
- SAFE (
search_knowledge_base): the tool runs immediately with zero overhead. No effect log entry is created, no compensation is registered. - COMPENSABLE (
create_user): the proxy logs the call and pushes adelete_usercompensation onto the session's undo stack beforecreate_userruns. If a later step fails, the engine walks the undo stack LIFO and calls each compensation. - IRREVERSIBLE (
process_payout): the proxy logs the call and creates an approval request. The tool never executes unless a human approves it. Your agent caught theAwaitingApprovalErrorgracefully and told the user where to go.
If any call after create_user had raised an exception (or if the process
crashed), the engine would invoke delete_user automatically, the
compensation was registered before the account was created, so it survives
crashes.
Next steps
- Read Core concepts to understand the effect journal and compensation model in depth.
- Browse the Integration guides for LangGraph, CrewAI, and custom framework adapters.
- Check the API reference for the full SDK surface.