Skip to content

feat(memory): Implement token based summarization - #701

Closed
Yaswanth-2874 wants to merge 1 commit into
juspay:releasefrom
Yaswanth-2874:BZ-47204-implement-token-based-summarizer-in-llm-context
Closed

Yaswanth-2874 wants to merge 1 commit into
juspay:releasefrom
Yaswanth-2874:BZ-47204-implement-token-based-summarizer-in-llm-context

Conversation

@Yaswanth-2874

@Yaswanth-2874 Yaswanth-2874 commented Dec 19, 2025 •

Copy link
Copy Markdown
Contributor

Pull Request

Description

This PR implements token-based automatic conversation summarization for NeuroLink's conversation memory system. The implementation intelligently manages long conversations by automatically summarizing older messages when token limits are approached, while keeping recent messages intact for context continuity.

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • ⚡ Performance improvement
  • 🧹 Code refactoring (with functional enhancements)

Related Issues

  • Fixes #BZ-47204

Changes Made

Core Features

  • Token-Based Summarization: Automatic conversation summarization triggered by token count (defaults to 80% of model's context window)
  • Per-Request Override: Added enableSummarization option to both StreamOptions and TextGenerationOptions for fine-grained control
  • Pointer-Based Memory: Non-destructive summarization using message pointers - preserves full history while providing condensed context
  • Smart Context Building: Automatically injects summary + recent messages as context for LLM prompts
  • Provider-Aware Thresholds: Automatically calculates optimal token thresholds based on model's context window

API Enhancements

  1. StreamOptions - Added enableSummarization?: boolean field
  2. TextGenerationOptions - Added enableSummarization?: boolean field
  3. StoreConversationTurnOptions - New type for cleaner parameter passing (fixes linting rule violation)
  4. Priority System: Request-level enableSummarization overrides instance-level configuration

Implementation Details

  • Refactored storeConversationTurn() to use options object pattern (was exceeding 6-parameter limit)
  • Implemented in both ConversationMemoryManager (in-memory) and RedisConversationMemoryManager (Redis)
  • Updated all utility functions (conversationMemory.ts, conversationMemoryUtils.ts) to support new signature
  • Added comprehensive logging for debugging and monitoring
  • Background summarization using setImmediate() to avoid blocking main operations

Configuration Changes

  • Fixed Azure OpenAI max completion tokens limit (32000 → 16384)
  • Updated factory logging to include enableSummarization status

AI Provider Impact

  • All providers

Note: Summarization feature works with all AI providers that support text generation. The summarization uses a configurable provider/model (defaults to the same provider being used).

Component Impact

  • SDK
  • Streaming
  • Configuration
  • Conversation Memory

Testing

Manual Testing Performed

  • Instance-level summarization configuration
  • Per-request summarization override (enable/disable)
  • Token threshold calculation for different models
  • Summarization triggering at threshold
  • Context injection with summary + recent messages
  • Redis and in-memory storage compatibility

Test Scenarios Validated

  1. ✅ Conversation grows beyond token threshold → automatic summarization
  2. ✅ Request with enableSummarization: false → skips summarization even if instance config enables it
  3. ✅ Request with enableSummarization: true → enables summarization even if instance config disables it
  4. ✅ Summary pointer tracking → only new messages get summarized on subsequent triggers
  5. ✅ Context building → summary + recent messages sent to LLM

Test Environment

  • OS: macOS
  • Node.js version: 20+
  • Package manager: pnpm

Performance Impact

  • Performance improvement

Benefits

  • Reduced Token Usage: Summarization prevents context overflow, reducing redundant token consumption
  • Faster Response Times: Smaller context = faster processing
  • Non-Blocking: Background summarization doesn't impact request latency
  • Memory Efficient: Pointer-based approach avoids duplicating message data

Overhead

  • Minimal: Summarization runs asynchronously in background
  • Token counting uses efficient estimation (not actual API calls)

Breaking Changes

None - This is a backward-compatible addition. All changes are opt-in:

  • Existing code without enableSummarization continues to work unchanged
  • Default behavior: respects instance-level config (if not set, no summarization)
  • New option allows per-request overrides without changing global config

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • New and existing type checks pass with my changes
  • Any dependent changes have been merged and published

Additional Notes

Priority System

The summarization control follows this priority order:

  1. Request-level enableSummarization (highest priority)
  2. Instance-level conversationMemory.enableSummarization config
  3. Default: false (no summarization)

Architecture Highlights

  • Refactored Parameter Passing: Moved from 7 individual parameters to StoreConversationTurnOptions object to comply with linting rules
  • Consistent Interface: Both in-memory and Redis implementations support the same API
  • Extensible: Token threshold can be configured per-session, via environment variable, or calculated automatically

Future Enhancements

  • Add metrics/analytics for summarization events
  • Configurable summarization strategies (e.g., sliding window, semantic clustering)
  • Support for custom summarization prompts

Summary by CodeRabbit

Release Notes

  • New Features

    • Token-based conversation memory system replaces turn-based approach
    • Per-request summarization toggle for fine-grained control
    • Public APIs for managing in-memory MCP servers
    • Expanded Redis configuration options for enhanced persistence
  • Configuration Changes

    • Summarization now enabled by default with token thresholds
    • Updated environment variable defaults for memory management

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 19, 2025 •

Copy link
Copy Markdown

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Transition conversation memory from turn-count to token-threshold management: introduce token-based summarization, add provider/model metadata, change storeConversationTurn to an options object, extend message/session types and Redis config, and wire summarization through NeuroLink and Redis-backed managers.

Changes

Cohort / File(s) Change Summary
Env / config
.env.example
Enable memory by default, replace per-session turn limits with token-threshold settings, set summarization provider/model defaults, and add Redis connection/persistence options (REDIS_PASSWORD, REDIS_DB, REDIS_KEY_PREFIX, REDIS_TTL, REDIS_CONNECT_TIMEOUT, REDIS_MAX_RETRIES, REDIS_RETRY_DELAY).
Config constants
src/lib/config/conversationMemory.ts
Add MEMORY_THRESHOLD_PERCENTAGE, DEFAULT_FALLBACK_THRESHOLD, RECENT_MESSAGES_RATIO; expose tokenThreshold, summarization enablement/provider/model in defaults.
Types
src/lib/types/conversation.ts, src/lib/types/generateTypes.ts, src/lib/types/streamTypes.ts, src/lib/types/sdkTypes.ts
Remove turn-based fields from ConversationMemoryConfig; add tokenThreshold; extend SessionMemory with token/summarization pointer fields; add id and metadata to ChatMessage; add StoreConversationTurnOptions and ProviderDetails; add enableSummarization to generation/stream options; remove exported ConversationMemoryStats.
Core managers
src/lib/core/conversationMemoryManager.ts, src/lib/core/redisConversationMemoryManager.ts
Change storeConversationTurn to accept StoreConversationTurnOptions; implement token estimation, validate/truncate messages, pointer-based summarization (checkAndSummarize, summarizeSessionTokenBased, findSplitIndexByTokens), per-session summarization guard, update summary message format (range metadata), and switch IDs to randomUUID.
Initialization / factory
src/lib/core/conversationMemoryFactory.ts, src/lib/core/conversationMemoryInitializer.ts
Remove logging of deprecated turn-count fields and trim Redis config details from success logs.
Utilities
src/lib/utils/conversationMemory.ts, src/lib/utils/conversationMemoryUtils.ts
Add token-based helpers (buildContextFromPointer, createSummarizationPrompt, calculateTokenThreshold, getEffectiveTokenThreshold, generateSummary); propagate providerDetails and enableSummarization; adapt store invocation to options object.
Redis utils
src/lib/utils/redis.ts
Remove two debug log statements from deserialization path.
CLI
src/cli/loop/optionsSchema.ts
Add CLI option enableSummarization to text generation options schema.
Integration
src/lib/neurolink.ts
Lazy/dynamic memory initializer import; attach providerDetails and enableSummarization through stream/generate flows; add public in-memory MCP server APIs (addInMemoryMCPServer, getInMemoryServers, getInMemoryServerInfos, getAutoDiscoveredServerInfos).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant NeuroLink
    participant MemoryMgr as ConversationMemoryManager
    participant TokenUtils
    participant Redis
    participant Summarizer as Summarization Service

    Client->>NeuroLink: generate/stream request (model)
    NeuroLink->>NeuroLink: extract providerDetails, enableSummarization
    NeuroLink->>MemoryMgr: storeConversationTurn(options)
    MemoryMgr->>MemoryMgr: validateAndPrepareMessage (may truncate)
    MemoryMgr->>TokenUtils: estimateTokens(messages)
    TokenUtils-->>MemoryMgr: token count
    MemoryMgr->>MemoryMgr: getEffectiveTokenThreshold
    MemoryMgr->>MemoryMgr: checkAndSummarize(session)
    alt summarization required
        MemoryMgr->>MemoryMgr: findSplitIndexByTokens -> select recent messages
        MemoryMgr->>Summarizer: generateSummary(recent messages, prompt)
        Summarizer-->>MemoryMgr: summary text
        MemoryMgr->>MemoryMgr: createSummarySystemMessage(metadata)
        MemoryMgr->>Redis: persist summarizedUpToMessageId + summarizedMessage
    else no summarization
        MemoryMgr->>Redis: persist new turn
    end
    Redis-->>MemoryMgr: ack
    MemoryMgr-->>NeuroLink: ack
    NeuroLink-->>Client: final response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas:
    • token estimation and threshold calculation (src/lib/utils/conversationMemory.ts)
    • storeConversationTurn signature changes and all call sites (core managers + utils + neurolink)
    • pointer-based summarization state (summarizedUpToMessageId, summarizedMessage) and concurrency guard (conversationMemoryManager / redisConversationMemoryManager)
    • message ID/metadata additions and any code paths that construct ChatMessage objects

Possibly related PRs

Suggested reviewers

  • murdore

Poem

🐰 Hopping through tokens, not turns anymore,

I tuck summaries neatly behind the door,
Redis hums and IDs gently chime,
Provider whispers help me keep in time,
Little rabbit notes: memory refined, sublime.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: implementing token-based summarization for conversation memory. It is concise, specific, and directly reflects the primary feature being added across multiple files and modules.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/neurolink.ts (1)

2772-2813: Add explicit string type check for userId in streaming memory write path

The streaming completion handler correctly accumulates aiResponse and writes a unified turn via conversationMemory.storeConversationTurn, including providerDetails and enableSummarization. One type-safety improvement:

userId is cast directly via as string without validation. The non-streaming path uses an explicit guard (typeof context.userId === "string" ? context.userId : undefined), which is safer and more consistent. Align the stream path to match:

-          const userId = (
-            enhancedOptions.context as Record<string, unknown>
-          )?.userId as string;
+          const rawUserId = (enhancedOptions.context as Record<string, unknown>)
+            ?.userId;
+          const userId =
+            typeof rawUserId === "string" ? rawUserId : undefined;
🧹 Nitpick comments (14)
src/lib/utils/conversationMemoryUtils.ts (1)

129-170: Redis availability check is well-implemented.

The function properly:

  • Uses short timeout (5000ms) appropriate for availability probing
  • Limits retries to 1 to fail fast
  • Ensures cleanup in the finally block with error handling for quit failures

One minor consideration: the testClient variable initialization to null and subsequent type narrowing works, but you could use optional chaining in the finally block for slightly cleaner code.

Optional: Simplify cleanup with optional chaining
   } finally {
-    if (testClient) {
-      try {
-        await testClient.quit();
-        logger.debug("Redis test client disconnected successfully");
-      } catch (quitError) {
-        logger.debug("Error during Redis test client disconnect", {
-          error:
-            quitError instanceof Error ? quitError.message : String(quitError),
-        });
-      }
+    try {
+      await testClient?.quit();
+      if (testClient) logger.debug("Redis test client disconnected successfully");
+    } catch (quitError) {
+      logger.debug("Error during Redis test client disconnect", {
+        error:
+          quitError instanceof Error ? quitError.message : String(quitError),
+      });
     }
   }
src/lib/utils/conversationMemory.ts (3)

230-272: Align summary message metadata with summarizesFrom for better traceability

buildContextFromPointer correctly injects a synthetic summary ChatMessage and sets metadata.isSummary and metadata.summarizesTo, but leaves summarizesFrom unset. For downstream tooling that wants to know the exact covered range, filling both endpoints makes this more useful and consistent with createSummarySystemMessage in the managers.

Optional metadata enhancement
  const summaryMessage: ChatMessage = {
    id: `summary-${session.summarizedUpToMessageId}`,
    role: "system",
    content: `Previous conversation summary: ${session.summarizedMessage}`,
    timestamp: new Date().toISOString(),
    metadata: {
      isSummary: true,
-     summarizesTo: session.summarizedUpToMessageId,
+     summarizesFrom: session.messages[0]?.id,
+     summarizesTo: session.summarizedUpToMessageId,
    },
  };

317-375: Clarify / simplify error handling in token-threshold helpers

calculateTokenThreshold already catches and logs errors and falls back to DEFAULT_FALLBACK_THRESHOLD, so the try/catch in getEffectiveTokenThreshold will never see an exception from it. The outer try/catch is therefore redundant and slightly obscures the priority logic.

You could simplify getEffectiveTokenThreshold to rely on calculateTokenThreshold’s own fallback and keep the priority comments as-is.

Suggested simplification
 export function getEffectiveTokenThreshold(
   provider: string,
   model: string,
   envOverride?: number,
   sessionOverride?: number,
 ): number {
   // Priority 1: Session-level override
   if (sessionOverride && sessionOverride > 0) {
     return sessionOverride;
   }

   // Priority 2: Environment variable override
   if (envOverride && envOverride > 0) {
     return envOverride;
   }

-  // Priority 3: Model-based calculation (80% of context window)
-  try {
-    return calculateTokenThreshold(provider, model);
-  } catch (error) {
-    logger.warn("Failed to calculate effective threshold, using fallback", {
-      provider,
-      model,
-      error: error instanceof Error ? error.message : String(error),
-    });
-    // Priority 4: Fallback for unknown models
-    return DEFAULT_FALLBACK_THRESHOLD;
-  }
+  // Priority 3: Model-based calculation (80% of context window) with internal fallback
+  return calculateTokenThreshold(provider, model);
 }

386-417: Avoid tight coupling and potential cycles by lazily importing NeuroLink in generateSummary

This utility module is now pulling in the full NeuroLink class via a static import and using it only inside generateSummary. Given NeuroLink already imports this file for storeConversationTurn / getConversationMessages, this creates a bidirectional dependency and also forces all of NeuroLink’s heavy initialization code to be eagerly linked whenever these helpers are imported.

You can decouple the layers and make initialization cheaper by lazily importing NeuroLink inside generateSummary:

Proposed lazy import for `NeuroLink`
-import { NeuroLink } from "../neurolink.js";
@@
 export async function generateSummary(
   messages: ChatMessage[],
   config: ConversationMemoryConfig,
   logPrefix = "[ConversationMemory]",
   previousSummary?: string,
 ): Promise<string | null> {
   const summarizationPrompt = createSummarizationPrompt(
     messages,
     previousSummary,
   );
-  const summarizer = new NeuroLink({
-    conversationMemory: { enabled: false },
-  });
+  // Lazy‑load NeuroLink to avoid hard module cycles and unnecessary startup work
+  const { NeuroLink } = await import("../neurolink.js");
+  const summarizer = new NeuroLink({
+    conversationMemory: { enabled: false },
+  });

This keeps the API the same but breaks the static coupling to the main SDK class.

src/lib/neurolink.ts (1)

3090-3129: Fallback stream memory write reuses context defensively but could be simplified

In the fallback streaming path, session and user IDs are computed as:

const sessionId = (enhancedOptions?.context as Record<string, unknown>)?.sessionId as string;
const userId = (enhancedOptions?.context as Record<string, unknown>)?.userId as string;

await self.conversationMemory.storeConversationTurn({
  sessionId: sessionId || (options.context?.sessionId as string),
  userId: userId || (options.context?.userId as string),
  ...
});

Given the outer if already checks enhancedOptions?.context?.sessionId, sessionId should always be a truthy string, so the || options.context?.sessionId fallback is effectively dead code. The same applies to userId if you add a proper type guard as suggested for the main stream path.

You can simplify this block and avoid redundant as string casts, which makes it easier to reason about and avoids accidentally passing undefined into StoreConversationTurnOptions in edge cases.

src/lib/types/conversation.ts (2)

39-47: Deprecated turn-based config fields are still read in logs

maxTurnsPerSession, summarizationThresholdTurns, and summarizationTargetTurns are now marked @deprecated in favor of tokenThreshold, but the Redis and in-memory managers still log maxTurnsPerSession in their initialization messages.

That’s harmless but slightly confusing for users migrating to token-based configs. Consider either:

  • Updating log messages to mention tokenThreshold (and possibly the effective threshold), or
  • Clearly flagging these fields as ignored in the runtime logic when tokenThreshold is set.

250-261: Consider carrying tokenThreshold into StoreConversationTurnOptions when using per-session overrides

StoreConversationTurnOptions currently carries sessionId, userId, messages, timestamps, providerDetails, and enableSummarization. The managers then recompute an effective threshold via getEffectiveTokenThreshold, using session.tokenThreshold as a possible override.

Right now there’s no way for a caller to set session.tokenThreshold through this options type; the managers never write it, they only read it. If per-session overrides are intended, you may want to:

  • Add an optional tokenThreshold?: number to StoreConversationTurnOptions, and
  • Persist it into the session/Redis object on first write.

Otherwise the sessionOverride path in getEffectiveTokenThreshold will effectively never be used.

src/lib/core/redisConversationMemoryManager.ts (3)

328-425: Token-threshold computation and enableSummarization precedence look correct, but conversation.tokenThreshold is never persisted

In storeConversationTurn you:

  • Compute tokenThreshold via getEffectiveTokenThreshold when options.providerDetails is present, otherwise fall back to this.config.tokenThreshold || 50000.
  • Use per-request options.enableSummarization (if defined) to override the instance-level config.enableSummarization.

That precedence is sensible. However, conversation.tokenThreshold is passed into getEffectiveTokenThreshold as a session override and later re-exposed in the SessionMemory shim, but it is never actually set on the conversation object in this file. As a result, the “sessionOverride” branch in getEffectiveTokenThreshold is effectively dead for Redis-backed sessions.

If you intend to support per-session thresholds, consider persisting tokenThreshold on first calculation:

Suggested persistence of `conversation.tokenThreshold`
-      const tokenThreshold = options.providerDetails
+      let tokenThreshold = options.providerDetails
         ? getEffectiveTokenThreshold(
             options.providerDetails.provider,
             options.providerDetails.model,
             this.config.tokenThreshold,
             conversation.tokenThreshold,
           )
         : this.config.tokenThreshold || 50000;
+
+      // Persist effective threshold on the conversation so future turns can reuse it
+      conversation.tokenThreshold = conversation.tokenThreshold ?? tokenThreshold;

453-475: Background summarization runs with a captured snapshot; be aware of concurrent write semantics

The setImmediate callback calls checkAndSummarize(conversation, tokenThreshold, options.sessionId, options.userId), where conversation is the deserialized object from this particular storeConversationTurn call. If multiple turns for the same session are stored in quick succession, each call will:

  • Read its own snapshot of the conversation from Redis.
  • Enqueue a summarization job based on that snapshot.
  • Write its snapshot back to Redis (in summarizeSessionTokenBased).

Because setImmediate is FIFO, the last turn’s summarization job should generally win, but intermediate summarization jobs can overwrite newer fields (e.g., pointer or token counts) computed by later jobs if anything unusual happens in scheduling.

Given this is already a best‑effort background process and not part of the main request path, this is probably acceptable, but it’s worth noting that the current implementation does not guarantee strictly monotonic updates to summarizedUpToMessageId / summarizedMessage under high concurrency for the same session.

If you see inconsistent summaries in practice, a minimal mitigation would be to re‑fetch the latest conversation inside checkAndSummarize using sessionId/userId before computing and writing summary data, instead of relying solely on the captured conversation object.


653-709: Context building and tool-message filtering semantics differ from in-memory manager

buildContextMessages:

  • Deserializes the Redis conversation,
  • Wraps it as SessionMemory,
  • Uses buildContextFromPointer to inject the summary and slice from the pointer,
  • Optionally filters out tool_call / tool_result messages when config.enableSummarization === true.

In the in-memory ConversationMemoryManager.buildContextMessages, context is also built via buildContextFromPointer, but no role-based filtering is applied, so tool messages are always included.

If the goal is to have symmetrical behavior between Redis and in-memory memory (especially when summarization is enabled), consider aligning the in-memory implementation to apply the same tool-message filtering. Right now, behavior differs by storage backend.

src/lib/core/conversationMemoryManager.ts (4)

65-103: Per-session threshold override is computed but not persisted

storeConversationTurn computes tokenThreshold via getEffectiveTokenThreshold, supplying session.tokenThreshold as a possible override, but createNewSession never sets tokenThreshold and this method doesn’t update it either. That means the sessionOverride branch in getEffectiveTokenThreshold is effectively unused for in-memory sessions.

If you want per-session thresholds to stick after the first effective calculation, mirror the Redis suggestion and persist it onto the session:

Example persistence on `SessionMemory`
-      const tokenThreshold = options.providerDetails
+      let tokenThreshold = options.providerDetails
         ? getEffectiveTokenThreshold(
             options.providerDetails.provider,
             options.providerDetails.model,
             this.config.tokenThreshold,
             session.tokenThreshold,
           )
         : this.config.tokenThreshold || 50000;
+
+      session.tokenThreshold = session.tokenThreshold ?? tokenThreshold;

130-174: validateAndPrepareMessage truncation logic is correct; async modifier is unnecessary

The helper:

  • Estimates tokens with TokenUtils.estimateTokenCount,
  • Truncates to threshold * MEMORY_THRESHOLD_PERCENTAGE when necessary,
  • Marks truncated messages via metadata.truncated = true,
  • Always assigns an id and timestamp.

This matches the new ChatMessage shape and keeps overlong messages in check. The function is marked async but doesn’t await anything, so it always returns a resolved Promise<ChatMessage>; that’s harmless but adds a bit of noise. You could safely make it synchronous and update the call sites to drop await for slightly simpler code.


217-225: In-memory buildContextMessages should probably mirror Redis tool-message filtering

The in-memory buildContextMessages now simply returns buildContextFromPointer(session) without any additional filtering, while the Redis manager optionally removes tool_call / tool_result messages when summarization is enabled. That means the same logical session can produce different contexts depending on backend.

If you want parity, consider applying the same optional role-based filter here (perhaps driven by this.config.enableSummarization) so callers get consistent behavior regardless of storage implementation.


252-304: Token-based summarization flow is correct but currently includes tool messages

summarizeSessionTokenBased:

  • Starts from summarizedUpToMessageId + 1,
  • Slices recentMessages,
  • Uses token-based splitting via findSplitIndexByTokens with RECENT_MESSAGES_RATIO,
  • Calls generateSummary with the new chunk and previous summary,
  • Advances the pointer and stores the concatenated summary.

Unlike the Redis implementation, it does not filter out tool_call / tool_result messages before summarizing, so tool traffic can dominate the token budget in memory-backed mode.

If that’s not intentional, you could mirror the Redis behavior by filtering roles before the split:

Optional alignment with Redis summarization
-    const recentMessages = session.messages.slice(startIndex);
+    const recentMessages = session.messages.slice(startIndex);
+    const filteredRecentMessages = recentMessages.filter(
+      (msg) => msg.role !== "tool_call" && msg.role !== "tool_result",
+    );
@@
-    const targetRecentTokens = threshold * RECENT_MESSAGES_RATIO;
-    const splitIndex = await this.findSplitIndexByTokens(
-      recentMessages,
-      targetRecentTokens,
-    );
-    const messagesToSummarize = recentMessages.slice(0, splitIndex);
+    const targetRecentTokens = threshold * RECENT_MESSAGES_RATIO;
+    const splitIndex = await this.findSplitIndexByTokens(
+      filteredRecentMessages,
+      targetRecentTokens,
+    );
+    const messagesToSummarize = filteredRecentMessages.slice(0, splitIndex);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 921fed2 and 7a84149.

📒 Files selected for processing (14)
  • .env.example (2 hunks)
  • src/lib/config/conversationMemory.ts (1 hunks)
  • src/lib/core/conversationMemoryFactory.ts (0 hunks)
  • src/lib/core/conversationMemoryInitializer.ts (0 hunks)
  • src/lib/core/conversationMemoryManager.ts (2 hunks)
  • src/lib/core/redisConversationMemoryManager.ts (10 hunks)
  • src/lib/neurolink.ts (4 hunks)
  • src/lib/types/conversation.ts (8 hunks)
  • src/lib/types/generateTypes.ts (1 hunks)
  • src/lib/types/sdkTypes.ts (0 hunks)
  • src/lib/types/streamTypes.ts (1 hunks)
  • src/lib/utils/conversationMemory.ts (3 hunks)
  • src/lib/utils/conversationMemoryUtils.ts (2 hunks)
  • src/lib/utils/redis.ts (0 hunks)
💤 Files with no reviewable changes (4)
  • src/lib/types/sdkTypes.ts
  • src/lib/utils/redis.ts
  • src/lib/core/conversationMemoryInitializer.ts
  • src/lib/core/conversationMemoryFactory.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Maintain strict TypeScript type safety across all modules with comprehensive type definitions organized by domain to avoid circular dependencies
Use ErrorFactory for creating typed errors throughout the application
Wrap async operations with withTimeout utility for timeout handling

Files:

  • src/lib/types/streamTypes.ts
  • src/lib/config/conversationMemory.ts
  • src/lib/utils/conversationMemory.ts
  • src/lib/utils/conversationMemoryUtils.ts
  • src/lib/core/conversationMemoryManager.ts
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/types/generateTypes.ts
  • src/lib/types/conversation.ts
  • src/lib/neurolink.ts
**/types/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Type definitions must be organized by domain (providers, generation, streaming, MCP, etc.) to avoid circular dependencies

Files:

  • src/lib/types/streamTypes.ts
  • src/lib/types/generateTypes.ts
  • src/lib/types/conversation.ts
🧠 Learnings (14)
📓 Common learnings
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Memory management should use Redis for distributed memory in production and in-memory store for development, with conversation summarization for long contexts
Learnt from: BoraYaswanthReddy
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2025-12-14T18:33:26.766Z
Learning: In the neurolink repository's Redis conversation memory implementation, message IDs were changed from sequential integers to UUIDs (using `generateUniqueId()`) as a security improvement to prevent enumeration attacks and information leakage about conversation volumes and patterns.
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Memory management should use Redis for distributed memory in production and in-memory store for development, with conversation summarization for long contexts

Applied to files:

  • .env.example
  • src/lib/config/conversationMemory.ts
  • src/lib/utils/conversationMemory.ts
  • src/lib/core/conversationMemoryManager.ts
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/types/conversation.ts
📚 Learning: 2025-12-14T18:33:26.766Z
Learnt from: BoraYaswanthReddy
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2025-12-14T18:33:26.766Z
Learning: In the neurolink repository's Redis conversation memory implementation, message IDs were changed from sequential integers to UUIDs (using `generateUniqueId()`) as a security improvement to prevent enumeration attacks and information leakage about conversation volumes and patterns.

Applied to files:

  • .env.example
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/neurolink.ts
📚 Learning: 2025-12-14T18:33:26.766Z
Learnt from: BoraYaswanthReddy
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2025-12-14T18:33:26.766Z
Learning: In the neurolink repository, the `separateLLMContext` configuration exists primarily to support agentic loops that need full conversation context including tool messages. The default is intentionally `true` because separating tool messages from LLM context is considered the better default behavior. For CLI usage, separation is always enabled and the option is not exposed to users.

Applied to files:

  • .env.example
📚 Learning: 2025-09-01T22:58:39.149Z
Learnt from: sudharsan-juspay
Repo: juspay/neurolink PR: 140
File: src/lib/core/types.ts:198-203
Timestamp: 2025-09-01T22:58:39.149Z
Learning: In src/lib/core/types.ts, StreamOptions (imported from streamTypes.js) and StreamingOptions are intentionally different types with different use cases. StreamingOptions is for unified AI requests with multiple provider configurations, while StreamOptions is for individual streaming operations.

Applied to files:

  • src/lib/types/streamTypes.ts
📚 Learning: 2025-09-01T06:15:59.759Z
Learnt from: amreetkhuntia
Repo: juspay/neurolink PR: 133
File: src/lib/core/types.ts:208-210
Timestamp: 2025-09-01T06:15:59.759Z
Learning: The middleware?: MiddlewareFactoryOptions field is already present in both TextGenerationOptions and StreamOptions interfaces in the neurolink codebase.

Applied to files:

  • src/lib/types/streamTypes.ts
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Applies to **/cli/loop/session.ts : Loop mode interactive sessions should be implemented in src/cli/loop/session.ts with persistent conversation memory and session-wide configuration

Applied to files:

  • src/lib/utils/conversationMemory.ts
  • src/lib/core/conversationMemoryManager.ts
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/types/conversation.ts
📚 Learning: 2025-09-17T17:55:15.261Z
Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/index.ts:16-16
Timestamp: 2025-09-17T17:55:15.261Z
Learning: In src/lib/types/providers.ts, ProviderConfig was renamed to AIModelProviderConfig to deduplicate type names, as there was an existing ProviderConfig type that better suited the "ProviderConfig" name. This was an intentional breaking change for better type organization.

Applied to files:

  • src/lib/utils/conversationMemoryUtils.ts
  • src/lib/types/conversation.ts
  • src/lib/neurolink.ts
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Applies to **/types/index.ts : Add new provider names to the AIProviderName enum in src/lib/types/index.ts when adding a new provider

Applied to files:

  • src/lib/utils/conversationMemoryUtils.ts
  • src/lib/types/conversation.ts
📚 Learning: 2025-09-17T18:14:34.960Z
Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/types/index.ts:58-62
Timestamp: 2025-09-17T18:14:34.960Z
Learning: RajuSudhar explained that in the Neurolink codebase, there are multiple ProviderConfig types causing inconsistency. One existing ProviderConfig type better suited the "ProviderConfig" name, so they renamed the less-suitable one to AIModelProviderConfig to free up the name. Adding backward compatibility aliases would worsen naming inconsistency rather than help. The remaining duplicates will be systematically deduplicated in the 07-Types-Module.md TODO as part of their phased refactor approach.

Applied to files:

  • src/lib/utils/conversationMemoryUtils.ts
📚 Learning: 2025-12-12T20:11:17.070Z
Learnt from: Yaswanth-2874
Repo: juspay/neurolink PR: 672
File: src/lib/core/redisConversationMemoryManager.ts:1082-1091
Timestamp: 2025-12-12T20:11:17.070Z
Learning: In the Redis conversation memory implementation, LLM context keys (when `separateLLMContext` is enabled) intentionally use only sessionId without userId: `llm:context:${sessionId}`. This is by design to scope LLM context purely at the session level, relying on sessionId global uniqueness.

Applied to files:

  • src/lib/core/redisConversationMemoryManager.ts
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Applies to **/utils/messageBuilder.ts : Message construction must be handled through MessageBuilder in src/lib/utils/messageBuilder.ts, which handles text, images, PDFs, and CSV files with provider-specific adapters

Applied to files:

  • src/lib/types/conversation.ts
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Applies to **/providers/*.ts : Providers must extend a base provider or implement the provider interface and register in ProviderRegistry.registerAllProviders() with provider name, factory function, default model, and aliases

Applied to files:

  • src/lib/types/conversation.ts
📚 Learning: 2025-09-24T06:42:06.088Z
Learnt from: amreetkhuntia
Repo: juspay/neurolink PR: 185
File: src/lib/evaluation/contextBuilder.ts:79-85
Timestamp: 2025-09-24T06:42:06.088Z
Learning: In the NeuroLink codebase, using `(options.prompt || [])` pattern for handling potentially undefined prompt arrays is the preferred approach over extracting to a normalized variable when building conversation history in the ContextBuilder class.

Applied to files:

  • src/lib/neurolink.ts
🧬 Code graph analysis (5)
src/lib/config/conversationMemory.ts (1)
src/lib/types/conversation.ts (1)
  • ConversationMemoryConfig (11-47)
src/lib/utils/conversationMemory.ts (4)
src/lib/types/conversation.ts (4)
  • ProviderDetails (397-400)
  • SessionMemory (52-97)
  • ChatMessage (113-152)
  • ConversationMemoryConfig (11-47)
src/lib/types/sdkTypes.ts (3)
  • SessionMemory (204-204)
  • ChatMessage (205-205)
  • ConversationMemoryConfig (203-203)
src/lib/config/conversationMemory.ts (2)
  • MEMORY_THRESHOLD_PERCENTAGE (40-40)
  • DEFAULT_FALLBACK_THRESHOLD (45-45)
src/lib/neurolink.ts (1)
  • NeuroLink (152-5954)
src/lib/utils/conversationMemoryUtils.ts (1)
src/lib/types/conversation.ts (1)
  • ProviderDetails (397-400)
src/lib/core/conversationMemoryManager.ts (4)
src/lib/types/conversation.ts (4)
  • StoreConversationTurnOptions (253-261)
  • ConversationMemoryError (212-225)
  • ChatMessage (113-152)
  • SessionMemory (52-97)
src/lib/utils/conversationMemory.ts (3)
  • getEffectiveTokenThreshold (347-375)
  • buildContextFromPointer (230-272)
  • generateSummary (386-418)
src/lib/types/sdkTypes.ts (3)
  • ConversationMemoryError (209-209)
  • ChatMessage (205-205)
  • SessionMemory (204-204)
src/lib/config/conversationMemory.ts (2)
  • MEMORY_THRESHOLD_PERCENTAGE (40-40)
  • RECENT_MESSAGES_RATIO (52-52)
src/lib/neurolink.ts (1)
src/lib/types/conversation.ts (1)
  • ProviderDetails (397-400)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 325-325: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 406-406: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 407-407: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 408-408: [UnorderedKey] The NEUROLINK_SUMMARIZATION_PROVIDER key should go before the NEUROLINK_TOKEN_THRESHOLD key

(UnorderedKey)


[warning] 408-408: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 409-409: [UnorderedKey] The NEUROLINK_SUMMARIZATION_MODEL key should go before the NEUROLINK_SUMMARIZATION_PROVIDER key

(UnorderedKey)


[warning] 409-409: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

🔇 Additional comments (14)
src/lib/types/streamTypes.ts (1)

220-221: LGTM!

The enableSummarization property addition is well-placed and correctly typed as an optional boolean, enabling per-request control over summarization behavior in streaming operations.

src/lib/types/generateTypes.ts (1)

228-229: LGTM!

The enableSummarization property is correctly added to TextGenerationOptions, maintaining consistency with the same property in StreamOptions for unified per-request summarization control.

src/lib/config/conversationMemory.ts (2)

36-52: LGTM!

The token-based memory constants are well-documented and sensibly configured:

  • 80% threshold leaves headroom for new messages
  • 50k fallback is reasonable for most models
  • 30% recent messages ratio balances context continuity with summarization efficiency

59-81: LGTM!

The getConversationMemoryDefaults function properly integrates token-based configuration while maintaining backward compatibility with deprecated turn-based fields. The conditional tokenThreshold assignment correctly allows dynamic calculation when the environment variable isn't set.

.env.example (1)

332-343: LGTM - Redis configuration additions.

The new Redis configuration variables are comprehensive and follow standard conventions. The documented defaults (TTL of 24 hours, reasonable timeouts and retry settings) are appropriate for production use.

src/lib/utils/conversationMemoryUtils.ts (1)

92-109: LGTM - Clean refactor to options-based API.

The transition to an options object for storeConversationTurn improves readability and maintainability. The ProviderDetails construction correctly guards against missing provider/model values.

src/lib/types/conversation.ts (3)

278-311: Keep ConversationBase and SessionMemory in sync for token-based fields

You’ve added token-based fields (summarizedUpToMessageId, summarizedMessage, tokenThreshold, lastTokenCount, lastCountedAt) to both SessionMemory and ConversationBase, which is good for alignment between in-memory and Redis.

Make sure all code that mutates these fields updates both representations consistently (e.g., when a Redis object is deserialized into a SessionMemory wrapper and then written back), as inconsistencies here will directly affect buildContextFromPointer and token-counting behavior. The current PR mostly does this, but it’s an easy place for future drift—worth keeping an eye on in follow-up changes.


397-400: ProviderDetails type is clear and minimal

The standalone ProviderDetails type (provider + model) is a good fit for both memory and logging metadata, and keeps token-threshold utilities decoupled from broader provider configs. No issues here.


113-152: ChatMessage.id requirement is already properly enforced across the codebase

The ChatMessage type correctly requires id: string (line 115, conversation.ts), and all actual ChatMessage constructions throughout the codebase properly supply it via randomUUID() or synthetic IDs:

  • src/lib/core/redisConversationMemoryManager.ts: User, assistant, tool call, and tool result messages all include id
  • src/lib/utils/conversationMemory.ts: Summary messages use synthetic IDs (e.g., summary-${sessionId})
  • src/lib/core/conversationMemoryManager.ts: validateAndPrepareMessage() method generates UUIDs for all messages

The message objects found in other locations (mem0.add calls, provider adapters, CLI factories) are not ChatMessage type—they are provider-specific or external library formats. MessageBuilder correctly creates CoreMessage[] arrays for AI SDK compatibility, not ChatMessage[]. No modifications needed; the type system already prevents ChatMessage construction without id.

src/lib/core/redisConversationMemoryManager.ts (2)

510-651: Token-based summarization pipeline is sound and filters out tool messages appropriately

checkAndSummarize builds a SessionMemory wrapper, uses buildContextFromPointer to respect any existing summary pointer, estimates tokens via TokenUtils.estimateTokenCount, and triggers summarizeSessionTokenBased when over threshold. summarizeSessionTokenBased then:

  • Starts from summarizedUpToMessageId + 1 (or 0),
  • Filters out tool_call/tool_result messages before summarizing,
  • Uses a token-based split so the most recent ~30% tokens stay as raw messages,
  • Updates summarizedUpToMessageId and summarizedMessage and writes back to Redis.

This is a solid, non‑destructive design and matches the intended token‑based summarization behavior.


1030-1045: createSummarySystemMessage metadata aligns with ChatMessage additions

The updated createSummarySystemMessage now sets an explicit id, timestamp, and metadata.isSummary/summarizesFrom/summarizesTo. This is consistent with the new ChatMessage shape and will play nicely with the token-based pointer logic. No changes needed here.

src/lib/core/conversationMemoryManager.ts (3)

179-206: Token-based summarization trigger is well-structured and logs useful diagnostics

checkAndSummarize:

  • Builds context via buildContextFromPointer(session),
  • Updates session.lastTokenCount / lastCountedAt,
  • Logs the current count vs threshold,
  • Invokes summarizeSessionTokenBased when appropriate, all within a guarded try/catch.

This is a clean trigger path and provides enough logging to debug threshold behavior. No changes needed.


231-247: Summary system message metadata matches type expectations

createSummarySystemMessage now creates a summary ChatMessage with an ID, timestamp, and metadata carrying isSummary and range bounds. This lines up with the new ChatMessage.metadata fields and will work correctly with pointer-based context building. Looks good.


309-329: findSplitIndexByTokens logic is correct and matches the Redis strategy

The function walks messages from the end, accumulating recent tokens until it would exceed targetRecentTokens, then chooses a split point so at least one message is always summarized. This matches the “keep the most recent ~RECENT_MESSAGES_RATIO tokens as raw messages” design, and looks correct.

Comment thread .env.example
Comment thread src/lib/utils/conversationMemory.ts
@Yaswanth-2874
Yaswanth-2874 force-pushed the BZ-47204-implement-token-based-summarizer-in-llm-context branch 2 times, most recently from 14740db to bc7cf1b Compare December 19, 2025 20:10
@BoraYaswanthReddy

Copy link
Copy Markdown
Contributor

@coderabbitai

Full review

@coderabbitai

coderabbitai Bot commented Dec 19, 2025

Copy link
Copy Markdown

@BoraYaswanthReddy I'll conduct a full review of this PR, analyzing the token-based summarization implementation across all changed files.

✅ Actions performed

Full review triggered.

@Yaswanth-2874
Yaswanth-2874 force-pushed the BZ-47204-implement-token-based-summarizer-in-llm-context branch 2 times, most recently from 60c87c5 to 5cb3e31 Compare December 19, 2025 20:24
@BoraYaswanthReddy

Copy link
Copy Markdown
Contributor

@coderabbitai Please review this pr

@coderabbitai

coderabbitai Bot commented Dec 20, 2025

Copy link
Copy Markdown

@BoraYaswanthReddy I'll provide a comprehensive review of this token-based summarization implementation.

🎯 Overall Architecture Assessment

This PR represents a significant architectural shift from turn-based to token-based conversation memory management. The approach is well-structured with proper separation of concerns.

Strengths:

  • Token-based approach is more flexible and aligns better with LLM context windows
  • Pointer-based summarization (summarizedUpToMessageId) is a smart design choice
  • Good utility function decomposition in conversationMemory.ts
  • Provider/model awareness enables dynamic threshold calculation

⚠️ Critical Issues

1. Breaking Changes Not Documented

The PR introduces several breaking changes but the description template isn't filled out:

  • ChatMessage now requires an id field (previously optional/missing)
  • storeConversationTurn method signature completely changed
  • ConversationMemoryStats type removed entirely
  • Removed config fields: maxTurnsPerSession, summarizationThresholdTurns, summarizationTargetTurns

Action needed: Update the PR description to clearly document these as breaking changes and provide migration guidance.

2. Existing Data Migration

// ChatMessage now requires id field
export interface ChatMessage {
  id: string; // NEW REQUIRED FIELD
  role: "system" | "user" | "assistant" | "tool_call" | "tool_result";
  content: string;
  metadata?: { ... };
}

Issue: Existing sessions in Redis will have messages without IDs. The code doesn't handle this gracefully.

Recommendation: Add migration logic or make id optional with runtime generation for backward compatibility.

3. Token Estimation Accuracy

// src/lib/core/conversationMemoryManager.ts
private estimateTokens(messages: ChatMessage[]): number {
  return messages.reduce((total, msg) => {
    return total + Math.ceil(msg.content.length / 4);
  }, 0);
}

Issues:

  • Simplistic character/4 estimation can be significantly off for non-English text
  • Doesn't account for metadata, role tokens, or message formatting overhead
  • Different models use different tokenizers (GPT vs Claude vs Gemini)

Recommendation: Use TokenUtils.estimateTokenCount() if available, or at least document the estimation limitations.

4. Error Handling in Background Summarization

// Both memory managers
private async checkAndSummarize(session: SessionMemory, threshold: number): Promise<void> {
  try {
    // ... summarization logic
  } catch (error) {
    this.logger.error(`${this.logPrefix} Background summarization failed:`, error);
    // ERROR IS SWALLOWED
  }
}

Issue: Background summarization failures are silently logged but don't provide any fallback or recovery mechanism. Sessions could grow unbounded if summarization consistently fails.

Recommendation:

  • Add retry logic with exponential backoff
  • Track consecutive failure count
  • Implement circuit breaker pattern for failing sessions
  • Consider alerting/metrics for summarization failures

5. Race Condition in Token Counting

// lastTokenCount is read/written without locking
session.lastTokenCount = totalTokens;
session.lastCountedAt = now;

Issue: Concurrent requests for the same session could cause race conditions in token counting and summarization triggers.

Recommendation: Implement proper locking/synchronization, especially for Redis-based storage.

🔍 Specific File Reviews

.env.example

NEUROLINK_MEMORY_ENABLED=true # Default: true (was false before)

Concern: Changing the default to true could impact existing deployments. Should this be explicitly called out in release notes?

src/lib/types/conversation.ts

Issue: Deprecated fields are still in the type definition with comments:

// Deprecated - kept for backward compatibility
maxTurnsPerSession?: number;

Recommendation: Use TypeScript's @deprecated JSDoc tag for proper IDE warnings:

/**
 * @deprecated Use tokenThreshold instead. Will be removed in v2.0.0
 */
maxTurnsPerSession?: number;

src/lib/utils/conversationMemory.ts

Issue in getEffectiveTokenThreshold:

export function getEffectiveTokenThreshold(
  provider: string,
  model: string,
  envOverride?: number,
  sessionOverride?: number
): number {
  // sessionOverride takes highest priority
  if (sessionOverride !== undefined && sessionOverride > 0) {
    return sessionOverride;
  }
  // ...
}

Problem: No validation that the threshold is reasonable. A user could set sessionOverride: 1000000 and break the system.

Recommendation: Add bounds checking:

const MAX_THRESHOLD = 500000; // or get from config
if (sessionOverride !== undefined && sessionOverride > 0) {
  if (sessionOverride > MAX_THRESHOLD) {
    logger.warn(`Session override ${sessionOverride} exceeds max ${MAX_THRESHOLD}, capping`);
    return MAX_THRESHOLD;
  }
  return sessionOverride;
}

src/lib/core/redisConversationMemoryManager.ts

Issue in summarizeSessionTokenBased:

const messagesToSummarize = recentMessages.slice(0, splitIndex);
const toKeep = recentMessages.slice(splitIndex);

Potential Bug: If splitIndex is 0, messagesToSummarize will be empty, but the code continues to call generateSummary with an empty array.

Recommendation: Add guard:

if (splitIndex === 0) {
  this.logger.debug(`${this.logPrefix} No messages to summarize for session ${session.sessionId}`);
  return;
}

🎨 Code Quality Observations

Positive:

  • Good use of TypeScript types throughout
  • Comprehensive error logging
  • Clean separation between in-memory and Redis implementations
  • Well-structured utility functions

Areas for Improvement:

  1. Magic Numbers:
const MEMORY_THRESHOLD_PERCENTAGE = 0.8;
const RECENT_MESSAGES_RATIO = 0.3;

These should be configurable or at least documented why these specific values were chosen.

  1. Duplicate Code:
    The summarization logic is duplicated between conversationMemoryManager.ts and redisConversationMemoryManager.ts. Consider creating a shared base class or composition.

  2. Missing Tests:
    The PR description mentions a testing checklist, but it's not filled out. This is a major feature that needs comprehensive testing.

📋 Missing Documentation

  1. Migration Guide: How do existing users upgrade?
  2. Token Calculation: Document the estimation strategy and its limitations
  3. Configuration Guide: New Redis settings and token thresholds need explanation
  4. Performance Impact: Token counting on every message could impact latency

🔒 Security Considerations

  1. Redis Password: .env.example adds REDIS_PASSWORD - ensure this is properly secured and not logged
  2. Token Threshold Injection: User-provided sessionOverride could be exploited (addressed above)
  3. Message Truncation: Truncated messages could lose important context - ensure this is intentional

⚡ Performance Considerations

  1. Token Counting Overhead: Every message now requires token estimation - profile this
  2. Background Summarization: Good use of background tasks, but needs circuit breaker
  3. Redis Calls: More metadata means larger payloads - monitor Redis memory usage

📝 Recommendations Summary

Must Fix Before Merge:

  1. Document breaking changes in PR description
  2. Add migration logic for existing data without IDs
  3. Fix race condition in token counting
  4. Add bounds checking for token thresholds
  5. Handle empty message arrays in summarization

Should Fix:

  1. Improve token estimation accuracy
  2. Add retry/circuit breaker for summarization
  3. Add TypeScript @deprecated tags
  4. Reduce code duplication between managers
  5. Fill out PR template completely

Nice to Have:

  1. Make magic numbers configurable
  2. Add comprehensive tests
  3. Create migration guide documentation
  4. Add performance benchmarks

Would you like me to deep-dive into any specific area or help with implementing any of these recommendations?

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/neurolink.ts (1)

3270-3293: Fallback streaming never stores to conversation memory due to enhancedOptions guard

In handleStreamError, the fallback stream’s finally block gates memory writes on enhancedOptions?.context?.sessionId, but the only call site passes undefined for enhancedOptions. As a result, fallback turns are never persisted or summarized, even when options.context.sessionId is present.

You can both pass the real enhancedOptions from stream() and fall back to options.context when it’s unavailable:

Proposed fix for fallback streaming memory write
--- a/src/lib/neurolink.ts
+++ b/src/lib/neurolink.ts
@@ -3006,12 +3006,16 @@ async stream(options: StreamOptions): Promise<StreamResult> {
       } catch (error) {
-        return this.handleStreamError(
-          error,
-          options,
-          startTime,
-          streamId,
-          undefined,
-          undefined,
-        );
+        return this.handleStreamError(
+          error,
+          options,
+          startTime,
+          streamId,
+          enhancedOptions,
+          factoryResult,
+        );
       }
@@ -3268,9 +3272,15 @@ const fallbackProcessedStream = (async function* (self: NeuroLink) {
       } finally {
         // Store memory after fallback stream consumption is complete
-        if (self.conversationMemory && enhancedOptions?.context?.sessionId) {
-          const sessionId = (
-            enhancedOptions?.context as Record<string, unknown>
-          )?.sessionId as string;
-          const userId = (enhancedOptions?.context as Record<string, unknown>)
-            ?.userId as string;
+        const context =
+          (enhancedOptions?.context as Record<string, unknown> | undefined) ??
+          (options.context as Record<string, unknown> | undefined);
+
+        if (self.conversationMemory && context?.sessionId) {
+          const sessionId = context.sessionId as string;
+          const userId = context.userId as string | undefined;
@@ -3283,12 +3293,12 @@ const fallbackProcessedStream = (async function* (self: NeuroLink) {
           try {
             await self.conversationMemory.storeConversationTurn({
-              sessionId: sessionId || (options.context?.sessionId as string),
-              userId: userId || (options.context?.userId as string),
+              sessionId,
+              userId,
               userMessage: originalPrompt ?? "",
               aiResponse: fallbackAccumulatedContent,
               startTimeStamp: new Date(startTime),
               providerDetails,
-              enableSummarization: enhancedOptions?.enableSummarization,
+              enableSummarization:
+                enhancedOptions?.enableSummarization ??
+                options.enableSummarization,
             });
src/lib/core/redisConversationMemoryManager.ts (1)

334-353: In-process summarization guard is good, but multi-node Redis setups still have a race window

The new summarizationInProgress set combined with setImmediate triggers in storeConversationTurn and the checkAndSummarize guard prevents overlapping summarizations per session within a single Node process. However, in a typical Redis-backed deployment with multiple Neurolink instances, each process has its own summarizationInProgress set; concurrent requests hitting different nodes can still:

  • Trigger multiple summarizations for the same {sessionId,userId} in parallel, and
  • Race writing updated RedisConversationObjects back to Redis (last writer wins on summarizedUpToMessageId/summarizedMessage/token counts).

Functionally this “just” causes redundant summarization and potentially non-monotonic pointer updates, but given Redis is intended for distributed memory, it’s worth addressing.

You may want to introduce a lightweight Redis-based lock (e.g., SETNX with TTL on a summary-lock:${sessionId}:${userId} key) around checkAndSummarize/summarizeSessionTokenBased so only one node can summarize a session at a time, while keeping the in-process summarizationInProgress as a fast local guard.

Also applies to: 429-437, 466-502, 538-598

🧹 Nitpick comments (9)
src/lib/config/conversationMemory.ts (1)

88-95: Consider adding @deprecated JSDoc tags for IDE visibility.

The inline comment // Deprecated (for backward compatibility) won't surface in IDE tooltips or generate warnings. Adding proper @deprecated JSDoc tags would improve developer experience:

+    /** @deprecated Use tokenThreshold instead */
     maxTurnsPerSession:
       Number(process.env.NEUROLINK_MEMORY_MAX_TURNS_PER_SESSION) ||
       DEFAULT_MAX_TURNS_PER_SESSION,
+    /** @deprecated Use tokenThreshold instead */
     summarizationThresholdTurns:
       Number(process.env.NEUROLINK_SUMMARIZATION_THRESHOLD_TURNS) || 20,
+    /** @deprecated Use tokenThreshold instead */
     summarizationTargetTurns:
       Number(process.env.NEUROLINK_SUMMARIZATION_TARGET_TURNS) || 10,
src/lib/utils/conversationMemory.ts (3)

231-248: Handle edge case where pointer message may be stale or removed.

The implementation correctly falls back to all messages when the pointer is not found. However, this could mask data integrity issues in long-running sessions.

Consider logging at a higher severity or tracking this as a metric since a missing pointer suggests the session state may be inconsistent (e.g., messages were deleted but pointer wasn't cleared).


348-376: Consider adding upper-bound validation for threshold overrides.

The function validates that overrides are positive (> 0) but doesn't cap unreasonably high values. An override exceeding the model's actual context window could lead to unexpected behavior.

Suggested validation
 export function getEffectiveTokenThreshold(
   provider: string,
   model: string,
   envOverride?: number,
   sessionOverride?: number,
 ): number {
+  const modelLimit = calculateTokenThreshold(provider, model);
+  const maxAllowed = modelLimit * 1.5; // Allow some headroom but cap extreme values
+
   // Priority 1: Session-level override
   if (sessionOverride && sessionOverride > 0) {
+    if (sessionOverride > maxAllowed) {
+      logger.warn("Session threshold override exceeds model limit, capping", {
+        requested: sessionOverride,
+        capped: maxAllowed,
+      });
+      return maxAllowed;
+    }
     return sessionOverride;
   }

397-399: New NeuroLink instance created per summarization call.

Creating a new NeuroLink instance for each summary generation works but may have overhead for frequent summarization. If performance becomes a concern, consider reusing a singleton summarizer instance or caching the NeuroLink instance at the module level.

src/lib/types/conversation.ts (1)

21-47: Config-level move to token thresholds with deprecated turn-based knobs looks good

Adding tokenThreshold?: number and marking the older turn-based knobs as @deprecated matches the new token-based summarization design while preserving backward compatibility at the type level. Consider also updating higher-level docs/usages (e.g., constructor JSDoc in NeuroLink) to steer callers toward tokenThreshold over the legacy turn-based fields.

src/lib/core/conversationMemoryManager.ts (1)

150-190: Minor behavioral differences vs Redis manager (tool messages, ratio constant)

The in‑memory manager’s summarizeSessionTokenBased currently:

  • Includes all message roles (user/assistant/system/tool_call/tool_result) in recentMessages, and
  • Uses RECENT_MESSAGES_RATIO from config for the recent‑token budget,

while the Redis manager filters out tool_call/tool_result messages and hardcodes 0.3 as the ratio. Behavior is correct here, but for predictability it would be cleaner to:

  • Reuse the same ratio constant in both managers, and
  • Decide consistently whether tool_* messages should be part of the summarized region.

Also applies to: 192-238, 284-336, 341-361

src/lib/core/redisConversationMemoryManager.ts (2)

612-673: Unify summarization behavior and constants with in-memory manager

In summarizeSessionTokenBased you:

  • Filter recentMessages to exclude tool_call and tool_result roles, and
  • Use a hardcoded threshold * 0.3 for targetRecentTokens,

while the in-memory ConversationMemoryManager:

  • Summarizes all roles, and
  • Uses RECENT_MESSAGES_RATIO from config/conversationMemory.ts.

The current behavior is not wrong, but it’s asymmetrical and duplicates the ratio. To keep both backends aligned and easier to tune, consider:

  • Importing and using RECENT_MESSAGES_RATIO here instead of the literal 0.3, and
  • Deciding consistently (both managers) whether tool_* messages should be summarized or always kept in the recent window.

Also applies to: 678-698


705-756: Redis context building and message IDs look consistent with new types

The updated buildContextMessages correctly:

  • Rehydrates a SessionMemory from the stored RedisConversationObject,
  • Uses buildContextFromPointer to inject summaries, and
  • Optionally filters tool_call/tool_result when summarization is enabled.

Similarly, createSummarySystemMessage and flushPendingToolData now assign UUID ids and appropriate metadata to summary/tool messages, which matches the new ChatMessage contract and pointer-based summarization logic.

Also applies to: 1077-1093, 1359-1452

src/lib/neurolink.ts (1)

110-116: Top-level await is safely supported in Node 20+ ESM; moving to lazy initializer is optional

Top-level await is natively available in Node 14.8+ when using ES modules, and your project targets Node 20.9.0+ with "type": "module", so this pattern incurs no compatibility constraints. If you prefer deferring initialization, moving the dynamic import into lazyInitializeConversationMemory is a valid stylistic choice, but not necessary.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a84149 and 5cb3e31.

📒 Files selected for processing (15)
  • .env.example (2 hunks)
  • src/cli/loop/optionsSchema.ts (1 hunks)
  • src/lib/config/conversationMemory.ts (2 hunks)
  • src/lib/core/conversationMemoryFactory.ts (0 hunks)
  • src/lib/core/conversationMemoryInitializer.ts (0 hunks)
  • src/lib/core/conversationMemoryManager.ts (2 hunks)
  • src/lib/core/redisConversationMemoryManager.ts (10 hunks)
  • src/lib/neurolink.ts (5 hunks)
  • src/lib/types/conversation.ts (8 hunks)
  • src/lib/types/generateTypes.ts (1 hunks)
  • src/lib/types/sdkTypes.ts (0 hunks)
  • src/lib/types/streamTypes.ts (1 hunks)
  • src/lib/utils/conversationMemory.ts (4 hunks)
  • src/lib/utils/conversationMemoryUtils.ts (2 hunks)
  • src/lib/utils/redis.ts (0 hunks)
💤 Files with no reviewable changes (4)
  • src/lib/types/sdkTypes.ts
  • src/lib/utils/redis.ts
  • src/lib/core/conversationMemoryFactory.ts
  • src/lib/core/conversationMemoryInitializer.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/types/streamTypes.ts
  • src/lib/types/generateTypes.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Maintain strict TypeScript type safety across all modules with comprehensive type definitions organized by domain to avoid circular dependencies
Use ErrorFactory for creating typed errors throughout the application
Wrap async operations with withTimeout utility for timeout handling

Files:

  • src/cli/loop/optionsSchema.ts
  • src/lib/utils/conversationMemory.ts
  • src/lib/utils/conversationMemoryUtils.ts
  • src/lib/core/conversationMemoryManager.ts
  • src/lib/config/conversationMemory.ts
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/neurolink.ts
  • src/lib/types/conversation.ts
**/types/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Type definitions must be organized by domain (providers, generation, streaming, MCP, etc.) to avoid circular dependencies

Files:

  • src/lib/types/conversation.ts
🧠 Learnings (14)
📓 Common learnings
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Memory management should use Redis for distributed memory in production and in-memory store for development, with conversation summarization for long contexts
Learnt from: BoraYaswanthReddy
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2025-12-14T18:33:26.766Z
Learning: In the neurolink repository's Redis conversation memory implementation, message IDs were changed from sequential integers to UUIDs (using `generateUniqueId()`) as a security improvement to prevent enumeration attacks and information leakage about conversation volumes and patterns.
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Memory management should use Redis for distributed memory in production and in-memory store for development, with conversation summarization for long contexts

Applied to files:

  • src/lib/utils/conversationMemory.ts
  • src/lib/core/conversationMemoryManager.ts
  • src/lib/config/conversationMemory.ts
  • src/lib/core/redisConversationMemoryManager.ts
  • .env.example
  • src/lib/types/conversation.ts
📚 Learning: 2025-09-24T06:42:06.088Z
Learnt from: amreetkhuntia
Repo: juspay/neurolink PR: 185
File: src/lib/evaluation/contextBuilder.ts:79-85
Timestamp: 2025-09-24T06:42:06.088Z
Learning: In the NeuroLink codebase, using `(options.prompt || [])` pattern for handling potentially undefined prompt arrays is the preferred approach over extracting to a normalized variable when building conversation history in the ContextBuilder class.

Applied to files:

  • src/lib/utils/conversationMemory.ts
  • src/lib/neurolink.ts
📚 Learning: 2025-09-24T07:26:41.988Z
Learnt from: amreetkhuntia
Repo: juspay/neurolink PR: 185
File: src/lib/evaluation/prompts.ts:86-101
Timestamp: 2025-09-24T07:26:41.988Z
Learning: In the neurolink codebase, maintainer amreetkhuntia consistently prefers to keep template literal indentation in LLM prompts (including evaluation prompts in src/lib/evaluation/prompts.ts) for readability, even when it results in extra whitespace in the output, as LLMs can parse and understand the content correctly.

Applied to files:

  • src/lib/utils/conversationMemory.ts
  • .env.example
📚 Learning: 2025-09-24T06:43:23.653Z
Learnt from: amreetkhuntia
Repo: juspay/neurolink PR: 185
File: src/lib/evaluation/prompts.ts:59-72
Timestamp: 2025-09-24T06:43:23.653Z
Learning: In the neurolink codebase, maintainer amreetkhuntia prefers to keep template literal indentation in LLM prompts even if it results in technically malformed JSON format, as LLMs can understand and parse it correctly despite formatting issues.

Applied to files:

  • src/lib/utils/conversationMemory.ts
  • .env.example
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Applies to **/cli/loop/session.ts : Loop mode interactive sessions should be implemented in src/cli/loop/session.ts with persistent conversation memory and session-wide configuration

Applied to files:

  • src/lib/utils/conversationMemory.ts
  • src/lib/core/conversationMemoryManager.ts
  • src/lib/core/redisConversationMemoryManager.ts
  • src/lib/types/conversation.ts
📚 Learning: 2025-09-17T17:55:15.261Z
Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/index.ts:16-16
Timestamp: 2025-09-17T17:55:15.261Z
Learning: In src/lib/types/providers.ts, ProviderConfig was renamed to AIModelProviderConfig to deduplicate type names, as there was an existing ProviderConfig type that better suited the "ProviderConfig" name. This was an intentional breaking change for better type organization.

Applied to files:

  • src/lib/utils/conversationMemoryUtils.ts
  • src/lib/neurolink.ts
  • src/lib/types/conversation.ts
📚 Learning: 2025-12-14T18:33:26.766Z
Learnt from: BoraYaswanthReddy
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2025-12-14T18:33:26.766Z
Learning: In the neurolink repository's Redis conversation memory implementation, message IDs were changed from sequential integers to UUIDs (using `generateUniqueId()`) as a security improvement to prevent enumeration attacks and information leakage about conversation volumes and patterns.

Applied to files:

  • src/lib/core/redisConversationMemoryManager.ts
  • .env.example
📚 Learning: 2025-12-12T20:11:17.070Z
Learnt from: Yaswanth-2874
Repo: juspay/neurolink PR: 672
File: src/lib/core/redisConversationMemoryManager.ts:1082-1091
Timestamp: 2025-12-12T20:11:17.070Z
Learning: In the Redis conversation memory implementation, LLM context keys (when `separateLLMContext` is enabled) intentionally use only sessionId without userId: `llm:context:${sessionId}`. This is by design to scope LLM context purely at the session level, relying on sessionId global uniqueness.

Applied to files:

  • src/lib/core/redisConversationMemoryManager.ts
📚 Learning: 2025-09-17T18:14:34.960Z
Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/types/index.ts:58-62
Timestamp: 2025-09-17T18:14:34.960Z
Learning: RajuSudhar explained that in the Neurolink codebase, there are multiple ProviderConfig types causing inconsistency. One existing ProviderConfig type better suited the "ProviderConfig" name, so they renamed the less-suitable one to AIModelProviderConfig to free up the name. Adding backward compatibility aliases would worsen naming inconsistency rather than help. The remaining duplicates will be systematically deduplicated in the 07-Types-Module.md TODO as part of their phased refactor approach.

Applied to files:

  • src/lib/neurolink.ts
📚 Learning: 2025-12-14T18:33:26.766Z
Learnt from: BoraYaswanthReddy
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2025-12-14T18:33:26.766Z
Learning: In the neurolink repository, the `separateLLMContext` configuration exists primarily to support agentic loops that need full conversation context including tool messages. The default is intentionally `true` because separating tool messages from LLM context is considered the better default behavior. For CLI usage, separation is always enabled and the option is not exposed to users.

Applied to files:

  • .env.example
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Applies to **/utils/messageBuilder.ts : Message construction must be handled through MessageBuilder in src/lib/utils/messageBuilder.ts, which handles text, images, PDFs, and CSV files with provider-specific adapters

Applied to files:

  • src/lib/types/conversation.ts
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Applies to **/providers/*.ts : Providers must extend a base provider or implement the provider interface and register in ProviderRegistry.registerAllProviders() with provider name, factory function, default model, and aliases

Applied to files:

  • src/lib/types/conversation.ts
📚 Learning: 2025-12-10T12:24:51.147Z
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Applies to **/types/index.ts : Add new provider names to the AIProviderName enum in src/lib/types/index.ts when adding a new provider

Applied to files:

  • src/lib/types/conversation.ts
🧬 Code graph analysis (4)
src/lib/utils/conversationMemory.ts (3)
src/lib/types/conversation.ts (3)
  • ProviderDetails (381-384)
  • SessionMemory (52-97)
  • ChatMessage (113-152)
src/lib/config/conversationMemory.ts (2)
  • MEMORY_THRESHOLD_PERCENTAGE (40-40)
  • DEFAULT_FALLBACK_THRESHOLD (45-45)
src/lib/neurolink.ts (1)
  • NeuroLink (152-6117)
src/lib/utils/conversationMemoryUtils.ts (1)
src/lib/types/conversation.ts (1)
  • ProviderDetails (381-384)
src/lib/core/conversationMemoryManager.ts (3)
src/lib/types/conversation.ts (4)
  • SessionMemory (52-97)
  • ConversationMemoryConfig (11-47)
  • StoreConversationTurnOptions (237-245)
  • ChatMessage (113-152)
src/lib/utils/conversationMemory.ts (3)
  • getEffectiveTokenThreshold (348-376)
  • buildContextFromPointer (231-273)
  • generateSummary (387-419)
src/lib/config/conversationMemory.ts (2)
  • MEMORY_THRESHOLD_PERCENTAGE (40-40)
  • RECENT_MESSAGES_RATIO (52-52)
src/lib/neurolink.ts (1)
src/lib/types/conversation.ts (1)
  • ProviderDetails (381-384)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 325-325: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 406-406: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 407-407: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 408-408: [UnorderedKey] The NEUROLINK_SUMMARIZATION_PROVIDER key should go before the NEUROLINK_TOKEN_THRESHOLD key

(UnorderedKey)


[warning] 408-408: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 409-409: [UnorderedKey] The NEUROLINK_SUMMARIZATION_MODEL key should go before the NEUROLINK_SUMMARIZATION_PROVIDER key

(UnorderedKey)


[warning] 409-409: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

🔇 Additional comments (13)
src/cli/loop/optionsSchema.ts (1)

90-94: LGTM!

The new enableSummarization option follows the established pattern for boolean CLI options and aligns with per-request summarization control introduced across the codebase.

src/lib/utils/conversationMemoryUtils.ts (1)

92-110: Well-structured options object pattern.

The refactoring to use a single options object for storeConversationTurn improves API clarity and extensibility. The conditional construction of providerDetails correctly guards against missing provider/model values.

.env.example (2)

324-325: Note: Default behavior change for memory feature.

NEUROLINK_MEMORY_ENABLED is now true by default. This changes the out-of-box behavior for new installations. Ensure this is documented in release notes as users who previously relied on memory being disabled by default may experience unexpected behavior.


404-413: Token-based summarization configuration looks good.

The documentation clearly explains the token-threshold approach with the 80% model context default. The deprecated turn-based settings are properly marked and commented out for backward compatibility reference.

src/lib/config/conversationMemory.ts (2)

36-52: Well-documented constants for token-based memory.

The new constants are clearly documented and provide sensible defaults. The 80% threshold and 30% recent messages ratio align with the PR objectives for provider-aware threshold calculation.


78-79: Consider the default-true behavior for enableSummarization.

The condition !== "false" means summarization is enabled by default when the env var is unset or set to any value other than "false". This is intentional but differs from the pattern used for enabled (which requires === "true"). Verify this asymmetry is desired.

src/lib/utils/conversationMemory.ts (3)

253-262: Good pointer-based context construction.

The summary message correctly uses role "system" and includes proper metadata (isSummary, summarizesTo) for downstream processing. The generated ID pattern summary-${pointerId} is deterministic and traceable.


78-83: Clean integration of per-request summarization toggle.

The enableSummarization is correctly extracted with nullish coalescing and passed through to buildContextMessages, enabling per-request control over summarization behavior.


184-202: Well-implemented conversation turn storage with provider metadata.

The code correctly:

  • Normalizes aiResponse with nullish coalescing (?? "")
  • Conditionally constructs providerDetails only when both provider and model are available
  • Passes the complete options object to storeConversationTurn

This aligns with the StoreConversationTurnOptions type and the broader refactoring pattern in this PR.

src/lib/neurolink.ts (1)

2931-2954: Streaming path memory write wiring looks correct

The main streaming path now stores conversation turns via conversationMemory.storeConversationTurn with sessionId, userId, userMessage (original prompt), aiResponse (accumulated stream), startTimeStamp, providerDetails, and enableSummarization. This matches StoreConversationTurnOptions and correctly forwards per‑request enableSummarization and provider/model metadata to the memory layer.

src/lib/types/conversation.ts (2)

234-245: StoreConversationTurnOptions and ProviderDetails are well-shaped for public API

The new StoreConversationTurnOptions cleanly captures the data needed by both in‑memory and Redis managers, and providerDetails/enableSummarization give the memory layer enough context for provider‑aware thresholds and per‑request overrides. The standalone ProviderDetails type keeps this reusable and is already being consumed correctly from NeuroLink.

Also applies to: 381-384


113-152: All ChatMessage instantiations already include required id field—no action needed

Verification confirms that every ChatMessage object created across conversationMemoryManager.ts, redisConversationMemoryManager.ts, and conversationMemory.ts properly sets the id field. User and assistant messages use randomUUID(), while summary messages use summary-${uuid} format. The initial concern was based on false positives from external Mem0 library calls and other message types (CoreMessage, MultimodalMessage) that are distinct from ChatMessage.

Likely an incorrect or invalid review comment.

src/lib/core/conversationMemoryManager.ts (1)

66-144: Token-based storeConversationTurn path and async summarization trigger look solid

Switching storeConversationTurn to StoreConversationTurnOptions, computing a provider‑aware tokenThreshold via getEffectiveTokenThreshold, validating/truncating messages with TokenUtils, and kicking off background summarization through checkAndSummarize (guarded by summarizationInProgress) gives the in‑memory manager a coherent token‑based flow and avoids the previous empty‑array summarization pitfall.

Comment thread src/lib/utils/conversationMemory.ts
@BoraYaswanthReddy

Copy link
Copy Markdown
Contributor

@coderabbitai

Even though id was optional earlier, we still generated id for every conversation Message
Token Estimation Accuracy can be improved further in future prs, since we need to estimate tokens after every message its better if the token estimation is as fast as possible

@coderabbitai

coderabbitai Bot commented Dec 20, 2025

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@BoraYaswanthReddy, thank you for those clarifications! That context is very helpful.

On the id field: If IDs were already being generated for every conversation message, then making it required in the type is simply enforcing existing behavior — not a breaking change. That addresses my backward compatibility concern.

On token estimation accuracy: You're absolutely right about the speed vs. accuracy trade-off. Reducing latency can significantly enhance user experience, but it often comes at the cost of model quality, and since token estimation happens after every message, intelligent context management requires controlling what information reaches the LLM while preserving the signal your application needs for accurate responses. Your character-based approximation (chars/4) provides fast estimation with acceptable accuracy for the use case, which is the right engineering decision for frequent operations.

Both of these are excellent clarifications that address my earlier concerns. The remaining items from my review (race conditions, error handling, bounds checking, etc.) are still worth considering, but these two points are well-justified.

🐰✨
rabbit adjusts learning notes thoughtfully


✏️ Learnings added
Learnt from: BoraYaswanthReddy
URL: https://github.com/juspay/neurolink/pull/701

Timestamp: 2025-12-20T07:04:14.171Z
Learning: In the neurolink repository, even though the ChatMessage `id` field was optional in the TypeScript interface, IDs were already being generated for every conversation message in practice. Making the field required enforces existing behavior rather than introducing a breaking change.

Learnt from: BoraYaswanthReddy
URL: https://github.com/juspay/neurolink/pull/701

Timestamp: 2025-12-20T07:04:14.171Z
Learning: In the neurolink repository's token-based conversation memory implementation, token estimation uses a simple character-based approximation (chars/4) rather than precise tokenizer calls. This trade-off prioritizes speed over perfect accuracy because token estimation happens after every message, and fast approximation is more important than slow precision for frequent operations.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T12:24:51.147Z
Learning: Memory management should use Redis for distributed memory in production and in-memory store for development, with conversation summarization for long contexts

Learnt from: BoraYaswanthReddy
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2025-12-14T18:33:26.766Z
Learning: In the neurolink repository's Redis conversation memory implementation, message IDs were changed from sequential integers to UUIDs (using `generateUniqueId()`) as a security improvement to prevent enumeration attacks and information leakage about conversation volumes and patterns.

- Add support for token based summarization
- Add param for generate and stream to enable/disable token based summarization
@Yaswanth-2874
Yaswanth-2874 force-pushed the BZ-47204-implement-token-based-summarizer-in-llm-context branch from 5cb3e31 to a81cf82 Compare December 20, 2025 08:16
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.

2 participants