Skip to content

FE-1341: Give experiment compute backends one interface - #9178

Open
kube wants to merge 3 commits into
cf/fe-1237-place-token-capacityfrom
cf/fe-1341-swappable-experiment-backends
Open

FE-1341: Give experiment compute backends one interface#9178
kube wants to merge 3 commits into
cf/fe-1237-place-token-capacityfrom
cf/fe-1341-swappable-experiment-backends

Conversation

@kube

@kube kube commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

🌟 What is the purpose of this PR?

Adds @hashintel/petrinaut-core/experiments: one interface both compute backends satisfy, and a registry that picks between them. ExperimentsProvider hardcodes the backend choice today; #9179 registers a backend against this interface instead of rewiring the provider. App behaviour is unchanged.

This PR sits above #9177 in stack #9280, and #9179 builds on it.

🔗 Related links

  • FE-1341 (internal): this PR.
  • FE-1340 (internal): the second backend.
  • FE-1429 (internal): a third consumer, the Petrinaut CLI's seeded optimization trials, adopts this interface for parallelism.

🚫 Blocked by

🔍 What does this change?

Both paths already produce a MonteCarloExperiment, and consumers drive one without branching. That half is untouched. This PR adds four types:

  • ExperimentRequest describes what to compute, as serializable data. Worker factories, the ODE method, signals, and callbacks describe how to compute it, and belong to backend construction or per-call options.
  • ExperimentAssessment is a backend's answer to "can you run this?", as a value. Each blocker carries a code, an optional itemId, and an origin.
  • ExperimentBackend.assess checks the net without acquiring a device or worker pool, then returns an instantiate() closure that reuses that work.
  • selectExperimentBackend walks registrations in preference order and records each refusal.

A blocker's origin tells a UI what to do: model means edit the net, configuration means edit the experiment, environment means hide the option, and capacity means retry or use fewer runs. Each registration's load is deferred, so a heavy backend stays out of a bundle that never uses it.

Adds content/experiments/backend-selection.mdx and the core.experiments architecture layer.

Review fixes

  • selectExperimentBackend treats a thrown assess() or instantiate() as that backend's refusal and moves to the next candidate instead of abandoning the walk. Abort errors are re-thrown, and the walk stops before assessing a candidate when the signal is already aborted, so a cancelled experiment cannot fall through and start on the fallback backend.
  • The worker-pool backend re-throws AbortError instead of reporting the cancellation as a configuration blocker.
  • The provider imports WORKER_POOL_BACKEND_ID instead of duplicating the literal.
  • The module docstring moved above the imports, and the load() doc no longer claims a promise cache that does not exist.
  • A patch changeset covers the new @hashintel/petrinaut-core/experiments entry point.

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

  • One backend is registered here, so assess never refuses and the blocker types stay unexercised until FE-1340: Add an experimental WebGPU compute backend for experiments #9179.
  • Worker pools and GPUDevice are still acquired per experiment. Reuse depends on the worker resetting frame and metric state; a worker that kept state would produce plausible-looking wrong results.

🐾 Next steps

Supply backends through a React context, so an embedder can register its own.

🛡 What tests cover this?

select-experiment-backend.test.ts runs 9 cases covering preference order, deferred loading, refusal recording, instantiation failure, blocker ordering, unavailable backends, module load failure, and per-requirement request building.

❓ How to test this?

Run an experiment. Behaviour is unchanged.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview Aug 22, 2026 3:06am
petrinaut Ready Ready Preview Aug 22, 2026 3:06am
petrinaut-docs Ready Ready Preview Aug 22, 2026 3:06am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Aug 22, 2026 3:06am

@github-actions github-actions Bot added area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team labels Aug 8, 2026
@kube
kube force-pushed the cf/fe-1341-swappable-experiment-backends branch from 976a2dd to afff6ce Compare August 13, 2026 08:20
@github-actions github-actions Bot added the type/eng > backend Owned by the @backend team label Aug 13, 2026
@kube
kube force-pushed the cf/fe-1341-swappable-experiment-backends branch from afff6ce to 8ad93e6 Compare August 14, 2026 23:53
@kube
kube force-pushed the cf/fe-1341-swappable-experiment-backends branch from 8ad93e6 to 1456a69 Compare August 15, 2026 00:51
@kube
kube force-pushed the cf/fe-1341-swappable-experiment-backends branch from 914345d to c46fb0a Compare August 18, 2026 00:05
@kube
kube force-pushed the cf/fe-1341-swappable-experiment-backends branch from c46fb0a to 6e22a1b Compare August 18, 2026 00:33
@cursor

cursor Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches experiment startup in petrinaut-core and ExperimentsProvider, including abort/fallback semantics. Runtime path is still the same CPU worker pool, so user-facing risk is moderate rather than a new compute engine.

Overview
Adds a dedicated @hashintel/petrinaut-core/experiments entry so Monte Carlo compute backends share one contract and can be chosen without editing the React provider.

ExperimentRequest is serializable “what to compute”; host wiring (workers, signals, callbacks) stays on backend construction or instantiate. assess returns structured blockers (model / configuration / environment / capacity) plus an instantiate() closure so eligibility never holds a device. selectExperimentBackend walks registrations in preference order, lazy-loads them, records every refusal, and treats thrown assess/instantiate as fallback (except abort).

The worker-pool path is the first implementation (id: "cpu"); it always assesses eligible and maps engine errors to configuration blockers. ExperimentsProvider now selects through that registry instead of calling createMonteCarloExperiment directly. App behavior is unchanged with a single backend registered.

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

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 was unable to review this pull request because the user who requested the review has reached their quota limit.

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 was unable to review this pull request because the user who requested the review has reached their quota limit.

@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 b75157f. Configure here.

selection.declined
.map((entry) => `${entry.backendId}: ${entry.reason}`)
.join("; ") || "No compute backend could run this experiment.",
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Prefixed setup failure messages

Low Severity

When the only registered backend refuses during instantiate, setup failures are rethrown as `${backendId}: ${reason}`. That reaches experiment status and error notifications, so users now see a cpu: prefix that was not present when createMonteCarloExperiment failed directly. The PR claims no behaviour change.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b75157f. Configure here.

kube added 3 commits August 22, 2026 02:53
Choosing a backend was hardcoded in ExperimentsProvider. The runtime
half of swappability already worked — a backend produces a
MonteCarloExperiment and consumers drive one with no branching — so
this adds only the missing part: asking a backend whether it can run a
net, and choosing between backends when one declines.

A request is plain serializable data; anything describing how to
compute belongs to the backend's construction or to per-call options.
Refusal is a value carrying structured blockers with a code, an item
id and an origin, so a UI can attribute a problem to the item that
caused it. Assessment settles the net without acquiring a device or
worker pool and hands back an instantiate() closure.

Pure refactor: no user-visible behaviour changes. Registrations carry
a deferred load so a heavy backend need not enter the bundle.
Replaces em dashes and drops 'deliberately' in
backend-selection.mdx. No content change.
Rewrites the module docstrings in src/experiments to state what each
file is before why it is that way, and replaces em dashes. No code
change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) 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.

2 participants