Skip to content

FE-1582: Add an in-browser optimization runtime to petrinaut-core - #9515

Draft
kube wants to merge 4 commits into
claude/web-optimizer-python-corefrom
claude/web-optimizer-core-runtime
Draft

FE-1582: Add an in-browser optimization runtime to petrinaut-core#9515
kube wants to merge 4 commits into
claude/web-optimizer-python-corefrom
claude/web-optimizer-core-runtime

Conversation

@kube

@kube kube commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Before this PR, the only optimization capability was the remote one: a host handed Petrinaut a PetrinautOptimization that talked to the Python service, and the service spawned a CLI process to simulate each trial. Running the study anywhere else meant re-implementing the protocol, and the host had no say in how trials were computed.

This PR adds an in-browser optimization runtime to @hashintel/petrinaut-core. A module Web Worker loads Pyodide, installs the pinned Optuna packages and the shared Python study library, and drives the study by ask and tell. Optimizer never simulates: a PetrinautConnectedOptimization is connected to a PetrinautOptimizationChannel the host implements, and every trial goes through that channel. Capability that comes back satisfies the existing PetrinautOptimization contract and adds a connected lifecycle on top: a finished or stopped study stays in the worker until released, so the host can extend it with more trials on the same sampler history, and a run may keep up to four trials in flight.

Links

Changes

Optimization contract

  • PetrinautOptimizationChannel, PetrinautOptimizationTrialRequest and PetrinautOptimizationTrialOutcome describe one trial handed to the host

    Request carries the frozen manifest, the suggested values, every scenario parameter's resolved value, the trial seeds and an abort signal.
    Outcome is an objective or a pruning reason.

  • PetrinautConnectedOptimization and PetrinautOptimizationSource let a host supply either a remote capability or one to connect locally

    isConnectedOptimization tells them apart.

  • PetrinautConnectedOptimizationCapability is what connect returns

    Everything of PetrinautOptimization, plus createOptimizationRun(input, { signal, parallelism }), extendOptimizationRun(runId, trials), releaseOptimizationRun(runId) and dispose().
    PETRINAUT_OPTIMIZATION_MAX_PARALLELISM caps parallelism at 4.

  • optimization/describe.ts owns the describe result, suggested-value validation, trial seed derivation and scenario value resolution

    CLI imports these instead of keeping its own copies; its experiment-based replicate runs keep pinning the same seeds.

Browser runtime

  • browser-optimization entry: createBrowserOptimization returns a connected optimization

    connect(channel) gives a capability with run ids, a per-run event log with dense sequence numbers, replay from a cursor, one study at a time on a shared worker, cancellation and disposal.

  • Run log is a sequence of segments

    Each segment starts with started, carrying the cumulative requestedTrials, and ends with a terminal event.
    Replay from a cursor ends at the first terminal event after it, so a provider re-attaching after Continue sees the new segment only.

  • A finished or stopped study stays in the worker

    complete and a stop leave the run resumable; extendOptimizationRun appends started and queues the next segment on the same study; releaseOptimizationRun drops it.
    Failures finish the run and drop the study.

  • Stale segments cannot touch the next one

    An evaluate posted after a stop is answered as pruned without running; an evaluation of a stopped segment that settles later is ignored.

  • workers/optimizer entry: a module worker that imports pyodide.mjs from the configured index URL, loads the distribution packages, installs Optuna and colorlog with deps=False, writes the Python sources into the virtual filesystem and runs the study

    start creates the study handle and runs the first segment, extend runs the next on the same handle, release drops it.
    Evaluate requests cross to the main thread by request id; cancel resolves a pending evaluate as pruned so the Python loop unwinds.

  • Python sources come from @local/petrinaut-optimizer-core through ?raw imports at build time; versions come from its runtime-lock.json

    Published package has no runtime dependency on the library.

  • Channel failures map to events

    An abort prunes the trial as cancelled, any other throw fails the run with trial_evaluation_failed, a runtime that fails to load fails the run with a retryable optimizer_unavailable and recreates the worker.

  • Attaching to an unknown run throws an error shaped like an HTTP 404, so the provider drops stale session-storage runs silently
  • Library build emits the worker as an asset referenced relative to the importing module

    ?worker&url with type: "module"; the existing inline workers are unchanged.

Known issues

  • Runtime and packages load from jsDelivr and PyPI at first use

    Self-hosting the Pyodide distribution is a follow-up.

Test coverage

  • browser-optimization.test.ts:

    Event sequence and seq, pruned trials, cancellation of running and pending runs, the 404 shape, channel throws and rejections, worker script errors, worker creation failures, study errors, one-at-a-time queueing, cursor replay and abort, disposal, manifest validation.
    Extend after complete and after a stop, rejection of extend on a running or released run and past the trial cap, release of a running run, stopped queued extension, parallelism validation and inheritance, stale evaluations of a stopped segment, an already-aborted signal.

  • study-runner.pyodide.test.ts:

    Real Pyodide under Node: a seeded 30-trial TPE study, a study of 8 then 8 trials that repeats nothing of its first segment while a seeded restart repeats it exactly, stop after 5 evaluations then resume with continued numbering, parallelism 2 with at most 2 in flight, release and failure dropping the study, deterministic suggestions.
    Skips in CI only when the package download fails.

  • run-log.test.ts, pyodide-config.test.ts, optimization/describe.test.ts:

    Log segments, replay and tailing, micropip requirements from the lock, describe result and seed sequence parity with the CLI fixtures.

  • Existing @hashintel/petrinaut-cli suite:

    Unchanged behaviour through the moved helpers.

How to test

  • yarn workspace @hashintel/petrinaut-core test:unit

    Expect the Pyodide suite to download the runtime once and pass

  • yarn workspace @hashintel/petrinaut-core build

    Check dist/assets contains the optimizer worker and dist/browser-optimization.d.ts imports nothing from @local

@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
hash Ready Ready Preview Sep 4, 2026 12:38pm UTC
petrinaut Ready Ready Preview Sep 4, 2026 12:38pm UTC
petrinaut-docs Ready Ready Preview Sep 4, 2026 12:38pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 4, 2026 12:38pm UTC

Request Review

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (claude/web-optimizer-python-core@2fa8ad3). Learn more about missing BASE report.

Additional details and impacted files
@@                         Coverage Diff                         @@
##             claude/web-optimizer-python-core    #9515   +/-   ##
===================================================================
  Coverage                                    ?   60.89%           
===================================================================
  Files                                       ?     1461           
  Lines                                       ?   146725           
  Branches                                    ?     6744           
===================================================================
  Hits                                        ?    89343           
  Misses                                      ?    56264           
  Partials                                    ?     1118           
Flag Coverage Δ
apps.hash-api 15.41% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kube
kube force-pushed the claude/web-optimizer-core-runtime branch from 186e031 to 289bcf0 Compare September 4, 2026 04:36
@kube
kube force-pushed the claude/web-optimizer-core-runtime branch from 289bcf0 to 5d76a1e Compare September 4, 2026 04:54
@kube
kube force-pushed the claude/web-optimizer-core-runtime branch from 5d76a1e to f47c52d Compare September 4, 2026 11:23
@kube
kube force-pushed the claude/web-optimizer-core-runtime branch from f47c52d to 01e05ef Compare September 4, 2026 12:02
@kube
kube force-pushed the claude/web-optimizer-core-runtime branch from 01e05ef to 44d1e14 Compare September 4, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/deps Relates to third-party dependencies (area) 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

Development

Successfully merging this pull request may close these issues.

1 participant