Skip to main content
Suspend workflows to wait for external input like human approvals.

Approval workflow

from pydantic import BaseModel
from polos import workflow, WorkflowContext

class ApprovalRequest(BaseModel):
    request_id: str
    requester: str
    description: str
    amount: float

@workflow(id="approval_workflow")
async def approval_workflow(ctx: WorkflowContext, payload: ApprovalRequest) -> ApprovalResult:
    # Prepare request
    await ctx.step.run(
        "prepare_request",
        lambda: {"prepared": True, "request_id": payload.request_id},
    )

    # Suspend and wait for approval (up to 24 hours)
    resume_data = await ctx.step.suspend(
        "await_approval",
        data={
            "request_id": payload.request_id,
            "requester": payload.requester,
            "description": payload.description,
            "amount": payload.amount,
            "message": "Please review and approve/reject this request",
        },
        timeout=86400,
    )

    # Process decision from resume data
    decision = ApprovalDecision.model_validate(resume_data.get("data", {}))
    if decision.approved:
        await ctx.step.run(
            "process_approval",
            lambda: {"action": "approved", "request_id": payload.request_id},
        )
        return ApprovalResult(status="approved", approved=True, approver=decision.approver)
    else:
        return ApprovalResult(status="rejected", approved=False, comments=decision.comments)

Multi-step form

Chain multiple suspend/resume steps to collect data across stages:
@workflow(id="multi_step_form")
async def multi_step_form(ctx: WorkflowContext, payload: MultiStepFormPayload) -> MultiStepFormResult:
    # Step 1: Collect personal info
    step1_data = await ctx.step.suspend(
        "personal_info",
        data={
            "form_id": payload.form_id,
            "step": 1,
            "total_steps": 3,
            "prompt": "Please provide your personal information",
            "fields": ["first_name", "last_name", "email"],
        },
    )
    personal_info = PersonalInfo.model_validate(step1_data.get("data", {}))

    # Step 2: Collect address
    step2_data = await ctx.step.suspend(
        "address_info",
        data={
            "form_id": payload.form_id,
            "step": 2,
            "total_steps": 3,
            "prompt": "Please provide your address",
            "fields": ["street", "city", "country"],
        },
    )
    address_info = AddressInfo.model_validate(step2_data.get("data", {}))

    # Step 3: Collect preferences
    step3_data = await ctx.step.suspend(
        "preferences",
        data={
            "form_id": payload.form_id,
            "step": 3,
            "total_steps": 3,
            "prompt": "Please select your preferences",
            "fields": ["newsletter", "notifications"],
        },
    )
    preferences = Preferences.model_validate(step3_data.get("data", {}))

    return MultiStepFormResult(
        form_id=payload.form_id,
        status="completed",
        personal_info=personal_info,
        address_info=address_info,
        preferences=preferences,
    )

Run it

git clone https://github.com/polos-dev/polos.git
cd polos/python-examples/09-suspend-resume
cp .env.example .env
uv sync
python worker.py      # Terminal 1
python main.py        # Terminal 2
Open http://localhost:5173 to view your agents and workflows, run them from the UI, and see execution traces. Python example on GitHub | TypeScript example on GitHub