Skip to content

fix(agent-sdk): add session timeout to ActionWizard to prevent memory leaks#1767

Open
mvanhorn wants to merge 2 commits intoxmtp:mainfrom
mvanhorn:fix/wizard-session-timeout
Open

fix(agent-sdk): add session timeout to ActionWizard to prevent memory leaks#1767
mvanhorn wants to merge 2 commits intoxmtp:mainfrom
mvanhorn:fix/wizard-session-timeout

Conversation

@mvanhorn
Copy link
Copy Markdown

@mvanhorn mvanhorn commented Mar 19, 2026

Summary

Adds lazy session expiration to ActionWizard so abandoned sessions don't accumulate in memory indefinitely.

Why this matters

ActionWizard stores user sessions in a Map (#sessions, ActionWizard.ts:83). Sessions are created in start() (L149-156) and only deleted via explicit cancel (#handleCancel, L193) or wizard completion (#advance, L209). If a user abandons a wizard mid-flow - closes the app, loses network, or stops responding - the session entry persists forever.

For a long-running agent serving 100 users/day with a 10% abandonment rate, that's ~10 orphaned sessions/day, ~300/month, growing without bound.

Changes

  • Added createdAt timestamp to ActionWizardSession (set in start())
  • Added sessionTimeoutMs option to ActionWizardOptions (default: 30 minutes)
  • Added lazy expiration check in middleware(): when a message arrives for a session older than the timeout, the session is expired via the existing #handleCancel path, then control passes to next()

The lazy approach avoids adding a background setInterval, which would complicate agent lifecycle management and require explicit cleanup on shutdown. Sessions are checked and cleaned only when a message arrives for that conversation+sender pair.

Testing

  • yarn format:check passes
  • yarn typecheck passes

This contribution was developed with AI assistance (Claude Code). I have reviewed all changes and can discuss implementation decisions.

Related: PR #1743 - original ActionWizard middleware by @bennycode

Note

Add session timeout to ActionWizard to prevent memory leaks from abandoned sessions

  • Adds a sessionTimeoutMs option to ActionWizardOptions, defaulting to 30 minutes, controlling the maximum idle time before a session is auto-expired.
  • Sessions now track createdAt and lastActivityAt timestamps; lastActivityAt is refreshed on each wizard step advance.
  • Before handling any message, the middleware checks idle time and calls #handleCancel on sessions that have exceeded the timeout.
  • Behavioral Change: previously abandoned sessions persisted indefinitely; they now cancel automatically after 30 minutes of inactivity by default.

Macroscope summarized 7166864.

… leaks

Sessions in ActionWizard are stored in a Map and only removed on
explicit cancel or completion. If a user abandons a wizard mid-flow
(closes the app, loses network, or stops responding), the session
entry persists in memory indefinitely.

For long-running agents with many users, this causes unbounded memory
growth proportional to the abandonment rate.

This adds lazy session expiration: when a message arrives for a session
older than sessionTimeoutMs (default: 30 minutes), the session is
expired and the cancel handler is called. This avoids background timers
while still preventing accumulation of orphaned sessions.
@mvanhorn mvanhorn requested review from a team as code owners March 19, 2026 20:08
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 19, 2026

🦋 Changeset detected

Latest commit: 7166864

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@xmtp/agent-sdk Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 19, 2026

@mvanhorn is attempting to deploy a commit to the XMTP Labs Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium

The sessionTimeoutMs option is documented as an idle timeout, but createdAt is set only at session start and never updated. Sessions that take longer than the timeout to complete are expired even when the user is actively responding. To match the documented behavior, update createdAt (or add lastActivityAt) in #advance() whenever the user provides input.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file sdks/agent-sdk/src/middleware/ActionWizard.ts around line 197:

The `sessionTimeoutMs` option is documented as an idle timeout, but `createdAt` is set only at session start and never updated. Sessions that take longer than the timeout to complete are expired even when the user is actively responding. To match the documented behavior, update `createdAt` (or add `lastActivityAt`) in `#advance()` whenever the user provides input.

Evidence trail:
sdks/agent-sdk/src/middleware/ActionWizard.ts:
- Line 62: Documentation says `sessionTimeoutMs` is for 'idle' timeout
- Lines 137-143: `createdAt` set only in `start()` method
- Lines 185-199: `#advance()` method does not update `createdAt` or any activity timestamp
- Lines 215-219: Timeout check uses `session.createdAt` to determine expiration

@macroscopeapp
Copy link
Copy Markdown

macroscopeapp Bot commented Mar 19, 2026

Approvability

Verdict: Needs human review

1 blocking correctness issue found. While this is a straightforward bug fix adding session timeout to prevent memory leaks, the author does not own any of the modified files. Both files are owned by other teams (@xmtp/documentation and @xmtp/protocol-sdk), so designated code owners should review these changes.

You can customize Macroscope's approvability policy. Learn more.

Track lastActivityAt on each user response so the session timeout
measures idle time, not total elapsed time. Sessions with active
users no longer expire prematurely.
@mvanhorn
Copy link
Copy Markdown
Author

Fixed in 7166864: timeout now tracks lastActivityAt (updated on each #advance()) instead of createdAt. Active sessions won't expire prematurely.

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.

1 participant