AI work is stretching beyond the lifespan of a normal chat session. OpenAI introduced ChatGPT Work on July 9 with tasks that can continue for hours, run on a schedule, or respond to a trigger. Its June research on agent adoption found that more than 70% of sampled individual users had delegated at least one Codex task estimated to take a person over an hour. Long-running work is moving from a specialist pattern into an interface customers will expect.
A website chatbot cannot keep a visitor staring at a typing indicator for an hour. The useful pattern is to turn the request into a durable job, return control immediately, and make every later state understandable. Start with this job contract:
| Field | What the visitor needs to know |
|---|---|
| Job ID | A stable reference for status, support, and deduplication |
| Accepted scope | The exact task, inputs, account, and requested output |
| Current state | Queued, running, waiting for input, completed, failed, cancelled, or expired |
| Next checkpoint | When the system will update the visitor, without inventing a completion time |
| Control | How to resume, add input, cancel, or safely retry |
| Delivery | Where the result will appear and who is allowed to open it |
| Receipt | What ran, what changed, what failed, and links to created records |
That contract turns “I’m working on it” into an operation the customer can leave, inspect, and trust.
Split the Conversation From the Work
The chat session and the background job have different lifecycles. A browser tab can close while the job continues. A visitor can return from another device. A worker can restart. An external API can accept a request and finish much later.
Store job state outside the model conversation. The transcript should explain the request and decisions; a database or queue should own execution state, attempts, timestamps, authorization scope, and result locations. Reconstructing those fields from a model-generated summary is fragile and can cause duplicate or unauthorized work.
Use an explicit state machine:
| State | Entry condition | Required customer-facing response |
|---|---|---|
| Queued | Request validated and stored | Confirm scope, job ID, and next checkpoint |
| Running | A worker owns the attempt | Show the current stage, not a permanent spinner |
| Waiting for input | Progress needs a customer decision or missing value | Name the question, deadline, and safe default |
| Completed | Result stored and side effects confirmed | Deliver the result and an execution receipt |
| Failed | Attempt ended without a confirmed outcome | Explain what did not happen and whether retry is safe |
| Cancelled | Authorized cancellation accepted | State which completed effects remain |
| Expired | Input, approval, or result window ended | Explain how to start a fresh job |
Avoid using “pending” for everything between request and result. It hides the difference between waiting in a queue, actively processing, blocked on the visitor, and stuck after an error. Those states demand different actions.
The broader chatbot actions guide helps decide which customer requests should trigger tools. The async boundary begins when that tool cannot produce a trustworthy final result within the current response.
Worked Example: An 11-Minute Account Report
Suppose a customer asks a billing chatbot to analyze a year of invoices and produce a downloadable reconciliation report. The work normally takes 8 to 15 minutes because the system must retrieve records, check duplicates, calculate totals, and render a file.
A weak interaction holds the request open:
Customer: Reconcile our 2025 invoices and flag duplicate charges. Chatbot: I'm working on that now. This may take a few minutes... [The browser sleeps after four minutes. The customer refreshes and asks again.] Chatbot: I'll start the reconciliation now.
The second message may launch a duplicate report. The customer has no reference, cannot distinguish a retry from a restart, and does not know whether the first job changed anything.
A durable interaction acknowledges the handoff:
Customer: Reconcile our 2025 invoices and flag duplicate charges. Chatbot: I accepted a read-only reconciliation for account Northwind EU. Job AR-2841 will check 1,842 invoices from Jan 1 through Dec 31, 2025 and create one CSV report. No invoice will be changed. Current state: queued Next update: when invoice retrieval finishes, or within 5 minutes Delivery: this conversation and [email protected] [View status] [Cancel job]
Six minutes later, the job finds that an encrypted archive needs a password. It enters waiting_for_input instead of failing or guessing:
Job AR-2841 is paused. The March archive is encrypted, so 123 invoices have not been checked. Add the archive password by July 23 at 14:00 UTC, continue without March, or cancel the job. [Add password] [Continue without March] [Cancel]
After the customer supplies the password, the original job resumes. At 11 minutes, it returns a receipt:
Job AR-2841 completed. Checked: 1,842 invoices Potential duplicates: 7 invoice pairs (€4,380 total) Records changed: 0 Report: reconciliation-2025.csv Completed: 2026-07-22 09:41 UTC
The useful artifact is not merely the CSV. It is the combination of scoped acceptance, resumable state, customer controls, and a final receipt.
Promise a Checkpoint, Not a Fake ETA
Background work often depends on queue depth, rate limits, third-party systems, and the size of customer data. A precise completion time may look reassuring but becomes a broken promise when any dependency slows down.
Give the visitor two separate signals:
Current stage. “Retrieving invoices,” “waiting for approval,” and “rendering the report” explain what is happening without exposing noisy internal logs.
Next checkpoint. Promise when the system will communicate again: after a stage completes, when input is required, or within a maximum silence window. “We will update you within five minutes even if the state has not changed” is controllable. “Your report will be ready in five minutes” may not be.
Send progress only when it changes the customer's understanding or options. A feed of “still working” messages creates notification fatigue and makes the important blocked state easier to miss. For predictable tasks, one acceptance, one blocked-state alert if needed, and one terminal result are often enough.
Make Resume, Retry, and Cancel Mean Different Things
These controls are commonly presented as variations of “run it again,” but they carry different risks.
Resume continues the same job. Use it after missing input, an approval, or a recoverable pause. Preserve the job ID, completed stages, and idempotency keys.
Retry creates a new attempt under the same job. Use it after a transient failure when repeating the next step is known to be safe. Increment an attempt number and keep the earlier error in the receipt.
Restart creates a new job. Use it when the scope, inputs, authorization, or desired output changed. It needs a new job ID and a fresh acceptance summary.
Cancel stops future work where possible. It cannot pretend already completed side effects never happened. If the job sent three of five approved emails before cancellation, say that three were sent and two were stopped.
Idempotency belongs at every side-effect boundary. A job-level key prevents double submission; step-level keys prevent a worker retry from issuing the same refund, message, or record update twice. The AI agent audit-trail guide shows how intent, attempt, and confirmed effect fit together when a retry crosses several systems.
Recheck Authority When the Work Resumes
Authorization at request time is not permanent authorization. A person can lose access, leave a team, change roles, or withdraw approval while a job is queued. The target record may move to another account before the worker acts.
Bind the job to an account, actor, requested capability, input snapshot, and approval version. Recheck authority before each consequential step and again before delivering a sensitive result. A valid job ID should not act as a bearer token that anyone can use to open the output.
If the job pauses for a material change, request a new approval. Adding a missing report format can be harmless; expanding a read-only reconciliation into automatic invoice deletion is new scope. The chatbot approval workflow explains how to show the exact payload, consequence, and expiry instead of asking for a vague “continue?”
Set retention explicitly. Raw input, intermediate files, progress events, final artifacts, and audit records may need different deletion windows. Expiring the download link should not erase the minimal receipt required to explain what happened.
Deliver the Result Without Breaking Context
A result can arrive after the visitor closed the page, logged out, or started another conversation. Choose delivery at acceptance time and confirm it before sending sensitive content.
For low-risk work, an in-product notification can link back to the original conversation. For private reports, notify the customer that the result is ready and require authentication before opening it. Avoid placing the report itself, customer data, or a permanent access URL in an email subject or push notification.
When the visitor returns, restore a compact job summary rather than replaying every progress event into the model context. Include the original scope, current state, outstanding decision, last meaningful checkpoint, and result reference. This follows the same discipline as a deliberate chatbot context-window policy: preserve verified state and current evidence, while keeping operational noise outside the prompt.
If several jobs share a conversation, name them. “Your export is finished” becomes ambiguous when a customer requested an invoice export, a user export, and a security report. Stable labels and IDs make follow-up questions route correctly.
Test the Hours Between Messages
Happy-path demos compress time and hide the failures unique to async work. Test the gaps:
| Scenario | Injected condition | Passing behavior |
|---|---|---|
| Duplicate submit | Visitor clicks twice or repeats after refresh | One job runs; the duplicate returns the existing job ID |
| Worker restart | Process exits after a side effect | Job resumes without repeating the confirmed effect |
| Lost access | Requester is removed from the account mid-run | Next protected step stops; result is not delivered |
| Late input | Visitor responds after the deadline | Expired job stays closed; a fresh job is offered |
| Partial cancellation | Some steps completed before cancel | Remaining work stops and completed effects are listed |
| Notification failure | Email or push delivery fails | Result remains available in the authenticated status view |
| Stale callback | An old attempt reports success after a retry | Attempt ordering prevents the job from regressing state |
Measure acceptance-to-start time, time spent in each state, jobs waiting past their deadline, duplicate suppression, retry success, cancellation latency, notification delivery, and result retrieval. Completion rate alone can hide jobs that finished correctly but never reached the customer.
Review a sample of receipts beside the conversation. The wording should let a support person answer three questions without querying several systems: What did the customer authorize? What actually happened? What can safely happen next?
Let the Customer Leave Without Losing Control
Long-running AI work changes the basic promise of a chatbot. The conversation no longer needs to remain open, but the commitment must remain visible. A durable job contract, explicit states, careful checkpoints, separate controls, renewed authorization, and a final receipt give the customer that continuity.
Design the hours between request and result with the same care as the first chatbot reply. That is where an impressive agent becomes a dependable service.
No credit card required.



