Define a Background Job Beyond Its Acceptance Receipt

Drafted and reviewed using AI assistance.

On this page

A background job needs more than a spinner and a final “done.” Before asking a coding agent to implement it, define what the service has accepted, which work is running, which output a user can retrieve, and what happens when observation stops.

Microsoft's asynchronous request-reply pattern separates an acceptance response, a status resource and the eventual result. In that pattern, a successful status lookup can return HTTP 200 while the job is still running. Its suggested status fields include timestamps, an optional progress indicator and an error. Those are useful starting points; the application still needs its own state and result contract.

Give one job three concrete stages

The fictional Atlas Bundle app produces a downloadable set of text tiles from a built-in preset. All IDs, paths, histories and timings below are original teaching fixtures. No service, queue, renderer or download endpoint was run for this example.

The complete input is:

{
  "job_id": "j-41",
  "preset": "coast",
  "pages": ["cover", "summary"],
  "format": "text-bundle-v1"
}

The preset's initial plan has cover-title, cover-key, summary-counts and summary-notes. Expansion discovers two required companions: cover-legend and summary-source. These six names are also the final .txt filenames. Nothing in this example depends on customer documents or a real rendering package.

Stage Work unit Completion condition
Validate One check: schema, preset, page list or format All four checks pass
Build One named text tile All six tiles in the revised plan exist in private staging
Package One action: write manifest.json, then write bundle.zip Both actions finish; archive index contains the six tiles and manifest

The proposed manifest lists the job ID, format and six filenames. An archive index is a requirement here, not an archive already generated by this article. After packaging, the service must publish the staged bundle and establish owner-accessible retrieval before marking the job succeeded.

The companion fixture, B62-14-case.json, contains the input, named work units, archive index and all four histories used below.

Specify states separately from delivery

Use these server-side transitions. Repeated observations of an unchanged state are allowed.

Current state Allowed next state Meaning
accepted running, cancel_requested Job record exists; execution need not have begun
running running, cancel_requested, succeeded, failed Work is active; finishing one stage need not finish the job
cancel_requested canceled, succeeded, failed Cancellation is pending; the service must resolve the race
succeeded No new execution state Result publication completed
failed No new execution state This execution ended with a recorded error
canceled No new execution state Service confirmed it stopped before result publication

Delivery has a different vocabulary: none, partial_private, staged_private, available, blocked or expired. These are proposed app fields, not HTTP status codes.

Partial tiles are never downloadable under this contract. A completed local staging file is also insufficient: publication might still fail. A successful job has an owner-accessible result reference at publication; a particular client's later fetch may still fail. Keep that fetch observation separate from the job state.

Walk the successful history without inventing an ETA

Times are elapsed seconds in the fictional fixture. Progress always names its stage and plan revision.

Time Job state / stage Stage work Delivery What the user can conclude
0 accepted / validate 0 of 4 none Received, not started
1 running / validate 0 of 4 none Validation started
3 running / validate 4 of 4 none Validation finished; job still running
4 running / build, revision 1 0 of 4 none Initial build plan selected
6 running / build, revision 1 2 of 4 partial_private Two tiles exist privately
7 running / build, revision 2 2 of 6 partial_private Two additional required tiles were discovered
10 running / build, revision 2 6 of 6 partial_private Tile creation finished
11 running / package 0 of 2 partial_private Packaging started
13 running / package 2 of 2 staged_private Bundle is staged; publication remains
14 succeeded / package 2 of 2 available Result published at /jobs/j-41/result
74 succeeded / package 2 of 2 expired Retention ended; this job still succeeded earlier

The build display moves from 50% to roughly 33% when its denominator changes. Preserve completed units and increment the plan revision; explain “2 of 6 tiles; plan expanded.” Do not silently reset completed work or keep showing the old percentage.

These units have different costs. Four validation checks, six tiles and two packaging actions do not form twelve equally weighted time slices. This design therefore shows stage counts, with no overall percent or remaining-time estimate. Even 2 of 2 packaging actions is not “download ready” before publication.

For this exercise, results are retained for 60 seconds after publication, making the boundary t=74 for j-41. In a real product, choose retention deliberately. After expiry, report “Finished; result expired,” rather than silently rerunning the job.

Decide what failure and missing observation mean

Case Recorded evidence Decision in this contract
j-42 build failure Validation completed; build reached 2 of 6, then TILE_BUILD_FAILED at t=8 Terminal failed; partial output blocked; no result link
j-43 confirmed cancellation Accepted at 0; validate 1 of 4 at 1; cancel requested at 2; stopped at 3 Terminal canceled; no result link
j-44 cancellation race Validation and build finished; package 2 of 2 at 13; cancellation requested at 13.2; publication wins at 14 Terminal succeeded; show “Finished before cancellation took effect”
Status lookup times out Client last saw j-41 at t=7; at t=20 it cannot read status Client shows status unavailable, last known build 2 of 6; server outcome remains unknown to it
Result fetch fails after success Client has a succeeded status but cannot fetch the result Preserve succeeded; show retrieval problem, not a fabricated download success

An unknown observation is a client condition, not a server execution state. In this fixture, a last update older than ten seconds is marked stale; it is not automatically converted into failed. At t=22, a successful new status observation can reveal that j-41 finished at 14. The lost-status interval did not restart or cancel it.

Likewise, Stop waiting stops this client's observation. Request cancellation asks the service to stop work. Neither proves a terminal cancellation until the service records it. Terminal state resolution must be serialized with publication so the UI never reports both canceled and successfully published for the same execution.

Recovery starts from the recorded state. For a known terminal failure, inspect its error and propose an explicitly requested replacement job if appropriate. This example provides no resumable checkpoint contract. For a lost status response, recover the existing job's status first; creating another job would be a new side effect.

Give the agent the lifecycle, not just the button label

Design the fictional Atlas Bundle job protocol and client presentation.
Work from B62-14-case.json and the states/tables in this guide.

The input is preset coast, pages cover and summary, format text-bundle-v1.
Validate four named checks. Build four initially planned tiles; expansion
adds cover-legend and summary-source, retaining completed work under plan
revision 2. Package manifest.json and bundle.zip. Publish only after all
required outputs are complete and owner retrieval has been established.

Keep server execution state, stage/count/plan revision, delivery state,
and client observation/fetch status separate. A status HTTP 200 is a read
result, not a job success signal. Never expose private partial/staged files.

Use the transition table, including cancel_requested resolving to canceled,
succeeded or failed. Define one serialized winner for publication versus
cancellation. Stop waiting is local; it does not cancel server work.
Do not change a terminal execution state when a result later expires.

Walk j-41 success and denominator expansion, j-42 failure, j-43 cancellation,
j-44 publication winning the race, and the t=7 to t=22 status-observation gap.
Show exact visible labels and whether a result link is available in each case.
Use stage units, not an invented overall percentage or ETA.

Return the complete state/output/error matrix and proposed status/result
representations first. Identify the persistence/publication/cancellation
boundary an implementation must enforce. Leave missing checkpoint, access
and retention implementation decisions explicit; the 60-second fixture
retention is illustrative, not a production requirement.

Use only a local teaching model for this task. Do not create workers/queues,
call cloud APIs, deploy endpoints or turn a status timeout into a new job.
Separate model checks, real implementation evidence and remaining unknowns.

This lifecycle extends the work after a creation receipt. It does not replace the separate decision about whether a retried creation refers to the same intended job. Keeping those contracts distinct lets a reviewer ask two answerable questions: which job was created, and what happened to that job afterward?