Skip to content

ai(lifecycle): unify Stop Generation, cancellation, supersession and duplicate-request semantics #714

Description

@qnbs

Context

Dedicated Wave 0C child of #704.

WorldScript Studio now has an explicit project-incarnation correctness layer:

Those correctness layers answer:

May this result still affect this project?

They do not by themselves answer the separate lifecycle question:

Should this AI request/stream/task still be running, and what happens when it is stopped, superseded, duplicated, unmounted or abandoned?

Cancellation behavior is currently spread across hooks, thunks, provider adapters, workers, local inference paths and UI controls. #704 previously carried this as “Wave 0C”, but the umbrella is now too broad to serve as the durable implementation owner.

This issue owns the bounded AI request lifecycle contract across currently admitted WorldScript AI surfaces.


Core invariant

Cancellation and project authority are complementary and must remain independent:

PROJECT AUTHORITY
= may a late result/chunk mutate/apply/export to the current project?

REQUEST LIFECYCLE
= should the underlying work continue, be superseded, stop producing UI state, or release resources?

A cancelled operation may still deliver a late provider callback.
A non-cancelled operation may become stale because the user switched projects.

Therefore:


1. Inventory every production AI lifecycle

Derive the authoritative inventory from current source, not historical assumptions.

Search every production path using or wrapping:

AbortController / AbortSignal
streaming callbacks
ReadableStream / async iterator
provider SDK stream APIs
fetch cancellation
worker message cancellation
local inference cancellation
generation ids/request ids
isGenerating flags
Stop Generation buttons
cancel/stop handlers
unmount cleanup
supersession/latest-request logic
retry/fallback loops
background AI tasks

At minimum classify known surfaces such as:

  • Writer generation/continuation/rewrite flows;
  • legacy streamGenerationThunk;
  • Global Copilot;
  • Character Interviews;
  • outline generation/regeneration;
  • character/world generation and field regeneration;
  • image generation/refinement/scene visualization;
  • logline/proofread/synopsis;
  • Critic/Consistency Checker;
  • ProForge/HITL stages;
  • provider connection tests where cancellable;
  • RAG/context-assisted generation;
  • plugin ai.invoke where admitted;
  • browser-local WebLLM/ONNX/Transformers paths;
  • Ollama/OpenAI-compatible local server paths;
  • background/deferred AI work if currently reachable.

Produce a lifecycle matrix:

Surface Starts where Stream? Current cancel primitive UI Stop? Supersession Cleanup Late-result guard owner Required disposition

Do not assume every operation needs a user-visible Stop button; classify intentionally.


2. Canonical request lifecycle vocabulary

Establish a small explicit state vocabulary sufficient for product/runtime correctness.

Conceptually:

IDLE
STARTING
RUNNING
STREAMING
STOP_REQUESTED
CANCELLED
SUCCEEDED
FAILED
SUPERSEDED

Exact implementation may differ.

Requirements:

  • terminal states are unambiguous;
  • a superseded request is distinguishable from provider failure where product behavior differs;
  • “cancel requested” does not falsely imply the provider has definitely stopped if the transport/runtime cannot guarantee it;
  • stale late callbacks after terminal state cannot reopen the operation;
  • UI derives from authoritative request state rather than multiple drifting booleans where practical.

Do not over-engineer a distributed workflow engine.


3. AbortSignal propagation

For every provider/runtime path that supports cooperative cancellation, propagate one admitted cancellation signal from product orchestration to the lowest practical boundary.

Audit:

UI/hook
→ thunk/service/orchestrator
→ routing/fallback
→ provider adapter
→ fetch/SDK/worker/runtime

Required properties:

  • no accidentally-created inner AbortController severs caller cancellation;
  • provider adapters do not swallow AbortError and convert it into generic failure;
  • fallback/retry loops stop when caller cancellation is authoritative;
  • a cancelled primary request must not silently trigger a fallback provider unless explicitly defined and safe;
  • local-only/privacy modes remain intact;
  • cleanup releases listeners/controllers/stream readers.

Where a provider SDK cannot truly cancel remote generation, stop local consumption and rely on mutation/project guards for late data.


4. Stop Generation product contract

Inventory every visible Stop/Cancel control and define what it guarantees.

A correct Stop Generation action should normally mean:

user requests stop
→ request transitions to stopping/cancelled semantics
→ transport/runtime cancellation attempted
→ no subsequent late chunk/result is admitted to active UI/project state
→ loading/streaming indicator settles
→ controls return to a usable state
→ partial output policy is explicit

For each surface decide whether partial output is:

DISCARD
KEEP_READ_ONLY
KEEP_EDITABLE
KEEP_AND_ALLOW_APPLY_IF_ORIGIN_STILL_VALID

Do not let individual hooks invent incompatible semantics accidentally.

Do not report cancellation as a provider/network error toast unless cancellation itself genuinely failed in an actionable way.


5. Supersession / latest-request semantics

Rapid repeated user actions can start overlapping work.

For each applicable surface decide explicitly:

REJECT_DUPLICATE_WHILE_RUNNING
CANCEL_PREVIOUS_AND_START_NEW
ALLOW_CONCURRENT_WITH_SEPARATE_IDS
QUEUE

Examples to inspect:

  • Writer regenerate/rewrite actions;
  • Copilot send while prior response streams;
  • outline/field regeneration;
  • portrait/world/scene image retry/refinement;
  • logline/proofread/synopsis repeated clicks;
  • provider connection tests;
  • ProForge stage actions.

Requirements:

Use explicit request IDs/generation tokens where needed, but do not create a second project-incarnation authority.


6. Streaming admission

Streaming paths require stronger lifecycle discipline than one-shot requests.

For every stream:

chunk arrives
→ request identity still current?
→ lifecycle still admits chunks?
→ project/result authority still valid where mutation/apply-capable?
→ then update admitted state

At minimum audit:

  • Writer;
  • Global Copilot;
  • Character Interviews;
  • provider-native text streaming;
  • any local model token streaming.

After Stop/Supersede/Unmount:

  • late chunks are ignored deterministically;
  • no UI state reopens;
  • no stale transcript/history mutation occurs;
  • stream-reader/iterator resources are released where possible.

#708/#713 remain the source of project ownership truth.


7. Component unmount / navigation lifecycle

Audit what happens when users:

  • navigate away from a view;
  • close a modal/panel;
  • switch feature modes;
  • disable a feature flag;
  • close Global Copilot;
  • leave Character Interviews;
  • switch projects;
  • reset/import/restore;
  • log out of a provider-like integration if applicable;
  • close/reload the application.

Do not indiscriminately cancel every operation on every UI unmount if the product intentionally supports background completion.

Instead classify each operation:

VIEW_SCOPED_CANCEL_ON_UNMOUNT
PROJECT_SCOPED_CONTINUE_WITH_AUTHORITY
APP_SCOPED_BACKGROUND
PERSISTENT_TASK
NOT_ALLOWED_BACKGROUND

Any operation that continues after view unmount must have an explicit result destination and ownership contract.


8. Error taxonomy: cancelled is not failed

Normalize cancellation-related error handling so user-visible behavior is truthful.

Distinguish at minimum:

USER_CANCELLED
SUPERSEDED
PROJECT_STALE_REJECTED
TIMEOUT
NETWORK_ABORTED
PROVIDER_ERROR
RATE_LIMITED
AUTH_ERROR
CAPABILITY_UNSUPPORTED
LOCAL_RUNTIME_TERMINATED

Do not expose raw provider exception strings containing sensitive metadata.

Do not count intentional cancellation as ordinary feature failure in diagnostics/telemetry-like local evidence.

#551 owns broader structured diagnostic architecture; reuse its semantics where available rather than creating a disconnected logging taxonomy.


9. Timeout semantics

Cancellation and timeout are related but not identical.

Audit current timeouts across providers/local inference.

Required policy:

  • timeout values are workload/provider appropriate rather than one arbitrary global constant;
  • timeout aborts the request when supported;
  • timeout produces an actionable TIMEOUT state, not USER_CANCELLED;
  • fallback after timeout follows explicit routing policy;
  • no retry storm;
  • image/long-form/local-model workloads may legitimately need different thresholds;
  • mandatory tests use deterministic fake clocks/controlled transport where possible rather than sleeping for real timeout durations.

Do not tune production timeouts merely to make CI pass.


10. Provider/fallback-chain cancellation

#704 owns model/provider correctness; this issue owns lifecycle propagation through routing.

Verify:

cancel before provider attempt
cancel during primary
cancel between primary failure and fallback
cancel during fallback
cancel during retry/backoff

Cancellation must short-circuit further attempts unless the product explicitly defines otherwise.

A user pressing Stop must not result in another cloud provider being contacted because the aborted request was misclassified as a retryable network error.

Preserve privacy/local-only boundaries.


11. Local inference lifecycle

Audit currently admitted local runtimes:

Ollama / OpenAI-compatible local server
WebLLM
ONNX Runtime Web
Transformers.js

Where applicable verify:

  • request cancellation;
  • token-stream stop;
  • worker termination vs reusable worker state;
  • model remains loaded when appropriate after one request cancellation;
  • no model/device resource leak per cancel/retry cycle;
  • device-lost/OOM remains a separate failure state;
  • cancelling inference does not delete downloaded model artifacts;
  • editor/autosave responsiveness recovers promptly.

Coordinate broad browser-local model lifecycle with #483/#556; do not duplicate model catalogue or artifact-storage work here.


12. Images and non-streaming long operations

Image generation may not expose token streaming but still needs lifecycle correctness.

Audit Character/World/Scene image paths for:

Cancellation alone must never authorize a late persistent write.


13. ProForge / agentic workflows

ProForge can have multi-stage or HITL semantics.

Do not apply a naive single-request AbortController if the pipeline has meaningful stage boundaries.

Derive current production behavior and define:

  • cancel current stage;
  • cancel whole run;
  • pause/wait at HITL;
  • superseding a run;
  • background continuation policy;
  • partial artifacts/state after cancellation;
  • rollback/non-mutation guarantees;
  • provider calls already in flight.

Keep this bounded to lifecycle semantics; do not redesign the entire ProForge architecture.


14. Plugin ai.invoke

If currently production-admitted for plugins, ensure plugin-triggered AI requests cannot create unbounded uncancellable work.

Required where applicable:

  • bounded timeout;
  • cancellation/termination when plugin execution is terminated;
  • plugin does not receive provider credentials;
  • provider/privacy policy remains authoritative;
  • late result cannot apply project mutation without the plugin side-effect authority contract.

Coordinate marketplace/security evolution with #348; do not broaden this issue into plugin distribution.


15. Background AI tasks

Inventory any background AI/task paths currently reachable.

Each must explicitly classify lifecycle:

foreground-only
continue after view close
continue after project switch only if project-owned and authority-bound
cancel on app/page lifecycle
persistent resumable task

Do not invent persistent background execution for browser/PWA merely to make cancellation architecture uniform.

PWA deferred work remains constrained by #488.


16. UI concurrency / control semantics

For every user-visible generation surface verify:

  • Start/Generate cannot spawn accidental uncontrolled duplicates;
  • Stop is available only when meaningful;
  • disabled/loading controls reflect actual request lifecycle;
  • Stop then immediate Regenerate is deterministic;
  • project switching does not leave permanently disabled controls;
  • feature disable/reenable does not resurrect old request state;
  • partial result policy is clear;
  • screen-reader/live-region status is meaningful where existing accessibility architecture supports it.

Coordinate broader accessibility requirements with #557.


17. Resource cleanup

Repeated generate/stop/regenerate cycles must not leak:

  • AbortControllers/listeners;
  • stream readers;
  • timers;
  • worker message handlers;
  • object URLs;
  • provider SDK streams;
  • queued fallback promises;
  • model request buffers;
  • React state updates after unmount.

Add focused leak/lifecycle assertions where deterministic; do not introduce brittle heap-size CI gates without evidence.


18. Deterministic regression matrix

Required source/integration coverage should include representative permutations:

One-shot

start → succeed
start → user cancel
start → timeout
start → supersede
start → unmount
cancel → late success callback

Streaming

start → chunk → stop → late chunks
start N → chunk → start N+1 → late N chunks
stream under A → switch to B → late chunks

The last case must prove lifecycle + project-authority composition rather than assigning both responsibilities to one guard.

Fallback

primary running → cancel → no fallback
primary fails → fallback starts → cancel
cancel during backoff → no next attempt

Duplicate action

rapid double click
Stop + immediate Regenerate
multiple independent surfaces concurrently

Use deterministic mocked provider/transport boundaries in mandatory CI.


19. Product-level E2E relationship

#710 owns the broad AI functional E2E inventory.

Once source lifecycle semantics are admitted, #710 should prove representative user journeys such as:

Writer stream → Stop → no late mutation
Character Interview → Stop → no late transcript mutation
Copilot → supersede/stop → only current result remains applicable

Do not duplicate every provider × cancellation permutation in Playwright.

Adapter/service/unit/integration tests carry the detailed lifecycle matrix.

#712 may select one production-bundle lifecycle canary only if it gives unique build/CSP/chunk signal.


20. Diagnostics / privacy-safe evidence

Record only lifecycle metadata needed for debugging, for example:

request kind
provider class/model id where non-sensitive
request id
start/end state
duration
cancel source
error category
application SHA/environment

Do not log:

  • manuscript content;
  • prompts/results;
  • API keys/tokens;
  • private filesystem paths;
  • provider account metadata.

Coordinate structured redaction/evidence format with #551/#704.


21. Implementation architecture constraints

Prefer reusable request-lifecycle semantics, but avoid an invasive rewrite merely for uniformity.

Good architecture should provide:

one caller-owned cancellation path
explicit request identity
explicit terminal state
late-event admission check
provider/runtime propagation
bounded cleanup

without forcing every provider to implement unsupported primitives.

Do not:

  • replace Redux/project authority with request IDs;
  • move all AI orchestration to Rust;
  • add a generic workflow framework unrelated to current needs;
  • make provider-specific limitations appear as universal guarantees;
  • silently change privacy fallback behavior.

Acceptance criteria


Non-goals


Priority / sequencing

P1 lifecycle correctness after/alongside terminal Wave 0B authority fixes.

Recommended dependency graph:

#708 direct/persistent/stream project authority
      +
#713 deferred/apply-later project authority
      ↓
THIS ISSUE — Wave 0C request lifecycle
      ↓
#710 functional AI E2E
#711 opt-in admission where Stop/network policy matters
#712 selected production canaries
      ↓
#623 evidence-based required/advisory graduation

Bounded implementation PRs are preferred by surface/lifecycle primitive rather than one repository-wide mega-PR. Preserve current work and rebase sequencing on live repository state before execution.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions