Skip to content

fix(slack): reduce token bloat by skipping thread context on existing sessions#32133

Merged
steipete merged 2 commits intoopenclaw:mainfrom
sourman:fix/slack-thread-context-bloat
Mar 2, 2026
Merged

fix(slack): reduce token bloat by skipping thread context on existing sessions#32133
steipete merged 2 commits intoopenclaw:mainfrom
sourman:fix/slack-thread-context-bloat

Conversation

@sourman
Copy link
Contributor

@sourman sourman commented Mar 2, 2026

Summary

Thread history and thread starter were being fetched and included on every message in a Slack thread, causing unnecessary token bloat. The session transcript already contains the full conversation history, so re-fetching and re-injecting thread history on each turn is redundant.

What Changed

  • src/slack/monitor/message-handler/prepare.ts:551 - Only fetch thread history for NEW sessions (!threadSessionPreviousTimestamp)
  • src/slack/monitor/message-handler/prepare.ts:641 - Only include ThreadStarterBody for NEW sessions
  • Updated test to reflect new behavior

Before vs After

Before: Every message in a Slack thread included [Thread history - for context] with the entire thread conversation, growing larger each turn.

After: Thread history context wrapper is not needed since the agent has context form response. No further context is included

Session Evidence

Message 2 ("checking in"):

[Thread history - for context]
[Slack Ahmed Mansour] <@U0AF3PBGARJ> ping
[Slack comply] Pong. What do you need?
...

Message 3 ("what is the fifth letter in four"):

[Thread history - for context]
[... entire conversation repeated again ...]
[Slack Ahmed Mansour] checking in
[Slack comply] All clear here...

With this fix, messages do NOT include the redundant thread history wrapper.

Impact

  • Cost: Fewer API calls to Slack for thread history (only on first message)
  • Tokens: Significant reduction for multi-turn thread conversations
  • Latency: Reduced per-message processing time

Test Results

  • All 345 Slack tests pass
  • Updated test skips loading thread history when thread session already exists in store (bloat fix) to verify new behavior

Fixes #32121

@openclaw-barnacle openclaw-barnacle bot added channel: slack Channel integration: slack size: XS labels Mar 2, 2026
@sourman sourman force-pushed the fix/slack-thread-context-bloat branch from 48420ad to 012d84e Compare March 2, 2026 21:02
@sourman sourman marked this pull request as ready for review March 2, 2026 21:05
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 012d84ec17

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

});
if (threadInitialHistoryLimit > 0) {
// Only fetch thread history for NEW sessions (existing sessions already have this context in their transcript)
if (threadInitialHistoryLimit > 0 && !threadSessionPreviousTimestamp) {

Choose a reason for hiding this comment

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

P1 Badge Check thread session freshness before skipping context preload

This condition treats any existing store entry as an active thread session, but session reuse is actually gated by freshness in initSessionState (src/auto-reply/reply/session.ts:411-424) and stale entries are reset (default mode is daily in src/config/sessions/reset.ts:20-23). In the common case where a thread has an old updatedAt (or idle-expired entry), prepareSlackMessage now skips loading thread history/starter context even though a brand new agent session will be created, so the first message after reset loses prior thread context and can produce incorrect replies.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Greptile Summary

Prevents redundant thread context from being fetched and included on every message in a Slack thread, significantly reducing token bloat and API calls for multi-turn conversations.

Key Changes:

  • Only fetches thread history for NEW thread sessions (!threadSessionPreviousTimestamp) - existing sessions already have complete conversation history in their transcript
  • Only includes ThreadStarterBody for NEW sessions - subsequent turns don't need this re-injected
  • Reduces API calls from 2 per message (starter + history) to 1 per message (starter only for metadata/media) for existing sessions

Implementation:

  • prepare.ts:556 - Added && !threadSessionPreviousTimestamp condition to thread history fetch
  • prepare.ts:647 - Made ThreadStarterBody conditional based on session existence
  • Thread starter is still fetched for ALL messages (needed for thread labels and media fallback), but its body is only included in context for first turn
  • Test updated to validate that existing sessions skip both history fetch and body inclusion

Impact:

  • Significant token savings for multi-turn thread conversations
  • Reduced Slack API calls (only on first message per thread)
  • Faster per-message processing for existing thread sessions
  • All 345 Slack tests pass

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk - changes are focused, well-tested, and backward compatible
  • Score reflects: (1) straightforward implementation with only two conditional checks added, (2) comprehensive test coverage for both new and existing session scenarios, (3) all 345 Slack tests passing, (4) well-motivated optimization addressing real token bloat issue, (5) backward compatible with no breaking changes, (6) clear code comments explaining rationale
  • No files require special attention - changes are minimal and focused

Last reviewed commit: 012d84e

sourman and others added 2 commits March 2, 2026 21:28
… sessions

Thread history and thread starter were being fetched and included on
every message in a Slack thread, causing unnecessary token bloat. The
session transcript already contains the full conversation history, so
re-fetching and re-injecting thread history on each turn is redundant.

Now thread history is only fetched for new thread sessions
(!threadSessionPreviousTimestamp). Existing sessions rely on their
transcript for context.

Fixes openclaw#32121
@steipete steipete force-pushed the fix/slack-thread-context-bloat branch from 012d84e to fc8f18e Compare March 2, 2026 21:29
@steipete steipete merged commit 2438fde into openclaw:main Mar 2, 2026
@steipete
Copy link
Contributor

steipete commented Mar 2, 2026

Landed via temp rebase onto main.

  • Gate: pnpm -s vitest run src/slack/monitor/message-handler/prepare.test.ts src/slack/monitor/allow-list.test.ts
  • Land commit: fc8f18e0233cdaf823c9523c12e50be4e34f34be
  • Merge commit: 2438fde

Thanks @sourman!

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc8f18e02b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

});
if (threadInitialHistoryLimit > 0) {
// Only fetch thread history for NEW sessions (existing sessions already have this context in their transcript)
if (threadInitialHistoryLimit > 0 && !threadSessionPreviousTimestamp) {

Choose a reason for hiding this comment

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

P1 Badge Check session freshness before skipping thread context

The new gate threadInitialHistoryLimit > 0 && !threadSessionPreviousTimestamp treats any stored updatedAt as an active thread session, but session reuse is actually decided by freshness in initSessionState via evaluateSessionFreshness (src/auto-reply/reply/session.ts). Fresh evidence is that a stale entry (daily/idle reset) still has updatedAt, so this branch skips loading thread history (and the later starter-body assignment also skips starter text), even though the run will create a brand-new session; that makes the first post-reset thread message lose prior context and can produce incorrect replies.

Useful? React with 👍 / 👎.

sourman added a commit to sourman/openclaw that referenced this pull request Mar 2, 2026
…eload

Previously, the fix only checked if a session timestamp existed, but didn't
account for session freshness. A stale session (e.g., from yesterday before
the daily reset hour) has a timestamp, so we skipped loading thread history,
but initSessionState creates a brand new session because it's stale - causing
the first message after reset to lose context.

Now we check session freshness using the same logic as initSessionState:
- Truly new session (no timestamp) → load thread history
- Stale session (exists but expired) → load thread history (will be reset)
- Fresh session (exists and active) → skip thread history (use transcript)

Fixes comment on PR openclaw#32133
planfit-alan pushed a commit to planfit/openclaw that referenced this pull request Mar 3, 2026
dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026
sourman added a commit to sourman/openclaw that referenced this pull request Mar 3, 2026
…eload

Previously, the fix (openclaw#32133) only checked if a session timestamp existed, but
didn't account for session freshness. A stale session (e.g., from yesterday
before the daily reset hour) has a timestamp, so we skipped loading thread
history, but initSessionState creates a brand new session because it's stale -
causing the first message after reset to lose context.

Now we check session freshness using the same logic as initSessionState:
- Truly new session (no timestamp) → load thread history
- Stale session (exists but expired) → load thread history (will be reset)
- Fresh session (exists and active) → skip thread history (use transcript)

Also fixes the wildcard bug in resolveChannelResetConfig so resetByChannel["*"]
works as a catch-all for channel-specific session reset policies.

Fixes comment on PR openclaw#32133
OWALabuy pushed a commit to kcinzgg/openclaw that referenced this pull request Mar 4, 2026
sourman added a commit to sourman/openclaw that referenced this pull request Mar 4, 2026
…eload

Previously, the fix (openclaw#32133) only checked if a session timestamp existed, but
didn't account for session freshness. A stale session (e.g., from yesterday
before the daily reset hour) has a timestamp, so we skipped loading thread
history, but initSessionState creates a brand new session because it's stale -
causing the first message after reset to lose context.

Now we check session freshness using the same logic as initSessionState:
- Truly new session (no timestamp) → load thread history
- Stale session (exists but expired) → load thread history (will be reset)
- Fresh session (exists and active) → skip thread history (use transcript)

Also fixes the wildcard bug in resolveChannelResetConfig so resetByChannel["*"]
works as a catch-all for channel-specific session reset policies.

Fixes comment on PR openclaw#32133
sourman added a commit to sourman/openclaw that referenced this pull request Mar 4, 2026
…eload

Previously, the fix (openclaw#32133) only checked if a session timestamp existed, but
didn't account for session freshness. A stale session (e.g., from yesterday
before the daily reset hour) has a timestamp, so we skipped loading thread
history, but initSessionState creates a brand new session because it's stale -
causing the first message after reset to lose context.

Now we check session freshness using the same logic as initSessionState:
- Truly new session (no timestamp) → load thread history
- Stale session (exists but expired) → load thread history (will be reset)
- Fresh session (exists and active) → skip thread history (use transcript)

Also fixes the wildcard bug in resolveChannelResetConfig so resetByChannel["*"]
works as a catch-all for channel-specific session reset policies.

Fixes comment on PR openclaw#32133
sourman added a commit to sourman/openclaw that referenced this pull request Mar 5, 2026
…eload

Previously, the fix (openclaw#32133) only checked if a session timestamp existed, but
didn't account for session freshness. A stale session (e.g., from yesterday
before the daily reset hour) has a timestamp, so we skipped loading thread
history, but initSessionState creates a brand new session because it's stale -
causing the first message after reset to lose context.

Now we check session freshness using the same logic as initSessionState:
- Truly new session (no timestamp) → load thread history
- Stale session (exists but expired) → load thread history (will be reset)
- Fresh session (exists and active) → skip thread history (use transcript)

Also fixes the wildcard bug in resolveChannelResetConfig so resetByChannel["*"]
works as a catch-all for channel-specific session reset policies.

Fixes comment on PR openclaw#32133
sourman added a commit to sourman/openclaw that referenced this pull request Mar 5, 2026
Previously, the fix (openclaw#32133) only checked if a session timestamp
existed, but didn't account for session freshness. A stale session
(e.g., from yesterday before the daily reset hour) has a timestamp, so
we skipped loading thread history, but initSessionState creates a brand
new session because it's stale - causing the first message after reset
to lose context.

Now we check session freshness using the same logic as initSessionState:
- Truly new session (no timestamp) → load thread history
- Stale session (exists but expired) → load thread history (will be
  reset)
- Fresh session (exists and active) → skip thread history (use
  transcript)

Also fixes the wildcard bug in resolveChannelResetConfig so
resetByChannel["*"] works as a catch-all for channel-specific session
reset policies.

Fixes comment on PR openclaw#32133
sourman added a commit to sourman/openclaw that referenced this pull request Mar 5, 2026
Previously, the fix (openclaw#32133) only checked if a session timestamp
existed, but didn't account for session freshness. A stale session
(e.g., from yesterday before the daily reset hour) has a timestamp, so
we skipped loading thread history, but initSessionState creates a brand
new session because it's stale - causing the first message after reset
to lose context.

Now we check session freshness using the same logic as initSessionState:
- Truly new session (no timestamp) → load thread history
- Stale session (exists but expired) → load thread history (will be
  reset)
- Fresh session (exists and active) → skip thread history (use
  transcript)

Also fixes the wildcard bug in resolveChannelResetConfig so
resetByChannel["*"] works as a catch-all for channel-specific session
reset policies.

Fixes comment on PR openclaw#32133
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: slack Channel integration: slack size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(slack): thread history redundantly included on every message causes token bloat

2 participants