Skip to main content

Approval Workflows

Approval workflows enable human oversight of AI agent actions. ASCEND automatically routes high-risk actions to qualified approvers based on risk scores and organizational policies.

How Workflows Work

┌─────────────────────────────────────────────────────────────────────────┐
│ APPROVAL WORKFLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Agent Action ──► Risk Score ──► Workflow Matching ──► Approver Assign │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ WORKFLOW MATCHING │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ 1. Query active workflows from database │ │
│ │ 2. Check risk score against trigger conditions │ │
│ │ 3. Match workflow if min_risk ≤ score ≤ max_risk │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

Workflow Service

Source: services/workflow_service.py

The Workflow Service handles workflow execution and matching.

Getting Matching Workflows

Source: workflow_service.py:21-31

# Source: workflow_service.py:21-28
def get_matching_workflows(self, risk_score: float) -> List:
"""Get active workflows that match risk score"""
try:
from models import Workflow
workflows = self.db.query(Workflow).filter(
Workflow.status == 'active'
).all()
return [w for w in workflows if self._check_risk_match(w, risk_score)]
except Exception as e:
logger.error(f"Failed to get workflows: {e}")
return []

Risk Score Matching

Source: workflow_service.py:64-76

# Source: workflow_service.py:64-76
def _check_risk_match(self, workflow, risk_score: float) -> bool:
"""Check if workflow matches risk score"""
try:
conditions = workflow.trigger_conditions
if isinstance(conditions, str):
conditions = json.loads(conditions)
if not conditions:
return True # No conditions = matches all
min_risk = conditions.get('min_risk', 0)
max_risk = conditions.get('max_risk', 100)
return min_risk <= risk_score <= max_risk
except:
return False

Example Trigger Conditions:

{
"min_risk": 70,
"max_risk": 100,
"resource_types": ["database", "rds"],
"environments": ["production"]
}

Triggering Workflow Execution

Source: workflow_service.py:33-62

# Source: workflow_service.py:33-62
def trigger_workflow(self, workflow_id: str, action_id: int, triggered_by: str = "system") -> Dict:
"""Trigger a workflow execution"""
try:
result = self.db.execute(text("""
INSERT INTO workflow_executions (
workflow_id, action_id, executed_by, execution_status,
current_stage, started_at
)
VALUES (:workflow_id, :action_id, :executed_by, 'in_progress', 0, :started_at)
RETURNING id
"""), {
"workflow_id": workflow_id,
"action_id": action_id,
"executed_by": triggered_by,
"started_at": datetime.utcnow()
})

self.db.commit()
execution_id = result.fetchone()[0]

return {
"execution_id": execution_id,
"workflow_id": workflow_id,
"action_id": action_id,
"status": "in_progress"
}
except Exception as e:
self.db.rollback()
logger.error(f"Failed to trigger workflow: {e}")
raise

Approver Assignment

Source: services/workflow_approver_service.py

The Workflow Approver Service automatically assigns qualified approvers when workflows are created.

Primary and Backup Approvers

Source: workflow_approver_service.py:17-68

# Source: workflow_approver_service.py:17-46
def assign_approvers_to_workflow(
self,
db: Session,
workflow_execution_id: int,
action_id: int,
risk_score: float,
required_approval_level: int,
department: str = "Engineering"
) -> Dict:
"""
Assign approvers to a workflow execution
Returns primary and backup approvers
"""

# Get qualified approvers
approvers = approver_selector.select_approvers(
db=db,
risk_score=risk_score,
approval_level=required_approval_level,
department=department
)

if not approvers:
logger.error(f"No approvers found for workflow {workflow_execution_id}")
return {"primary": None, "backups": []}

# First approver is primary, rest are backups
primary = approvers[0]
backups = approvers[1:3] # Keep top 2 backups

# Update agent_action with primary approver
self._assign_to_action(db, action_id, primary["email"])

# Store approver chain in workflow_execution
self._store_approver_chain(
db, workflow_execution_id, primary, backups
)

return {
"primary": primary,
"backups": backups,
"total_available": len(approvers)
}

Updating Action with Approver

Source: workflow_approver_service.py:70-80

# Source: workflow_approver_service.py:70-80
def _assign_to_action(self, db: Session, action_id: int, approver_email: str):
"""Update agent_action with assigned approver"""
query = text("""
UPDATE agent_actions
SET pending_approvers = :email,
updated_at = NOW()
WHERE id = :action_id
""")

db.execute(query, {"email": approver_email, "action_id": action_id})
db.commit()

Storing Approver Chain

Source: workflow_approver_service.py:82-108

# Source: workflow_approver_service.py:82-108
def _store_approver_chain(
self,
db: Session,
workflow_execution_id: int,
primary: Dict,
backups: List[Dict]
):
"""Store approver information in workflow_execution metadata"""
approver_data = {
"primary_approver": {
"email": primary["email"],
"level": primary["approval_level"],
"department": primary["department"]
},
"backup_approvers": [
{
"email": b["email"],
"level": b["approval_level"],
"department": b["department"]
}
for b in backups
]
}

# Store in workflow_executions metadata column (JSONB)
logger.info(f"Approver chain for workflow {workflow_execution_id}: {approver_data}")

Reassignment When Unavailable

Source: workflow_approver_service.py:110-153

# Source: workflow_approver_service.py:110-153
def reassign_on_unavailable(
self,
db: Session,
action_id: int,
unavailable_email: str
) -> str:
"""
Reassign to backup approver if primary is unavailable
Returns new approver email
"""
logger.warning(f"Primary approver {unavailable_email} unavailable for action {action_id}")

# Get action details to find new approver
query = text("""
SELECT risk_score, required_approval_level, user_id
FROM agent_actions
WHERE id = :action_id
""")

result = db.execute(query, {"action_id": action_id}).fetchone()
if not result:
return None

risk_score, req_level, _ = result

# Get new approvers, excluding unavailable one
approvers = approver_selector.select_approvers(
db=db,
risk_score=risk_score,
approval_level=req_level
)

# Filter out unavailable approver
available = [a for a in approvers if a["email"] != unavailable_email]

if not available:
logger.error(f"No alternative approvers for action {action_id}")
return None

new_approver = available[0]["email"]
self._assign_to_action(db, action_id, new_approver)

logger.info(f"Reassigned action {action_id} to {new_approver}")
return new_approver

Workflow Database Schema

Source: models.py (Workflow table)

Workflow Model

class Workflow(Base):
__tablename__ = "workflows"

id = Column(Integer, primary_key=True, index=True)
organization_id = Column(Integer, ForeignKey("organizations.id"), nullable=False, index=True)
name = Column(String, nullable=False)
description = Column(Text, nullable=True)
status = Column(String, default="active") # active, paused, archived
trigger_conditions = Column(JSONB, nullable=True) # JSON conditions for matching
created_at = Column(DateTime, default=datetime.now(UTC))
updated_at = Column(DateTime, nullable=True)

Workflow Execution Model

class WorkflowExecution(Base):
__tablename__ = "workflow_executions"

id = Column(Integer, primary_key=True, index=True)
workflow_id = Column(Integer, ForeignKey("workflows.id"), nullable=False)
action_id = Column(Integer, ForeignKey("agent_actions.id"), nullable=False)
executed_by = Column(String, nullable=False) # user_id or "system"
execution_status = Column(String, default="in_progress") # in_progress, completed, failed
current_stage = Column(Integer, default=0) # Current approval stage
started_at = Column(DateTime, default=datetime.now(UTC))
completed_at = Column(DateTime, nullable=True)
metadata = Column(JSONB, nullable=True) # Stores approver_chain and other data

Example Workflow Configuration

High-Risk Database Operations

{
"name": "Production Database Changes",
"description": "Approval workflow for production database modifications",
"status": "active",
"trigger_conditions": {
"min_risk": 70,
"max_risk": 100,
"resource_types": ["rds", "database", "aurora"],
"environments": ["production"],
"action_types": ["write", "delete", "update"]
},
"approval_levels": [
{
"level": 1,
"required_role": "senior_engineer",
"approval_threshold": 1
},
{
"level": 2,
"required_role": "engineering_manager",
"approval_threshold": 1
}
]
}

Medium-Risk Changes

{
"name": "Standard Approval",
"description": "Single approval for medium-risk actions",
"status": "active",
"trigger_conditions": {
"min_risk": 45,
"max_risk": 69
},
"approval_levels": [
{
"level": 1,
"required_role": "engineer",
"approval_threshold": 1
}
]
}

User Approval Levels

Source: models.py:48-50 (User model)

Users have approval qualifications:

class User(Base):
# ... other fields ...

# Enterprise authorization fields
approval_level = Column(Integer, default=1) # 1=Junior, 2=Senior, 3=Manager, 4=Executive
is_emergency_approver = Column(Boolean, default=False) # Can approve emergency actions
max_risk_approval = Column(Integer, default=50) # Maximum risk score they can approve
Approval LevelRoleMax Risk ScoreTypical Actions
1Junior Approver50Low-risk actions only
2Senior Approver70Medium to high-risk
3Manager85High-risk and critical
4Executive100Any risk level, emergency overrides

Integration Example

Backend Workflow Routing

from services.workflow_service import WorkflowService
from services.workflow_approver_service import workflow_approver_service

# 1. Get workflows matching risk score
workflow_service = WorkflowService(db)
matching_workflows = workflow_service.get_matching_workflows(risk_score=75)

if matching_workflows:
# 2. Trigger the first matching workflow
workflow = matching_workflows[0]
execution = workflow_service.trigger_workflow(
workflow_id=workflow.id,
action_id=action_id,
triggered_by="system"
)

# 3. Assign approvers
assignment = workflow_approver_service.assign_approvers_to_workflow(
db=db,
workflow_execution_id=execution["execution_id"],
action_id=action_id,
risk_score=75,
required_approval_level=2,
department="Engineering"
)

# 4. Notify primary approver
primary = assignment["primary"]
# Send email/Slack notification to primary["email"]

Current Limitations

The current implementation provides basic workflow routing and approver assignment. The following features are not yet implemented:

Not Yet Available

  • Multi-level approval chains: Sequential approval through multiple levels
  • Parallel approval: Multiple approvers must all approve
  • Timeout handling: Auto-escalate or deny on timeout
  • Delegation: Out-of-office approver substitution
  • Email/Slack notifications: Automated approver notifications (requires integration)
  • Approval UI: Web-based approval interface (frontend implementation pending)
  • Workflow templates: Pre-built workflow configurations
  • Conditional routing: Complex routing logic based on multiple factors

Planned Enhancements

Future versions will include:

  • Multi-stage approval workflows with timeout handling
  • Notification integrations (Email, Slack, Teams)
  • Approval dashboard UI
  • Delegation and out-of-office management
  • Workflow analytics and metrics
  • Approval SLA tracking

Best Practices

1. Define Clear Risk Boundaries

// Good: Clear, non-overlapping ranges
{
"workflows": [
{"min_risk": 0, "max_risk": 44, "approvers": "auto_approve"},
{"min_risk": 45, "max_risk": 69, "approvers": "single_approval"},
{"min_risk": 70, "max_risk": 100, "approvers": "senior_approval"}
]
}

// Bad: Overlapping ranges
{
"workflows": [
{"min_risk": 0, "max_risk": 50},
{"min_risk": 45, "max_risk": 75} // Overlaps with previous
]
}

2. Maintain Backup Approvers

# Always check backup approvers exist
assignment = workflow_approver_service.assign_approvers_to_workflow(...)

if assignment["primary"] is None:
logger.error("No primary approver available")
# Escalate to emergency approvers or deny action

if len(assignment["backups"]) < 2:
logger.warning("Insufficient backup approvers")

3. Handle Unavailability

# If primary approver is unavailable
if approver_unavailable:
new_approver = workflow_approver_service.reassign_on_unavailable(
db=db,
action_id=action_id,
unavailable_email=original_approver
)

if new_approver is None:
# No alternative approvers - escalate or deny
pass

Next Steps