<@ULVA73B9P> my current setup to make prefect depl...
# ask-marvin
s
@Marvin my current setup to make prefect deployment as tool to my ai agent where n8n just used for wait/approval layer and FastMCP as python runtime to get run deployment results back. How you will plan my tools to get approvals + results
m
thought for 155.5 seconds
I’d plan it as a 3-layer contract: 1. FastMCP = tool facade for the agent 2. Prefect deployment = actual long-running work + state machine 3. n8n = approval / wait / callback router If you want, I can also turn this into a concrete reference implementation, but here’s the architecture I’d recommend first. --- ## Recommended responsibility split ### 1) FastMCP FastMCP should expose small, predictable tools to the agent, for example: -
start_deployment_tool
-
check_deployment_run
-
approve_deployment_run
-
reject_deployment_run
Do not make one giant tool that does trigger + wait + approve + fetch result unless all jobs are short-lived. FastMCP’s job: - validate tool inputs - create a
request_id
/ correlation ID - trigger the Prefect deployment - return a normalized response to the agent - optionally poll briefly for quick jobs - hand off long waits to async flow via n8n --- ### 2) Prefect Prefect should own: - execution - retries - task orchestration - pause/resume for approval - final state/result production This is the system of record for: -
flow_run_id
- current status - approval pending state - final completion / failure For approval steps, use Prefect’s pause/resume flow behavior rather than building your own fake waiting layer inside n8n. --- ### 3) n8n n8n should own: - human approval UX - waiting for external user action - routing approval/rejection back - optional notification fanout n8n should not be the source of truth for deployment state. It should be the approval broker. --- ## Best overall pattern ## Pattern A: Async-first tool design This is the best fit for agent tools. ### Tool 1: Start run Agent calls FastMCP tool:
Copy code
text
run_data_quality_check(
  dataset="s3://...",
  require_approval=true
)
FastMCP: - creates
request_id
- calls Prefect deployment - passes: - business parameters -
request_id
-
callback metadata
-
approval_required=true
Prefect starts run and returns
flow_run_id
. FastMCP returns immediately:
Copy code
json
{
  "status": "started",
  "flow_run_id": "....",
  "request_id": "req_123",
  "approval_expected": true
}
This is better than blocking the agent for long-running workflows. --- ### Tool 2: Get run status Agent or your runtime calls:
Copy code
text
get_run_status(flow_run_id="...")
Returns normalized status:
Copy code
json
{
  "flow_run_id": "...",
  "status": "PAUSED",
  "awaiting_approval": true,
  "message": "Waiting for manager approval"
}
or
Copy code
json
{
  "flow_run_id": "...",
  "status": "COMPLETED",
  "result": {...}
}
--- ### Tool 3: Approve run If approval comes from agent-side workflow, expose:
Copy code
text
approve_run(flow_run_id="...", approved=true, notes="looks good")
But if n8n handles approval, then n8n can call Prefect directly to resume the paused flow. --- ## Approval flow I would design ## Option 1: Prefect pauses, n8n resumes This is the cleanest design. ### In the Prefect flow The flow does: 1. pre-checks / preparation 2. if approval required: - call
pause_flow_run(...)
3. wait for approval payload 4. continue execution 5. produce final result Conceptually:
Copy code
python
from prefect import flow
from prefect.flow_runs import pause_flow_run
from prefect.input import RunInput

class ApprovalInput(RunInput):
    approved: bool
    approver: str
    notes: str = ""

@flow
def my_tool_flow(payload: dict, approval_required: bool = False, request_id: str = ""):
    preview = {"request_id": request_id, "summary": "planned action summary"}

    if approval_required:
        approval = pause_flow_run(wait_for_input=ApprovalInput)
        if not approval.approved:
            return {
                "request_id": request_id,
                "status": "rejected",
                "notes": approval.notes,
            }

    result = do_actual_work(payload)

    return {
        "request_id": request_id,
        "status": "completed",
        "result": result,
    }
### In n8n
When Prefect run enters paused state: - n8n receives webhook/event - n8n sends approval request to Slack/email/UI - human approves/rejects - n8n calls Prefect resume API with approval payload This keeps approval state inside the flow itself, which is much easier to reason about. --- ## Result return pattern You said: FastMCP as python runtime to get run deployment results back. I’d support two result modes: ### Mode 1: Synchronous short wait For short jobs, FastMCP waits up to, say, 30–60 seconds. If completed quickly: - return final result directly If not completed: - return
started
+
flow_run_id
This hybrid pattern is ideal for agent tools. --- ### Mode 2: Asynchronous callback For long jobs, do not keep FastMCP blocked forever. Instead: - Prefect completes - Prefect automation or polling layer notifies n8n - n8n stores/sends result back where your agent can fetch it - FastMCP exposes
get_run_status
or
get_run_result
--- ## The tool contract I’d define I would define normalized tool responses so the agent always sees a stable schema. ###
start_deployment_tool
Input:
Copy code
json
{
  "deployment_name": "team/flow-deployment",
  "parameters": {...},
  "require_approval": true
}
Output:
Copy code
json
{
  "ok": true,
  "request_id": "req_123",
  "flow_run_id": "fr_abc",
  "status": "started",
  "awaiting_approval": false,
  "final": false
}
Possible statuses: -
started
-
running
-
paused_for_approval
-
completed
-
failed
-
rejected
--- ###
get_deployment_result
Output:
Copy code
json
{
  "ok": true,
  "flow_run_id": "fr_abc",
  "status": "completed",
  "final": true,
  "result": {...},
  "error": null
}
If paused:
Copy code
json
{
  "ok": true,
  "flow_run_id": "fr_abc",
  "status": "paused_for_approval",
  "final": false,
  "approval_context": {
    "message": "Waiting for approval from finance"
  }
}
--- ## Important design choices ## 1) Use correlation IDs everywhere Every request should have: -
request_id
-
flow_run_id
- optional
conversation_id
/
agent_run_id
Pass
request_id
into: - Prefect flow parameters - Prefect tags - logs - n8n payloads - FastMCP responses That gives you traceability across all 3 systems. --- ## 2) Always use idempotency When FastMCP creates a flow run, use an
idempotency_key
. That protects you when: - the agent retries - n8n retries - network timeout happens after run creation Use a deterministic key such as:
Copy code
text
agent_name:tool_name:request_id
--- ## 3) Separate “tool intent” from “workflow execution” Your agent tool should say: - what it wants done - whether approval is required - whether to wait for completion Prefect decides: - how the work executes - whether it pauses - what state it is in This separation makes the tool stable even if the flow internals evolve. --- ## 4) Keep approval payloads structured Don’t just send
"approved": true
. Use something like:
Copy code
json
{
  "approved": true,
  "approver": "<mailto:alice@example.com|alice@example.com>",
  "notes": "Reviewed and approved",
  "approved_at": "2026-07-13T10:30:00Z"
}
That becomes useful later for auditing. --- ## My preferred implementation plan ## Phase 1: Basic reliable pattern Build these first: ### FastMCP tools -
run_prefect_deployment
-
get_prefect_run_status
### Prefect flow behavior - accept
request_id
- accept
approval_required
- pause if approval is needed - return structured result ### n8n behavior - receive “approval needed” notification - human approves/rejects - call Prefect resume endpoint This is enough for a working MVP. --- ## Phase 2: Better async UX Add: - Prefect event/webhook notifications to n8n on: - run paused - run completed - run failed - n8n updates your external store / agent memory / ticket This avoids constant polling. --- ## Phase 3: Strong operational guarantees Add: - idempotency keys - retry-safe callback handling - result persistence outside run return for large outputs - timeout policies - cancellation handling ---
## Concrete workflow I’d recommend ### A. Agent asks for tool execution FastMCP receives tool call. ### B. FastMCP triggers Prefect deployment Using Prefect client / deployment run API. Pass: -
parameters
-
request_id
-
approval_required
-
idempotency_key
### C. Prefect starts work - if no approval needed: continue - if approval needed: pause flow run ### D. n8n handles approval - receives notification - waits for human input - resumes Prefect flow with approval payload ### E. Prefect finishes - returns final structured result - optionally emits completion callback/webhook ### F. FastMCP or agent fetches result Via: - short synchronous wait, or - later
get_run_status
/
get_run_result
--- ## What I would not do I would avoid these designs: ### 1) Letting n8n be the real state machine If n8n is deciding all transitions, retries, and execution state, it becomes harder to debug than letting Prefect orchestrate. ### 2) Blocking FastMCP forever Long waits are brittle for tool calls. ### 3) Returning unstructured raw flow state to the agent Map Prefect states to agent-friendly statuses. ### 4) Making approval live outside the flow with no resume point If approval is external but the flow doesn’t actually pause, you’ll end up stitching state manually. --- ## Suggested status mapping Map Prefect states to a simpler tool schema: -
Scheduled
/
Pending
/
Running
->
running
-
Paused
->
paused_for_approval
-
Completed
->
completed
-
Failed
->
failed
-
Cancelled
->
cancelled
That keeps the agent logic simple. --- ## Minimal architecture summary Best plan: - FastMCP starts deployments and returns
flow_run_id
- Prefect owns execution and approval pause/resume - n8n owns human approval interaction and resumes the flow - FastMCP fetches final result via status/result tool --- ## If you want my exact recommendation in one sentence Use Prefect as the execution + approval state machine, n8n as the human approval broker, and FastMCP as a thin tool API that starts runs and fetches results. --- ## Useful starting points - Prefect deployments concepts: Deployments - Prefect flows: Flows - Prefect Python SDK / client concepts: Python API reference - Prefect automations/events: Automations - Prefect Cloud UI: Prefect Cloud If you want, I can next give you one of these: 1. a reference architecture diagram 2. a FastMCP tool implementation skeleton 3. a Prefect flow example with pause/resume approval 4. an n8n workflow design for approval + callback
s
@Marvin earlier I planned n8n pauses and FastMCP returns and on approval only prefect will be called for direct action. In your suggested way if I resume prefect from UX will this start from where it left off since my worker is docker-worker and it may takes weeks for approval so I don't want container running that long. How paise and resume works in prefect
m
thought for 165.5 seconds
Yes — your concern is exactly the right one. For weeks-long approvals, you generally do not want a Docker worker container sitting there alive. And the key distinction in Prefect is: -
pause_flow_run()
keeps the run process alive -
suspend_flow_run()
stops the process/container and resumes later in a new one So for your case, suspend is usually the right primitive, not pause. --- ## Short answer If you resume a flow that used `pause_flow_run()`: - it resumes from where it left off - in the same running process/container - so the container may need to remain alive the whole time If you resume a flow that used `suspend_flow_run()`: - the original process/container exits - later, Prefect starts a new worker/container - the flow resumes via orchestration, not by keeping Python memory alive So for a Docker worker + approvals that can take weeks, I would design around
suspend_flow_run()
. --- ## What this means practically ##
pause_flow_run()
Think of this as:
“stop here and wait, but keep this execution environment around”
That means: - same Python process - same container - local variables still exist - in-memory objects may still be there - resume is quick But: - bad fit for week-long human approval - fragile if worker/container restarts - wastes resources --- ##
suspend_flow_run()
Think of this as:
“checkpoint orchestration state, exit cleanly, and come back later”
That means: - container exits - worker is free - later on approval, Prefect schedules/resumes the run - a new container starts - execution continues under Prefect orchestration But: - do not rely on local in-memory variables after suspension - you must persist what you need --- ## Important correction to my earlier suggestion For your setup, I would refine the recommendation to: - n8n handles UX + approval collection - Prefect flow uses
suspend_flow_run()
for long waits
- on approval, n8n calls Prefect resume - Docker worker starts a fresh container to continue work So your earlier instinct was actually good — you just want Prefect to own the suspended state rather than n8n faking execution state. --- ## How resume works with suspend With `suspend_flow_run()`: 1. Flow runs up to the suspend point 2. Prefect marks the run suspended 3. The container exits 4. Days/weeks later, approval happens 5. Someone/something calls resume 6. Prefect orchestrates the flow to continue 7. Docker worker launches a new container for that run So no, the old container does not need to stay running. --- ## The big caveat: memory is gone This is the most important design rule. After `suspend_flow_run()`: - local Python variables are not something you should depend on - open DB connections are gone - temporary files in the container are gone - anything only in memory is gone So before suspending, persist what you’ll need later: - parameters - intermediate artifacts - generated plan - approval payload context - object references / IDs - task results --- ## Best practice for long approvals ## Use
suspend_flow_run()
if approval can take days/weeks That is the safest pattern for Docker/Kubernetes/ephemeral workers. ### Good flow shape
Copy code
text
prepare work
persist plan/context/artifacts
suspend for approval
resume later
load persisted context
execute approved action
return result
--- ## Example mental model ### Bad pattern
Copy code
python
@flow
def my_flow():
    plan = build_big_plan()
    suspend_flow_run()
    execute_plan(plan)
Why bad: -
plan
is just a local variable - after resume in a new container, you should not assume it’s still there --- ### Better pattern
Copy code
python
@flow
def my_flow(request_id: str):
    plan = build_big_plan()
    save_plan_somewhere(request_id, plan)
    suspend_flow_run()

    approved_plan = load_plan_somewhere(request_id)
    execute_plan(approved_plan)
That is the right model for long-lived approvals. ---
## For Docker workers: result persistence matters If you use
suspend_flow_run()
, you should also think about result persistence. Especially with ephemeral infrastructure, you generally want: - persisted task results - remote storage for results/artifacts if needed Examples: - S3 - GCS - Azure Blob - database - object store Because a new container won’t have access to the old container’s local filesystem. --- ## My recommendation for your architecture Given your setup: - AI agent calls FastMCP - FastMCP triggers Prefect deployment - Prefect flow prepares an action plan and suspends - n8n sends approval UI to user - on approval, n8n resumes the Prefect flow run - Docker worker launches a new container - flow loads persisted context and performs the direct action - FastMCP can fetch final result later This gives you: - no long-running approval containers - clear audit trail - reliable execution handoff - proper orchestration ownership in Prefect --- ## So does it “start from where it left off”? ### With
pause_flow_run()
Yes, basically from where it left off in the same process. ### With
suspend_flow_run()
Logically yes from workflow perspective, but not by preserving Python memory/process. It resumes under orchestration, with a new container/process. So architecturally, treat it as: - “continue the workflow” - not “continue the same Python stack frame with all local memory intact” That distinction matters a lot. --- ## My concrete advice for you For approvals that may take weeks: - Do not use
pause_flow_run()
- Use
suspend_flow_run()
- persist all needed context before suspend - assume resumed execution is on a new container - let n8n call Prefect resume when user approves --- ## Suggested split for your exact case ### FastMCP tools -
start_action_request
-
get_action_status
-
get_action_result
### Prefect deployment - validate request - generate plan - persist plan/context -
suspend_flow_run()
- on resume, reload plan/context - perform direct action - persist final result ### n8n - approval UI - reminder/escalation - resume/reject callback into Prefect That’s probably the cleanest version of your original idea. --- ## Useful docs - Flows: Prefect flows - Deployments: Deployments - Python API reference: Python API - Prefect Cloud: Prefect Cloud If you want, next I can give you: 1. a sample Prefect flow using
suspend_flow_run()
for approval
2. a FastMCP tool contract for start/status/result 3. an n8n-to-Prefect approval/resume design
s
@Marvin supported methods to resume the suspended docker run . Like before suspend can I get the unique url/is to resume from ui form
m
thought for 129.6 seconds
Yes — you can resume a suspended flow run from the Prefect UI, and yes, you can capture/share a unique flow run URL or ID before suspension. ## Supported ways to resume a suspended run ### 1) Prefect UI If the flow run is suspended/paused, the run page in the UI will show a Resume action. If your flow is waiting for input, the UI can render a form for that input. So the approval path can be: - create flow run - get
flow_run_id
and UI URL - send URL to user/n8n/Slack/email - user opens run page - clicks Resume - fills approval input if required This works well if you want humans to approve directly in Prefect UI. --- ### 2) Python SDK / API Programmatically resume the flow run with input:
Copy code
python
from prefect.client.orchestration import get_client
from uuid import UUID

async with get_client() as client:
    await client.resume_flow_run(
        flow_run_id=UUID("your-flow-run-id"),
        run_input={
            "approved": True,
            "notes": "Approved from external UX"
        }
    )
The callable signature is:
Copy code
text
resume_flow_run(flow_run_id: UUID, run_input: dict[str, Any] | None = None)
This is the best fit if: - n8n collects approval - then n8n or FastMCP resumes the run programmatically --- ### 3) Direct REST API Prefect exposes a resume endpoint:
Copy code
text
POST /flow_runs/{id}/resume
So if n8n wants to call Prefect directly, this is a clean pattern too. --- ## Can you get the unique URL/ID before suspend? Yes. A flow run gets its ID when it is created, so you can capture: -
flow_run_id
- flow run UI URL before the flow reaches
suspend_flow_run()
. That means your approval message can include a link immediately. --- ## Practical pattern for your case ### When FastMCP starts the deployment Return something like:
Copy code
json
{
  "flow_run_id": "2b5c...",
  "request_id": "req_123",
  "ui_url": "<https://app.prefect.cloud/account/.../workspace/.../runs/flow-run/2b5c...>"
}
Then n8n can: - store that URL - send it in Slack/email/UI - let the approver click into Prefect directly --- ## Can the UI show a form for approval? Yes, if you suspend/pause with expected input. Using a
RunInput
schema lets Prefect UI render an input form on resume. Conceptually:
Copy code
python
from prefect import flow
from prefect.flow_runs import suspend_flow_run
from prefect.input import RunInput

class ApprovalInput(RunInput):
    approved: bool
    notes: str = ""

@flow
async def my_flow():
    approval = await suspend_flow_run(wait_for_input=ApprovalInput)

    if not approval.approved:
        return {"status": "rejected", "notes": approval.notes}

    return {"status": "approved"}
Then in the Prefect UI, the user can resume with structured input. --- ## Which approach should you choose? ## Option A: Approve in Prefect UI Use this if: - you want minimal custom UX - approvers can access Prefect - Prefect UI is acceptable as the approval interface Flow: - FastMCP starts run - capture
flow_run_id
+ UI URL - send URL to approver - approver resumes from Prefect UI form --- ## Option B: Approve in your own UX / n8n Use this if: - approval must happen in your own app - users should not access Prefect directly - you want a custom approval experience Flow: - FastMCP starts run - flow suspends - n8n stores
flow_run_id
- approval happens in your UX - n8n calls Prefect
resume_flow_run(...)
This is probably closer to your original design. --- ## For Docker worker + long wait, my preferred pattern I’d use: -
suspend_flow_run(wait_for_input=ApprovalInput)
inside the flow - keep
flow_run_id
- optionally generate/share the Prefect UI link - either: - let user resume in Prefect UI, or - have n8n resume via API This gives you both: - no long-running container - optional built-in approval form in Prefect UI --- ## Caveats ### 1) Self-hosted vs Cloud URL In Prefect Cloud, the UI URL points to
app.prefect.cloud
.
In self-hosted Prefect Server, the URL depends on your configured UI base URL. If URL generation is important there, make sure your Prefect UI/API URLs are configured correctly. ### 2) Resume only works from paused/suspended state You can’t resume a run that is already completed/failed/cancelled. ### 3) Don’t rely on local memory after suspend Even if the UI can resume it, resumed execution is on a fresh container/process. --- ## Best answer to your exact question
before suspend can I get the unique url/id to resume from ui form
Yes — you can get and store the
flow_run_id
as soon as the run is created, and you can share the run’s UI URL for human approval in Prefect UI. If the suspended flow expects input, the UI can present a resume form. --- ## Useful docs - Flows: Flows - Deployments: Deployments - Python API reference: Python API reference - Prefect Cloud UI: Prefect Cloud If you want, I can next give you: 1. a code snippet to generate/store the Prefect UI run URL 2. a full suspend/resume approval flow example 3. an n8n HTTP call example for resuming the flow run
s
@Marvin how the resume url looks if my prefect is onpremise and worker type is docker and I have to just resume without providing input. And also send the confirmation mail once action doneafter resume
m
thought for 220.8 seconds
For on-prem/self-hosted Prefect, the flow run UI URL generally looks like:
Copy code
text
{PREFECT_UI_URL}/flow-runs/flow-run/{flow_run_id}
Example:
Copy code
text
<https://prefect.mycompany.internal/flow-runs/flow-run/2b5c9d8e-1234-4567-89ab-cdef01234567>
If your UI is local/dev:
Copy code
text
<http://localhost:4200/flow-runs/flow-run/2b5c9d8e-1234-4567-89ab-cdef01234567>
Docker worker type does not change this URL. The URL depends only on: - your UI base URL - the
flow_run_id
--- ## Resume without input If your flow is suspended and does not require input, the user just opens that run page and clicks Resume. Programmatically, that is:
Copy code
python
from prefect.client.orchestration import get_client
from uuid import UUID

async with get_client() as client:
    await client.resume_flow_run(
        flow_run_id=UUID("2b5c9d8e-1234-4567-89ab-cdef01234567")
    )
No
run_input
needed. --- ## How to get the URL inside the flow You can access the current run UI URL from runtime context:
Copy code
python
from prefect import flow
from prefect.runtime.flow_run import ui_url

@flow
def my_flow():
    print(ui_url)
So before suspending, you can: - log it - store it - email it - send it to n8n --- ## Recommended pattern for your use case ### Step 1: Start run FastMCP triggers the deployment and gets
flow_run_id
. ### Step 2: Prefect flow sends approval email Before
suspend_flow_run()
, send an email containing: - action summary - run URL - maybe request ID ### Step 3: User clicks run URL They land on the Prefect run page and click Resume. ### Step 4: Flow continues After resume, the Docker worker starts a new container and continues execution. ### Step 5: Send confirmation email At the end of the resumed flow, send another email saying the action completed. --- ## Example flow shape
Copy code
python
from prefect import flow
from prefect.flow_runs import suspend_flow_run
from prefect.runtime.flow_run import ui_url

@flow
async def approval_action_flow():
    # prepare action
    action_summary = "Delete 25 stale records"

    # send approval email here with ui_url
    send_email(
        subject="Approval needed",
        body=f"Please review and resume this run:\n{ui_url}\n\nAction: {action_summary}"
    )

    # suspend until user resumes in UI
    await suspend_flow_run()

    # after resume, do the action
    do_action()

    # send confirmation email after action
    send_email(
        subject="Action completed",
        body=f"Action completed successfully.\nRun: {ui_url}"
    )
--- ## Sending the confirmation email For self-hosted Prefect, simplest is to send the email directly from the flow code after resume. That is usually easier than building server-side automations. You can use: - SMTP library - SendGrid - any internal mail API - Prefect notification blocks if that matches your setup --- ## Simple SMTP example
Copy code
python
import smtplib
from email.mime.text import MIMEText

def send_email(subject: str, body: str):
    msg = MIMEText(body)
    msg["Subject"] = subject
    msg["From"] = "<mailto:noreply@mycompany.com|noreply@mycompany.com>"
    msg["To"] = "<mailto:approver@mycompany.com|approver@mycompany.com>"

    with smtplib.SMTP("<http://smtp.mycompany.com|smtp.mycompany.com>", 587) as server:
        server.starttls()
        server.login("smtp-user", "smtp-password")
        server.send_message(msg)
Then in flow:
Copy code
python
from prefect import flow
from prefect.flow_runs import suspend_flow_run
from prefect.runtime.flow_run import ui_url

@flow
async def approval_action_flow():
    send_email(
        subject="Approval required",
        body=f"Resume this flow run from the UI:\n{ui_url}"
    )

    await suspend_flow_run()

    # action after approval
    # ... do work ...

    send_email(
        subject="Action completed",
        body=f"The action has completed successfully.\nRun URL: {ui_url}"
    )
--- ## Important caveat with
ui_url
For self-hosted deployments, make sure your environment is configured so Prefect knows the UI base URL correctly.
Typically: -
PREFECT_UI_URL
-
PREFECT_API_URL
Otherwise generated URLs may be missing or point somewhere wrong. --- ## If you want a one-click email approval Prefect UI link is great for “open and resume manually”. But if you want an email button like: - Approve - Reject then that usually means: - email links go to your app/n8n endpoint - your app/n8n calls Prefect
resume_flow_run(...)
That gives a smoother approval UX than asking users to open Prefect UI. --- ## Direct answer ### Resume URL format on on-prem:
Copy code
text
<https://your-prefect-ui/flow-runs/flow-run/{flow_run_id}>
### Does Docker worker affect it? No. ### Resume without input? User opens the run page and clicks Resume, or call:
Copy code
python
resume_flow_run(flow_run_id=...)
### Send confirmation mail after resumed action? Best simple approach: send the email directly from the flow code after the resumed action completes. --- ## Relevant docs - Flows: Flows - Python API reference: Python API reference - Prefect Cloud / UI reference point: Prefect UI If you want, I can give you: 1. a full working Prefect flow example with suspend + email + resume URL 2. a FastMCP starter tool that returns
flow_run_id
and
ui_url
3. an n8n approval email pattern with approve link calling Prefect resume API
s
@Marvin if I resume suspended flow via api on docker worker. Does it start from start again or from wait point
m
thought for 7.3 seconds
It resumes from the wait point / suspend point, not as a brand new logical run. But with an important nuance: - workflow/orchestration-wise: it continues from the suspend point - infrastructure-wise: it runs in a new Docker container / new process So the right mental model is: - not “start the whole flow over from scratch” - not “same Python process continues in memory” - yes “Prefect resumes the flow after the suspend point” --- ## For your exact case If your flow does:
Copy code
python
@flow
async def my_flow():
    step1()
    await suspend_flow_run()
    step2()
and you resume via API on a Docker worker: -
step1()
is not supposed to rerun as normal flow progress - the resumed run continues to
step2()
- but it does so in a new container So from your business logic point of view, it resumes from where it waited. --- ## Important practical caveat Even though it resumes from the suspend point, you should still design as if: - local variables are gone - memory is gone - temp files in container are gone - open connections are gone So this is good:
Copy code
python
@flow
async def my_flow(request_id: str):
    plan_id = save_plan_to_db(request_id)
    await suspend_flow_run()
    plan = load_plan_from_db(plan_id)
    execute_plan(plan)
And this is bad:
Copy code
python
@flow
async def my_flow():
    plan = build_plan()
    await suspend_flow_run()
    execute_plan(plan)
because after resume on a new Docker container,
plan
should not be something you rely on being in memory. --- ## Simple answer ###
pause_flow_run()
- resumes in same process - continues exactly there - container stays alive ###
suspend_flow_run()
- resumes after that wait point - but in a new process/container - so persist anything needed before suspend --- ## For API resume specifically If you call:
Copy code
python
await client.resume_flow_run(flow_run_id=...)
for a flow that used `suspend_flow_run()`: - Prefect marks it for resumption - Docker worker picks it up - new container starts - execution continues from after the suspend point --- ## Bottom line It resumes from the wait point, not from the start — but on a fresh Docker container, so don’t depend on in-memory state. If you want, I can draw the exact execution timeline for: - trigger - pre-suspend steps - suspend - API resume - post-resume steps on Docker worker