Menu

How to Build Agentic Workflows That Can Pause and Resume

How to Build Agentic Workflows That Can Pause and Resume - 9
Paul Francis

Table of content

    Summary

    Key takeaways

    • Agentic workflows must preserve execution state across pauses, approvals, process restarts, and external service failures. A successful model response alone does not guarantee that a business action has been completed.
    • Uvik Software’s vendor onboarding example separates five responsibilities: application-controlled intake, agent-based evidence extraction, deterministic routing, authorized human approval, and external updates through a durable outbox worker.
    • Every workflow run should have a stable identifier, persistent state, version number, packet digest, approval deadline, and assigned reviewer so interrupted processes can resume without losing business context.
    • Frameworks such as LangGraph and Temporal support durable execution, but the application remains responsible for authorization, approval validity, business rules, and preventing duplicate external actions.
    • Human approval must be tied to the exact version of the proposed action and its supporting evidence. Changed documents, expired deadlines, unauthorized reviewers, and repeated events require explicit validation.
    • A transactional outbox records approved state changes and pending external actions within one database transaction, allowing delivery workers to recover after crashes without losing committed work.
    • Idempotency guarantees depend on the receiving system. In Uvik Software’s nine simulated receiver-contract scenarios, a 25-hour retry created a duplicate record when the destination had already discarded its 24-hour request key.
    • Stable unique external identifiers provided stronger duplicate protection in the local tests: retries at one, 23, and 25 hours returned the existing record rather than creating another one.
    • Uvik Software validated eight approval-transition scenarios and five process-recovery scenarios using Python and SQLite fixtures. These tests demonstrate specific local behaviors, not guaranteed reliability across production orchestrators or third-party APIs.
    • Production readiness should be measured through correct completed business actions, approval waiting time, expired requests, resume completion, delivery delays, duplicate effects, and recoverability rather than agent activity or model output alone.

    When this applies

    This framework applies when an organization is building AI-powered workflows that must pause for human decisions, wait for external events, survive application restarts, and safely resume business operations. It is especially relevant for procurement approvals, vendor onboarding, payment processing, customer account changes, enterprise automation, and other workflows involving asynchronous decisions or state-changing API calls. The approach is useful for Python engineers, AI architects, platform teams, and CTOs implementing durable workflows with LangGraph, Temporal, database state machines, message queues, and transactional outbox patterns. It becomes particularly important when an external system may successfully execute an operation but fail to return a confirmation, creating uncertainty about whether retrying the action would cause duplicate business effects.

    When this does not apply

    This architecture may be unnecessarily complex for simple requests that can be completed through deterministic application logic, a single API call, or a short workflow without pauses, external side effects, or recovery requirements. It should not be introduced solely because an AI framework supports agents or durable execution. The article’s vendor onboarding example is a constructed scenario, and its local Python and SQLite fixtures do not establish production reliability for every database, queue, orchestrator, or third-party service. The suggested recovery mechanisms also cannot guarantee exactly-once external effects when the receiving system lacks reliable deduplication, authoritative record lookup, or reconciliation capabilities. Organizations must validate the actual integration contracts and failure behavior before enabling automated retries for consequential operations.

    Checklist

    1. Map the existing manual workflow and measure correct outcomes, human effort, completion time, and operational errors before introducing AI.
    2. Identify which steps require language-model interpretation and which can remain deterministic application rules.
    3. Assign explicit ownership for intake, evidence extraction, routing, approval, external execution, and recovery.
    4. Create a persistent workflow record with a stable run ID, current state, version number, source references, and policy version.
    5. Store an immutable representation of the proposed action and its supporting evidence, including a digest that identifies the exact approved packet.
    6. Define approval deadlines, authorized reviewers, tenant boundaries, and the conditions that require a new approval request.
    7. Choose the authoritative system for business decisions and implement durable transitions through a database state machine, LangGraph, Temporal, or an equivalent mechanism.
    8. Validate every approval or denial against authenticated identity, current permissions, packet version, digest, workflow state, and the service-controlled deadline.
    9. Use stable event identifiers, payload fingerprints, and conditional state updates to prevent conflicting or repeated decisions from creating additional actions.
    10. Commit the approved state and corresponding outbox actions atomically so external updates remain recoverable after process failures.
    11. Configure delivery workers with durable claims, bounded retries, lease management, stable action keys, and protection against stale acknowledgments.
    12. Verify the receiving API’s idempotency scope, key retention period, changed-payload behavior, regional restrictions, and ability to return authoritative receipts.
    13. Define reconciliation procedures for ambiguous outcomes, expired deduplication keys, missing confirmations, and conflicting destination records.
    14. Test concurrent approvals, stale packets, expired decisions, cross-tenant access, process restarts, lost acknowledgments, duplicate delivery, and workflow-version compatibility.
    15. Monitor approval age, expiry rate, resume completion, pending outbox age, duplicate business effects, and correct completed actions before expanding production traffic.

    Common pitfalls

    • Using an autonomous agent for every workflow step instead of retaining deterministic rules for routing, authorization, deadlines, and state transitions.
    • Keeping workflow state only in application memory or model context, causing approvals and pending actions to disappear after a process restart.
    • Assuming that LangGraph checkpointing or Temporal durability automatically provides business authorization, exactly-once execution, or protection against duplicate external writes.
    • Accepting human approval without validating the reviewer’s current permissions, tenant access, packet version, supporting evidence, and approval deadline.
    • Updating business state and sending external API requests as unrelated operations without a durable record connecting the approved decision to its pending effects.
    • Assuming that a transactional outbox alone prevents duplicate business records, even when the receiving service does not support sufficiently durable idempotency.
    • Retrying an external operation after a timeout without checking whether the destination already committed the original request or whether its deduplication window has expired.
    • Using a client-side lookup followed by a create request as a substitute for an atomic uniqueness guarantee, leaving a race condition between concurrent workers.
    • Combining approval status and external delivery status into one field, making it difficult to distinguish authorized actions from completed, pending, or uncertain effects.
    • Testing only successful approvals and normal execution paths while ignoring crashes, delayed events, expired idempotency keys, concurrent decisions, deployment changes, and reconciliation failures.

    A vendor onboarding workflow with approval deadlines and safe retries

    Suppose procurement approves a vendor on Friday. The register creates the vendor record, and the response disappears. On Saturday the workflow wakes up with the same approval and an empty receipt field. The next decision depends on how long the register remembers the original request.

    In Uvik Software’s local test, a receiving service with a 24-hour request key returned the original record on retries at one hour and 23 hours. At 25 hours, the same retry created a second record. The comparison starts from the same approved packet in each run. Only the receiving contract and retry time change.

    The ambiguity occurs in real systems. On 6 November 2026, Printavo reported a payment processor disruption in which transactions that appeared to fail might already have charged the customer. It warned against immediate retries and said it would reconcile affected transactions. The incident notice documents the risk and response. This vendor-register example applies the same recovery problem to a different business operation. 13

    Quick answer

    An agentic workflow can pause and resume when the application stores the run, packet version and next allowed action. In Uvik Software’s vendor example, the agent extracts evidence and rules choose the route. A reviewer approves the exact packet. The application commits the decision and queues any effects. Recover an ambiguous external call with the same action identity while the destination guarantees deduplication. Reconcile against the destination when the guarantee ends.

    Disclosure. Uvik Software provides agentic AI engineering services. The vendor packet is a constructed example. Results come from local Python and SQLite transition, recovery and receiver-contract fixtures. The provider documents describe their own APIs.

    Establish the simplest viable workflow

    Start with the current manual process. A vendor coordinator reads the packet, tracks missing evidence, obtains approval and records the vendor. Measure elapsed time to a correct register entry, coordinator touch time and rework across the same eligible packet types. When a structured form can supply every decision input, automate intake and routing with rules and retain the existing approval policy. That is the comparison for adding extraction.

    Consider an agent when meaningful inputs arrive as varied PDFs or emails. Have it point a reviewer to the source for each field. An unfamiliar legal entity, conflicting account identifiers or missing evidence can route to the existing manual queue. For example, a well-formed extracted tax identifier can still belong to a different entity in the same document. Use a separate rules check to match the identifier and legal name against your authoritative records.

    Approval itself has a cost. In a July 2026 Wharton and GBK Collective survey of large US organizations, 16% of manager-level or more senior respondents who reported human sign-off for every agent action met the study’s criteria for workflow transformation in their department (n = 468). The share was 25% for advice-only systems (n = 348) and 31% for agents operating under real-time monitoring and guardrails (n = 68). The study measured self-reported practices and defined transformation through changes to core processes and agent adoption. Different teams can choose different controls because they handle different work. The association gives teams a reason to measure reviewer workload and keep approvals where the consequence warrants one. 14

    For vendor intake, a rule can ask for a missing document automatically, while the approved record creation can still need a human decision. Set the policy for each action according to reversibility, exposure and available evidence. Track errors, audit exceptions and delays against the manual and rules-only baselines.

    Give each step a clear owner

    Use the agent for work that needs interpretation. In this example it extracts fields from documents, links each field to a file and page, and summarizes missing evidence. The application validates the response against a typed schema and keeps a record of the input files and extraction version.

    Give routing to application code. Procurement can maintain explicit rules for missing documents, access level and required reviewers. The packet can contain arbitrary text, including instructions addressed to the agent. Treat that text and the extracted output as untrusted input. Restrict the agent to the documents it can read and keep vendor register writes behind a separate application permission check. 1

    Step Owner Output
    Intake Application Run ID and an immutable reference to the uploaded packet
    Extract and summarize Agent Typed fields, evidence locations and missing items
    Route Application rules Request missing evidence or assign a procurement reviewer
    Decide Authorized reviewer Approval or denial bound to the exact packet
    Update the register Outbox worker Receipt for an external action keyed to the approved packet

    This split lets the team update a routing rule and test the change without changing how the model reads the packet. If the task is still at the design stage, use Uvik Software’s agentic design patterns guide to decide where the workflow needs model judgment and where ordinary code is enough.

    An agent extracts a supplier packet, application rules validate the draft, and the workflow pauses for procurement approval. Approval queues a single downstream action. Denial or expiry closes the request. Resume restores the saved case.

    Figure 1. Uvik Software vendor onboarding example. Approval and the queued update are recorded together. The external call is handled separately.

    Persist the run before asking for approval

    Store the run in durable storage and give it a stable identifier. At the approval step, save its current state, version, packet digest, deadline and reviewer assignment. A digest is a fingerprint of the exact materials being approved. A useful implementation derives it from a canonical record that identifies the attachment versions and approved action.

    Store the policy version used for routing so a reviewer can reconstruct why the request reached their queue. Send the reviewer a link to the authenticated approval screen and show the proposed update and its evidence there. Use the application’s current identity and access controls when the person opens the request and when they submit the decision. If the packet or proposed action changes, increment the run version, record the new digest and issue a new approval request.

    Use Uvik Software’s human approval guide to decide which actions need a reviewer and what evidence the reviewer should see.

    LangGraph provides a checkpointer and an interrupt that can suspend execution until input arrives. Resume using the same thread identifier and a Command carrying the response. When an interrupted node resumes, its code runs again from the beginning. Put repeatable reads or checks before the interrupt. Put writes in a separate step or guard them with an idempotency key. The workflow runtime handles pausing and restoring execution state. The application still owns the authorization and transition rules. 2 3

    Choose one place to accept the decision

    A workflow runtime and a business database often keep different records. The runtime remembers the execution point and the application database tracks who can approve a particular packet. Decide which component accepts the decision and commits the authoritative state. Give the other component a recoverable way to learn that the commit happened.

    Choice What the operator can recover What still needs application code
    Database state machine and durable queue Current packet, approval decision and pending actions Retry schedule, deadline processing and workflow transition code
    LangGraph with a persistent checkpointer Graph state for the same thread and interrupt Version binding, current approver permissions and a stable key for writes
    Temporal workflow Workflow history and message handlers that receive decisions Authorization at ingress and idempotent Activities for external calls

    In Temporal, use a Signal for asynchronous delivery of a decision or an Update when the caller needs an accepted or rejected result from the handler. Put external I/O in Activities and account for Activity retries when assigning action keys. If a run can wait across a deployment, use compatible changes or version running workflows explicitly. For example, preserve the meaning of the approval event expected by a run that started before a schema change. 4 5 6

    For the vendor packet, the application database owns the decision. An approved action can go to the register independently of the graph’s resumed run. If your next graph step needs the register’s receipt, have the worker record it and emit a completion event. The graph then waits for that event before advancing to the dependent step.

    Validate each decision before changing state

    Have the approval endpoint obtain the actor from authenticated identity. Load the run and verify the tenant, reviewer assignment and current permission for the proposed action. Compare the submitted version and digest with the saved values. Use the service clock to enforce the deadline. The browser’s timestamp belongs in the audit trail if useful, while the service clock decides whether the event arrived in time.

    A second click should return the stored result or an explicit duplicate status. Assign a stable event ID before submission, reuse it on transport retries, and retain a fingerprint of its contents. If the ID is reused with changed contents, return a conflict. Scope event IDs to the run and tenant when a delivery source only guarantees uniqueness within that scope.

    Event while waiting Required checks Transition
    Approve Authorized reviewer, matching version and digest, before deadline Approved and queue the specific update
    Deny Authorized reviewer, matching version and digest, before deadline Denied and record the reason
    Expire Trusted scheduler, matching version, deadline reached Expired and close the request
    Same event delivered again Same ID and fingerprint as an accepted event Return the recorded result or duplicate status
    Changed packet or late decision Current version, digest or deadline check fails Reject the event and retain the current state

    Use a conditional database update on the expected version and waiting state to guard the transition. When two workers receive decisions near the same time, only a transition that wins the state update can proceed to record its result. If an approval arrives after the deadline and the timer is delayed, the handler can reject the approval immediately. The timer still closes the pending request when it runs.

    Record the update and deliver it separately

    In one database transaction, record the approved state and insert the outbox row. Give the row a stable action key tied to the approved run version and the specific operation. An outbox is a table that stores work for a separate delivery process. A committed approval leaves a durable delivery record even if the worker restarts before making the external call. 7

    The worker claims the row, sends the same action key to the vendor register on every attempt, and records the response. If the vendor register accepts the write and the response is lost, a retry can reach it again. Use the destination’s documented idempotency mechanism or an application adapter that can safely reconcile the earlier write. If the destination offers neither and the result is ambiguous, route the item for reconciliation before another write. The outbox supports repeatable delivery. Duplicate prevention at the destination depends on the receiving interface.

    Keep approval and delivery status visible as separate fields. Procurement may have approved an onboarding packet even while a connector outage leaves the vendor register update queued. An operator needs to see both facts to decide whether to investigate the approval record or the connector.

    If the approval database and the graph runtime are separate, make the committed decision the source of truth. Persist a resume event alongside the decision and let its consumer retry with the same run and event IDs. This gives the graph a way to resume after a crash between the approval commit and delivery of the resume command.

    Design the delivery worker around ambiguous results

    A worker can die after claiming a row or after the destination commits the change. For a PostgreSQL-backed outbox, a short transaction can claim available rows with FOR UPDATE SKIP LOCKED and persist a lease, attempt identifier and next attempt time. Commit the claim before the network call. When acknowledging the result, check the current lease token so a late worker cannot mark another worker’s attempt complete. Use SKIP LOCKED for queue claims. PostgreSQL documents that skipped locks produce an incomplete view, so calculate business totals from the business records. 8

    An expired lease makes the row available to another worker. It also allows an overlap if the original request is still executing. Reuse the same action key across both attempts and compare the payload associated with that key. With a queue such as Amazon SQS Standard, the same message can also arrive again independently of your worker logic. Check the receiving system’s behavior as part of the design. 9

    Record the destination’s actual response and the transport result separately. A repeatable transport response can still report a service error. Stripe API v1, for example, can return a previously stored server error for the same key. Mark the row delivered after a matching confirmation, and retain the action key, destination ID and confirmation time. 10

    What the transition checks found

    Uvik Software ran eight transition checks against a small SQLite fixture. The checks exercised runs waiting on version 3 of a packet and replayed an already accepted approval. The test clock used a deadline of 100. An approval at time 90 was eligible and time 100 reached the deadline. The actor labels stand in for identities already authenticated by the caller.

    Scenario Recorded result Outbox rows
    Authorized approval at time 90 Approved, version 4 1
    Identical approval delivered again Duplicate, approved state retained 1
    Same event ID with changed contents Conflict, approved state retained 1
    Authorized denial before deadline Denied, version 4 0
    Vendor submits approval Rejected, waiting state retained 0
    Reviewer submits a stale packet digest Stale, waiting state retained 0
    Approval arrives at time 100 Rejected, waiting state retained 0
    Scheduler expires the request at time 100 Expired, version 4 0

    Uvik Software sequential transition fixture. Python 3.12.14 and SQLite 3.53.1. The outbox count measures stored rows for one run.

    Here is the transaction excerpt for an event that has already passed the state, actor and deadline checks. In the fixture it executes inside the same SQLite transaction as the checks and replay lookup.

    updated = db.execute(
        """UPDATE run SET state = ?, version = version + 1
           WHERE id = ? AND version = ? AND state = 'waiting'""",
        (next_state, row["id"], event.version),
    ).rowcount
    assert updated == 1
    db.execute("INSERT INTO event_log VALUES (?, ?)", (event.event_id, fingerprint))
    if next_state == "approved":
        action_key = f'{row["id"]}:{event.version}:sync'
        db.execute(
            "INSERT INTO outbox VALUES (?, ?, ?)",
            (action_key, row["id"], event.digest),
        )

    Treat zero updated rows as a stale decision in production and exit before queuing delivery. The checks exercise local state and outbox records. Before rollout, test concurrent decisions, a worker crash after commit, and an external timeout after the destination applies the change. Exercise restart behavior in your actual orchestrator and verify that a cross-tenant user cannot approve another organization’s request.

    Reproduce the transition checks

    Save the following Python and SQLite fixture as check_workflow.py and run python3 check_workflow.py. It creates a new in-memory database for the examples, prints each outcome and checks the stored state, version and outbox count.

    import sqlite3
    from dataclasses import dataclass
    from hashlib import sha256
    import json
    
    
    @dataclass(frozen=True)
    class Event:
        event_id: str
        action: str
        actor: str
        version: int
        digest: str
    
    
    def open_fixture():
        db = sqlite3.connect(":memory:")
        db.row_factory = sqlite3.Row
        db.executescript("""
            CREATE TABLE run (
                id TEXT PRIMARY KEY, state TEXT NOT NULL,
                version INTEGER NOT NULL, digest TEXT NOT NULL,
                deadline INTEGER NOT NULL
            );
            CREATE TABLE event_log (
                id TEXT PRIMARY KEY, fingerprint TEXT NOT NULL
            );
            CREATE TABLE outbox (
                action_key TEXT PRIMARY KEY, run_id TEXT NOT NULL,
                digest TEXT NOT NULL
            );
            INSERT INTO run VALUES ('vendor-1', 'waiting', 3, 'packet-v3', 100);
        """)
        return db
    
    
    def apply(db, event, now):
        fingerprint = sha256(json.dumps([
            event.action, event.actor, event.version, event.digest
        ]).encode()).hexdigest()
        with db:
            db.execute("BEGIN IMMEDIATE")
            seen = db.execute(
                "SELECT fingerprint FROM event_log WHERE id = ?", (event.event_id,)
            ).fetchone()
            if seen:
                return "duplicate" if seen["fingerprint"] == fingerprint else "conflict"
            row = db.execute("SELECT * FROM run WHERE id = 'vendor-1'").fetchone()
            if (event.version, event.digest) != (row["version"], row["digest"]):
                return "stale"
            if row["state"] != "waiting":
                return "invalid_state"
            if event.action == "expire":
                if event.actor != "scheduler" or now < row["deadline"]:
                    return "rejected"
                next_state = "expired"
            elif event.action in {"approve", "deny"}:
                if event.actor != "procurement" or now >= row["deadline"]:
                    return "rejected"
                next_state = "approved" if event.action == "approve" else "denied"
            else:
                return "rejected"
    
            updated = db.execute(
                """UPDATE run SET state = ?, version = version + 1
                   WHERE id = ? AND version = ? AND state = 'waiting'""",
                (next_state, row["id"], event.version),
            ).rowcount
            assert updated == 1
            db.execute("INSERT INTO event_log VALUES (?, ?)", (event.event_id, fingerprint))
            if next_state == "approved":
                action_key = f'{row["id"]}:{event.version}:sync'
                db.execute(
                    "INSERT INTO outbox VALUES (?, ?, ?)",
                    (action_key, row["id"], event.digest),
                )
        return next_state
    
    
    def state(db):
        result = db.execute("SELECT state, version FROM run").fetchone()
        count = db.execute("SELECT count(*) FROM outbox").fetchone()[0]
        return tuple(result) + (count,)
    
    
    def check():
        # Actor labels stand for identities already authenticated by the caller.
        approve = Event("evt-1", "approve", "procurement", 3, "packet-v3")
        db = open_fixture()
        assert apply(db, approve, 90) == "approved"
        assert state(db) == ("approved", 4, 1)
        print("approve: approved v4, 1 outbox row")
    
        assert apply(db, approve, 90) == "duplicate"
        assert state(db) == ("approved", 4, 1)
        print("duplicate approval: approved v4, still 1 outbox row")
    
        conflict = Event("evt-1", "deny", "procurement", 3, "packet-v3")
        assert apply(db, conflict, 90) == "conflict"
        assert state(db) == ("approved", 4, 1)
        print("event ID reuse with altered content: conflict, 1 outbox row")
    
        db = open_fixture()
        deny = Event("evt-2", "deny", "procurement", 3, "packet-v3")
        assert apply(db, deny, 90) == "denied"
        assert state(db) == ("denied", 4, 0)
        print("deny: denied v4, 0 outbox rows")
    
        db = open_fixture()
        unauthorized = Event("evt-3", "approve", "vendor", 3, "packet-v3")
        assert apply(db, unauthorized, 90) == "rejected"
        assert state(db) == ("waiting", 3, 0)
        print("wrong actor: waiting v3, 0 outbox rows")
    
        stale = Event("evt-4", "approve", "procurement", 3, "packet-v2")
        assert apply(db, stale, 90) == "stale"
        assert state(db) == ("waiting", 3, 0)
        print("stale packet: waiting v3, 0 outbox rows")
    
        assert apply(db, approve, 100) == "rejected"
        assert state(db) == ("waiting", 3, 0)
        print("approval at deadline: waiting v3, 0 outbox rows")
    
        expiry = Event("evt-5", "expire", "scheduler", 3, "packet-v3")
        assert apply(db, expiry, 100) == "expired"
        assert state(db) == ("expired", 4, 0)
        print("scheduler expiry: expired v4, 0 outbox rows")
    
    
    if __name__ == "__main__":
        print("SQLite", sqlite3.sqlite_version)
        check()

    What happened when the process stopped

    Uvik Software also ran a recovery fixture using two SQLite files and separate Python subprocesses. One file held the application run and its outbox. A second simulated the receiving side and counted delivery calls and applied records for each action key. The receiving stub retained the same digest on duplicate delivery. These scenarios exercise local disk-backed commits and process exit, with the workflow runtime and register both represented by the stub.

    The harness committed approval with two outbox rows, one for graph resume and one for register delivery. It deliberately exited with code 23 at selected boundaries. A fresh process then loaded the saved rows and delivered any pending action. The local consumer counted each delivery, used a unique action key for the applied record and checked that the digest stayed the same. SQLite transactions support the local commit boundary exercised here. 11

    Process exit or replay point Pending rows at boundary Resume calls / applied Register calls / applied Pending after recovery
    Exit after approval update and before outbox insert or commit 0, run still waiting 0 / 0 0 / 0 0
    Exit after approval and both outbox rows commit 2, run approved 1 / 1 1 / 1 0
    Exit after register commit and before its local acknowledgement 2, run approved 1 / 1 2 / 1 0
    Exit after resume commit and before its local acknowledgement 2, run approved 2 / 1 1 / 1 0
    Scan again after both rows are acknowledged 0, run approved 1 / 1 1 / 1 0

    Uvik Software process-recovery fixture. Python 3.12.14 and SQLite 3.53.1. Calls and applied records are cumulative per action after recovery. Each scenario used a fresh pair of database files.

    The precommit exit left the run waiting at version 3. The postcommit exit left it approved at version 4 with both actions pending. In the lost-acknowledgement cases, the selected action reached the receiving stub twice and produced one applied record because the stub enforced the key. The original transition fixture queues one update. The separate recovery fixture adds the resume action to exercise the cross-runtime gap.

    The receiver in this test is a second SQLite database with a uniqueness constraint. The result supports the transaction and replay behavior shown for that fixture. Before shipping, repeat the same fault injection against your actual broker, connector and graph runtime. Ask the register to return the original receipt for a duplicate key and inspect its records after a simulated network failure.

    Test the destination before automating a retry

    Outbox delivery handles a missed acknowledgement by trying the same action again. Its safety depends on what the receiving service can identify. Stripe’s API v1 allows keys to be pruned once they are at least 24 hours old and can treat reuse after pruning as a new request. Cash App Pay documents that its deduplication is scoped to the request’s region, so the same key in a different region can create a second payment. The AWS Builders’ Library describes an EC2 design that retains the client token for the resource’s lifetime plus a late-arrival interval. These are different contracts. Check the operation, account, region, retention and behavior for a changed payload before you choose an automatic retry budget. 10 15 16

    Uvik Software compared three simulated register contracts. The first create committed one row, after which the fixture deliberately dropped its response. A fresh connection retried the unchanged operation at a simulated age of one, 23 or 25 hours. The three contracts were a plain create, a request key pruned at 24 hours, and an atomic create or return of a matching record with a stable, unique external ID. Each of the nine cases used a separate SQLite file. The metric was the count of business records after the retry.

    Receiving contract After first response is lost Retry at 1 hour Retry at 23 hours Retry at 25 hours
    Plain create 1 2 2 2
    Request key pruned at 24 hours 1 1 1 2
    Permanent unique external ID 1 1 1 1

    Business rows after a retry at one, 23 and 25 hours. Plain create produces two rows at all ages. A request key pruned at 24 hours leaves one row at one and 23 hours, then two at 25 hours. A permanent external ID preserves one row at all ages. Each SQLite trial starts the retry with one committed row.

    Figure 2. Register records after one retry in nine Uvik Software fault scenarios. Python 3.12.14 and SQLite 3.53.1. The clock and pruner were controlled in a local stub. The 24-hour setting describes the fixture. The asset bundle includes the runnable comparison and all nine results.

    To explore the results, download the case-level CSV data covering all nine retry scenarios. For full reproducibility, download the complete evidence bundle (ZIP), which includes the CSV results, runnable Python and SQLite fixtures, workflow recovery tests, and reproduction instructions.

    Every contract also created one record when the first call never reached the receiver, the three positive controls. Two additional controls sent a changed packet digest with the same still-valid request key or unique external ID. Both returned a conflict and kept the original record. That is why the receiver needs both an operation identifier and a binding to its original payload. A reused identity with a different packet requires investigation or a new authorized operation.

    The stable ID result depends on a durable destination uniqueness constraint and an atomic create-or-return operation. A client-side lookup followed by create leaves a race between callers. The local register keeps the record throughout the test. If a production register can delete or recycle the ID, define what a late replay should do and how long a tombstone or receipt remains queryable. Confirm that a matching record is actually the approved effect before marking the outbox row delivered. 16

    Write an explicit policy for an unknown outcome

    Give the operator three facts on one screen. Show the approved packet and digest, the destination and operation identity, and the timestamp and result of the last attempt. A timeout becomes an unknown outcome with a clear owner and next check.

    Destination evidence Next action Evidence required to advance
    Retained key is valid for the same endpoint, scope and payload Retry with that key and a bounded backoff A matching receipt confirms the effect
    Destination has an authoritative lookup by stable ID Read the record and compare the approved digest The matching effect is confirmed or the destination contract proves a fresh attempt is safe
    Receipt expired or lookup can lag Keep the run unresolved and route to reconciliation The connector owner verifies the record or authorizes a fresh operation
    A record is present with changed details Stop retries and investigate An authorized correction or new version is recorded

    Maintain two clocks. The approval deadline governs which decisions the system can commit for the first time. The destination’s retry window governs how long an already committed action can be replayed with its documented guarantee. A committed approval can be valid even after its retry guarantee has expired. Keep the audit link and original action key during reconciliation.

    Use a run state and a delivery state. A run can be approved while its external effect is still unknown. The sender should preserve that distinction during restarts, dashboards and on-call handoffs. If register delivery depends on successful graph resume, make the dependency explicit in durable state and check it before dispatch. If they are independent, track completion for each effect. The local recovery fixture compares the two delivery channels separately.

    Decide what to watch after launch

    Track time spent waiting for approval separately from time spent delivering an approved update. Monitor the oldest pending review, the number of expired requests and the age of the oldest undelivered outbox row. Use one run ID across the extractor, approval endpoint and delivery logs. Record which control stopped a failed transition so operations staff can act without reading the model’s reasoning.

    Budget human review time separately

    Suppose intake receives 90 packets on a normal day and routes 30 for human review. At eight active minutes per packet, reviewers need 240 minutes. Two reviewers who can each allocate 150 minutes provide 300 minutes, so the planned workload uses 80% of the available time. If 48 packets reach review after a quarter-end deadline, demand rises to 384 minutes. With the same staffing, the team needs 84 more review minutes or carries work into the next staffed window.

    These are planning inputs. Replace them with observed arrival counts, touch time and the hours reviewers can actually spend on the queue. Track approval age from the time the packet is ready for review, including time spent waiting for a reviewer. Publish the clock policy and track expired requests separately so an improvement in decided requests cannot hide a larger open backlog.

    Metric Definition Decision it supports
    Approval time Time from ready for review to a recorded decision, with open and expired cases counted separately Adjust reviewer capacity, reminders or escalation
    Expiry rate Expired requests divided by approved, denied and expired requests in the same closed cohort Check whether deadlines and staffing match demand
    Resume completion Committed decisions that reach their expected checkpoint within the time budget divided by decisions requiring resume Find dispatch or orchestration gaps
    Delivery age Time since commit for the oldest undelivered action, with attempts and permanent errors visible Distinguish a connector backlog from a review backlog
    Duplicate business effects Extra applied records per intended action key, reconciled with destination records Verify that receiver deduplication works

    If you use SQS, CloudWatch exposes an approximate age for the oldest unprocessed message and separate queue depth metrics. Compare them with the application outbox age and the destination receipts. The metrics reflect different parts of delivery. 12

    Use an acceptance matrix before rollout

    Scenario to run Evidence to accept
    Two reviewers race to approve or deny One decision wins the version check. The losing event produces no new action.
    A reviewer approves after a new packet version is saved The old decision is rejected. The current packet remains pending.
    A graph worker restarts while the run waits The same run ID restores the checkpoint and the approval deadline.
    The approval commits while the graph runtime is unavailable The resume event remains available and advances the same run when the runtime returns.
    The register applies an update and drops the response Reconciliation or a retry with the same key produces one destination record.
    A reviewer from another tenant opens a valid-looking link The application denies the request and leaves the packet state unchanged.
    A new deployment changes the approval event structure A previously paused run can still receive and process its version of the event.

    Use representative role assignments and destination behavior. Capture run version, action key and relevant audit records for each test. A workflow that passes a normal approval can still fail at a commit or replay boundary.

    Decide what would change the rollout

    Test the agent against structured intake and the current human queue on the same packet cohort before enabling writes. Have a reviewer verify a sample of the extraction against source documents. Compare correct register records per eligible packet, reviewer touch time and total age including expiry and rework. Expose the same risk policy across groups so the comparison measures the value of better intake. A reduction in review time that leaves more incorrect records or unresolved packets is a poor basis for expansion.

    Use the real connector’s test environment to exercise both sides of a timeout. Force a failure before it can apply, then separately drop the response after it applies. Repeat near the documented retention edge, with a worker restart and with regional failover if the service supports it. Capture destination records and stable IDs. Make every unsupported receiver action a reviewed exception until the connector has a documented recovery path.

    What the client examples measured

    The Uvik Software Tines case study reports that median analyst approval time moved from 14 minutes to 90 seconds and the share of paused runs that expired without an answer moved from 23% to 1.4% during the reported 15-month program. The implementation combined resumable state with policy-based routing and improved approval context.

    The Uvik Software Zip procurement case study reports a reduction in median purchase request cycle time from 11 days to 2 days during the reported 14-month program. The system evaluated policy at intake and routed requests to the applicable approval chain. Both sets of figures are project-specific outcomes reported by Uvik Software. They show which outcomes the teams measured.

    If you already have a workflow that loses decisions after restarts or creates duplicate updates, discuss it with Uvik Software’s agentic AI consultants. Bring one real run trace, its approval event and the downstream API response so the team can identify where the handoff failed.

    Make recovery part of the launch decision

    An approval means someone authorized a particular change. A destination receipt or reconciled record means that change happened. Build the workflow so the team can establish both after a restart. Launch with rules or manual intake if that meets the business need. Add extraction where it reduces the work of reaching a correct, recoverable decision.

    How to cite this guide

    Uvik Software. How to Build Agentic Workflows That Can Pause and Resume. 2026. https://uvik.net/blog/how-to-build-agentic-workflows/

    Attribute the eight transition checks, five process-recovery scenarios and nine receiver-contract cases to Uvik Software. In the local comparison, a replay at 25 hours created a second register row under the contract that pruned the request key at 24 hours. With a stable, unique external ID, the same replay returned the existing record.

    Sources

    1. OWASP GenAI Security Project, Prompt Injection. Trust boundaries for external content and high-risk actions.
    2. LangGraph documentation, Interrupts. Checkpointed pause and resume, thread identifiers and node restart behavior.
    3. LangGraph documentation, Persistence. Durable state and recovery.
    4. Temporal, Workflow message passing. Signals and Updates for durable workflows.
    5. Temporal, Activities. Retry behavior and idempotent work.
    6. Temporal Python SDK, Workflow versioning. Deterministic replay and compatibility across deployments.
    7. AWS Prescriptive Guidance, Transactional outbox pattern. Atomic recording and handling duplicate messages.
    8. PostgreSQL, SELECT locking clauses. Queue-like use of row-level locking and SKIP LOCKED.
    9. Amazon SQS, At-least-once delivery. Duplicate delivery for standard queues.
    10. Stripe API, Idempotent requests. Idempotency keys, retained responses and expiry behavior.
    11. SQLite, Transactions. Commit and rollback behavior in the recovery fixture.
    12. Amazon SQS, Available CloudWatch metrics. Queue age and depth.
    13. Printavo incident report, Delays and errors processing payments, 6 November 2026. Potentially charged payments with apparent failure and planned reconciliation.
    14. Wharton Human-AI Research and GBK Collective, 2026 AI Adoption Report, p. 54. Oversight comparisons and sample scope.
    15. Cash App Pay, Idempotency. Region-scoped deduplication and changed-payload handling.
    16. AWS Builders’ Library, Making retries safe with idempotent APIs. Caller-supplied identifiers, retention and semantic equivalence.

    Common questions

    What makes an agentic workflow durable?

    It can save the state of an individual run and recover from that state after an interruption. Uvik Software recommends also keeping a record of each decision and the delivery status of every approved external action.

    How do you resume a workflow after human approval?

    Use the stored run identifier, verify the actor and match the approval to the current version and packet. Apply the decision through the workflow’s transition handler, then continue from the committed state.

    Does a transactional outbox prevent duplicate writes?

    It durably records which updates need to be delivered. The sender can retry and the destination can deduplicate using the same action key, provided that the receiving interface supports it.

    What should happen when an approval expires?

    Close the review through a trusted expiry event. A new review request can carry the current packet and a fresh deadline. Validate the deadline at submission even if the timer has yet to run.

    How useful was this post?

    No votes so far! Be the first to rate this post.

    Share:
    How to Build Agentic Workflows That Can Pause and Resume - 12

    Need to augment your IT team with top talents?

    Uvik can help!
    Contact
    Uvik Software
    Privacy Overview

    This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

    Get a free project quote!
    Fill out the inquiry form and we'll get back as soon as possible.

      Subscribe to TechTides – Your Biweekly Tech Pulse!
      Join 750+ subscribers who receive 'TechTides' directly on LinkedIn. Curated by Paul Francis, our founder, this newsletter delivers a regular and reliable flow of tech trends, insights, and Uvik updates. Don’t miss out on the next wave of industry knowledge!
      Subscribe on LinkedIn