Repository navigation
fix(session): isolate stalled presentation probes by scope family - #405
Conversation
PTC and discovery assembled the whole prompt once per tracked agent on every reconcile, so creating one session paid for every session still tracked. 38 bundles on Windows x64 grew 264/308/396/475/590/681/748/947 ms across eight session/create calls; with this change the same eight rounds measure 195/226/270/260/295/329/358/373 ms. Each agent now memoizes its presentation verdict against the tool names it publishes and re-probes only when that set changes. reconcile() takes the agents a change actually touches, and the publish-time snapshot reads names instead of rebuilding a scope view per tool. Refs #333, #359
`reconcile` advanced `epoch` on every call, so the passes the new `targets` argument turns into no-ops still invalidated every published `execute` guard and every Native shadow. Those then answered POLICY_DENIED for a scope that was still authoritative, which is why `discovery-composition` lost its combined Native/PTC checks and `ptc` lost its presentation-change race. A probe also recorded its surface on the entry before the republish that consumes it ran. A pass a later reconcile superseded therefore left the next pass reading that surface as already published, returning early forever; the PTC verdict never took effect and `native-result` timed out waiting for the Native/PTC projections. A separate `published` counter now moves only when a pass actually republishes. `ptc`'s shadows and `discovery`'s guards compare it, because `discovery`'s probe assembles the prompt and that reconciles other scopes. Probed surfaces stay pending until the republish commits them. Verified: `npm run typecheck`, `npm run verify` — 125 of 127 suites pass, the two remaining are the local-only failures `bash-ipc` and `permissions`, which need the pinned runtime receipts this checkout does not build and fail identically on b963fb2.
…napshot A departure passed an empty target set, which the pass normalized to "no restriction" and then probed every tracked agent on the one pass that has nothing to probe. An empty set is now a probe-free pass: it names nobody to probe while still asking for the republish a departure owes. The per-publish snapshot now reads tool names through one src/surface.ts helper instead of an identical surfaceOf in each plugin, and both memo keys document that names are the whole key while the presentation verdict also reads the tools:ptc-only section text, so a flip without a name change costs one pass and never a permanent wrong surface.
`surfaceOf` asked `tools.schemas()` for the names it memoizes, and that projector rebuilds one deep copy of every visible parameter schema per call. Read the same visible map through `tools.view(scope)` instead, keeping `schemas()` as a fallback for a runtime that exposes no view, and pair each name with a `WeakMap` identity for the definition the view holds. A names-only key returns a stale hit when a scoped registration shadows a global of the same name, so missing the shadow publishes nothing on the change that owed a republish. Identity costs one lookup against an object already in hand rather than the `tools.get()` pairing that would rebuild the whole view per name.
visibleTools (the tooling:shell/file prompt section and the host-shell denial gate) asked tools.schemas() and kept only the names, paying one deep copy of every visible definition per assembly: 34.2 ms for 258 tools at the issue's own surface shape on the published 0.2.1-rc.2 runtime, against 0.043 ms through tools.view() (~800x). Cost tracks nested schema nodes, not payload bytes (811 KB flat = 16.5 ms; 705 KB nested = 34.2 ms), so the fixture is pinned to the issue's measurement. visibleNames() reads view().keys() with schemas() as the fallback for a host whose view is not reachable, so correctness outranks the cost there. The discovery pre-step assembly is deliberately left alone: it is the self-correction path for a presentation that flips without the names moving, and memoizing it there breaks the ptc-sdk projections (#369). Tests assert the two review gaps as well: a tools/change with no registry movement reconciles from the discovery probe memo without assembling, and the denial gate restricts without projecting schemas. Both fail when reverted. Stacked on #368: surface.ts and the view machinery come from there.
…efresh # Conflicts: # docs/CHANGELOG.md
|
Combined compatibility has now been checked on work/reviewed-integration-20261025, commit It preserves #344, #381, #383 and #388–#405, with prerequisite ancestry reused. Draft/NO-MERGE #362 is excluded. Competing changelog additions were preserved together; the only workflow conflict was a comment. Source changes combined without textual conflicts. No GitHub PR was merged. Integration workflow 40115118596 passed all twelve jobs, including Windows, Linux, macOS arm64, macOS Intel, Node-floor and all three optional model-free benchmark smoke legs. Local combined verification also passed build/typecheck, 89 workflow checks, 133 manifest checks, the 426-file size gate, 23 benchmark/session/review/manifest suites and five metadata/settings/policy/presentation suites. The local catalogue suite skipped for a missing integration worker and is not counted as a local pass; the complete platform CI gate supplies the runtime coverage. Dependency review order remains #383 → #390 → #405, #391 → #396, and #381 → #399 → #402 → #404. #404 also contains #401's all-target CI change and targets #402's branch to keep its own diff small. The integration branch retains the cross-PR conflict resolutions for review. #261, #207, #303 and #360 remain open under the requested exceptions. Administrator enforcement, Desktop validation and the controlled #360 model experiment are not inferred from these passing gates. |
The merge-base changed after approval.
Fixes #359. Refs #346. Depends on #383 and #390 (their commits are included; merge those first to narrow this diff).
What was wrong
PTC and discovery each put every tracked agent's presentation probe on one shared promise chain. A real
system-prompt/assemblehook that never resolved for one root blocked creation and prompt assembly in a second root, and unload waited on that unanswered hook. Memoization reduced probe cost but did not remove this dependency.The change
Run probe work independently for each inheritance family. Ancestor/descendant overlays still publish atomically; unrelated roots have separate readiness and execution barriers. Supersession, agent disposal and component unload cancel the obsolete OPS wait even when a host hook ignores its signal. Late responses cannot commit registrations or run an old assembly's withdrawal guard against a newer publication.
Discovery uses a synchronous registration helper for its already-known exact agent scope, keeping the whole family commit synchronous. Mutable session/policy input checks, attributed invalidation, cached verdicts, ownership precedence and live invocation checks remain intact. Fresh pre-step and callback probes forward/check cancellation before consuming or adding selections.
This proves the shared-queue failure class, not reproduction of the original 36-plugin Desktop freeze. An uncooperative hook can still delay its own inheritance family, or its own direct turn/call probe; this change adds no timeout reset and does not manufacture a successful presentation verdict. The repository does not own another plugin's underlying promise. No new dependencies, schemas, argument contracts or runtime binaries changed.
The follow-up itself adds fewer than 200 implementation lines. Base is main so the repository's existing
pull_requestbranch filter runs; #383/#390 are preserved without rewriting their history.Validation
edf70d8: all eight initial isolation checks failed against the old implementation. Removing only the cancelled-waterfall guards failed exactly the two stale-withdrawal checks; restoration passed. Four further parent-child/direct-cancellation checks were added after independent review, giving twelve passing checks.cb162e5passed every executed check, including Windows, Linux, macOS, Node-floor and CI required. Conditional benchmark smoke was skipped as designed.Environment
Windows x64, Node v24.19.0, real locked DSH host packages. Based on #390
edf70d8, including #383 and main662a388. Native source is unchanged from main; staged worker SHA-256d0df8353a57ecda0abe13dc55989012baa61f11bcb484dc26550935a070a379c. Publication-only regressions point at the current Node executable and assert that no worker is spawned. No provider/model request or Desktop recovery is claimed.