Skip to content

FE-1222, FE-1226: Classify optimization transport errors, auto-reconnect runs by id + cursor, and remove the legacy optimization path - #9066

Merged
kube merged 2 commits into
mainfrom
cf/fe-1226-petrinaut-optimization-reconnect
Aug 4, 2026
Merged

FE-1222, FE-1226: Classify optimization transport errors, auto-reconnect runs by id + cursor, and remove the legacy optimization path#9066
kube merged 2 commits into
mainfrom
cf/fe-1226-petrinaut-optimization-reconnect

Conversation

@kube

@kube kube commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

🌟 What is the purpose of this PR?

Cuts the frontend over to the detached-run optimization contract from #9067 — and makes it the only contract. A dropped connection no longer fails an optimization: transport failures are classified and the UI auto-reconnects by run id and cursor, resuming exactly where it stopped. The legacy single-connection path is removed end to end (frontend, NodeAPI surface, optimizer routes, client, demo hosts).

Consolidates FE-1222 (transport-error classification, previously #9063) and FE-1226 (auto-reconnect). Stacked on #9067.

🔗 Related links

🔍 What does this change?

Classified transport errors (FE-1222)

  • The iframe bridge classifies every optimization transport failure (network / http / protocol / aborted) and carries the x-hash-request-id / X-Optimization-Run-ID correlation ids; the provider builds safe, progress-aware messages ("…after 73 of 100 trials. Retry the optimization.") instead of surfacing raw exception strings. Already-received trials are preserved, and Retry remains available from the error state.

Auto-reconnect (FE-1226)

  • The provider tracks runId + last applied seq. Classified network/protocol failures, NodeAPI's retryable: true attachment-window errors, and transient gateway statuses (502/503/504) re-attach with exponential backoff (1s → 30s, capped at 8 consecutive failures) from the cursor; replayed events are de-duplicated by seq, so trials are never double-counted.
  • Active runs are recorded in sessionStorage so a reload can re-attach and rebuild from a full replay; runs the service no longer knows are dropped silently. On every give-up path the possibly-live run is cancelled so the account's single-flight frees up.

Legacy cutover

  • @hashintel/petrinaut-core: PetrinautOptimization drops optimize; createOptimizationRun/attachOptimizationRun/cancelOptimizationRun are required. This is a breaking change to that interface, released as a patch — the convention for these pre-v1 packages, which have no external consumers yet.
  • @hashintel/petrinaut: the provider's legacy stream fork is gone; the Storybook fake implements the detached contract.
  • apps/hash-frontend: the optimizationRequest bridge flow and its host handler are removed.
  • apps/petrinaut-opt: the legacy POST /optimize/all + POST /optimize/best routes and their stream_all/stream_best/run_stream generators are deleted; OpenAPI + generated client types re-synced.
  • @local/petrinaut-optimizer-client: openPetrinautOptimizationStream is deleted; the decoder loses its study mode (synthetic started, best-so-far aggregation — the consumer's running best was already authoritative across cursors).
  • apps/petrinaut-website: the optimization demo drives the detached-run API through the shared openapi-fetch client via its dev proxy.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s)

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

⚠️ Known issues

🛡 What tests cover this?

  • @hashintel/petrinaut: 16 provider tests over the reconnect state machine (seq de-duplication across overlapping replays, backoff exhaustion + orphan cancel, retryable attachment-window errors, gateway retries, definitive mid-run 404s, stored-run recovery, StrictMode double-mounting).
  • apps/hash-frontend: bridge unit + browser tests (create/attach/cancel round-trips, tombstoned late create replies self-cancelling orphaned runs, abort relay, mid-stream error classification with correlation ids).
  • apps/petrinaut-opt / @local/petrinaut-optimizer-client: updated suites for the single-mode decoder and the removed legacy routes.

❓ How to test this?

  1. yarn dev with the optimizer running; start an optimization with ~100 trials.
  2. Mid-run, toggle the network offline for a few seconds (dev tools) — the run shows (reconnecting…), then resumes; the trial count keeps increasing without duplicates and the final summary is consistent.
  3. Restart NodeAPI mid-run — same recovery via the gateway-status path.
  4. Cancel from the drawer — the run stops promptly server-side (no lingering CLI in docker stats).

@kube kube self-assigned this Jul 19, 2026
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Actions Updated (UTC)
hash Ignored Ignored Preview Aug 3, 2026 6:13pm
hashdotdesign-tokens Ignored Ignored Preview Aug 3, 2026 6:13pm
petrinaut Skipped Skipped Aug 3, 2026 6:13pm

@github-actions github-actions Bot added area/apps > hash* Affects HASH (a `hash-*` app) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/apps labels Jul 19, 2026
@semgrep-code-hashintel

Copy link
Copy Markdown

Semgrep found 1 insufficient-postmessage-origin-validation finding:

  • apps/hash-frontend/src/pages/processes/[uuid]/embed.page/create-bridge-petrinaut-optimization.ts

No validation of origin is done by the addEventListener API. It may be possible to exploit this flaw to perform Cross Origin attacks such as Cross-Site Scripting(XSS).

@cursor

cursor Bot commented Jul 19, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Breaking change to the optimization host API plus coordinated frontend, NodeAPI proxy, and Python optimizer cutover; reconnect/cancel/orphan-run logic affects account single-flight and trial accounting.

Overview
Detached-run contract only: PetrinautOptimization drops optimize in favor of required createOptimizationRun, attachOptimizationRun (with cursor + onAttached), and cancelOptimizationRun. Events may carry server seq for replay; cancellation uses PETRINAUT_OPTIMIZATION_CANCELLED_ERROR_CODE.

HASH frontend bridge: Host iframe messages split into optimizationCreate / optimizationAttach / optimizationCancel with classified PetrinautOptimizationTransportError, correlation headers, and NDJSON relay shared via relayOptimizationStream. Late create replies after abort trigger orphan run cancellation.

Optimizations UI: The provider creates runs, tracks runId/lastSeq, reconnects with backoff on network/protocol/retryable errors and 502–504, dedupes trials, persists active runs in sessionStorage for reload re-attach, surfaces actionable errors + Retry, and cancels possibly-live runs on give-up.

Optimizer service: POST /optimize/all and /optimize/best and their streaming generators are removed; run lifecycle is POST /optimize/runs plus GET …/events and DELETE.

Clients & docs: Website demo and optimizer client use detached-run APIs; docs describe reconnect vs reload/cancel behavior.

Reviewed by Cursor Bugbot for commit bb41b6c. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread libs/@hashintel/petrinaut/src/react/optimizations/provider.tsx
YannisZa
YannisZa previously approved these changes Jul 19, 2026
@kube
kube force-pushed the cf/fe-1222-classify-petrinaut-optimization-transport-errors branch from b54c931 to cf08515 Compare July 23, 2026 16:23
@kube
kube force-pushed the cf/fe-1226-petrinaut-optimization-reconnect branch from 198a2ce to 469d5b1 Compare July 23, 2026 16:24
Comment thread libs/@hashintel/petrinaut/src/react/optimizations/provider.tsx
@kube
kube changed the base branch from cf/fe-1222-classify-petrinaut-optimization-transport-errors to cf/fe-1225-nodeapi-detached-run-proxy July 23, 2026 18:10
@kube
kube marked this pull request as draft July 23, 2026 18:10
@github-actions github-actions Bot added type/eng > backend Owned by the @backend team area/tests New or updated tests area/apps > hash.design Affects the `hash.design` design site (app) labels Jul 23, 2026
@kube kube changed the title FE-1226: Auto-reconnect Petrinaut optimization streams by run id and cursor FE-1222, FE-1226: Classify optimization transport errors, auto-reconnect runs by id + cursor, and remove the legacy optimization path Jul 23, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated no new comments.

Suppressed comments (3)

libs/@hashintel/petrinaut/src/react/optimizations/provider.tsx:198

  • after counts only successful trials. A run with 5 completed, 3 pruned, and 2 failed trials therefore reports “after 5 … trials” even though 10 trial events were applied (the UI progress elsewhere sums all three states). Include pruned and failed counts so the progress-aware transport error reflects the actual resume cursor/progress.
  const after = `after ${progress.completedTrials} of ${progress.requestedTrials} trials`;

libs/@hashintel/petrinaut-core/src/optimization.ts:501

  • This accepts seq: 0, but the consumer initializes its cursor to 0 and skips every event with seq <= lastSeq (provider.tsx:496,525-529), so a schema-valid first event numbered 0 is silently discarded. The detached-run producer actually starts sequences at 1 (apps/petrinaut-opt/src/optimization_runs.py:102-104), and cursor 0 means “replay everything”; encode that contract by requiring a positive sequence number.
const optimizationEventSeqSchema = z.number().int().nonnegative().optional();

libs/@hashintel/petrinaut/docs/optimization.md:99

  • This says every reload loses the run view, but the new provider restores stored runs from sessionStorage (provider.tsx:730-776) in same-origin hosts; only the known opaque-origin HASH iframe degrades to this behavior. As written, the user guide contradicts the reload-recovery feature and advises same-origin users to cancel unnecessarily. Describe the conditional behavior and distinguish reload from closing the tab.
Reloading or closing the page is different: the page loses its view of a
still-running optimization. The run itself continues on the server until it
finishes or is cleaned up, and it can block you from starting a new
optimization until then — so use **Cancel** first if you intend to reload and
run something else. **Cancel** is also what actually stops a run: it ends the

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 74f438d. Configure here.

Comment thread libs/@hashintel/petrinaut/src/react/optimizations/provider.tsx
…onnect detached runs

- Classify Petrinaut optimization transport failures in the UI so the user sees
  whether a run was rejected, dropped, or is retrying, instead of one opaque error.
- Auto-reconnect detached optimization runs by run id and event `seq` cursor,
  re-attaching after connection drops and (where storage allows) page reloads;
  report an honest connection state from the new `onAttached` signal.
- Make the detached contract the only one: the host capability is now
  `createOptimizationRun`/`attachOptimizationRun`/`cancelOptimizationRun`, and the
  legacy single-connection `optimize` method is removed from petrinaut-core.
- Remove the legacy optimization path end to end: the optimizer's `/optimize/all`
  and `/optimize/best` routes and their `stream_all`/`stream_best` generators, the
  client's `openPetrinautOptimizationStream`, and the provider's legacy fork.
- Migrate the website optimization demo and the SimulateView stories to the
  detached-run API, and update the Petrinaut optimization user docs.
@kube
kube force-pushed the cf/fe-1226-petrinaut-optimization-reconnect branch from 74f438d to af8d841 Compare August 3, 2026 17:32
Copilot AI review requested due to automatic review settings August 3, 2026 17:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI review requested due to automatic review settings August 3, 2026 18:13
@vercel
vercel Bot temporarily deployed to Preview – petrinaut August 3, 2026 18:13 Inactive
@kube
kube requested review from CiaranMn and YannisZa August 3, 2026 18:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

libs/@hashintel/petrinaut/src/react/optimizations/provider.tsx:199

  • The displayed progress counts only successful trials. Pruned and failed trials are also received and retained, so a drop after 10 complete + 63 pruned trials is reported as “after 10 of 100” instead of 73. Include all three terminal trial counters when building the progress message.
  progress: { completedTrials: number; requestedTrials: number },
): string {
  const after = `after ${progress.completedTrials} of ${progress.requestedTrials} trials`;

apps/petrinaut-website/src/main/app/optimization-demo/petrinaut-opt-optimization.ts:70

  • This labels every non-HTTP request error as network, but attachPetrinautOptimizationRunStream also throws a plain Error when a successful response has no body (attach-optimization-run.ts:88-89). That protocol violation is therefore retried and eventually shown as a connection interruption. As in classifyStreamError, only TypeError should be classified as network and other non-abort errors as protocol.
const classifyRequestError = (error: unknown): unknown =>
  error instanceof PetrinautOptimizerHttpError
    ? classifyHttpError(error)
    : error instanceof Error && error.name !== "AbortError"
      ? Object.assign(error, { category: "network" })
      : error;

libs/@hashintel/petrinaut-core/src/optimization.ts:501

  • The detached capability now depends on seq for both resume cursors and replay de-duplication, but this schema still makes it optional. A host can therefore satisfy PetrinautOptimization.attachOptimizationRun while yielding unsequenced trials; runAttachLoop then keeps cursor 0 and appends the full replay again after every reconnect. Since the legacy optimize capability is removed, expose a detached-run event type/schema that requires seq for replayable run events (while separately allowing attachment-scoped timeout/superseded errors without one).
 * replayed event at or below that cursor. Optional so streams from hosts that
 * predate detached runs keep validating.
 */
const optimizationEventSeqSchema = z.number().int().nonnegative().optional();

@kube
kube enabled auto-merge August 3, 2026 18:29
@kube
kube added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 30eaafa Aug 4, 2026
65 checks passed
@kube
kube deleted the cf/fe-1226-petrinaut-optimization-reconnect branch August 4, 2026 07:16
@hash-worker hash-worker Bot mentioned this pull request Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash.design Affects the `hash.design` design site (app) area/apps > hash* Affects HASH (a `hash-*` app) area/apps area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) area/tests New or updated tests type/eng > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

4 participants