Tool Tiers Reference
Every tool is classified at registration time into exactly one tier. Classification is declarative (SDK annotation), never inferred by the LLM.
SAFE
Enum value: "safe" (Python: ToolTier.SAFE, Rust: ToolTier::Safe)
When to use
- Read-only operations (no side effects)
- Idempotent operations (repeated execution produces the same result)
- Query-like tools:
search_web,read_file,get_user,list_records - Tools that call internal read APIs with no mutation
System behaviour
| Aspect | Behaviour |
|---|---|
| Effect log | No entry created. Bypasses the effect log entirely. |
| Interception | Not intercepted. The function body executes immediately. |
| Compensation | Not required. No undo stack entry. |
| Approval | Not required. |
| Proxy call | Not sent to the proxy. Direct execution only. |
| Replay | Not applicable. No effect to replay. |
| Session state | Step index still incremented. |
Example declaration
Python:
@undolog_tool(tier=ToolTier.SAFE)
async def search_web(query: str) -> str:
...
Rust (registry):
ToolTier::Safe
COMPENSABLE
Enum value: "compensable" (Python: ToolTier.COMPENSABLE, Rust: ToolTier::Compensable { compensation })
When to use
- Write operations with a well-defined compensation (undo) function
- Operations that can be reversed by calling another tool
- Examples:
send_email,transfer_funds,create_record,update_profile
System behaviour
| Aspect | Behaviour |
|---|---|
| Effect log | Entry created in undolog_effect_log with state Pending → Executing → Committed on success. |
| Interception | Call sent to proxy via UndoLogClient.intercept(). |
| Compensation | Required. CompensationDescriptor must be provided. Registered in undolog_undo_stack before execution. |
| Approval | Not required. |
| Compensated on failure | Yes. The undo stack is walked LIFO when a session fails. |
| Retry | Compensation function retries up to max_retries with retry_backoff_ms backoff. |
| Replay | Possible if the same call_signature exists (exactly-once semantics). |
Compensation requirements
| Requirement | Description |
|---|---|
fn_name |
Must match a registered function in undolog_compensation_registry. |
fn_version |
Semver of the compensation function. |
args |
Arguments captured from the original call before execution. |
max_retries |
Max retry attempts before escalating to CompensationFailed. Default 3. |
retry_backoff_ms |
Base delay between retries in ms. Default 1000. |
Error on missing compensation
If COMPENSABLE is used without a CompensationDescriptor, undolog_tool raises ValueError.
Example declaration
Python:
@undolog_tool(
tier=ToolTier.COMPENSABLE,
compensation=CompensationDescriptor.new("undo_send_email", {"to": "{to}"}),
)
async def send_email(to: str, subject: str) -> dict:
...
IRREVERSIBLE
Enum value: "irreversible" (Python: ToolTier.IRREVERSIBLE, Rust: ToolTier::Irreversible { reason })
When to use
- Operations that cannot be undone programmatically
- Destructive operations with permanent consequences
- Operations requiring human oversight before execution
- Examples:
delete_database,publish_to_production,wire_large_amount,terminate_instances
System behaviour
| Aspect | Behaviour |
|---|---|
| Effect log | Entry created in undolog_effect_log with state Pending → Approved → Committed on success. |
| Interception | Call sent to proxy via UndoLogClient.intercept(). |
| Compensation | Not supported. The operation is irreversible. |
| Approval | Required. Execution is suspended until a human approves or rejects via the dashboard API. |
| Session state | Session transitions to AwaitingApproval while pending. |
| On approval | Effect transitions to approved. The proxy executes the tool separately and commits the result. No engine-level executing state. |
| On rejection | Session transitions to Halted, effect transitions to Rejected. |
| Retry | No automatic retry. The caller must resubmit after approval resolution. |
| Replay | Not applicable for pending/approved effects. Possible if already committed. |
Approval requirements
| Requirement | Description |
|---|---|
reason |
Human-readable explanation shown in the approval UI. Must be non-empty. |
risk_tags |
Tags categorising the risk (e.g. ["financial", "external-api"]). |
estimated_impact |
Free-text description of what will happen (e.g. "debits up to $amount"). |
proposed_args |
The original tool arguments. Shown in the UI for review. |
agent_context |
Session context (last N tool calls + reasoning trace). Shown alongside the proposal. |
timeout_at |
Deadline for approval. Configurable per organisation. |
auto_approve_on_timeout |
Whether the tool auto-executes if no decision is made by timeout_at. |
Error on missing reason
Rust returns ToolNotRegistered if reason is empty. Python SDK does not enforce at decoration time.
Example declaration
Python:
@undolog_tool(tier=ToolTier.IRREVERSIBLE)
async def delete_database(db_name: str) -> dict:
...
Comparison Table
| Property | SAFE | COMPENSABLE | IRREVERSIBLE |
|---|---|---|---|
| Effect log entry | No | Yes | Yes |
| Proxy interception | No | Yes | Yes |
| Compensation | Not required | Required | Not supported |
| Human approval | Not required | Not required | Required |
| Session blocks on call | No | No | Yes (until approval) |
| Can be replayed | No | Yes | Yes (after committed) |
| Can be undone | N/A (no effect) | Yes (via compensation) | No |
| Step index incremented | Yes | Yes | Yes |
| Call signature computed | No | Yes | Yes |
| Affects undo stack | No | Yes | No |