One intent, several attempts

Z29C stopped turning retries into new jobs and gave the user's requested outcome a stable identity across browsers, workers, adapters, and recovery.

Z29C's first run history showed three failed jobs and one successful job. The person who requested the synthetic document had performed one action.

The mismatch mattered. A browser retried creation after losing the API response. Workers retried the delivery step. A recovery process resumed after a lease expired. Each mechanism created a record it called a job or run.

The interface displayed four pieces of work and could not say which one represented the user's request. Cancellation targeted one job. Notifications fired for each. A later successful attempt made the request look duplicated even when the destination had deduplicated it.

I separated intent from execution. One durable intent could produce several attempts without becoming several requested outcomes.

Intent began before a worker existed

The browser generated an idempotency key when the person confirmed the action. The key identified that one deliberate submission across transport retries.

The API normalized and validated the command, then committed in one PostgreSQL transaction:

  • Stable workflow ID.
  • Initiating subject and authorization context.
  • Command type and immutable normalized input.
  • Client idempotency key.
  • Workflow definition version.
  • Initial state and first scheduling event.

Only after the commit did the interface say Request recorded. If the response disappeared, the browser could resubmit the same key or query its receipt. The API returned the existing workflow.

No queue message was the source of the intent. Queue delivery could be delayed or duplicated while the durable record remained one.

This boundary made the page refreshable. A workflow URL existed independently of the worker currently trying to advance it.

The first deduplication scheme hashed request data. Two deliberate weekly exports with the same inputs collapsed into one workflow. A retry that differed only in client metadata created a second.

Payload equality and intent identity were different questions.

A new deliberate action received a new key even if its input matched an earlier one. A transport retry reused the old key even if a harmless request header or timing field differed. The server compared the normalized semantic command stored under the key.

If the same key arrived with different command data, Z29C returned key-conflict. It did not silently apply the new payload to the old workflow or create another run.

The key scope included the initiating subject and command boundary so unrelated people or action types could not collide accidentally.

I retained a bounded key history long enough to cover client retry and recovery promises. Expiry was part of the API contract; a client could not assume a key deduplicated forever after the workflow itself left retention.

Identity came from the action, not from guessing sameness of content.

The command became immutable evidence

Once accepted, the original command did not change under the workflow ID. A correction created an explicit input revision or a new workflow according to the domain.

Mutating input in place would make attempt receipts ambiguous. If delivery attempt two used different recipient data, did it retry the same step or execute a new intention? Which input digest should an external idempotency key represent?

Z29C stored a normalized input digest with every step attempt and receipt. A handler loaded the input revision named by its definition. A mismatch between stored digest and adapter request stopped execution.

For a correction before side effects began, the workflow could accept a new command revision and supersede pending steps through a typed transition. After consequential effects, a correction usually became a new workflow or compensating path.

The interface said Create corrected request rather than editing history.

Immutability was not rigidity for its own sake. It made every later attempt answerable: what exactly was this worker trying to do?

Attempts were evidence about execution

Each logical step could have several attempts. An attempt recorded:

attemptId
stepId
workerLease
inputDigest
handlerVersion
adapterVersion
startedAt
sideEffectRequestIdentity
lastObservation
finishedAt

An attempt could end before invocation, be rejected, time out with unknown outcome, produce a receipt, or lose its worker lease. Those facts remained after another attempt succeeded.

The workflow projection used the current accepted step state. The detailed history showed every attempt and why another was allowed.

This prevented retries from laundering errors. A step that succeeded on attempt four still revealed three preceding transient failures. A duplicate external reference remained an integrity problem even if the final projection reached completed.

Attempts were not user-visible tasks. They were the execution evidence supporting one step under one intent.

A retry preserved step identity

The first scheduler created a new step row on retry. Dependant edges, metrics, and external references fragmented across rows.

Z29C kept one stable internal step ID from the workflow definition. Display labels could change in later releases without changing identity. Each retry created a new attempt under the same step.

External request identity followed adapter rules. If the destination supported idempotency, every attempt for the same logical side effect reused a stable key derived from workflow and step identity. If a materially new effect was required, it was a new step or explicit replacement operation.

A query attempt did not reuse the creation request ID as if it were another creation. It had its own attempt record while looking up the stable client reference.

The distinction made the run graph coherent:

workflow intent
  └── deliver step
        ├── attempt 1: response lost
        ├── recovery query: external result found
        └── one step receipt

Several executions could support one logical completion.

Resumption was not always retry

After a worker crash, Z29C's first action had been to retry the step handler. Some steps instead needed to resume from a durable checkpoint or inspect external state.

I used recovery verbs precisely:

  • Retry: perform the same safe request again under the same logical identity.
  • Resume: continue from a durable internal checkpoint without repeating completed work.
  • Query: ask an external system about a previously issued request.
  • Reconcile: compare local and external evidence and create a typed receipt.
  • Restart: begin a new logical step or workflow from its initial state.

The scheduler selected a verb from step state and adapter capability. A lease expiry only initiated recovery inspection.

The interface avoided saying “retrying workflow” when it was querying a destination or resuming document generation from a stored artifact. Language made the execution model inspectable.

One intent could survive all five paths, but their safety rules differed.

Cancellation targeted the intent's remaining work

When execution records were treated as jobs, Cancel stopped one currently visible job. A queued retry could continue later. A completed external step remained unaffected.

Z29C represented cancellation as new intent against the workflow. It asked what outcome was still possible:

  • Before side effects, cancel pending work and end.
  • During pure computation, request cooperative stop and discard incomplete output.
  • After external acceptance, stop future steps and consider compensation.
  • During unknown outcome, preserve investigation before claiming cancellation.

The cancellation request had its own ID, author, time, and receipt. Pressing it twice did not create two compensation paths.

The user-facing language changed by state. Cancel request appeared only when the original outcome could still be prevented. Later it became Stop remaining work or Request withdrawal, with already completed consequences listed.

Closing a browser had no effect on execution. The durable workflow continued or waited according to its state.

Cancellation became a workflow transition, not a signal sent to whichever process happened to be visible.

Progress belonged to milestones, not attempts

The early interface incremented a progress bar when jobs completed. A retry made the denominator change. A step with three failed attempts looked like three quarters of something.

I projected stable milestones from the workflow definition:

  • Request recorded.
  • Input validated.
  • Document prepared.
  • Delivery verified.
  • Completion recorded.

Attempts appeared only in technical details or when they affected the next safe action. A transient retry could show Waiting on delivery service without adding another user-facing step. Unknown outcome became Delivery status needs review.

I removed percentage completion. Steps differed in consequence and duration. Validation and external delivery were not equal units merely because each occupied one node.

The page showed the latest meaningful state, prior completed milestones, and any action required from the person. It read from the durable projection, so reopening on another device produced the same account.

One intent deserved one progress narrative.

The job-based model sent failure notifications for each attempt. A temporary adapter outage produced a stack of messages, followed by success.

Z29C notified on user-relevant workflow transitions:

  • The request was durably recorded.
  • Current state required review or new input.
  • A declared deadline approached while blocked.
  • The requested outcome completed.
  • The workflow ended without completion.
  • A compensating action changed an earlier effect.

Worker exceptions and retry scheduling remained operational events. They did not all reach the initiating person.

A notification candidate named the workflow ID and projection revision. Before delivery it rechecked current state, so a recovered transient failure did not send an obsolete alarm.

This reused D4U7's attention model. The person cared about the outcome and obligations, not the number of worker processes that had touched it.

Stable intent gave notification grouping a natural unit.

Metrics changed denominator

Job success rate had counted retries as independent work. A difficult workflow with several attempts inflated both failure and eventual success counts. Duplicate workflows from client retry inflated demand.

Z29C reported at several levels without collapsing them:

  • Intent creation duplicates safely returned.
  • Workflow outcome by definition version.
  • Logical step outcome and time in each state.
  • Attempt count and category per step.
  • Unknown-outcome age and resolution path.
  • Duplicate external effects per one-effect step.

The denominators stayed visible. An adapter's attempt reliability could improve while workflow completion remained unchanged. A workflow could complete while spending an excessive attempt budget.

I did not invent scale or commercial service levels. The synthetic scenarios produced deterministic counts used to validate the model. Metrics were design instruments, not portfolio proof.

Changing the unit from jobs to intent made the system's failures correspond to what the person had asked for.

A workflow could wait long enough for current authority or policy to change. The original intent remained a historical fact. Pending attempts did not inherit permanent permission from it.

Before a side-effect boundary, Z29C evaluated current policy against workflow, step, initiating subject, definition version, and environment. A denied step entered a visible blocked or terminal state with a policy receipt.

This did not mutate the original command or pretend it had never been accepted. The interface could say Request recorded under policy 3; delivery blocked under current policy 4.

If review authorized a bounded exception, that decision became a new receipt. If the request needed correction, it created a new command revision or workflow.

Stable intent therefore supported stronger governance. It separated what had been requested from what was allowed to happen now.

I expect the distinction to matter even more if software begins choosing tools through less predetermined control flow. Stable intent would let policy judge the proposed consequence without confusing a model's repeated attempts for several independent user requests.

Workflow definitions also had identity

One intent named the workflow definition version accepted at creation. Deploying new code did not reinterpret its steps automatically.

Handlers could support several old definition versions. A structural migration needed an explicit mapping of pending state, stable step IDs, external request identities, and receipts. Completed attempts remained tied to the code and adapter versions that had produced them.

This prevented a running intent from becoming several conceptual workflows across deployments. The person had requested the outcome under one defined path, even if compatible bug fixes improved its execution.

The interface did not expose definition internals by default. It could still explain why an old run followed a different milestone sequence from a new one.

Intent identity anchored time. Definition identity anchored interpretation.

The October migration article would explore this boundary more deeply.

Revisions created lineage, not mutation

Some requested outcomes legitimately changed before completion. A synthetic delivery recipient could be corrected before the delivery step began.

Z29C allowed a command revision only through a typed transition that checked completed effects and definition rules. The new revision kept the same broader workflow when the outcome remained conceptually one request and no external identity became ambiguous.

The revision recorded:

  • Prior input digest.
  • New normalized command and digest.
  • Author and reason.
  • Steps invalidated or rebuilt.
  • Existing receipts retained.
  • New external request identities required.

If a consequential effect already used the prior input, the correction became a new workflow linked as replacement or a compensation-and-reissue sequence. It did not overwrite the old command.

This kept “one intent” from becoming an excuse to mutate anything under one ID. Identity needed a clear semantic boundary.

The run timeline told one story

With stable intent, Z29C could assemble a coherent timeline:

09:00 request recorded
09:01 document prepared, receipt d-17
09:02 delivery attempt a-1 began with request key r-44
09:03 response deadline passed; outcome unknown
09:04 worker lease expired
09:05 destination query found delivery x-82
09:05 verified receipt recorded
09:06 workflow completed

The timeline separated observation, attempt, and conclusion. A later query did not rewrite the earlier timeout into success. It added evidence that resolved the unknown state.

Opening attempt details showed handler, worker, adapter, input digest, and bounded logs. The main view stayed organized around the intended outcome.

W93H's incident narrative had taught me that a system can retain every event and still tell a misleading story if their epistemic status is flattened. Z29C applied that lesson to execution.

Several attempts became one explainable history rather than several disconnected jobs.

The original submission UI held progress in component state. Losing the creation response left it unsure whether to navigate, retry, or show an error.

The stable client key allowed receipt lookup. After submission, the browser could navigate to a pending local route, retry creation safely, and replace it with the durable workflow ID when known. Refresh did not restart work.

The workflow ran on the server independently. The browser subscribed or polled for projection changes and could reconnect from a revision. An offline page displayed the last confirmed state and preserved any new cancellation or review intent under its delivery policy.

This removed the emotional use of disabled buttons as duplicate protection. The button could re-enable according to clear state because the server carried identity.

Safe behavior survived a second device, page reload, and lost response.

The user interface became a view and command surface over durable intent, not the only process remembering that an action had begun.

One became a maintained invariant

Z29C asserted properties around stable intent:

  • Repeating one client key with equivalent command returns one workflow.
  • Reusing the key with different command never mutates or duplicates silently.
  • One step has one logical identity across attempts.
  • A one-effect step cannot project clean success with two external references.
  • A receipt names the input and adapter versions that produced it.
  • Cancellation and correction create explicit transitions.
  • Deployment does not change a running definition implicitly.

Fault scenarios lost API responses, killed workers at checkpoints, duplicated queue messages, reordered status queries, and opened the workflow from another device.

The invariant was not “execute exactly once.” External systems did not all provide that guarantee. It was “represent one requested outcome once, repeat safely where possible, and expose uncertainty where not.”

That was a property Z29C could actually defend.

Attempts became implementation; intent became product

The three failed jobs and one successful job had described the worker system accurately. They described the person's action badly.

After the redesign, the run showed one workflow, one delivery step, several attempts, and one verified external receipt. Operational details remained visible without fragmenting the product narrative.

This separation made retries safer, cancellation more honest, notifications quieter, metrics meaningful, and migrations explainable. It also created the durable unit every later recovery decision could reference.

The user did not ask for a job. They asked for an outcome. Workers, leases, queue messages, and adapter calls were attempts the system made on their behalf.

One intent deserved one identity, however many times execution had to find its way there.