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:
- Installed the ASCEND SDK (Quick Start)
- Registered your agent (Register Your First Agent)
- Optionally created policies (Create Your First Policy)
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:
- Evaluates the action before execution
- Blocks if denied
- Waits for approval if pending
- 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:
- Dashboard Tour: Monitor your actions in the console
- Next Steps: Advanced topics and integrations
- Actions API Reference: Complete API documentation
Last Updated: 2026-01-20