RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Fredoline Eruo
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /Business Automation with Local AI
  6. /Ch. 10
Business Automation with Local AI

10. Approval Workflows

Chapter 10 of 18 · 20 min
KEY INSIGHT

Approval workflows add latency but provide essential risk mitigation. Design for human-readable summaries that enable fast decisions without sacrificing oversight.

Business processes frequently require human decision points. Approval workflows bridge the gap between automated data processing and human judgment, ensuring that consequential actions receive appropriate review before execution.

Architecture Overview

An approval workflow consists of three components: a triggering event, a notification mechanism, and a resolution handler. The triggering event initiates the workflow when conditions are met. The notification mechanism alerts the appropriate approver through their preferred channel. The resolution handler executes the corresponding action based on the approver's decision.

# approval_workflow.py
import json
import time
from datetime import datetime
from pathlib import Path
import ollama

class ApprovalWorkflow:
    def __init__(self, config_path: str = "workflow_config.json"):
        self.config = self._load_config(config_path)
        self.pending_approvals = self._load_pending()
        self.approved_actions = []
        self.rejected_actions = []
    
    def _load_config(self, path: str) -> dict:
        with open(path) as f:
            return json.load(f)
    
    def _load_pending(self) -> dict:
        pending_path = Path(self.config.get("pending_file", "pending_approvals.jsonl"))
        if pending_path.exists():
            with open(pending_path) as f:
                return {json.loads(line)["id"]: json.loads(line) for line in f}
        return {}
    
    def trigger_approval(self, action_type: str, action_data: dict) -> str:
        """Create a new approval request."""
        approval_id = f"{action_type}_{int(time.time())}"
        
        # Generate AI summary for approver
        summary = self._generate_summary(action_type, action_data)
        
        approval_request = {
            "id": approval_id,
            "type": action_type,
            "data": action_data,
            "summary": summary,
            "created_at": datetime.now().isoformat(),
            "status": "pending",
            "required_approver": self._get_approver(action_type)
        }
        
        self.pending_approvals[approval_id] = approval_request
        self._persist_pending()
        self._notify_approver(approval_request)
        
        return approval_id
    
    def _generate_summary(self, action_type: str, action_data: dict) -> str:
        """Use local LLM to generate human-readable summary."""
        prompt = f"""Summarize this {action_type} request for an approver:
{json.dumps(action_data, indent=2)}

Format as: SUMMARY: <one sentence>
IMPACT: <one sentence>
RISK: <low/medium/high>
"""
        response = ollama.chat(
            model=self.config.get("summary_model", "llama3"),
            messages=[{"role": "user", "content": prompt}]
        )
        return response["message"]["content"]

Escalation and Timeouts

Unchecked approvals create risk. A production workflow requires timeout handling and escalation paths.

    def process_approval(self, approval_id: str, decision: str, 
                        approver_id: str, comments: str = "") -> dict:
        """Process an approval decision."""
        if approval_id not in self.pending_approvals:
            return {"status": "error", "message": "Approval not found"}
        
        approval = self.pending_approvals[approval_id]
        
        if decision not in ["approved", "rejected"]:
            return {"status": "error", "message": "Invalid decision"}
        
        approval["status"] = decision
        approval["decided_at"] = datetime.now().isoformat()
        approval["approver_id"] = approver_id
        approval["comments"] = comments
        
        # Remove from pending and record in history
        del self.pending_approvals[approval_id]
        self._persist_pending()
        
        if decision == "approved":
            self.approved_actions.append(approval)
            self._execute_action(approval)
        else:
            self.rejected_actions.append(approval)
            self._notify_requester(approval, rejected=True)
        
        return {"status": "success", "approval": approval}
    
    def check_timeouts(self) -> list:
        """Find and handle timed-out approvals."""
        timeout_hours = self.config.get("timeout_hours", 24)
        timed_out = []
        
        for approval in self.pending_approvals.values():
            created = datetime.fromisoformat(approval["created_at"])
            age_hours = (datetime.now() - created).total_seconds() / 3600
            
            if age_hours > timeout_hours:
                approval["status"] = "timeout"
                timed_out.append(approval)
                self._escalate(approval)
        
        return timed_out

Integration Patterns

For critical workflows, integrate with existing systems through webhooks.

    def _notify_approver(self, approval: dict):
        """Send notification to approver."""
        webhook_url = approval.get("required_approver", {}).get("webhook")
        if webhook_url:
            import requests
            requests.post(webhook_url, json={
                "approval_id": approval["id"],
                "summary": approval["summary"],
                "action_type": approval["type"],
                "created_at": approval["created_at"]
            })
EXERCISE

Implement an approval workflow for expense reports exceeding $1000. Add automatic escalation after 8 hours and require dual approval for amounts exceeding $10,000.

← Chapter 9
Workflow Agents
Chapter 11 →
Scheduling Reports