> ## Documentation Index
> Fetch the complete documentation index at: https://polos.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Suspend and Resume

> Pause workflows for human approval

Suspend workflows to wait for external input like human approvals.

## Approval workflow

<CodeGroup>
  ```python Python theme={null}
  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)
  ```

  ```typescript TypeScript theme={null}
  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'] ?? '') };
      }
    },
  );
  ```
</CodeGroup>

## Multi-step form

Chain multiple suspend/resume steps to collect data across stages:

<CodeGroup>
  ```python Python theme={null}
  @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,
      )
  ```

  ```typescript TypeScript theme={null}
  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,
      };
    },
  );
  ```
</CodeGroup>

## Run it

<CodeGroup>
  ```bash Python theme={null}
  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
  ```

  ```bash TypeScript theme={null}
  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
  ```
</CodeGroup>

Open [http://localhost:5173](http://localhost:5173) to view your agents and workflows, run them from the UI, and see execution traces.

[Python example on GitHub](https://github.com/polos-dev/polos/tree/main/python-examples/09-suspend-resume) | [TypeScript example on GitHub](https://github.com/polos-dev/polos/tree/main/typescript-examples/09-suspend-resume)
