Skip to content

Latest commit

 

History

History
309 lines (234 loc) · 9.79 KB

File metadata and controls

309 lines (234 loc) · 9.79 KB

Approval Workflow Without a Workflow Engine

You need a simple approval chain: purchase request goes to a manager, then to finance, then processing. Temporal needs a platform team. Airflow is batch-only. Step Functions is AWS-locked. You just want approvals.

There is a better way. Model each approval step as an intent with a human gate. No workflow engine, no state machine, no infrastructure to manage.

Alpha · Built with AXME (AXP Intent Protocol). cloud.axme.ai · contact@axme.ai


The Problem

A two-step approval chain should be simple. Instead you build:

1. User submits purchase request → insert into DB
2. Email manager with approval link → track email delivery
3. Manager clicks approve → update DB → email finance
4. Finance clicks approve → update DB → trigger processing
5. Cron job checks for stale requests (3 days? 7 days?)
6. Edge cases: manager on vacation, finance rejects, re-approval needed

What you end up maintaining:

  • Database state machinepending_manager, approved_manager, pending_finance, approved, rejected, expired
  • Email delivery — templates, SMTP config, bounce handling, tracking
  • Timeout logic — cron job to escalate or expire stale approvals
  • Audit trail — who approved what, when, with what comments
  • Error recovery — what happens when the DB update succeeds but the email fails?

The Solution: Intent with Human Approval Gates

Client → send_intent("purchase request")
         ↓
   Manager gate → approve/reject
         ↓
   Finance gate → approve/reject
         ↓
   Processing → COMPLETED

Each approval step is a durable intent. The platform waits for human input, handles timeouts, and tracks the full audit trail.


Quick Start

Python

pip install axme
export AXME_API_KEY="your-key"   # Get one: axme login
from axme import AxmeClient, AxmeClientConfig
import os

client = AxmeClient(AxmeClientConfig(api_key=os.environ["AXME_API_KEY"]))

# Submit purchase request with approval chain
intent_id = client.send_intent({
    "intent_type": "purchase.request.v1",
    "to_agent": "agent://myorg/production/procurement-service",
    "payload": {
        "item": "MacBook Pro M4",
        "amount_usd": 3499,
        "requester": "alice@company.com",
        "cost_center": "engineering",
    },
})

print(f"Purchase request submitted: {intent_id}")

# Wait for full approval chain to complete
result = client.wait_for(intent_id)
print(f"Final status: {result['status']}")
# COMPLETED = approved + processed
# FAILED = rejected at any step

TypeScript

npm install @axme/axme
import { AxmeClient } from "@axme/axme";

const client = new AxmeClient({ apiKey: process.env.AXME_API_KEY! });

const intentId = await client.sendIntent({
  intentType: "purchase.request.v1",
  toAgent: "agent://myorg/production/procurement-service",
  payload: {
    item: "MacBook Pro M4",
    amountUsd: 3499,
    requester: "alice@company.com",
    costCenter: "engineering",
  },
});

console.log(`Purchase request submitted: ${intentId}`);

const result = await client.waitFor(intentId);
console.log(`Final status: ${result.status}`);

More Languages

Full implementations in all 5 languages:

Language Directory Install
Python python/ pip install axme
TypeScript typescript/ npm install @axme/axme
Go go/ go get github.com/AxmeAI/axme-sdk-go
Java java/ Maven Central: ai.axme:axme-sdk
.NET dotnet/ dotnet add package Axme.Sdk

Before / After

Before: Manual Approval Chain (300+ lines)

@app.post("/purchase/request")
async def create_request(req):
    request_id = str(uuid4())
    db.insert("purchase_requests", {
        "id": request_id, "status": "pending_manager",
        "item": req.item, "amount": req.amount,
        "requester": req.requester, "created_at": datetime.now(),
    })
    send_email(get_manager(req.requester),
        subject=f"Approval needed: {req.item} (${req.amount})",
        body=f"Click to approve: {BASE_URL}/approve/{request_id}")
    return {"request_id": request_id, "status": "pending_manager"}

@app.post("/approve/{request_id}")
async def approve(request_id, decision):
    row = db.get("purchase_requests", request_id)
    if row["status"] == "pending_manager":
        if decision == "approve":
            db.update(request_id, {"status": "pending_finance"})
            send_email(get_finance_approver(), ...)  # another email
        else:
            db.update(request_id, {"status": "rejected"})
    elif row["status"] == "pending_finance":
        if decision == "approve":
            db.update(request_id, {"status": "approved"})
            queue.enqueue(process_purchase, request_id)
        else:
            db.update(request_id, {"status": "rejected"})

# Plus: cron for expired approvals, audit log table, email bounce handling...

After: AXME Approval Workflow (20 lines)

from axme import AxmeClient, AxmeClientConfig

client = AxmeClient(AxmeClientConfig(api_key=os.environ["AXME_API_KEY"]))

intent_id = client.send_intent({
    "intent_type": "purchase.request.v1",
    "to_agent": "agent://myorg/production/procurement-service",
    "payload": {
        "item": "MacBook Pro M4",
        "amount_usd": 3499,
        "requester": "alice@company.com",
        "cost_center": "engineering",
    },
})

# Observe the full approval chain
for event in client.observe(intent_id):
    print(f"[{event['status']}] {event['event_type']}")
    if event["status"] in ("COMPLETED", "FAILED"):
        break

No state machine. No email templates. No cron job. No audit log table. The platform tracks it all.


How It Works

┌────────────┐  send_intent()   ┌────────────────┐   deliver    ┌──────────────┐
│            │ ───────────────> │                │ ──────────>  │              │
│ Requester  │                  │   AXME Cloud   │              │ Procurement  │
│            │ <─ observe(SSE)  │   (platform)   │              │   Service    │
│            │                  │                │              │   (agent)    │
└────────────┘                  └───────┬────────┘              └──────┬───────┘
                                        │                              │
                                ┌───────▼────────┐                     │
                                │    Manager     │<── approval gate ───┘
                                │    approves    │
                                └───────┬────────┘
                                        │
                                ┌───────▼────────┐
                                │    Finance     │<── approval gate
                                │    approves    │
                                └───────┬────────┘
                                        │
                                   COMPLETED
  1. Requester submits a purchase intent via AXME SDK
  2. Platform delivers it to the procurement service agent
  3. Service creates manager approval gate — intent pauses, waits for human input
  4. Manager approves — service creates finance approval gate
  5. Finance approves — service resumes with completion
  6. Requester observes every step via SSE — full visibility, full audit trail

Run the Full Example

Prerequisites

# Install CLI (one-time)
curl -fsSL https://raw.githubusercontent.com/AxmeAI/axme-cli/main/install.sh | sh
# Open a new terminal, or run the "source" command shown by the installer

# Log in
axme login

# Install Python SDK
pip install axme

Terminal 1 - submit the intent

axme scenarios apply scenario.json
# Note the intent_id in the output

Terminal 2 - start the agent

Get the agent key after scenario apply:

# macOS
cat ~/Library/Application\ Support/axme/scenario-agents.json | grep -A2 procurement-service-demo

# Linux
cat ~/.config/axme/scenario-agents.json | grep -A2 procurement-service-demo

Then run the agent in your language of choice:

# Python (SSE stream listener)
AXME_API_KEY=<agent-key> python agent.py

# TypeScript (SSE stream listener, requires Node 20+)
cd typescript && npm install
AXME_API_KEY=<agent-key> npx tsx agent.ts

# Go (SSE stream listener)
cd go && go run ./cmd/agent/

# Java (processes a single intent by ID)
cd java/agent && mvn compile
AXME_API_KEY=<agent-key> mvn -q exec:java -Dexec.mainClass="Agent" -Dexec.args="<step-intent-id>"

# .NET (processes a single intent by ID)
cd dotnet/agent && dotnet run -- <step-intent-id>

Terminal 1 - approve (after agent completes its step)

# Intent will be in WAITING status after agent step
axme tasks approve <intent_id>

Verify

axme intents get <intent_id>
# lifecycle_status: COMPLETED

Related

  • AXME — project overview
  • AXP Spec — open Intent Protocol specification
  • AXME Examples — 20+ runnable examples across 5 languages
  • AXME CLI — manage intents, agents, scenarios from the terminal

Built with AXME (AXP Intent Protocol).