Skip to main content

Evaluate Your First Action

This guide walks you through evaluating your first action with ASCEND. You will learn how to submit actions for authorization, handle different decision types, and complete the action lifecycle.

Prerequisites

Before evaluating actions, ensure you have:

The Action Lifecycle

Every action follows a lifecycle:

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│ Evaluate │────▶│ Handle │────▶│ Execute │────▶│ Log │
│ Action │ │ Decision │ │ Action │ │ Complete │
└──────────┘ └──────────┘ └──────────┘ └──────────┘

Step 1: Evaluate an Action

Submit an action for authorization before executing it:

Python

from ascend import AscendClient, Decision, FailMode
import os

# Initialize client (assumes agent is already registered)
client = AscendClient(
api_key=os.getenv("ASCEND_API_KEY"),
agent_id="customer-service-bot-001",
agent_name="Customer Service Bot",
fail_mode=FailMode.CLOSED
)

# Evaluate an action
decision = client.evaluate_action(
action_type="database.query",
resource="customer_db",
parameters={
"sql": "SELECT name, email FROM customers WHERE id = 12345"
},
context={
"user_request": "Look up customer information",
"session_id": "sess_abc123"
}
)

print(f"Action ID: {decision.action_id}")
print(f"Decision: {decision.decision.value}")
print(f"Risk Score: {decision.risk_score}")
print(f"Reason: {decision.reason}")

Node.js

import { AscendClient, Decision, FailMode } from '@ascend-ai/sdk';

// Initialize client (assumes agent is already registered)
const client = new AscendClient({
apiKey: process.env.ASCEND_API_KEY,
agentId: 'customer-service-bot-001',
agentName: 'Customer Service Bot',
failMode: FailMode.CLOSED
});

// Evaluate an action
const decision = await client.evaluateAction({
actionType: 'database.query',
resource: 'customer_db',
parameters: {
sql: 'SELECT name, email FROM customers WHERE id = 12345'
},
context: {
userRequest: 'Look up customer information',
sessionId: 'sess_abc123'
}
});

console.log(`Action ID: ${decision.actionId}`);
console.log(`Decision: ${decision.decision}`);
console.log(`Risk Score: ${decision.riskScore}`);
console.log(`Reason: ${decision.reason}`);

Step 2: Handle the Decision

Handle the three possible decision types:

Handling ALLOWED

When the action is allowed, proceed with execution:

if decision.decision == Decision.ALLOWED:
print("Action authorized - proceeding with execution")

# Execute your action
try:
result = execute_database_query(decision.parameters["sql"])

# Log successful completion (required for audit trail)
client.log_action_completed(
action_id=decision.action_id,
result={"rows_returned": len(result), "success": True}
)

print(f"Action completed successfully: {result}")

except Exception as e:
# Log failure if execution fails
client.log_action_failed(
action_id=decision.action_id,
error={"code": "EXECUTION_ERROR", "message": str(e)}
)
raise

Handling DENIED

When the action is denied, respect the decision:

if decision.decision == Decision.DENIED:
print(f"Action denied: {decision.reason}")

# Log the policy violations
for violation in decision.policy_violations:
print(f" - Policy violation: {violation}")

# Optionally notify the user or log for review
notify_user_action_denied(
reason=decision.reason,
action_id=decision.action_id
)

# Do NOT proceed with the action

Handling PENDING

When approval is required, wait or notify:

if decision.decision == Decision.PENDING:
print(f"Action requires approval")
print(f"Approval Request ID: {decision.approval_request_id}")
print(f"Required Approvers: {decision.required_approvers}")

# Option 1: Wait for approval (blocking)
final_decision = client.wait_for_decision(
action_id=decision.action_id,
timeout=300 # Wait up to 5 minutes
)

if final_decision.decision == Decision.ALLOWED:
# Approval granted - proceed
result = execute_action()
client.log_action_completed(final_decision.action_id, result)
else:
print(f"Approval denied: {final_decision.reason}")

# Option 2: Non-blocking notification
# notify_approvers(decision.approval_request_id)
# return {"status": "pending_approval", "request_id": decision.approval_request_id}

Step 3: Complete the Lifecycle

Always log action completion for audit compliance:

Log Success

# After successful execution
client.log_action_completed(
action_id=decision.action_id,
result={
"status": "success",
"rows_affected": 1,
"execution_time_ms": 45
},
duration_ms=45
)

Log Failure

# If execution fails
client.log_action_failed(
action_id=decision.action_id,
error={
"code": "DATABASE_ERROR",
"message": "Connection timeout",
"details": {"host": "db.example.com", "port": 5432}
},
duration_ms=5000
)

Complete Example: Database Query Agent

Here is a complete example showing the full action lifecycle:

"""
ASCEND Action Evaluation Example
--------------------------------
Complete example demonstrating the full action lifecycle.
"""

import os
import time
from ascend import AscendClient, Decision, FailMode

def execute_customer_lookup(customer_id: str) -> dict:
"""
Execute a customer lookup with ASCEND governance.
"""
# Initialize client
client = AscendClient(
api_key=os.getenv("ASCEND_API_KEY"),
agent_id="customer-service-bot-001",
agent_name="Customer Service Bot",
fail_mode=FailMode.CLOSED
)

# Prepare the action
action_type = "database.query"
resource = "customer_db"
parameters = {
"sql": f"SELECT name, email, phone FROM customers WHERE id = {customer_id}",
"table": "customers",
"columns": ["name", "email", "phone"]
}
context = {
"user_request": f"Look up customer {customer_id}",
"session_id": os.getenv("SESSION_ID", "unknown"),
"source": "customer_service_portal"
}

# Evaluate the action
print(f"Evaluating action: {action_type} on {resource}")
start_time = time.time()

decision = client.evaluate_action(
action_type=action_type,
resource=resource,
parameters=parameters,
context=context
)

evaluation_time = int((time.time() - start_time) * 1000)
print(f"Decision received in {evaluation_time}ms")
print(f" Action ID: {decision.action_id}")
print(f" Decision: {decision.decision.value}")
print(f" Risk Score: {decision.risk_score}")

# Handle the decision
if decision.decision == Decision.ALLOWED:
return handle_allowed(client, decision, customer_id)

elif decision.decision == Decision.PENDING:
return handle_pending(client, decision)

else: # DENIED
return handle_denied(decision)


def handle_allowed(client: AscendClient, decision, customer_id: str) -> dict:
"""Handle an allowed decision."""
print("Action ALLOWED - executing query")
start_time = time.time()

try:
# Simulate database query
result = {
"customer_id": customer_id,
"name": "John Doe",
"email": "john.doe@example.com",
"phone": "+1-555-0123"
}

duration_ms = int((time.time() - start_time) * 1000)

# Log successful completion
client.log_action_completed(
action_id=decision.action_id,
result={"success": True, "record_found": True},
duration_ms=duration_ms
)

print(f"Query completed in {duration_ms}ms")
return {
"status": "success",
"data": result,
"action_id": decision.action_id
}

except Exception as e:
duration_ms = int((time.time() - start_time) * 1000)

# Log failure
client.log_action_failed(
action_id=decision.action_id,
error={"code": "QUERY_ERROR", "message": str(e)},
duration_ms=duration_ms
)

return {
"status": "error",
"error": str(e),
"action_id": decision.action_id
}


def handle_pending(client: AscendClient, decision) -> dict:
"""Handle a pending decision."""
print(f"Action PENDING - waiting for approval")
print(f" Approval Request: {decision.approval_request_id}")
print(f" Required Approvers: {decision.required_approvers}")

# Wait for approval (with timeout)
try:
final_decision = client.wait_for_decision(
action_id=decision.action_id,
timeout=60, # 1 minute timeout
poll_interval=5 # Check every 5 seconds
)

if final_decision.decision == Decision.ALLOWED:
print("Approval GRANTED")
# In a real application, you would execute the action here
return {
"status": "approved",
"approved_by": final_decision.approved_by,
"action_id": decision.action_id
}
else:
print(f"Approval DENIED: {final_decision.reason}")
return {
"status": "denied",
"reason": final_decision.reason,
"action_id": decision.action_id
}

except TimeoutError:
print("Approval timeout - action not executed")
return {
"status": "timeout",
"message": "Approval not received within timeout period",
"approval_request_id": decision.approval_request_id
}


def handle_denied(decision) -> dict:
"""Handle a denied decision."""
print(f"Action DENIED: {decision.reason}")

if decision.policy_violations:
print("Policy violations:")
for violation in decision.policy_violations:
print(f" - {violation}")

return {
"status": "denied",
"reason": decision.reason,
"policy_violations": decision.policy_violations,
"action_id": decision.action_id
}


if __name__ == "__main__":
result = execute_customer_lookup("12345")
print(f"\nFinal Result: {result}")

Common Action Types

Here are examples of common action types and how to evaluate them:

Financial Transaction

decision = client.evaluate_action(
action_type="financial.refund",
resource="stripe_api",
parameters={
"amount": 150.00,
"currency": "USD",
"customer_id": "cust_abc123",
"order_id": "ord_xyz789",
"reason": "Customer requested refund"
},
context={
"agent_session": "sess_123",
"customer_tier": "premium"
}
)

File Operation

decision = client.evaluate_action(
action_type="file.write",
resource="/var/log/application",
parameters={
"path": "/var/log/application/audit.log",
"content_size_bytes": 1024,
"operation": "append"
}
)

External API Call

decision = client.evaluate_action(
action_type="api_call",
resource="salesforce_api",
parameters={
"endpoint": "/services/data/v52.0/sobjects/Contact",
"method": "POST",
"body": {"FirstName": "John", "LastName": "Doe"}
}
)

System Configuration

decision = client.evaluate_action(
action_type="system.config",
resource="application_config",
parameters={
"key": "max_connections",
"old_value": 100,
"new_value": 200
},
context={
"environment": "production",
"change_ticket": "CHG-12345"
}
)

MCP Server Integration

For MCP servers, use the @mcp_governance decorator:

from ascend import AscendClient
from ascend.mcp import mcp_governance, high_risk_action

client = AscendClient(
api_key=os.getenv("ASCEND_API_KEY"),
agent_id="mcp-database-server",
agent_name="Database MCP Server"
)

@mcp_server.tool()
@mcp_governance(client, action_type="database.query", resource="production_db")
async def query_database(sql: str) -> dict:
"""Execute a database query with ASCEND governance."""
return await db.execute(sql)

@mcp_server.tool()
@high_risk_action(client, action_type="database.delete", resource="production_db")
async def delete_records(table: str, where_clause: str) -> dict:
"""Delete records (requires human approval)."""
return await db.execute(f"DELETE FROM {table} WHERE {where_clause}")

The decorator automatically:

  1. Evaluates the action before execution
  2. Blocks if denied
  3. Waits for approval if pending
  4. Logs completion or failure

Error Handling

Handle common errors gracefully:

from ascend.exceptions import (
AuthenticationError,
AuthorizationError,
TimeoutError,
RateLimitError,
CircuitBreakerOpen
)

try:
decision = client.evaluate_action(
action_type="database.query",
resource="customer_db",
parameters={"sql": "SELECT * FROM customers"}
)

except AuthenticationError:
# API key is invalid or expired
print("Authentication failed - check your API key")

except AuthorizationError as e:
# Action was denied by policy
print(f"Authorization denied: {e.message}")
print(f"Policy violations: {e.policy_violations}")

except TimeoutError:
# Request timed out
print("Request timed out - ASCEND may be unavailable")

except RateLimitError as e:
# Rate limit exceeded
print(f"Rate limited - retry after {e.retry_after} seconds")

except CircuitBreakerOpen:
# Too many failures - circuit breaker activated
print("Circuit breaker open - ASCEND appears to be down")

Best Practices

1. Always Log Completion

Every authorized action should have a completion log:

if decision.decision == Decision.ALLOWED:
try:
result = execute_action()
client.log_action_completed(decision.action_id, result)
except Exception as e:
client.log_action_failed(decision.action_id, {"error": str(e)})

2. Include Rich Context

Provide context to improve risk assessment:

decision = client.evaluate_action(
action_type="financial.transfer",
resource="bank_api",
parameters={"amount": 10000},
context={
"user_id": "user_123",
"ip_address": "192.168.1.100",
"device_id": "device_abc",
"geolocation": "US",
"time_of_day": "business_hours"
}
)

3. Handle Pending Appropriately

Do not block indefinitely on pending approvals:

# Set reasonable timeout
final_decision = client.wait_for_decision(
action_id=decision.action_id,
timeout=300 # 5 minutes max
)

# Or use non-blocking approach
if decision.decision == Decision.PENDING:
# Store the pending request
store_pending_request(decision.approval_request_id)
# Return to user
return {"status": "pending", "message": "Awaiting approval"}

4. Use Fail Mode Appropriately

Choose the right fail mode for your use case:

# For security-critical applications (recommended)
client = AscendClient(fail_mode=FailMode.CLOSED)

# For high-availability requirements (use with caution)
client = AscendClient(fail_mode=FailMode.OPEN)

Next Steps

You have now evaluated your first action. Continue with:

  1. Dashboard Tour: Monitor your actions in the console
  2. Next Steps: Advanced topics and integrations
  3. Actions API Reference: Complete API documentation

Last Updated: 2026-01-20