Skip to content

fix(desktop): thread foreground spawn priority through requestGatewayForAgent/retainGatewayForAgent - #105390

Closed
nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/gateway-foreground-spawn-priority
Closed

nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/gateway-foreground-spawn-priority

Conversation

@nftpoetrist

Copy link
Copy Markdown
Contributor

Summary

requestGatewayForAgent/retainGatewayForAgent are the session-scoped RPC lease pair createBackendSessionForSend (send on a fresh chat) and openNewSessionTile ("New session" row / tab-strip "+") dial through when the target registry route isn't already open. Neither function ever forwards a spawnPriority, so every dial through them defaults to 'background' — even though both call sites are a direct user click.

The #102281/#104139 chain (merged) tagged every activation-door dial (openGatewayForAgent, openGatewayForProfile, ensureGatewayForAgent, sharedPrimaryRoute) as 'foreground' specifically so a user-initiated open takes a reserved pool slot instead of queuing behind background roster-hydration spawns. requestGatewayForAgent/retainGatewayForAgent were not part of that chain's diff (verified against PR #104139's actual patch — neither function name appears in it), so they still hit the same class of bug the chain fixed: on a saturated pool (maxBackends = 3), a user's first message to a not-yet-dialed bot, or a "New session" click, can queue behind background hydration for up to the pool slot wait timeout.

Change

  • gateway.ts: add an optional spawnPriority parameter to requestGatewayForAgent (7th positional arg) and retainGatewayForAgent (3rd positional arg), both defaulting to 'background' (matching every other dial function in this file, e.g. openGatewayForAgent/openGatewayForProfile). Thread it into their internal isAttachedSharedRemote/openSecondary calls — the same two chokepoints the activation-door functions already use.
  • use-session-actions/index.ts: pass 'foreground' from the two confirmed user-initiated call sites — createBackendSessionForSend's retainGatewayForAgent/requestGatewayForAgent('session.create', ...) pair, and openNewSessionTile's identical pair.

Deliberately left unchanged:

  • The session.close cleanup calls in both functions (failure-path cleanup, not a user-facing dial).
  • createSessionBranch's requestGatewayForAgent call (line ~2082) — it runs after ensureGatewayAgent, which already dials at foreground priority internally (ensureGatewayForAgent hardcodes 'foreground', it doesn't take a priority param), so by the time this call runs the socket is already open and the priority argument would be a no-op.
  • Every other requestGatewayForAgent/retainGatewayForAgent caller (session-import/api.ts, sdk/index.ts, api/mcp.ts, session-request-router.ts) — these are import/plugin/OAuth/generic-routed paths, not direct user-open clicks.

Verification

  • New regression tests in gateway-spawn-priority.test.ts (mirroring the existing #102281 test file's pattern): both functions now tag the registry dial 'foreground' when asked, and leave it untagged by default.
  • Mutation-verify: reverted the gateway.ts change only, confirmed the two new positive tests fail (expected false to be true), reapplied, confirmed all pass again.
  • Full apps/desktop/src/store/gateway*.test.ts suite: 14 files, 107 tests, all passing.
  • apps/desktop/src/app/session/hooks/use-session-actions.test.tsx: 105 tests passing (one pre-existing exact-arg assertion on the createBackendSessionForSend → requestGatewayForAgent call updated to include the new trailing args).
  • tsc --noEmit: clean.

Competing PRs

Checked #101554, #100467, #101482, #103275, #104871 (all open, adjacent territory — bot-mode routing, floating pets, session-tile pinning, cross-profile bridge timeouts, LRU pool eviction) and the closed/merged #102281 chain PRs (#104139 merged, #102496 closed-salvaged-into-104139). None touch requestGatewayForAgent/retainGatewayForAgent — confirmed by grepping the actual diffs, not just titles. #104871's gateway.ts hunks (retainGatewayForSessionTurn, openSecondaryCount) don't overlap the lines this PR touches.

Test plan

  • npx vitest run src/store/gateway-spawn-priority.test.ts — 8/8 passing
  • npx vitest run src/store/gateway*.test.ts — 14 files / 107 tests passing
  • npx vitest run src/app/session/hooks/use-session-actions.test.tsx — 105/105 passing
  • npx tsc -p tsconfig.json --noEmit — clean
  • Mutation-verify on the two new positive tests

…ForAgent/retainGatewayForAgent

The NousResearch#102281/NousResearch#104139 chain tagged every activation-door dial
(openGatewayForAgent/openGatewayForProfile/ensureGatewayForAgent/
sharedPrimaryRoute) as 'foreground' so a user-initiated open takes a
reserved pool slot instead of queuing behind background roster
hydration. requestGatewayForAgent and retainGatewayForAgent never
got a spawnPriority parameter, so they always dialed 'background' —
and they are the only dial path createBackendSessionForSend (send on
a fresh chat) and openNewSessionTile ("New session" / tab-strip "+")
use. On a saturated pool (max 3 local backends), a user's first
message or new-tab click could queue behind background hydration for
up to the pool slot wait timeout.

Add an optional spawnPriority parameter (defaulting to 'background',
matching every other dial function in this file) to both functions
and thread it into their isAttachedSharedRemote/openSecondary calls.
Pass 'foreground' from the two confirmed user-initiated call sites.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) labels Sep 7, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference; please use your judgment.

Summary

Threads the #102281 foreground spawn priority through requestGatewayForAgent/retainGatewayForAgent (new trailing spawnPriority param, default 'background') and marks both user-initiated session-creation paths (first-message send, "New session" click) as foreground, so a saturated pool no longer queues the user's click behind background roster hydration.

Findings

  • Non-blocking, note: apps/desktop/src/store/gateway.ts:163 — the default 'background' preserves existing behavior for all other callers; only the two user-click paths pass 'foreground', so background work can't accidentally inherit priority.
  • Non-blocking, note: apps/desktop/src/app/session/hooks/use-session-actions/index.ts:40-43 — the undefined, undefined positional placeholders for timeoutMs/signal are slightly brittle against future signature reordering, but match the existing call style and are covered by the updated expectation test.
  • Positive: both polarities tested (gateway-spawn-priority.test.ts:107-151) — with-priority forwards foreground, without-priority never tags the dial — pinning no background caller regresses into foreground.

Verdict

Looks good. Narrow, well-tested fix for a real UX hang. Safe to merge.

@bounce12340

Copy link
Copy Markdown
Contributor

Related / complementary work: #104871 (ours) reclaims the oldest idle pool resident for later foreground dials when the pool is already full (#102281 follow-up after #104139).

This PR threads foreground spawnPriority through requestGatewayForAgent / retainGatewayForAgent so those dials aren't stuck on 'background'.

You noted no line-level overlap with #104871 — agreed these look complementary. Happy for maintainers to review both together; they can coexist, or we can follow guidance on how to split/merge.

@austinpickett

Copy link
Copy Markdown
Collaborator

Superseded by #113269.

Kept your fix as-is in spirit: requestGatewayForAgent / retainGatewayForAgent were the one untagged dial pair left after #102281, and the four session-create call sites now pass foreground. Two changes: the priority rides an options object ({ spawnPriority }) to match requestGatewayForProfile on main, and the retain's plain-profile branch (gatewayForProfile(key, true, ...)) is tagged too, with tests for both polarities proven red on main. You are the author of that commit. Thanks @nftpoetrist.

austinpickett added a commit that referenced this pull request Sep 16, 2026
… foreground priority (#113269)

* fix(desktop): foreground spawn priority through requestGatewayForAgent/retainGatewayForAgent and the session-create dials

The #102281/#104139 chain tagged every activation-door dial
(openGatewayForAgent/openGatewayForProfile/ensureGatewayForAgent/
sharedPrimaryRoute) as 'foreground' so a user-initiated open takes the
pool's reserved slot instead of queuing behind background roster
hydration. requestGatewayForAgent and retainGatewayForAgent never got a
spawnPriority, so they always dialed 'background', and they are the only
dial path createBackendSessionForSend (first send on a fresh chat) and
openNewSessionTile ("New session" / tab-strip "+") use. On a saturated
3-slot local pool the user's first message or new-tab click could wait
out the background slot timeout.

Add `{ spawnPriority }` options (main's requestGatewayForProfile shape,
5eb0ed4) to both functions and forward it into every dial they make:
requestGatewayForProfile on the scope===key and primary-registry
branches, isAttachedSharedRemote, openSecondary, and the plain-profile
retain branch's gatewayForProfile(key, true, ...) which neither #105390
nor #110354 covered. The four session-create call sites pass
'foreground'.

Tests: gateway-spawn-priority.test.ts gains both polarities for
request/retain plus the plain-profile retain branch; the
use-session-actions and default-new-session matchers pin the tagged
dial. SpawnPriority is now exported for the SDK.

* fix(desktop): canonical Bot Chat lookup dials foreground through host.requestProfile options

Clicking a bot in the roster runs findExistingCanonicalChat ->
requestForBot(bot, 'session.list') -> host.requestProfile(route, ...)
-> requestGatewayForAgent with no intent, so the first RPC of the
gesture, the one that cold-spawns the bot's backend on a local pool,
dialed 'background'. With three roster backends already hydrating the
click produced no backend activity and the fail-closed lookup surfaced
as a "try again" toast (#105104). The later host.openSession is already
foreground, but it never runs until this lookup returns.

host.requestProfile gains a fifth `options?: { spawnPriority }`
argument; `timeoutMs` stays the fourth positional so no existing caller
changes shape, and the SDK keeps its exact call arity when no options
are given. requestForBot takes the same options and forwards them only
when set, so passive roster warming (profiles.list, ui_meta) still
dials background. The canonical `session.list` and the `session.create`
that follows on a first-ever open both pass 'foreground'.

Tests: profile-routing.test.ts (options form with and without a
timeout), routing.test.ts (tag forwarded, untagged call keeps three
args), canonical-chat-registry.test.ts (session.list and session.create
carry foreground; session.title does not).

---------

Co-authored-by: nftpoetrist <264138787+nftpoetrist@users.noreply.github.com>
Co-authored-by: jxfjosh <282433633+jxfjosh@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants