fix(slack): reduce token bloat by skipping thread context on existing 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 #32121
This commit is contained in:
Ahmed Mansour
2026-03-02 15:11:10 -05:00
committed by Peter Steinberger
parent 42e402dfba
commit 7a99027ef6
2 changed files with 15 additions and 20 deletions

View File

@@ -516,7 +516,7 @@ describe("slack prepareSlackMessage inbound contract", () => {
expect(replies).toHaveBeenCalledTimes(2);
});
it("keeps loading thread history when thread session already exists in store", async () => {
it("skips loading thread history when thread session already exists in store (bloat fix)", async () => {
const { storePath } = makeTmpStorePath();
const cfg = {
session: { store: storePath },
@@ -533,24 +533,15 @@ describe("slack prepareSlackMessage inbound contract", () => {
baseSessionKey: route.sessionKey,
threadId: "200.000",
});
// Simulate existing session - thread history should NOT be fetched (bloat fix)
fs.writeFileSync(
storePath,
JSON.stringify({ [threadKeys.sessionKey]: { updatedAt: Date.now() } }, null, 2),
);
const replies = vi
.fn()
.mockResolvedValueOnce({
messages: [{ text: "starter", user: "U2", ts: "200.000" }],
})
.mockResolvedValueOnce({
messages: [
{ text: "starter", user: "U2", ts: "200.000" },
{ text: "assistant follow-up", bot_id: "B1", ts: "200.500" },
{ text: "user follow-up", user: "U1", ts: "200.800" },
{ text: "current message", user: "U1", ts: "201.000" },
],
});
const replies = vi.fn().mockResolvedValueOnce({
messages: [{ text: "starter", user: "U2", ts: "200.000" }],
});
const slackCtx = createThreadSlackCtx({ cfg, replies });
slackCtx.resolveUserName = async () => ({ name: "Alice" });
slackCtx.resolveChannelName = async () => ({ name: "general", type: "channel" });
@@ -563,10 +554,12 @@ describe("slack prepareSlackMessage inbound contract", () => {
expect(prepared).toBeTruthy();
expect(prepared!.ctxPayload.IsFirstThreadTurn).toBeUndefined();
expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("assistant follow-up");
expect(prepared!.ctxPayload.ThreadHistoryBody).toContain("user follow-up");
expect(prepared!.ctxPayload.ThreadHistoryBody).not.toContain("current message");
expect(replies).toHaveBeenCalledTimes(2);
// Thread history should NOT be fetched for existing sessions (bloat fix)
expect(prepared!.ctxPayload.ThreadHistoryBody).toBeUndefined();
// Thread starter should also be skipped for existing sessions
expect(prepared!.ctxPayload.ThreadStarterBody).toBeUndefined();
// Replies API should only be called once (for thread starter lookup, not history)
expect(replies).toHaveBeenCalledTimes(1);
});
it("includes thread_ts and parent_user_id metadata in thread replies", async () => {

View File

@@ -594,7 +594,8 @@ export async function prepareSlackMessage(params: {
storePath,
sessionKey, // Thread-specific session key
});
if (threadInitialHistoryLimit > 0) {
// Only fetch thread history for NEW sessions (existing sessions already have this context in their transcript)
if (threadInitialHistoryLimit > 0 && !threadSessionPreviousTimestamp) {
const threadHistory = await resolveSlackThreadHistory({
channelId: message.channel,
threadTs,
@@ -684,7 +685,8 @@ export async function prepareSlackMessage(params: {
// Preserve thread context for routed tool notifications.
MessageThreadId: threadContext.messageThreadId,
ParentSessionKey: threadKeys.parentSessionKey,
ThreadStarterBody: threadStarterBody,
// Only include thread starter body for NEW sessions (existing sessions already have it in their transcript)
ThreadStarterBody: !threadSessionPreviousTimestamp ? threadStarterBody : undefined,
ThreadHistoryBody: threadHistoryBody,
IsFirstThreadTurn:
isThreadReply && threadTs && !threadSessionPreviousTimestamp ? true : undefined,