Declare the tier.
The runtime does the rest.

Every tool call is classified as SAFE, compensable, or irreversible, with no fragile rollback code and no runaway agents.

Sessions12
a1b2c3d42m ago
5e6f7a8b5m ago
9c0d1e2f12m ago
3a4b5c6d18m ago
Tiers
SAFE2
COMPENSABLE1
IRREVERSIBLE1
undolog~/agent$python agent.py --customer alice
● SAFE lookup_customerc_8a7b6c5d
✓ replayed from cache (call_signature match)
→ name: Alice Chen · plan: enterprise
● COMP. create_ticket
⤵ compensation: undo_create_ticket
→ TKT-4421 · status: draft
● IRREV. send_email
⚡ approval_id: apr_9f8e, session suspended
waiting for human decision...
SESSION
a1b2c3d4
STATUS
awaiting_approval
ORG
org_demo
CALLS
4 total (2 SAFE · 1 COMP. · 1 IRREV.)
DURATION
12.4s

A single decorator classifies every tool call by reversibility tier.

Mark any async function by its reversibility tier: idempotency, effect logging, and compensation wiring are handled automatically.

agent.py
tools.py
session.py
1# agent.py — Powered by UndoLog
2 
3from undolog_sdk import undolog_tool, ToolTier
4from undolog_sdk.tier import CompensationDescriptor
5 
6# SAFE — read-only · no log entry · replayed on retry
7@undolog_tool(tier=ToolTier.SAFE)
8async def lookup_customer(customer_id: str) → dict:
9return {name: ..., plan: ...}
10 
11# COMPENSABLE — writes · logs effect · registers undo
12@undolog_tool(
13tier=ToolTier.COMPENSABLE,
14compensation=CompensationDescriptor.new(
15"undo_create_ticket",
16args={{"ticket_id": "{ticket_id}"}},
17),
18)
19async def create_ticket(customer_id: str, amount: float) → dict:
20await tools.create_ticket(customer_id, amount)
21 
22# IRREVERSIBLE — approval gate · session suspended
23@undolog_tool(tier=ToolTier.IRREVERSIBLE)
24async def send_email(to: str, subject: str, body: str) → dict:
25return await mailer.send(to, subject, body)
26
Python 3.12UTF-8Ln 26, Col 1
Tier Inspector
SAFE
No effect log. Auto-replayed on cache hit.
COMPENSABLE
Pre-registers compensation. Logged to journal. Saga LIFO on failure.
IRREVERSIBLE
Approval gate. Session suspended until human judgment.

Every tool call is intercepted, classified, and routed in real time.

Each tool call is intercepted at runtime, classified by tier, and routed through the correct safety path, without touching your agent logic.

undolog~/agent$python agent.py --customer alice --ticket high
● SAFE lookup_customer
✓ replayed from cache (call_signature: a1b2…3e4f)
→ name: Alice Chen · plan: enterprise · balance: $249
● SAFE search_knowledge_base
✓ 2 results (KB#412: Enterprise duplicate charge workflow)
● COMP. create_ticket
⤵ compensation: undo_create_ticket registered
→ TKT-4421 · status: draft · refund: $249
● IRREV. send_email
⚡ approval_id: apr_9f8e, session suspended
waiting for human decision...
session: a1b2c3d4 · 4 tool calls · 2 SAFE · 1 COMP. · 1 pending approval
Effect JournalLIVE
eff_a1b2c3d4COMP.
create_ticketcommitted
eff_5e6f7a8bIRREV.
send_emailpending
append-only · BLAKE3 deduplicated

When an action is irreversible, only a human can grant approval.

The session suspends the moment an irreversible action is requested. Risk tags, full context, and a single click: approved or rejected.

ACTION REQUIRED
Approval Request
PENDING
TOOL
send_email
SESSION
a1b2c3d4-e5f6
external_communicationfinancial_datacustomer_facing
ARGS
{
to: "alice@example.com",
subject: "Refund processing",
body: "Hi Alice, we're processing your $249 refund..."
}
Event Stream
SSE · LIVE
event: effect_committed
event: compensation_registered
event: approval_required apr_9f8e
event: session_suspended
...
effect_intercepted compensation_registered
approval_requiredsession_suspended