Skip to content

feat: sticky session for multi-turn conversation continuity#162

Merged
dwgx merged 2 commits into
dwgx:masterfrom
you922:feat/sticky-session
May 12, 2026
Merged

feat: sticky session for multi-turn conversation continuity#162
dwgx merged 2 commits into
dwgx:masterfrom
you922:feat/sticky-session

Conversation

@you922

@you922 you922 commented May 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in sticky session feature that binds a caller (identified by callerKey) to a specific upstream Windsurf account for the duration of a multi-turn conversation.

Problem

When using Claude Code, Cursor, or any multi-turn agent with WindsurfAPI, the account pool selects a different account for each request. The cascade_id from the previous turn is only valid on the originating account — switching accounts mid-conversation causes context loss (#93, #133).

Solution

New module src/account/sticky-session.js maintains an in-memory binding map:

  • Key: (callerKey, modelKey) — model dimension prevents cross-model collision
  • Binding created when a response completes successfully
  • Binding checked on the next request before falling through to normal pool selection
  • Auto-cleared when the bound account becomes unavailable (rate limited, banned)
  • TTL-based expiry: 30 minutes by default
  • LRU eviction when at capacity (default: 10,000)

Integration

  • src/auth.js: getApiKey() now accepts optional callerKey parameter, checks sticky binding first
  • src/handlers/chat.js: calls setStickyBinding() after successful response

Configuration (opt-in)

STICKY_SESSION_ENABLED=1        # default: 0 (off)
STICKY_SESSION_TTL_MS=1800000   # default: 30 minutes
STICKY_SESSION_MAX=10000        # default: 10000 max bindings

Backward Compatibility

  • STICKY_SESSION_ENABLED defaults to 0 (off)
  • callerKey is null by default in getApiKey(), existing callers unaffected
  • No breaking changes to public API

Closes #93, #133

Chen Jiangjiang added 2 commits May 10, 2026 17:02
Add account/sticky-session.js module that binds (callerKey, modelKey)
to a specific upstream account so multi-turn conversations stay on the
same account throughout their lifetime. Without this, the pool selects
a different account for every request and the cascade_id from the
previous turn becomes invalid — context is lost mid-task.

Integration:
- auth.js getApiKey() now accepts optional callerKey and checks
  sticky binding before falling through to normal pool selection
- chat.js handler calls setStickyBinding() after successful
  response completion, binding the caller to the account that
  just handled their last message

Configuration (opt-in):
  STICKY_SESSION_ENABLED=1        # default: 0 (off)
  STICKY_SESSION_TTL_MS=1800000   # default: 30 minutes
  STICKY_SESSION_MAX=10000        # default: 10000 max bindings

Fixes: dwgx#93, dwgx#133 (context loss mid-task)
The sticky session binding was only set in nonStreamResponse,
missing the primary streaming path used by Claude Code, Cursor,
and all OpenAI stream:true clients.

Added setStickyBinding call in streamResponse success path
(after poolCheckin, guarded by isStickyEnabled()).
@dwgx dwgx merged commit dd6e1bf into dwgx:master May 12, 2026
dwgx pushed a commit that referenced this pull request Jun 4, 2026
* feat: sticky session for multi-turn conversation continuity

Add account/sticky-session.js module that binds (callerKey, modelKey)
to a specific upstream account so multi-turn conversations stay on the
same account throughout their lifetime. Without this, the pool selects
a different account for every request and the cascade_id from the
previous turn becomes invalid — context is lost mid-task.

Integration:
- auth.js getApiKey() now accepts optional callerKey and checks
  sticky binding before falling through to normal pool selection
- chat.js handler calls setStickyBinding() after successful
  response completion, binding the caller to the account that
  just handled their last message

Configuration (opt-in):
  STICKY_SESSION_ENABLED=1        # default: 0 (off)
  STICKY_SESSION_TTL_MS=1800000   # default: 30 minutes
  STICKY_SESSION_MAX=10000        # default: 10000 max bindings

Fixes: #93, #133 (context loss mid-task)

* fix: add sticky binding in streaming response path (Oracle review)

The sticky session binding was only set in nonStreamResponse,
missing the primary streaming path used by Claude Code, Cursor,
and all OpenAI stream:true clients.

Added setStickyBinding call in streamResponse success path
(after poolCheckin, guarded by isStickyEnabled()).

---------

Co-authored-by: Chen Jiangjiang <chenjiangjiang@kt51.cc>
dwgx pushed a commit that referenced this pull request Jun 4, 2026
* feat: sticky session for multi-turn conversation continuity

Add account/sticky-session.js module that binds (callerKey, modelKey)
to a specific upstream account so multi-turn conversations stay on the
same account throughout their lifetime. Without this, the pool selects
a different account for every request and the cascade_id from the
previous turn becomes invalid — context is lost mid-task.

Integration:
- auth.js getApiKey() now accepts optional callerKey and checks
  sticky binding before falling through to normal pool selection
- chat.js handler calls setStickyBinding() after successful
  response completion, binding the caller to the account that
  just handled their last message

Configuration (opt-in):
  STICKY_SESSION_ENABLED=1        # default: 0 (off)
  STICKY_SESSION_TTL_MS=1800000   # default: 30 minutes
  STICKY_SESSION_MAX=10000        # default: 10000 max bindings

Fixes: #93, #133 (context loss mid-task)

* fix: add sticky binding in streaming response path (Oracle review)

The sticky session binding was only set in nonStreamResponse,
missing the primary streaming path used by Claude Code, Cursor,
and all OpenAI stream:true clients.

Added setStickyBinding call in streamResponse success path
(after poolCheckin, guarded by isStickyEnabled()).

---------

Co-authored-by: Chen Jiangjiang <chenjiangjiang@kt51.cc>
dwgx pushed a commit that referenced this pull request Jun 4, 2026
* feat: sticky session for multi-turn conversation continuity

Add account/sticky-session.js module that binds (callerKey, modelKey)
to a specific upstream account so multi-turn conversations stay on the
same account throughout their lifetime. Without this, the pool selects
a different account for every request and the cascade_id from the
previous turn becomes invalid — context is lost mid-task.

Integration:
- auth.js getApiKey() now accepts optional callerKey and checks
  sticky binding before falling through to normal pool selection
- chat.js handler calls setStickyBinding() after successful
  response completion, binding the caller to the account that
  just handled their last message

Configuration (opt-in):
  STICKY_SESSION_ENABLED=1        # default: 0 (off)
  STICKY_SESSION_TTL_MS=1800000   # default: 30 minutes
  STICKY_SESSION_MAX=10000        # default: 10000 max bindings

Fixes: #93, #133 (context loss mid-task)

* fix: add sticky binding in streaming response path (Oracle review)

The sticky session binding was only set in nonStreamResponse,
missing the primary streaming path used by Claude Code, Cursor,
and all OpenAI stream:true clients.

Added setStickyBinding call in streamResponse success path
(after poolCheckin, guarded by isStickyEnabled()).

---------

Co-authored-by: Chen Jiangjiang <chenjiangjiang@kt51.cc>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 上下文会丢

2 participants