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

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.restate.dev/feedback

```json
{
  "path": "/ai/patterns/human-in-the-loop",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Approvals with Pause & Resume

> Agents that pause for human approval and resume when it arrives, even across restarts and infrastructure changes.

export const GitHubLink = ({url}) => <div style={{
  marginTop: '-8px',
  marginBottom: '8px',
  textAlign: 'right'
}}>
    <a href={url} target="_blank" rel="noopener noreferrer" style={{
  fontSize: '0.75rem',
  color: '#6B7280',
  textDecoration: 'none',
  display: 'inline-flex',
  alignItems: 'center',
  gap: '3px',
  padding: '2px 6px',
  borderRadius: '3px',
  border: '1px solid #E5E7EB',
  backgroundColor: 'transparent',
  transition: 'all 0.2s ease'
}} onMouseOver={e => {
  e.target.style.color = '#6B7280';
  e.target.style.backgroundColor = '#F9FAFB';
}} onMouseOut={e => {
  e.target.style.color = '#6B7280';
  e.target.style.backgroundColor = 'transparent';
}}>
      <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
        <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.230 3.297-1.230.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
      </svg>
      View on GitHub
    </a>
  </div>;

export const GlobalTab = ({title, icon, children}) => {
  return <div>{children}</div>;
};

export const GlobalTabs = ({children, className = ''}) => {
  const [activeTab, setActiveTab] = useState(0);
  const tabs = React.Children.toArray(children).filter(child => child.type && child.type.name === 'GlobalTab');
  useEffect(() => {
    const savedLanguage = localStorage.getItem('language');
    if (savedLanguage) {
      const matchingIndex = tabs.findIndex(tab => tab.props.title === savedLanguage);
      if (matchingIndex !== -1) {
        setActiveTab(matchingIndex);
      }
    }
  }, [tabs]);
  useEffect(() => {
    const handleGlobalTabChange = event => {
      const targetTitle = event.detail.title;
      const matchingIndex = tabs.findIndex(tab => tab.props.title === targetTitle);
      if (matchingIndex !== -1 && matchingIndex !== activeTab) {
        setActiveTab(matchingIndex);
      }
    };
    window.addEventListener('globalTabChange', handleGlobalTabChange);
    return () => window.removeEventListener('globalTabChange', handleGlobalTabChange);
  }, [tabs, activeTab]);
  const handleTabClick = index => {
    setActiveTab(index);
    const title = tabs[index].props.title;
    localStorage.setItem('language', title);
    window.dispatchEvent(new CustomEvent('globalTabChange', {
      detail: {
        title
      }
    }));
  };
  return <div className={`tabs tabs tab-container ${className}`}>
            <ul className="not-prose mb-6 pb-[1px] flex-none min-w-full overflow-auto border-b border-gray-200 gap-x-6 flex dark:border-gray-200/10" data-component-part="tabs-list">
                {tabs.map((tab, index) => <li key={index} className="cursor-pointer">
                        <button className={index === activeTab ? "flex text-sm items-center gap-1.5 leading-6 font-semibold whitespace-nowrap pt-3 pb-2.5 -mb-px max-w-max border-b text-primary dark:text-primary-light border-current" : "flex text-sm items-center gap-1.5 leading-6 font-semibold whitespace-nowrap pt-3 pb-2.5 -mb-px max-w-max border-b text-gray-900 border-transparent hover:border-gray-300 dark:text-gray-200 dark:hover:border-gray-700"} data-component-part="tab-button" data-active={index === activeTab} onClick={() => handleTabClick(index)}>
                            {tab.props.icon && <img src={tab.props.icon} alt="" className="h-4 w-4 not-prose" noZoom />}
                            {tab.props.title}
                        </button>
                    </li>)}
            </ul>
            <div className="prose dark:prose-dark overflow-x-auto" data-component-part="tab-content">
                {tabs[activeTab]?.props.children}
            </div>
        </div>;
};

Some agent actions need human approvals for dangerous actions: deploying code, sending money, modifying user accounts. With Restate, your agent can **pause** execution, wait for an external signal, and **resume** exactly where it left off, even if the process restarts in the meantime.

<GlobalTabs>
  <GlobalTab title="Vercel AI" icon={"/img/languages/typescript.svg"} />

  <GlobalTab title="OpenAI Agents" icon={"/img/languages/python.svg"} />

  <GlobalTab title="Google ADK" icon={"/img/languages/python.svg"} />

  <GlobalTab title="Pydantic AI" icon={"/img/languages/python.svg"} />

  <GlobalTab title="Restate TS" icon={"/img/languages/typescript.svg"} />

  <GlobalTab title="Restate Py" icon={"/img/languages/python.svg"} />
</GlobalTabs>

## How it works

Restate provides **durable promises** (awakeables) that survive crashes and restarts:

1. The agent creates a durable promise and gets a unique ID
2. The agent sends this ID to whoever needs to approve (Slack, email, dashboard)
3. The agent **suspends**, freeing compute resources (no idle billing on serverless)
4. When the approver responds, they resolve the promise via HTTP
5. The agent **resumes** from the exact point it paused

## Example: approval tool for an agent

<GlobalTabs className={"hidden-tabs"}>
  <GlobalTab title="Vercel AI">
    ```typescript human-approval-agent.ts {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/vercel-ai/tour-of-agents/src/human-approval-agent.ts#here"}  theme={null}
    const run = async (ctx: restate.Context, { prompt }: ClaimPrompt) => {
      const model = wrapLanguageModel({
        model: openai("gpt-4o"),
        middleware: durableCalls(ctx, { maxRetryAttempts: 3 }),
      });

      const { text } = await generateText({
        model,
        system:
          "You are an insurance claim evaluation agent. Use these rules: " +
          "* if the amount is more than 1000, ask for human approval, " +
          "* if the amount is less than 1000, decide by yourself",
        prompt,
        tools: {
          humanApproval: tool({
            description: "Ask for human approval for high-value claims.",
            inputSchema: InsuranceClaimSchema,
            execute: async (claim: InsuranceClaim): Promise<boolean> => {
              const approval = ctx.awakeable<boolean>();
              await ctx.run("request-review", () =>
                requestHumanReview(claim, approval.id),
              );
              return approval.promise;
            },
          }),
        },
        stopWhen: [stepCountIs(5)],
        providerOptions: { openai: { parallelToolCalls: false } },
      });
      return text;
    };
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/tree/main/vercel-ai/tour-of-agents/src/human-approval-agent.ts" />

    <Accordion title="Try out human approval" icon="laptop">
      [Install Restate](/installation) and launch it:

      ```bash theme={null}
      npm install --global @restatedev/restate-server@latest @restatedev/restate@latest
      restate-server
      ```

      Get the example:

      ```bash theme={null}
      restate example typescript-vercel-ai-tour-of-agents && cd typescript-vercel-ai-tour-of-agents
      npm install
      ```

      Export your [OpenAI API key](https://platform.openai.com/api-keys) and run the agent:

      ```bash theme={null}
      export OPENAI_API_KEY=sk-...
      ```

      ```bash theme={null}
      npx tsx ./src/human-approval-agent.ts
      ```

      Register the agents with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Use `curl` with `/send` to start the claim asynchronously, without waiting for the result:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalAgent/run/send \
      --json '{"prompt": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      You can restart the service to see how Restate continues waiting for the approval.

      If you wait for more than a minute, the invocation will get suspended.

      <Frame>
        <img src="https://mintcdn.com/restate-6d46e1dc/HVS5SVWE1DQCXD5l/img/tour/agents/suspensions.png?fit=max&auto=format&n=HVS5SVWE1DQCXD5l&q=85&s=a9e1e8be4a379971894a3ffadbe267ac" alt="Invocation overview" width="1864" height="1027" data-path="img/tour/agents/suspensions.png" />
      </Frame>

      Simulate approving the claim by executing the **curl request that was printed in the service logs**, similar to:

      ```bash theme={null}
      curl localhost:8080/restate/awakeables/sign_1M28aqY6ZfuwBmRnmyP/resolve --json 'true'
      ```

      See in the UI how the workflow resumes and finishes after the approval.

      <Frame>
        <img src="https://mintcdn.com/restate-6d46e1dc/BuMemVygOLN8C4_0/img/tour/agents/human-approval.png?fit=max&auto=format&n=BuMemVygOLN8C4_0&q=85&s=94ca60d7d2f853c85bb76d129274682f" alt="Invocation overview" width="2146" height="919" data-path="img/tour/agents/human-approval.png" />
      </Frame>
    </Accordion>
  </GlobalTab>

  <GlobalTab title="OpenAI Agents">
    ```python human_approval_agent.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/openai-agents/tour-of-agents/app/human_approval_agent.py#here"}  theme={null}
    @durable_function_tool
    async def human_approval(claim: InsuranceClaim) -> str:
        """Ask for human approval for high-value claims."""

        # Create an awakeable for human approval
        approval_id, approval_promise = restate_context().awakeable(type_hint=str)

        # Request human review
        await restate_context().run_typed(
            "Request review", request_human_review, claim=claim, awakeable_id=approval_id
        )

        # Wait for human approval
        return await approval_promise
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/blob/main/openai-agents/tour-of-agents/app/human_approval_agent.py" />

    <Accordion title="Try out human approval" icon="laptop">
      [Install Restate](/installation) and launch it:

      ```bash theme={null}
      restate-server
      ```

      Get the example:

      ```bash theme={null}
      restate example python-openai-agents-tour-of-agents && cd python-openai-agents-tour-of-agents
      ```

      Export your [OpenAI API key](https://platform.openai.com/api-keys) and run the agent:

      ```bash theme={null}
      export OPENAI_API_KEY=sk-...
      ```

      ```bash theme={null}
      uv run app/human_approval_agent.py
      ```

      Register the agents with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Use `curl` with `/send` to start the claim asynchronously, without waiting for the result:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalAgent/run/send \
      --json '{"message": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      You can restart the service to see how Restate continues waiting for the approval.

      If you wait for more than a minute, the invocation will get suspended.

      <Frame>
        <img src="https://mintcdn.com/restate-6d46e1dc/HVS5SVWE1DQCXD5l/img/tour/agents/openai/suspensions.png?fit=max&auto=format&n=HVS5SVWE1DQCXD5l&q=85&s=8d8837e032c528a78cb15ddf62729f79" alt="Invocation overview" width="1068" height="596" data-path="img/tour/agents/openai/suspensions.png" />
      </Frame>

      Simulate approving the claim by executing the **curl request that was printed in the service logs**, similar to:

      ```bash theme={null}
      curl localhost:8080/restate/awakeables/sign_1M28aqY6ZfuwBmRnmyP/resolve --json 'true'
      ```

      See in the UI how the workflow resumes and finishes after the approval.

      <Frame>
        <img src="https://mintcdn.com/restate-6d46e1dc/AlmW9-xJqv-0ObCA/img/tour/agents/openai/human-approval.png?fit=max&auto=format&n=AlmW9-xJqv-0ObCA&q=85&s=b9e58ecbf6350c3e29e6be4e6e6a0b94" alt="Invocation overview" width="1065" height="469" data-path="img/tour/agents/openai/human-approval.png" />
      </Frame>
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Google ADK">
    ```python human_approval_agent.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/google-adk/tour-of-agents/app/human_approval_agent.py#here"}  theme={null}
    async def human_approval(claim: InsuranceClaim) -> str:
        """Ask for human approval for high-value claims."""
        # Create an awakeable for human approval
        approval_id, approval_promise = restate_context().awakeable(type_hint=str)

        # Request human review
        await restate_context().run_typed(
            "Request review",
            request_review,
            claim=claim,
            awakeable_id=approval_id,
        )

        # Wait for human approval
        return await approval_promise
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/blob/main/google-adk/tour-of-agents/app/human_approval_agent.py" />

    <Accordion title="Try out human approval" icon="laptop">
      [Install Restate](/installation) and launch it:

      ```bash theme={null}
      restate-server
      ```

      Get the example:

      ```bash theme={null}
      restate example python-google-adk-tour-of-agents && cd python-google-adk-tour-of-agents
      ```

      Export your [Google API key](https://aistudio.google.com/app/apikey) and run the agent:

      ```bash theme={null}
      export GOOGLE_API_KEY=your-api-key
      ```

      ```bash theme={null}
      uv run app/human_approval_agent.py
      ```

      Register the agents with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Use `curl` with `/send` to start the claim asynchronously, without waiting for the result:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalAgent/user-123/run/send \
      --json '{"message": "Process my hospital bill of 3000USD for a broken leg.", "session_id": "session-123"}'
      ```

      You can restart the service to see how Restate continues waiting for the approval.

      If you wait for more than a minute, the invocation will get suspended.

      <Frame>
        <img src="https://mintcdn.com/restate-6d46e1dc/Nqw04BBNfCiHrELc/img/tour/agents/adk/suspensions.png?fit=max&auto=format&n=Nqw04BBNfCiHrELc&q=85&s=97fccec36b681a1440580b6ce768a875" alt="Invocation overview" width="1851" height="549" data-path="img/tour/agents/adk/suspensions.png" />
      </Frame>

      Simulate approving the claim by executing the **curl request that was printed in the service logs**, similar to:

      ```bash theme={null}
      curl localhost:8080/restate/awakeables/sign_1M28aqY6ZfuwBmRnmyP/resolve --json 'true'
      ```

      See in the UI how the workflow resumes and finishes after the approval.

      <Frame>
        <img src="https://mintcdn.com/restate-6d46e1dc/Nqw04BBNfCiHrELc/img/tour/agents/adk/human-approval.png?fit=max&auto=format&n=Nqw04BBNfCiHrELc&q=85&s=f965e311402eac47334a0853153b5ca7" alt="Invocation overview" width="1864" height="736" data-path="img/tour/agents/adk/human-approval.png" />
      </Frame>
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Pydantic AI">
    ```python human_approval_agent.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/pydantic-ai/tour-of-agents/app/human_approval_agent.py#here"}  theme={null}
    @agent.tool
    async def human_approval(_run_ctx: RunContext[None], claim: InsuranceClaim) -> str:
        """Ask for human approval for high-value claims."""

        # Create an awakeable for human approval
        approval_id, approval_promise = restate_context().awakeable(type_hint=str)

        # Request human review
        await restate_context().run_typed(
            "Request review", request_human_review, claim=claim, awakeable_id=approval_id
        )

        # Wait for human approval
        return await approval_promise
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/blob/main/pydantic-ai/tour-of-agents/app/human_approval_agent.py" />

    <Accordion title="Try out human approval" icon="laptop">
      [Install Restate](/installation) and launch it:

      ```bash theme={null}
      restate-server
      ```

      Get the example:

      ```bash theme={null}
      restate example python-pydantic-ai-tour-of-agents && cd python-pydantic-ai-tour-of-agents
      ```

      Export your [OpenAI API key](https://platform.openai.com/api-keys) and run the agent:

      ```bash theme={null}
      export OPENAI_API_KEY=sk-...
      ```

      ```bash theme={null}
      uv run app/human_approval_agent.py
      ```

      Register the agents with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Use `curl` with `/send` to start the claim asynchronously, without waiting for the result:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalAgent/run/send \
      --json '{"message": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      You can restart the service to see how Restate continues waiting for the approval.

      If you wait for more than a minute, the invocation will get suspended.

      <Frame>
        <img src="https://mintcdn.com/restate-6d46e1dc/HVS5SVWE1DQCXD5l/img/tour/agents/openai/suspensions.png?fit=max&auto=format&n=HVS5SVWE1DQCXD5l&q=85&s=8d8837e032c528a78cb15ddf62729f79" alt="Invocation overview" width="1068" height="596" data-path="img/tour/agents/openai/suspensions.png" />
      </Frame>

      Simulate approving the claim by executing the **curl request that was printed in the service logs**, similar to:

      ```bash theme={null}
      curl localhost:8080/restate/awakeables/sign_1M28aqY6ZfuwBmRnmyP/resolve --json 'true'
      ```

      See in the UI how the workflow resumes and finishes after the approval.

      <Frame>
        <img src="https://mintcdn.com/restate-6d46e1dc/AlmW9-xJqv-0ObCA/img/tour/agents/openai/human-approval.png?fit=max&auto=format&n=AlmW9-xJqv-0ObCA&q=85&s=b9e58ecbf6350c3e29e6be4e6e6a0b94" alt="Invocation overview" width="1065" height="469" data-path="img/tour/agents/openai/human-approval.png" />
      </Frame>
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Restate TS">
    ```typescript human-approval-agent.ts {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/typescript-restate-only/tour-of-agents/src/human-approval-agent.ts#here"}  theme={null}
    async function run(ctx: Context, { message }: { message: string }) {
      const prompt =
        "You are an insurance claim evaluation agent. Use these rules: " +
        "* if the amount is more than 1000, ask for human approval, " +
        "* if the amount is less than 1000, decide by yourself. " +
        `Evaluate this claim: ${message}`;
      const { text, toolCalls } = await ctx.run(
        "LLM call",
        // Use your preferred LLM SDK here
        async () => llmCall(prompt, tools),
        { maxRetryAttempts: 3 },
      );

      if (toolCalls?.[0]?.toolName === "humanApproval") {
        // Create a recoverable approval promise
        const approval = ctx.awakeable<boolean>();
        await ctx.run("request-review", () =>
          requestClaimReview(message, approval.id),
        );

        // Suspend until reviewer resolves the approval
        // Check the service logs to see how to resolve it over HTTP, e.g.:
        // curl http://localhost:8080/restate/awakeables/sign_.../resolve --json 'true'
        return approval.promise;
      }

      return text;
    }
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/blob/main/typescript-restate-only/tour-of-agents/src/human-approval-agent.ts" />

    <Accordion title="Try out human approval" icon="laptop">
      [Install Restate](/installation) and launch it:

      ```bash theme={null}
      restate-server
      ```

      Get the example:

      ```bash theme={null}
      restate example typescript-restate-tour-of-agents && cd typescript-restate-tour-of-agents
      npm install
      ```

      Export your API key:

      ```bash theme={null}
      export OPENAI_API_KEY=sk-...
      ```

      ```bash theme={null}
      npx tsx ./src/human-approval-agent.ts
      ```

      Register the services with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Use `curl` with `/send` to start the moderation asynchronously:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalAgent/run/send \
      --json '{"message": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      If you wait for more than a minute, the invocation will get suspended.

      Simulate approving by executing the **curl request that was printed in the service logs**, similar to:

      ```bash theme={null}
      curl localhost:8080/restate/awakeables/sign_1M28aqY6ZfuwBmRnmyP/resolve --json '"approved"'
      ```

      See in the UI how the workflow resumes and finishes after the approval.
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Restate Py">
    ```python human_approval_agent.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/python-restate-only/tour-of-agents/app/human_approval_agent.py#here"}  theme={null}
    # TOOL IMPLEMENTATION
    async def request_human_approval(ctx: restate.Context, claim: InsuranceClaim) -> str:
        # Create a recoverable approval promise
        approval_id, approval_promise = ctx.awakeable(type_hint=str)

        await ctx.run_typed(
            "Request review", request_review, claim=claim, approval_id=approval_id
        )

        # Suspend until human resolves the approval
        # Check the service logs to see how to resolve it over HTTP, e.g.:
        # curl http://localhost:8080/restate/awakeables/sign_.../resolve --json '"approved"'
        return await approval_promise


    # AGENT SERVICE
    claim_approval_agent = restate.Service("HumanClaimApprovalAgent")


    @claim_approval_agent.handler()
    async def run(ctx: restate.Context, req: ClaimPrompt) -> str | None:
        """Evaluate an insurance claim with optional human approval for high-value claims."""

        # LLM evaluates the claim and decides if human approval is needed
        result = await ctx.run_typed(
            "Evaluate claim",
            llm_call,  # Use your preferred LLM SDK here
            RunOptions(max_attempts=3),
            messages=f"""You are an insurance claim evaluation agent. Use these rules:
            - if the amount is more than 1000, ask for human approval using tools;
            - if the amount is less than 1000, decide by yourself.

            Claim: {req.message}""",
            tools=[
                tool(
                    "request_human_approval",
                    "Ask for human approval for high-value claims",
                    InsuranceClaim.model_json_schema(),
                )
            ],
        )

        # If the LLM requests human approval, suspend until a human resolves it
        if (
            result.tool_calls
            and result.tool_calls[0].function.name == "request_human_approval"
        ):
            claim = InsuranceClaim.model_validate_json(
                result.tool_calls[0].function.arguments
            )
            return await request_human_approval(ctx, claim)

        return result.content
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/blob/main/python-restate-only/tour-of-agents/app/human_approval_agent.py" />

    <Accordion title="Try out human approval" icon="laptop">
      [Install Restate](/installation) and launch it:

      ```bash theme={null}
      restate-server
      ```

      Get the example:

      ```bash theme={null}
      restate example python-restate-tour-of-agents && cd python-restate-tour-of-agents
      ```

      Export your API key:

      ```bash theme={null}
      export OPENAI_API_KEY=sk-...
      ```

      ```bash theme={null}
      uv run app/human_approval_agent.py
      ```

      Register the services with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Use `curl` with `/send` to start the claim asynchronously, without waiting for the result:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalAgent/run/send \
      --json '{"message": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      If you wait for more than a minute, the invocation will get suspended.

      Simulate approving by executing the **curl request that was printed in the service logs**, similar to:

      ```bash theme={null}
      curl localhost:8080/restate/awakeables/sign_1M28aqY6ZfuwBmRnmyP/resolve --json '"approved"'
      ```

      See in the UI how the workflow resumes and finishes after the approval.
    </Accordion>
  </GlobalTab>
</GlobalTabs>

## Why this matters for agents

* **No idle resources**: The agent suspends while waiting. On serverless infrastructure, you pay nothing during the wait.
* **Survives restarts**: Even if the process or infrastructure changes, the agent resumes when the approval arrives.
* **Composable**: Combine with tool calls, multi-step workflows, and other patterns. The approval is just another durable step.

<img src="https://mintcdn.com/restate-6d46e1dc/eyiUDPHMMaoJj2hw/img/ai/patterns/human_approval_typescript.gif?s=9d0a47d7aa7484eb8f789ff5e37dcc75" width="1920" height="1080" data-path="img/ai/patterns/human_approval_typescript.gif" />

## Adding timeouts

Add a timeout to prevent approval steps from hanging indefinitely. Restate persists both the timer and the approval promise, so if the service crashes or is restarted, it will continue waiting with the correct remaining time.

<GlobalTabs className={"hidden-tabs"}>
  <GlobalTab title="Vercel AI">
    ```typescript human-approval-agent-with-timeout.ts {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/vercel-ai/tour-of-agents/src/human-approval-agent-with-timeout.ts#here"}  theme={null}
    const approval = ctx.awakeable<boolean>();
    await ctx.run("request-review", () =>
      requestHumanReview(claim, approval.id),
    );
    try {
      // At most 3 hours, to reach our SLA
      const approved = await approval.promise.orTimeout({ hours: 3 });
      return { approved };
    } catch (e) {
      if (e instanceof TimeoutError) {
        return {
          approved: false,
          reason: "Approval timed out - Evaluate with AI",
        };
      }
      throw e;
    }
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/tree/main/vercel-ai/tour-of-agents/src/human-approval-agent-with-timeout.ts" />

    <Accordion title="Try out timeouts" icon="laptop">
      Start the timeout agent (stop any previously running agent first):

      ```bash theme={null}
      npx tsx ./src/human-approval-agent-with-timeout.ts
      ```

      Register the agents with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Send a request to the service:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalWithTimeoutsAgent/run/send \
      --json '{"prompt": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      Restart the service and check in the UI how the process will block for the remaining time without starting over.

      You can also lower the timeout to a few seconds to see how the timeout path is taken.
    </Accordion>
  </GlobalTab>

  <GlobalTab title="OpenAI Agents">
    ```python human_approval_agent_with_timeout.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py#here"}  theme={null}
    @durable_function_tool
    async def human_approval(claim: InsuranceClaim) -> str:
        """Ask for human approval for high-value claims."""
        # Create an awakeable for human approval
        approval_id, approval_promise = restate_context().awakeable(type_hint=bool)

        # Request human review
        await restate_context().run_typed(
            "Request review", request_human_review, claim=claim, awakeable_id=approval_id
        )

        # Wait for human approval for at most 3 hours to reach our SLA
        match await restate.select(
            approval=approval_promise,
            timeout=restate_context().sleep(timedelta(hours=3)),
        ):
            case ["approval", approved]:
                return "Approved" if approved else "Rejected"
            case _:
                return "Approval timed out - Evaluate with AI"
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/blob/main/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py" />

    <Accordion title="Try out timeouts" icon="laptop">
      Start the timeout agent (stop any previously running agent first):

      ```bash theme={null}
      uv run app/human_approval_agent_with_timeout.py
      ```

      Register the agents with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Send a request to the service:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalWithTimeoutsAgent/run/send \
      --json '{"message": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      Restart the service and check in the UI how the process will block for the remaining time without starting over.

      You can also lower the timeout to a few seconds to see how the timeout path is taken.
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Google ADK">
    ```python human_approval_agent_with_timeout.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/google-adk/tour-of-agents/app/human_approval_agent_with_timeout.py#here"}  theme={null}
    async def human_approval(claim: InsuranceClaim) -> str:
        """Ask for human approval for high-value claims."""
        # Create an awakeable for human approval
        approval_id, approval_promise = restate_context().awakeable(type_hint=str)

        # Request human review
        await restate_context().run_typed(
            "Request review",
            request_review,
            claim=claim,
            awakeable_id=approval_id,
        )

        # Wait for human approval for at most 3 hours to reach our SLA
        match await restate.select(
            approval=approval_promise,
            timeout=restate_context().sleep(timedelta(hours=3)),
        ):
            case ["approval", approved]:
                return "Approved" if approved else "Rejected"
            case _:
                return "Approval timed out - Evaluate with AI"
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/blob/main/google-adk/tour-of-agents/app/human_approval_agent_with_timeout.py" />

    <Accordion title="Try out timeouts" icon="laptop">
      Start the timeout agent (stop any previously running agent first):

      ```bash theme={null}
      uv run app/human_approval_agent_with_timeout.py
      ```

      Register the agents with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Send a request to the service:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalWithTimeoutsAgent/user-123/run/send \
      --json '{"message": "Process my hospital bill of 3000USD for a broken leg.", "session_id": "session-123"}'
      ```

      Restart the service and check in the UI how the process will block for the remaining time without starting over.

      You can also lower the timeout to a few seconds to see how the timeout path is taken.
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Pydantic AI">
    Use `restate.select` to race the approval promise against a sleep timer:

    ```python human_approval_agent_with_timeout.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/pydantic-ai/tour-of-agents/app/human_approval_agent_with_timeout.py#here"}  theme={null}
    @agent.tool
    async def human_approval(_run_ctx: RunContext[None], claim: InsuranceClaim) -> str:
        """Ask for human approval for high-value claims."""

        # Create an awakeable for human approval
        approval_id, approval_promise = restate_context().awakeable(type_hint=bool)

        # Request human review
        await restate_context().run_typed(
            "Request review", request_human_review, claim=claim, awakeable_id=approval_id
        )

        # Wait for human approval for at most 3 hours to reach our SLA
        match await restate.select(
            approval=approval_promise,
            timeout=restate_context().sleep(timedelta(hours=3)),
        ):
            case ["approval", approved]:
                return "Approved" if approved else "Rejected"
            case _:
                return "Approval timed out - Evaluate with AI"
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/blob/main/pydantic-ai/tour-of-agents/app/human_approval_agent_with_timeout.py" />

    <Accordion title="Try out timeouts" icon="laptop">
      Start the timeout agent (stop any previously running agent first):

      ```bash theme={null}
      uv run app/human_approval_agent_with_timeout.py
      ```

      Register the agents with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Send a request to the service:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalWithTimeoutsAgent/run/send \
      --json '{"message": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      Restart the service and check in the UI how the process will block for the remaining time without starting over.

      You can also lower the timeout to a few seconds to see how the timeout path is taken.
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Restate TS">
    Use `orTimeout` on the awakeable promise:

    ```typescript theme={null}
    const approval = ctx.awakeable<string>();
    await ctx.run("Ask review", () => notifyModerator(message, approval.id));

    try {
      // At most 3 hours, to reach our SLA
      const result = await approval.promise.orTimeout({ hours: 3 });
      return result;
    } catch (e) {
      if (e instanceof TimeoutError) {
        return "Approval timed out - Evaluate with AI";
      }
      throw e;
    }
    ```

    <Accordion title="Try out timeouts" icon="laptop">
      Start the timeout agent (stop any previously running agent first):

      ```bash theme={null}
      npx tsx ./src/human-approval-agent-with-timeout.ts
      ```

      Register the agents with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Send a request to the service:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalWithTimeoutsAgent/run/send \
      --json '{"prompt": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      Restart the service and check in the UI how the process will block for the remaining time without starting over.

      You can also lower the timeout to a few seconds to see how the timeout path is taken.
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Restate Py">
    ```python human_approval_agent_with_timeout.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/python-restate-only/tour-of-agents/app/human_approval_agent_with_timeout.py#here"}  theme={null}
    async def request_human_approval(ctx: restate.Context, claim: InsuranceClaim) -> str:
        # Create a recoverable approval promise
        approval_id, approval_promise = ctx.awakeable(type_hint=str)

        await ctx.run_typed(
            "Request review", request_review, claim=claim, approval_id=approval_id
        )

        # Suspend until human resolves the approval or until the timeout hits
        # Check the service logs to see how to resolve it over HTTP, e.g.:
        # curl http://localhost:8080/restate/awakeables/sign_.../resolve --json '"approved"'
        match await restate.select(
            approval=approval_promise,
            timeout=ctx.sleep(timedelta(hours=3)),
        ):
            case ["approval", approved]:
                return approved
            case _:
                return "Approval timed out - Evaluate with AI"
    ```

    <GitHubLink url="https://github.com/restatedev/ai-examples/blob/main/python-restate-only/tour-of-agents/app/human_approval_agent_with_timeout.py" />

    <Accordion title="Try out timeouts" icon="laptop">
      Start the timeout agent (stop any previously running agent first):

      ```bash theme={null}
      uv run app/human_approval_agent_with_timeout.py
      ```

      Register the services with Restate:

      ```bash theme={null}
      restate deployments register http://localhost:9080 --force --yes # dev only: overrides previous registrations
      ```

      Send a request to the service:

      ```bash theme={null}
      curl localhost:8080/HumanClaimApprovalWithTimeoutsAgent/run/send \
      --json '{"message": "Process my hospital bill of 3000USD for a broken leg."}'
      ```

      Restart the service and check in the UI how the process will block for the remaining time without starting over.

      You can also lower the timeout to a few seconds to see how the timeout path is taken.
    </Accordion>
  </GlobalTab>
</GlobalTabs>
