Repository navigation
refactor(core): make plugin flush an initial readiness barrier #35942
Description
Context
PR #35755 fixes sessions racing plugin activation, but its revision-vector flush contract is stronger and more cross-domain than the behavior we actually need.
Discussion: https://slack.com/archives/C0BE69AHCQP/p1783527674019519
Decision
PluginSupervisor.flush is a one-shot initial-readiness barrier, not a deterministic hot-reload synchronization primitive.
“Initially ready” means:
- at least one complete plugin activation has succeeded;
- all Config/SDK plugin updates observed while startup activation is in progress have been incorporated by subsequent activation passes; and
- the observed update stream has then remained quiet for a bounded debounce interval.
Consequences:
- If plugins continue being registered forever,
flushintentionally waits forever. - After initial readiness completes once, later hot reloads continue in the background but never block
flushagain. - This is intentionally heuristic/event-observed readiness: it does not promise that every source mutation has been applied at every later call site.
Scope constraint
Implement this entirely inside packages/core/src/plugin/supervisor.ts (plus focused supervisor/session tests and the call site needed to await flush). Do not add readiness/revision machinery to:
- Config
- SDK plugin storage
- the plugin registry/activation domain
- Location services
In particular, remove the Config and SDK revision counters introduced by #35755 and avoid a revision vector.
Proposed shape
The supervisor should own:
- a one-shot
Deferred<void>representing initial readiness; - one serialized activation driver;
- local dirty/epoch state derived from the existing
Config.Event.UpdatedandSdkPlugins.Updatedstreams; and - debounced quiescence detection.
Subscribe before starting boot activation so startup updates are observed. Each update marks the supervisor dirty and wakes the driver. Let the current activation finish, then rerun if updates arrived during it. Do not interrupt in-progress activation: registry replacement is currently destructive/non-transactional, so cancellation can expose partial state.
Complete the readiness deferred only after a successful activation and a debounce interval with no newly observed updates. Prefer stream debounce/quiescence signaling over an unexplained inline sleep. Continuous updates must continually reset quiescence.
flush simply awaits that deferred. Session agent resolution should await flush before reading the selected agent.
Do not broaden readiness by making unrelated plugin work synchronous. In particular, keep the OpenAI and OpenCode provider initial refreshes forked; they may resolve credentials or fetch remote provider configuration and can progressively update the catalog after initial plugin activation. flush covers the plugin setup work that plugins already choose to await, not every background task they spawn.
Acceptance criteria
- A session started during boot waits until the first settled plugin generation is active.
- Updates arriving during activation cause another activation before readiness completes.
- Continuous SDK/config plugin updates keep
flushpending. - Once readiness completes, repeated
flushcalls return immediately. - Hot reload still runs after readiness without re-closing the barrier.
- Interrupted session waiters do not cancel the shared supervisor activation.
- No Config/SDK/plugin-registry/Location-service revision or readiness APIs are added.
- Built-in provider refreshes remain asynchronous; this issue does not change provider plugin startup policy.
- Focused tests cover initial activation, updates during activation, continuous-update non-settlement, one-shot completion, and waiter interruption.
Non-goals
- deterministic synchronization with every future config or SDK mutation;
- a generation lease spanning model preparation, streaming, or tool execution;
- transactional/cancellable plugin generation replacement.
Activity
kitlangton commentedon Jul 8, 2026
Implemented in PR #35755 at
f49e25408b. The supervisor owns the one-shotDeferred, serialized activation, local epochs, and 100 ms stream debounce. Startup updates rerun activation, continuous updates keep readiness pending, later reloads never re-close the barrier, and interrupted Session waiters do not own activation.No Config, SDK, registry, or Location readiness revisions were added. Provider refreshes remain forked. The final pass also uses capacity-one sliding queues to conflate stale pending invalidations while preserving the latest current-state activation. Combined focused suites pass (343 tests), and repository typecheck passes (31 tasks). Linux CI is green; Windows CI is still running.
kitlangton commentedon Jul 8, 2026
Implemented and merged in PR #35755 (
5b39972947). The final implementation keeps readiness supervisor-local and one-shot, preserves asynchronous provider refresh and later hot reload, and uses bounded sliding invalidation queues.