← Back to Home

HaltState AI: Two Patterns: Scripts vs Services

HaltState supports two usage patterns depending on your architecture.

Pattern 1: Cron Jobs & Scripts (Exit and Retry)

For scheduled tasks that run periodically, use guard() with idempotency keys:

#!/usr/bin/env python3
"""Nightly maintenance script - runs via cron"""
from haltstate import HaltStateClient, ApprovalPending, ActionDenied
from datetime import date
import sys

client = HaltStateClient(tenant_id="...", api_key="hs_...")

op_key = f"maintenance-{date.today().isoformat()}"

try:
    with client.guard(
        action="logs.prune",
        params={"days": 30},
        idempotency_key=op_key
    ):
        prune_old_logs()
        print("Maintenance complete")

except ApprovalPending as e:
    print(f"Pending approval: {e.approval_id}")
    print("Will retry on next cron run")
    sys.exit(0)  # Clean exit - cron retries later

except ActionDenied as e:
    print(f"Denied: {e}")
    sys.exit(1)

Cron handles the retry:

# /etc/cron.d/maintenance
0 3 * * * root /opt/scripts/maintenance.py

Pattern 2: Long-Running Services (Callbacks)

For Flask, FastAPI, or daemon processes, use on_approval() callbacks:

from haltstate import HaltStateClient

client = HaltStateClient(tenant_id="...", api_key="hs_...")

def handle_approval(decision):
    """Called automatically when human approves/rejects"""
    if decision.approved:
        print(f"Approved by {decision.approver}: {decision.action}")
        execute_pending_action(decision.request_id)
    else:
        print(f"Rejected: {decision.reason}")

# Register callback - starts SSE listener in background thread
client.on_approval(handle_approval)

# Your service continues running...
app.run()

Which Pattern to Use?

Scenario Pattern Why
Cron jobs Exit & Retry Natural scheduler retry
CI/CD pipelines Exit & Retry Pipeline handles retry
Web servers Callbacks Always running
Queue workers Callbacks Long-lived process
CLI tools Exit & Retry User re-runs command