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)
import { defineWorkflow } from '@polos/sdk';
interface ApprovalRequest {
requestId: string;
requester: string;
description: string;
amount: number;
}
const approvalWorkflow = defineWorkflow<ApprovalRequest, unknown, ApprovalResult>(
{ id: 'approval_workflow' },
async (ctx, payload) => {
// Prepare request
await ctx.step.run('prepare_request', () => ({
prepared: true,
requestId: payload.requestId,
}));
// Suspend and wait for approval (up to 24 hours)
const resumeData = await ctx.step.suspend<Record<string, unknown>, Record<string, unknown>>(
'await_approval',
{
data: {
request_id: payload.requestId,
requester: payload.requester,
description: payload.description,
amount: payload.amount,
message: 'Please review and approve/reject this request',
},
timeout: 86400,
},
);
// Process decision from resume data
const decision = (resumeData?.['data'] ?? resumeData) as Record<string, unknown>;
if (decision['approved']) {
await ctx.step.run('process_approval', () => ({
action: 'approved',
requestId: payload.requestId,
}));
return { status: 'approved', approved: true, approver: String(decision['approver']) };
} else {
return { status: 'rejected', approved: false, comments: String(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,
)
const multiStepForm = defineWorkflow<MultiStepFormPayload, unknown, MultiStepFormResult>(
{ id: 'multi_step_form' },
async (ctx, payload) => {
// Step 1: Collect personal info
const step1Data = await ctx.step.suspend<Record<string, unknown>, Record<string, unknown>>(
'personal_info',
{
data: {
form_id: payload.formId,
step: 1,
total_steps: 3,
prompt: 'Please provide your personal information',
fields: ['first_name', 'last_name', 'email'],
},
},
);
const personalInfo = (step1Data?.['data'] ?? step1Data) as Record<string, unknown>;
// Step 2: Collect address
const step2Data = await ctx.step.suspend<Record<string, unknown>, Record<string, unknown>>(
'address_info',
{
data: {
form_id: payload.formId,
step: 2,
total_steps: 3,
prompt: 'Please provide your address',
fields: ['street', 'city', 'country'],
},
},
);
const addressInfo = (step2Data?.['data'] ?? step2Data) as Record<string, unknown>;
// Step 3: Collect preferences
const step3Data = await ctx.step.suspend<Record<string, unknown>, Record<string, unknown>>(
'preferences',
{
data: {
form_id: payload.formId,
step: 3,
total_steps: 3,
prompt: 'Please select your preferences',
fields: ['newsletter', 'notifications'],
},
},
);
const preferences = (step3Data?.['data'] ?? step3Data) as Record<string, unknown>;
return {
formId: payload.formId,
status: 'completed',
personalInfo,
addressInfo,
preferences,
};
},
);
Run it
git clone https://github.com/polos-dev/polos.git
cd polos/python-examples/09-suspend-resume
cp .env.example .env # Add your POLOS_PROJECT_ID and API key
uv sync
python main.py
git clone https://github.com/polos-dev/polos.git
cd polos/typescript-examples/09-suspend-resume
cp .env.example .env # Add your POLOS_PROJECT_ID and API key
npm install
npx tsx main.ts