Error Handling

This guide covers common errors, error codes, and how to handle them in agents.

Error Format

All errors follow JSON-RPC 2.0 error format:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,
    "message": "Invalid Request",
    "data": "decision_id is required"
  }
}

Common Error Codes

JSON-RPC Errors

  • -32600 Invalid Request - Missing required parameters or malformed request
  • -32601 Method not found - Unknown tool name or method
  • -32602 Invalid params - Wrong parameter types or values
  • -32603 Internal error - Server error

HTTP Status Codes

  • 200 OK - Success (even for JSON-RPC errors)
  • 401 Unauthorized - Invalid API key
  • 403 Forbidden - Permission denied
  • 429 Too Many Requests - Rate limited

Common Errors

Invalid API Key

Error:

json
{
  "error": {
    "code": -32603,
    "message": "Internal error",
    "data": "Unauthorized"
  }
}

HTTP Status: 401

Solution:

  • Verify API key is correct
  • Check that agent is active
  • Ensure API key hasn't been revoked

Missing Required Parameter

Error:

json
{
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": "decision_id is required"
  }
}

Solution:

  • Check that all required parameters are provided
  • Verify parameter names are correct
  • Review tool documentation for required parameters

Invalid Purpose

Error:

json
{
  "error": {
    "code": -32603,
    "message": "Internal error",
    "data": "Purpose 'general_access' not in allowed_purposes"
  }
}

Solution:

  • Check Data Product's allowed purposes
  • Use a purpose that's in the allowed list
  • Verify purpose spelling is correct

Policy Not Found

Error:

json
{
  "error": {
    "code": -32603,
    "message": "Internal error",
    "data": "Policy 'discount_cap_v1' not found"
  }
}

Solution:

  • Verify policy exists and is published
  • Check policy ID spelling
  • Ensure policy is accessible to your agent

Data Product Not Found

Error:

json
{
  "error": {
    "code": -32603,
    "message": "Internal error",
    "data": "Data Product 'customer_data' not found"
  }
}

Solution:

  • Verify Data Product exists and is published
  • Check Data Product ID spelling
  • Ensure Data Product is accessible to your agent

Rate Limited

Error:

json
{
  "error": {
    "code": -32603,
    "message": "Internal error",
    "data": "Rate limit exceeded"
  }
}

HTTP Status: 429

Solution:

  • Check Retry-After header for wait time
  • Implement exponential backoff
  • Reduce request frequency

Error Handling Patterns

Basic Error Handling

python
def call_tool_safe(client, name, arguments):
    """Call tool with basic error handling."""
    try:
        return client.call_tool(name, arguments)
    except Exception as e:
        error_str = str(e)
        
        if "401" in error_str or "Unauthorized" in error_str:
            raise Exception("Invalid API key")
        elif "403" in error_str or "Forbidden" in error_str:
            raise Exception("Permission denied")
        elif "429" in error_str or "Rate limit" in error_str:
            raise Exception("Rate limited - retry later")
        else:
            raise

Retry with Backoff

python
import time
import random

def call_tool_with_retry(client, name, arguments, max_retries=3):
    """Call tool with retry and exponential backoff."""
    for attempt in range(max_retries):
        try:
            return client.call_tool(name, arguments)
        except Exception as e:
            error_str = str(e)
            
            # Don't retry on auth/permission errors
            if "401" in error_str or "403" in error_str:
                raise
            
            # Retry on rate limits and server errors
            if "429" in error_str or "500" in error_str:
                if attempt < max_retries - 1:
                    wait_time = (2 ** attempt) + random.uniform(0, 1)
                    time.sleep(wait_time)
                    continue
                else:
                    raise
            
            # Don't retry on other errors
            raise
    
    raise Exception("Max retries exceeded")

Decision Error Handling

python
decision_id = None
try:
    # Create decision
    decision = client.call_tool("decision_create", {...})
    decision_id = decision["decision_id"]
    
    # ... perform operations ...
    
    # Commit on success
    client.call_tool("decision_close", {
        "decision_id": decision_id,
        "action": "commit"
    })
    
except Exception as e:
    # Rollback on any error
    if decision_id:
        try:
            client.call_tool("decision_close", {
                "decision_id": decision_id,
                "action": "rollback"
            })
        except Exception as rollback_error:
            # Log rollback error but don't mask original error
            print(f"Failed to rollback decision: {rollback_error}")
    
    raise

Idempotency

Use idempotency keys for writes that may retry:

python
import time

idempotency_key = f"order-{order_id}-{int(time.time())}"

try:
    write_result = client.call_tool("decision_write", {
        "decision_id": decision_id,
        "product": "orders",
        "purpose": "order_creation",
        "mutation": {
            "operation": "insert",
            "records": [{"order_id": order_id, "total": 1000.00}]
        },
        "idempotency_key": idempotency_key
    })
except Exception as e:
    # Retry with same idempotency key
    if "retry" in str(e).lower():
        write_result = client.call_tool("decision_write", {
            "decision_id": decision_id,
            "product": "orders",
            "purpose": "order_creation",
            "mutation": {...},
            "idempotency_key": idempotency_key  # Same key
        })

Failure Modes

SDK-Side Errors

Errors that prevent the envelope from opening:

  • Missing intent
  • Missing automation_mode
  • Missing purpose

Result: Envelope never opens, no trace is created

Runtime Policy Denial

Policy returns deny:

  • Envelope transitions to aborted
  • Policy evaluation event is recorded
  • Decision cannot proceed

Handling:

python
if policy_result["outcome"] == "deny":
    client.call_tool("decision_close", {
        "decision_id": decision_id,
        "action": "rollback"
    })

Approval Rejection

Human explicitly rejects:

  • Envelope transitions to rejected
  • Rejection rationale is recorded
  • Decision must abort

Handling:

python
if decision_status["status"] == "rejected":
    client.call_tool("decision_close", {
        "decision_id": decision_id,
        "action": "rollback"
    })

Backend Failure

Crash or timeout before commit:

  • Envelope transitions to failed
  • Partial events are preserved
  • No commit occurs

Handling:

python
# Check decision status
decision_status = client.call_tool("decision_get", {
    "decision_id": decision_id
})

if decision_status["status"] == "failed":
    # Handle failure
    # Optionally retry or abort
    client.call_tool("decision_close", {
        "decision_id": decision_id,
        "action": "rollback"
    })

Best Practices

  1. Always handle errors - Don't let errors go unhandled
  2. Rollback on error - Always rollback decisions on error
  3. Use idempotency keys - For writes that may retry
  4. Implement retries - For transient errors (rate limits, server errors)
  5. Don't retry auth errors - 401/403 errors won't succeed on retry
  6. Log errors - Log errors for debugging and monitoring

TraceMem is trace-native infrastructure for AI agents