Skip to content

fix: curator production fixes — compaction, timeout, MCP events, Langfuse - #937

Merged
murdore merged 1 commit into
releasefrom
fix/curator-production-fixes
Apr 9, 2026
Merged

murdore merged 1 commit into
releasefrom
fix/curator-production-fixes

Conversation

@murdore

@murdore murdore commented Apr 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes 6 production issues identified across 4 Curator monitoring cycles (2.27M telemetry rows, 10.3 GB, Mar 18 – Apr 8 2026) where the root cause lives in the NeuroLink SDK, not in Curator.

Root Cause

Production telemetry from Curator (AI Slack assistant using NeuroLink as core engine) revealed six compounding issues:

  1. Token overflow — compaction pipeline ineffective — `pruneMinimumSavings: 20_000` blocked Stage 1 from pruning tool outputs unless savings exceeded 20K tokens. In typical Curator conversations, stages 1-2 found nothing, stage 3 summarization failed silently, and stage 4 truncation used coarse 25%/iteration jumps (3 max). Evidence: 207K tokens vs 200K limit, compaction removed ~5 tokens, 3 wasted retry attempts per overflow.

  2. Compaction watermark prevented recovery — After compaction failed and threw `ContextBudgetExceededError`, the `lastCompactionMessageCount` watermark was stale in all three paths (`compactMCPConversationForBudget`, `directProviderGeneration`, `createMCPStream`). Subsequent `handleContextOverflow` recovery calls saw "already compacted" and skipped re-compaction.

  3. Vertex AI timeout too short — `BaseProvider.defaultTimeout = 30000` (30s) was hardcoded. `DEFAULT_TIMEOUTS.providers.vertex = "60s"` existed in `timeout.ts` as dead code — `Utilities.getTimeout()` never called `getDefaultTimeout()`. Gemini p90 latency for large contexts (69K-353K tokens) is 54.5s. Evidence: 21 timeout errors in 5-day period. Additionally, no dynamic scaling existed for large contexts.

  4. MCP events missing serverName — All `externalMCP:*` events carried `serverId` but not `serverName`. The `removeServer` path was especially broken — it called `getServerName()` after `servers.delete()` had already destroyed the instance.

  5. Duplicate Langfuse spans — When both NeuroLink and the consumer registered a `LangfuseSpanProcessor`, duplicate spans were exported. No auto-detection existed.

  6. AsyncLocalStorage context loss — `setImmediate()` in `storeMemoryInBackground` lost AsyncLocalStorage context. Background memory writes appeared as orphaned root spans in Langfuse. Also lacked a timeout, so hung Redis connections could block indefinitely.

Changes

Context Compaction (FIX 1-2):

  • `pruneMinimumSavings` 20K → 500 so Stage 1 prunes tool outputs with modest savings
  • Stage 4 truncation escalation: +25%/3 iterations → +10%/6 iterations (18%→28%→38%→48%→58%→68%)
  • `this.lastCompactionMessageCount.delete()` before every `ContextBudgetExceededError` throw in all 3 paths (`compactMCPConversationForBudget`, `directProviderGeneration`, `createMCPStream`)
  • slidingWindowTruncator doc comment updated from "3 passes" to "6 passes"

Timeout (FIX 3):

  • `Utilities.getTimeout()` now calls `getDefaultTimeout(providerName, "generate")` returning per-provider values (vertex=60s, ollama=5m, bedrock=45s) instead of hardcoded 30s
  • Timeout check uses `!== undefined && !== null` to honor zero/falsy values
  • Comment explains why "generate" is always used (both `TextGenerationOptions` and `StreamOptions` share the `input` property — no reliable discriminator; streaming has its own timeout infrastructure)
  • New `getContextAwareTimeout()` utility: >100K → 1.5x, >200K → 2x, >300K → 2.5x (capped at 4x). Uses `(estimatedTokens - 1)` divisor so exact boundaries stay in lower tier
  • Wired up in `ensureMCPGenerationBudget`: after `checkContextBudget` returns `estimatedInputTokens`, sets `options.timeout` with scaled value if user didn't specify one — propagates to all downstream provider calls naturally via `getTimeout(options)`

MCP Events (FIX 4):

  • `serverName: string` added to all 7 `ExternalMCPServerEvents` type payloads
  • `getServerName()` helper made public (resolves `instance.config.name || serverId`)
  • `removeServer` captures name before `servers.delete()`
  • `removeExternalMCPServer` in neurolink.ts captures name via `getServerName()` before `removeServer()` destroys the instance

Langfuse Observability (FIX 5-6):

  • Auto-detect existing `LangfuseSpanProcessor` with 3-tier detection: (1) `instanceof LangfuseSpanProcessor`, (2) duck-type `langfuseClient` property, (3) `constructor.name` fallback. Detailed comments explain fragility of `_registeredSpanProcessors` internal API and fallback behavior
  • `storeMemoryInBackground` setImmediate wrapped with `runWithCurrentLangfuseContext()`
  • `Promise.all(writeOps)` wrapped in `withTimeout(…, 30_000)` with comment explaining why underlying ops aren't cancelled (Mem0 client doesn't support AbortSignal; fire-and-forget background writes where stale completion is harmless)

Verified — No Change Needed

  • Tool call corruption (FIX 6) — Role filter at `messageBuilder.ts:1392-1397` and `isValidRole` guard at line 313 confirmed present.

Files Changed

File What
`src/lib/context/contextCompactor.ts` `pruneMinimumSavings` 20K→500, `maxIterations` 3→6
`src/lib/context/stages/slidingWindowTruncator.ts` Escalation +25%→+10%, doc "3 passes"→"6 passes"
`src/lib/core/modules/Utilities.ts` `getTimeout()` per-provider defaults, null check, `getContextAwareTimeout()` with `(n-1)` divisor
`src/lib/types/externalMcp.ts` `serverName: string` in all 7 event types
`src/lib/mcp/externalServerManager.ts` Public `getServerName()`, all emit sites, capture name before `servers.delete()`
`src/lib/neurolink.ts` Watermark delete in 3 paths, timeout scaling in `ensureMCPGenerationBudget`, `serverName` capture before removal, `runWithCurrentLangfuseContext` + `withTimeout` for memory
`src/lib/services/server/ai/observability/instrumentation.ts` 3-tier Langfuse detection (`instanceof` → duck-type → constructor name) with fragility comments
`docs/superpowers/plans/2026-04-09-curator-production-fixes.md` Updated plan with wiring step and corrected snippets

Test Plan

  • `pnpm run check` — 0 errors, 0 warnings across 3,746 files
  • `pnpm run lint` — passes (1 pre-existing warning for function length)
  • `pnpm run build` — full build succeeds (svelte-package + CLI + browser)
  • Pre-commit hooks pass
  • Deploy to staging, send >100K token Curator conversation → verify timeout log shows scaled value
  • Verify Vertex timeout errors drop (30s → 60s+ default)
  • Verify MCP metrics show `server=` instead of `server=unknown`
  • Verify `externalMCP:serverRemoved` emits real name after removal
  • Verify no duplicate Langfuse spans with default config
  • Verify background memory writes appear under parent Langfuse trace
  • Verify memory write timeout fires after 30s on hung Redis

Copilot AI review requested due to automatic review settings April 9, 2026 04:42
@vercel

vercel Bot commented Apr 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
neurolink Ready Ready Preview, Comment Apr 9, 2026 7:47pm

@coderabbitai

coderabbitai Bot commented Apr 9, 2026 •

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.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7768a3eb-48fd-436f-ac1a-87d73e65404d

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR adds a rollout plan document and implements six production fixes: context compaction/truncation tuning, provider-aware and context‑scaled timeouts, extended External MCP event payloads with serverName, Langfuse span processor duplicate detection, preservation of AsyncLocalStorage Langfuse context for background writes, and bounded background memory writes with compaction recovery watermark clearing.

Changes

Cohort / File(s) Summary
Context Optimization
src/lib/context/contextCompactor.ts, src/lib/context/stages/slidingWindowTruncator.ts
Lowered Stage‑1 pruneMinimumSavings 20_000→500; increased sliding‑window maxIterations 3→6 and reduced per‑iteration escalation from +0.25→+0.1.
Timeout Handling
src/lib/core/modules/Utilities.ts
getTimeout now prefers per‑provider defaults via getDefaultTimeout + parseTimeout and respects explicit caller timeouts; added getContextAwareTimeout(options, estimatedTokens?) to scale timeouts (stepwise, capped at 4×) for very large inputs.
External MCP Events & Types
src/lib/types/externalMcp.ts, src/lib/mcp/externalServerManager.ts, src/lib/neurolink.ts
Added serverName: string to ExternalMCPServerEvents; added getServerName(serverId); emit serverName on tool discovery/removal and server connected/disconnected/health/status events, capturing name before removal.
Langfuse Observability
src/lib/services/server/ai/observability/instrumentation.ts
Detects existing LangfuseSpanProcessor on the global TracerProvider (reads internal _registeredSpanProcessors) and skips registering the SDK's Langfuse processor when one already exists or when configured to skip.
AsyncLocalStorage Context & Compaction Recovery
src/lib/neurolink.ts
Wraps background setImmediate memory-write callbacks with runWithCurrentLangfuseContext(...); bounds background writes with a 30s timeout; clears lastCompactionMessageCount before throwing ContextBudgetExceededError to enable re-compaction.
Documentation / Plan
docs/superpowers/plans/2026-04-09-curator-production-fixes.md
Adds a checklist-driven plan describing seven commit‑able tasks covering the above fixes with verification commands and staging checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

released

Suggested reviewers

  • Pdogra2520
  • pdogra1299

Poem

🐰 I hop through logs and silent streams,
I tune the trims and steady dreams,
Names on servers wear a bow,
Langfuse traces safe in tow,
Async writes snug—now onward, team!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: six production fixes addressing compaction, timeout, MCP events, and Langfuse issues mentioned in the objectives.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/curator-production-fixes

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.

@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@github-actions

github-actions Bot commented Apr 9, 2026 •

Copy link
Copy Markdown
Contributor

Documentation Validation Results

🚀 Documentation validation passed!

Check Status Result
Frontmatter Validation ✅ Passed
TypeScript Check ✅ Passed
Build ✅ Passed
Link Validation ✅ Passed

📦 Build artifact uploaded successfully. Ready for deployment preview.

Commit: 3fe2172a70a66a19a13d8f5271259bb6ab05e5e7 | Workflow: View logs

Copilot AI 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.

Pull request overview

This PR applies a set of NeuroLink SDK production fixes observed via Curator monitoring, spanning context compaction behavior, provider timeouts, external MCP event payloads, and Langfuse/OpenTelemetry instrumentation.

Changes:

  • Adjusts context compaction behavior (lower prune threshold; finer truncation escalation; clears compaction watermark on failure).
  • Updates timeout selection to use per-provider defaults and adds context-size-based timeout scaling.
  • Adds serverName to external MCP event payloads and avoids duplicate Langfuse span processor registration; preserves ALS context across background memory writes.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/lib/context/contextCompactor.ts Lowers pruneMinimumSavings and increases truncation maxIterations to reduce near-miss over-truncation.
src/lib/context/stages/slidingWindowTruncator.ts Changes iterative truncation escalation step from +25% to +10%.
src/lib/neurolink.ts Clears compaction watermark on final-budget failure; wraps background memory write to preserve Langfuse ALS; adds serverName to SDK-emitted MCP events.
src/lib/core/modules/Utilities.ts Uses per-provider default timeouts (via getDefaultTimeout) and adds getContextAwareTimeout().
src/lib/types/externalMcp.ts Extends exported MCP server event payload types with serverName.
src/lib/mcp/externalServerManager.ts Populates serverName for emitted server/tool lifecycle events.
src/lib/services/server/ai/observability/instrumentation.ts Skips registering Langfuse span processor if one is already present on the global tracer provider.
docs/superpowers/plans/2026-04-09-curator-production-fixes.md Adds an implementation/verification plan documenting the fixes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/core/modules/Utilities.ts Outdated
Comment on lines +234 to +238
if (options.timeout) {
const parsed = parseTimeout(options.timeout);
if (parsed !== undefined) {
return parsed;
}

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

getTimeout() calls parseTimeout() without guarding for exceptions. parseTimeout() throws for values like "0s"/"0" (<= 0), but validateTextGenerationOptions() currently allows those formats, so a caller can pass validation and still crash at runtime. Consider either tightening validation to reject non-positive timeouts or wrapping parseTimeout in try/catch and falling back to defaults with a warning/error.

Copilot uses AI. Check for mistakes.
Comment on lines 305 to +309
export type ExternalMCPServerEvents = {
/** Server status changed */
statusChanged: {
serverId: string;
serverName: string;

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

ExternalMCPServerEvents is exported as part of the public SDK types. Making serverName a required field is a TypeScript-breaking change for any downstream code that constructs these event payloads (e.g., tests/mocks). If you want to keep this backwards compatible, consider making serverName optional in the type (while still always emitting it at runtime), or introduce a new versioned event type.

Copilot uses AI. Check for mistakes.
Comment on lines 960 to 964
this.emit("disconnected", {
serverId,
serverName: this.getServerName(serverId),
reason: "Manually removed",
timestamp: new Date(),

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

In removeServer(), the server is deleted from this.servers before emitting the disconnected event, so getServerName(serverId) will always fall back to serverId (the config name is no longer available). Capture serverName (or the instance/config) before deleting, and use that captured value in the emitted payload.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/neurolink.ts
Comment on lines 10801 to 10804
this.emitter.emit("externalMCP:serverRemoved", {
serverId,
serverName: serverId,
timestamp: Date.now(),

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

externalMCP:serverRemoved emits serverName: serverId, which defeats the goal of providing a human-readable server name (and differs from serverAdded, which uses config.name || serverId). Consider resolving the name before removal (e.g., via externalServerManager.getServer(serverId)?.config.name) and emitting that value (fall back to serverId if unavailable).

Copilot uses AI. Check for mistakes.

@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: 3

Caution

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

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

956-965: ⚠️ Potential issue | 🟡 Minor

Capture serverName before removing the server entry.

this.servers.delete(serverId) runs immediately before this emit, so getServerName(serverId) falls back to serverId on manual removals. That leaves disconnected.serverName wrong for named servers in the one path this PR is meant to normalize.

🛠️ Minimal fix
+      const serverName = instance.config.name || serverId;
+
       // Remove from registry
       this.servers.delete(serverId);

       // Emit event
       this.emit("disconnected", {
         serverId,
-        serverName: this.getServerName(serverId),
+        serverName,
         reason: "Manually removed",
         timestamp: new Date(),
       } satisfies ExternalMCPServerEvents["disconnected"]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/mcp/externalServerManager.ts` around lines 956 - 965, Before calling
this.servers.delete(serverId) capture the server name into a local variable so
the emitted "disconnected" event includes the correct name; i.e., call const
serverName = this.getServerName(serverId) (or similar) before
this.servers.delete(serverId) and then pass serverName into
this.emit("disconnected", { serverId, serverName, reason: "Manually removed",
timestamp: new Date() } as ExternalMCPServerEvents["disconnected"]). Ensure you
reference the same symbol names (getServerName, servers.delete, emit,
ExternalMCPServerEvents["disconnected"]) used in the diff.
🧹 Nitpick comments (3)
src/lib/neurolink.ts (1)

1719-1750: Wrap background memory writes with a timeout guard.

The new background async path still runs unbounded (Promise.all(writeOps)), which can hang indefinitely under downstream latency spikes. Please wrap it with withTimeout.

♻️ Suggested patch
-        await Promise.all(writeOps);
+        await withTimeout(
+          Promise.all(writeOps),
+          10000,
+          new Error("Background memory write timed out"),
+        );
As per coding guidelines `**/*.ts`: Use `withTimeout` utility to wrap async calls for error handling.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/neurolink.ts` around lines 1719 - 1750, The background memory write
block created in runWithCurrentLangfuseContext (wrappedMemoryWrite) currently
awaits Promise.all(writeOps) without a timeout; replace that await with a call
to withTimeout(Promise.all(writeOps), <reasonable ms>) and handle the potential
timeout/rejection in the catch so the operation cannot hang indefinitely; keep
the existing logger.warn in the catch and include timeout context in the
message. Ensure you import/use withTimeout and apply this change where
ensureMemoryReady(), client.add(...), and Promise.all(writeOps) are used before
setImmediate(wrappedMemoryWrite).
docs/superpowers/plans/2026-04-09-curator-production-fixes.md (2)

628-646: Enhance verification checklist to cover all changes.

The verification commands are generally good, but consider adding:

  1. TypeScript compilation check for Task 3 providerName changes:

    # Verify Utilities constructor accepts providerName parameter
    grep -A 3 "constructor.*defaultTimeout.*providerName" src/lib/core/modules/Utilities.ts
    
    # Verify BaseProvider passes providerName
    grep -A 2 "new Utilities.*this.name" src/lib/core/baseProvider.ts
  2. Verify Task 4 serverRemoved uses getServerName (not hardcoded serverId):

    # Check serverRemoved doesn't hardcode serverId as serverName
    rg "serverRemoved.*serverName.*serverId[^,]*(,|\})" src/lib/neurolink.ts
  3. Add a runtime verification step for staging deployment to confirm:

    • Langfuse duplicate span prevention
    • Timeout scaling for large contexts
    • MCP event serverName population
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md` around lines
628 - 646, Update the Verification Checklist to include static checks that
confirm the Utilities constructor now accepts providerName and that BaseProvider
forwards its name: add grep/rg checks for
"constructor.*defaultTimeout.*providerName" in Utilities
(src/lib/core/modules/Utilities.ts) and for "new Utilities.*this.name" in
BaseProvider (src/lib/core/baseProvider.ts); add a pattern check ensuring
serverRemoved uses getServerName instead of hardcoded serverId by searching for
serverRemoved and instances of serverId used as serverName in neurolink
(src/lib/neurolink.ts); and append runtime staging verification steps to
exercise Langfuse duplicate-span prevention, timeout scaling for large contexts,
and that MCP events include populated serverName (verify via logs or a small
staged scenario).

15-26: Clarify file structure table entries.

The table lists src/lib/neurolink.ts three times (lines 19, 24, 20) and mentions src/lib/core/baseProvider.ts (line 20), but:

  • The multiple neurolink.ts entries could be consolidated or differentiated more clearly (e.g., "neurolink.ts (compaction)", "neurolink.ts (MCP events)", "neurolink.ts (AsyncLocalStorage)").
  • baseProvider.ts is listed in the table but not referenced in any task steps. Task 3 modifies only Utilities.ts and doesn't touch baseProvider.ts. Either remove it from the table or add the corresponding implementation steps.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md` around lines
15 - 26, The table has duplicate neurolink.ts rows and a dangling
baseProvider.ts entry; consolidate the three neurolink rows into clearly labeled
entries (e.g., "src/lib/neurolink.ts (compaction)", "src/lib/neurolink.ts (MCP
events)", "src/lib/neurolink.ts (AsyncLocalStorage/ALS)") so each task maps to
the specific change (clear compaction watermark on budget failure, include
serverName in serverAdded/serverRemoved events, wrap setImmediate with ALS
context/auto-detect Langfuse processor). Also either remove the
`src/lib/core/baseProvider.ts` row if no change is required, or add a
corresponding implementation step that updates baseProvider to use per-provider
default timeouts (pointing reviewers to the Utilities.getDefaultTimeout change
and the baseProvider methods that currently hardcode 30000ms).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md`:
- Around line 417-424: The serverRemoved emitter currently hardcodes serverName
to serverId; update the emit to resolve the canonical name like other events by
calling the same helper or lookup used elsewhere (e.g., use
getServerName(serverId) or retrieve the server instance from
this.mcpServerManager and use instance.config.name || serverId) before emitting
externalMCP:serverRemoved so add serverName: getServerName(serverId) (or
serverName: instance.config.name || serverId) and ensure this.mcpServerManager
is available in the current scope or fetch the instance first.

In `@src/lib/core/modules/Utilities.ts`:
- Around line 232-245: In getTimeout, don't gate parseTimeout on truthiness (it
should accept 0 or empty-string), so call parseTimeout(options.timeout) directly
and return the parsed value if it's not undefined; also pick the provider
default based on whether options is streaming or generation (use
getDefaultTimeout(this.providerName, ..."stream" or "generate")—detect
StreamOptions via a streaming discriminator present on StreamOptions such as an
onData/onChunk/onStream callback or a boolean stream flag) instead of hardcoding
"generate"; reference getTimeout, parseTimeout, getDefaultTimeout,
TextGenerationOptions, StreamOptions, providerName, and defaultTimeout when
making the change.

In `@src/lib/neurolink.ts`:
- Around line 10801-10804: When emitting the "externalMCP:serverRemoved" event,
preserve the configured human-readable name instead of setting serverName to
serverId: before you remove the server from the internal store, read the
server's configured name (e.g., from the server instance or config object held
in the class) into a local variable and then call
this.emitter.emit("externalMCP:serverRemoved", { serverId, serverName:
<capturedName>, timestamp: Date.now() }) so the event includes the real name;
update the code around the removal logic that currently calls this.emitter.emit
to use that capturedName (refer to the this.emitter.emit invocation and the
surrounding removal function).

---

Outside diff comments:
In `@src/lib/mcp/externalServerManager.ts`:
- Around line 956-965: Before calling this.servers.delete(serverId) capture the
server name into a local variable so the emitted "disconnected" event includes
the correct name; i.e., call const serverName = this.getServerName(serverId) (or
similar) before this.servers.delete(serverId) and then pass serverName into
this.emit("disconnected", { serverId, serverName, reason: "Manually removed",
timestamp: new Date() } as ExternalMCPServerEvents["disconnected"]). Ensure you
reference the same symbol names (getServerName, servers.delete, emit,
ExternalMCPServerEvents["disconnected"]) used in the diff.

---

Nitpick comments:
In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md`:
- Around line 628-646: Update the Verification Checklist to include static
checks that confirm the Utilities constructor now accepts providerName and that
BaseProvider forwards its name: add grep/rg checks for
"constructor.*defaultTimeout.*providerName" in Utilities
(src/lib/core/modules/Utilities.ts) and for "new Utilities.*this.name" in
BaseProvider (src/lib/core/baseProvider.ts); add a pattern check ensuring
serverRemoved uses getServerName instead of hardcoded serverId by searching for
serverRemoved and instances of serverId used as serverName in neurolink
(src/lib/neurolink.ts); and append runtime staging verification steps to
exercise Langfuse duplicate-span prevention, timeout scaling for large contexts,
and that MCP events include populated serverName (verify via logs or a small
staged scenario).
- Around line 15-26: The table has duplicate neurolink.ts rows and a dangling
baseProvider.ts entry; consolidate the three neurolink rows into clearly labeled
entries (e.g., "src/lib/neurolink.ts (compaction)", "src/lib/neurolink.ts (MCP
events)", "src/lib/neurolink.ts (AsyncLocalStorage/ALS)") so each task maps to
the specific change (clear compaction watermark on budget failure, include
serverName in serverAdded/serverRemoved events, wrap setImmediate with ALS
context/auto-detect Langfuse processor). Also either remove the
`src/lib/core/baseProvider.ts` row if no change is required, or add a
corresponding implementation step that updates baseProvider to use per-provider
default timeouts (pointing reviewers to the Utilities.getDefaultTimeout change
and the baseProvider methods that currently hardcode 30000ms).

In `@src/lib/neurolink.ts`:
- Around line 1719-1750: The background memory write block created in
runWithCurrentLangfuseContext (wrappedMemoryWrite) currently awaits
Promise.all(writeOps) without a timeout; replace that await with a call to
withTimeout(Promise.all(writeOps), <reasonable ms>) and handle the potential
timeout/rejection in the catch so the operation cannot hang indefinitely; keep
the existing logger.warn in the catch and include timeout context in the
message. Ensure you import/use withTimeout and apply this change where
ensureMemoryReady(), client.add(...), and Promise.all(writeOps) are used before
setImmediate(wrappedMemoryWrite).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55a01c34-9937-4f26-b0de-fcf83f4d0261

📥 Commits

Reviewing files that changed from the base of the PR and between 92572ac and 02b6136.

📒 Files selected for processing (8)
  • docs/superpowers/plans/2026-04-09-curator-production-fixes.md
  • src/lib/context/contextCompactor.ts
  • src/lib/context/stages/slidingWindowTruncator.ts
  • src/lib/core/modules/Utilities.ts
  • src/lib/mcp/externalServerManager.ts
  • src/lib/neurolink.ts
  • src/lib/services/server/ai/observability/instrumentation.ts
  • src/lib/types/externalMcp.ts

Comment thread docs/superpowers/plans/2026-04-09-curator-production-fixes.md Outdated
Comment thread src/lib/core/modules/Utilities.ts
Comment thread src/lib/neurolink.ts
@murdore
murdore force-pushed the fix/curator-production-fixes branch from 02b6136 to 88c6bf1 Compare April 9, 2026 09:19
@github-actions

github-actions Bot commented Apr 9, 2026 •

Copy link
Copy Markdown
Contributor

✅ Single Commit Policy - COMPLIANT

Status: Policy requirements met • 1 commit • Valid format • Ready for merge

📊 View validation details

📝 Commit Details

  • Hash: 1fce85156a29c476cee66edbd1ae1bf9836a0ae0
  • Message: fix(sdk): curator production fixes — compaction, timeout, MCP events, Langfuse
  • Author: Sachin Sharma

✅ Validation Results

  • Single commit requirement met
  • No merge commits in branch
  • Semantic commit message format verified
  • Ready for squash merge to release branch

🤖 Automated validation by NeuroLink Single Commit Enforcement

@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore

murdore commented Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai do full review and verify if all the comments are resolved.

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown

@murdore Sure! I'll kick off a full review of PR #937 and verify the status of all existing comments.

✅ Actions performed

Full review triggered.

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
src/lib/context/stages/slidingWindowTruncator.ts (1)

9-10: ⚠️ Potential issue | 🟡 Minor

Stale comment: "up to 3 passes" should be "up to 6 passes".

The doc comment references the old iteration count. With maxIterations increased to 6 in contextCompactor.ts, this should be updated for consistency.

- *   instead of fixed 50%, with iterative refinement up to 3 passes.
+ *   instead of fixed 50%, with iterative refinement up to 6 passes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/context/stages/slidingWindowTruncator.ts` around lines 9 - 10, The
doc comment in slidingWindowTruncator.ts incorrectly states "up to 3 passes" for
the adaptive truncation loop; update that comment to "up to 6 passes" to match
the increased maxIterations used in contextCompactor.ts (and search for any
other stale mentions of "3 passes" related to slidingWindowTruncator or adaptive
truncation and update them to 6). Ensure the comment near the
slidingWindowTruncator adaptive truncation description is changed accordingly.
♻️ Duplicate comments (1)
src/lib/core/modules/Utilities.ts (1)

241-249: ⚠️ Potential issue | 🟠 Major

getTimeout() misclassifies multimodal generate calls as stream.

Line 244 infers "stream" from options.input, but TextGenerationOptions in this file also uses input (see Line 173-Line 179). That routes generate requests to streaming defaults (2m) and bypasses provider-specific generate defaults.

💡 Suggested fix
-  getTimeout(options: TextGenerationOptions | StreamOptions): number {
+  getTimeout(
+    options: TextGenerationOptions | StreamOptions,
+    operation: "generate" | "stream" = "generate",
+  ): number {
@@
-    // Detect streaming vs generation for per-provider default lookup.
-    // StreamOptions has an `input` property; TextGenerationOptions does not.
-    const operation =
-      "input" in options && options.input ? "stream" : "generate";
-
     // Use per-provider default (e.g., vertex=60s, streaming=2m) instead of global 30s
     const providerDefault = parseTimeout(
       getDefaultTimeout(this.providerName, operation),
     );
#!/bin/bash
# Verify whether `input` exists on both option types and inspect getTimeout call sites.
rg -n -C3 'type\s+TextGenerationOptions|interface\s+TextGenerationOptions|input\??\s*:' src/lib/types
rg -n -C3 'type\s+StreamOptions|interface\s+StreamOptions|input\??\s*:' src/lib/types src/lib/types/streamTypes.ts
rg -n -C2 '\.getTimeout\s*\(' src/lib
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/core/modules/Utilities.ts` around lines 241 - 249, getTimeout
incorrectly classifies calls as "stream" by checking "input" in options; since
TextGenerationOptions also defines input, change the detection in
Utilities.getTimeout (the block that sets operation and calls
getDefaultTimeout/parseTimeout) to use a reliable discriminator between
StreamOptions and TextGenerationOptions (e.g., check for a StreamOptions-only
property or an explicit options.mode/stream flag) instead of "input", so
generate requests use provider generate defaults; update the logic around
operation, getDefaultTimeout(this.providerName, operation), and parseTimeout
accordingly and add/adjust any type guards for
StreamOptions/TextGenerationOptions to ensure correct routing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md`:
- Around line 214-229: The getTimeout method's snippet is stale: replace the
truthy check "if (options.timeout)" with an explicit undefined/null check (e.g.,
options.timeout !== undefined && options.timeout !== null) so zero or falsy
timeouts are honored, and remove the hardcoded "generate" operation when calling
getDefaultTimeout — instead pass the actual operation context used by this class
(e.g., use this.operation or an operation parameter) so
getDefaultTimeout(this.providerName, <operation>) is called; update references
in getTimeout and any callers to align with the current implementation.

In `@src/lib/core/modules/Utilities.ts`:
- Around line 267-269: The scaling currently uses Math.floor(estimatedTokens /
100_000) which makes exact multiples (100_000, 200_000, 300_000) count as the
next tier; change the divisor logic to use Math.floor((estimatedTokens - 1) /
100_000) so that thresholds are strictly ">" (e.g., 100_000 stays 1x, 100_001 →
1.5x), keeping the rest of the computation (scale = 1 + ... * 0.5,
Math.min(scale, 4), and Math.round(baseTimeout * ...)) unchanged and referencing
the existing estimatedTokens, scale, and baseTimeout variables.

In `@src/lib/neurolink.ts`:
- Around line 5411-5412: The code currently deletes lastCompactionMessageCount
for compactionSessionId in one failure path but not in other exits; update the
other compaction failure paths (specifically inside directProviderGeneration and
createMCPStream where they set lastCompactionMessageCount then throw
ContextBudgetExceededError) to also remove the watermark before throwing so
handleContextOverflow can later re-compact; locate the places in
directProviderGeneration and createMCPStream that call
this.lastCompactionMessageCount.set(...) then throw ContextBudgetExceededError
and insert this.lastCompactionMessageCount.delete(compactionSessionId) (or
ensure a finally/cleanup block runs) immediately before the throw.

In `@src/lib/services/server/ai/observability/instrumentation.ts`:
- Around line 671-682: The code reads the internal OpenTelemetry field
_registeredSpanProcessors on provider and compares constructor?.name to
"LangfuseSpanProcessor" (in hasExistingLangfuse), which is fragile and may break
on minified builds or OpenTelemetry changes; update the instrumentation.ts
around the provider/_registeredSpanProcessors check to (1) add a clear comment
explaining this is using an internal API and the constructor-name check is
brittle, (2) document the intended fallback behavior if the field or name check
fails (e.g., treat as no existing Langfuse processor and proceed or provide an
explicit opt-out/config flag), and (3) optionally recommend a more robust
detection approach in the comment (such as exposing a public registration flag,
using instanceof when available, or relying on explicit configuration) so future
maintainers know why this is fragile and what to change (referencing variables:
provider, _registeredSpanProcessors, hasExistingLangfuse, and
LangfuseSpanProcessor).

---

Outside diff comments:
In `@src/lib/context/stages/slidingWindowTruncator.ts`:
- Around line 9-10: The doc comment in slidingWindowTruncator.ts incorrectly
states "up to 3 passes" for the adaptive truncation loop; update that comment to
"up to 6 passes" to match the increased maxIterations used in
contextCompactor.ts (and search for any other stale mentions of "3 passes"
related to slidingWindowTruncator or adaptive truncation and update them to 6).
Ensure the comment near the slidingWindowTruncator adaptive truncation
description is changed accordingly.

---

Duplicate comments:
In `@src/lib/core/modules/Utilities.ts`:
- Around line 241-249: getTimeout incorrectly classifies calls as "stream" by
checking "input" in options; since TextGenerationOptions also defines input,
change the detection in Utilities.getTimeout (the block that sets operation and
calls getDefaultTimeout/parseTimeout) to use a reliable discriminator between
StreamOptions and TextGenerationOptions (e.g., check for a StreamOptions-only
property or an explicit options.mode/stream flag) instead of "input", so
generate requests use provider generate defaults; update the logic around
operation, getDefaultTimeout(this.providerName, operation), and parseTimeout
accordingly and add/adjust any type guards for
StreamOptions/TextGenerationOptions to ensure correct routing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f22fd21-00c7-43f4-aaf9-e7b63bbed2dc

📥 Commits

Reviewing files that changed from the base of the PR and between 92572ac and 0d7e87b.

📒 Files selected for processing (8)
  • docs/superpowers/plans/2026-04-09-curator-production-fixes.md
  • src/lib/context/contextCompactor.ts
  • src/lib/context/stages/slidingWindowTruncator.ts
  • src/lib/core/modules/Utilities.ts
  • src/lib/mcp/externalServerManager.ts
  • src/lib/neurolink.ts
  • src/lib/services/server/ai/observability/instrumentation.ts
  • src/lib/types/externalMcp.ts

Comment on lines +214 to +229
getTimeout(options: TextGenerationOptions | StreamOptions): number {
// If caller specified a timeout, use it (supports number ms and string formats)
if (options.timeout) {
const parsed = parseTimeout(options.timeout);
if (parsed !== undefined) {
return parsed;
}
}

// Use per-provider default (e.g., vertex=60s, ollama=5m) instead of global 30s
const providerDefault = parseTimeout(
getDefaultTimeout(this.providerName, "generate"),
);
return providerDefault ?? this.defaultTimeout;
}
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Timeout code snippet is stale vs current fix.

This snippet still shows if (options.timeout) and hardcoded "generate". If reused, it reintroduces the timeout regression that was just fixed. Please align the snippet with the current implementation behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md` around lines
214 - 229, The getTimeout method's snippet is stale: replace the truthy check
"if (options.timeout)" with an explicit undefined/null check (e.g.,
options.timeout !== undefined && options.timeout !== null) so zero or falsy
timeouts are honored, and remove the hardcoded "generate" operation when calling
getDefaultTimeout — instead pass the actual operation context used by this class
(e.g., use this.operation or an operation parameter) so
getDefaultTimeout(this.providerName, <operation>) is called; update references
in getTimeout and any callers to align with the current implementation.

Comment thread src/lib/core/modules/Utilities.ts
Comment thread src/lib/neurolink.ts
Comment thread src/lib/services/server/ai/observability/instrumentation.ts Outdated
@murdore
murdore force-pushed the fix/curator-production-fixes branch from 0d7e87b to 042e341 Compare April 9, 2026 14:56
@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore

murdore commented Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
✅ 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: 4

♻️ Duplicate comments (1)
docs/superpowers/plans/2026-04-09-curator-production-fixes.md (1)

214-233: ⚠️ Potential issue | 🟠 Major

Hardcoded "generate" operation causes streaming operations to use incorrect timeout values.

The code calls getTimeout(options) from multiple streaming methods (anthropic.ts:980, openAI.ts:348, openRouter.ts:304, mistral.ts:79, etc.), but getTimeout() hardcodes the "generate" operation instead of "stream".

This means:

  • OpenAI streaming gets 30s timeout instead of 2m
  • Vertex streaming gets 60s timeout instead of 2m
  • Anthropic streaming gets 60s timeout instead of 2m

The comment claiming "BaseProvider.stream() applies its own longer timeout" is incorrect—no such override exists. BaseProvider.stream() simply calls executeStream(), which uses the timeout from getTimeout() directly.

Fix: Pass the operation type through to getTimeout(), or detect it dynamically from the options parameter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md` around lines
214 - 233, getTimeout currently hardcodes the "generate" operation causing
streaming paths to get wrong timeouts; change getTimeout signature to accept an
operation parameter (e.g., operation: "generate" | "stream") or infer it from
whether options is StreamOptions, then use that operation when calling
getDefaultTimeout(this.providerName, operation). Update all callers (streaming
methods like those in anthropic.ts, openAI.ts, openRouter.ts, mistral.ts and any
BaseProvider.stream/executeStream invocations) to pass "stream" for streaming
paths (and "generate" for non-streaming calls or keep default behavior),
ensuring TextGenerationOptions/StreamOptions callers are adjusted accordingly.
🧹 Nitpick comments (1)
docs/superpowers/plans/2026-04-09-curator-production-fixes.md (1)

482-514: Optional: Consider more robust processor detection.

The auto-detection at lines 487-498 relies on:

  1. Internal property _registeredSpanProcessors (underscore-prefixed, suggesting it's private/internal to the OpenTelemetry SDK).
  2. Constructor name comparison (p.constructor?.name === "LangfuseSpanProcessor"), which can break with minification or class wrapping.

While this works for the immediate use case, consider:

Alternative detection approaches
  • Check for a unique public method or property specific to LangfuseSpanProcessor (e.g., typeof p.flush === 'function' && p.constructor.name.includes('Langfuse')).
  • If the Langfuse SDK exposes a way to query registered processors, use that instead.
  • Document the fragility in a code comment so future maintainers are aware.

Example:

const hasExistingLangfuse = existingProcessors.some(
  (p) =>
    p &&
    typeof p === "object" &&
    // Fragile: relies on constructor name (breaks with minification)
    (p.constructor?.name === "LangfuseSpanProcessor" ||
     // Fallback: check for Langfuse-specific methods
     (typeof (p as any).flush === "function" && "langfuseClient" in (p as any)))
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md` around lines
482 - 514, The current auto-detection uses the internal
_registeredSpanProcessors array and a fragile constructor name check
(hasExistingLangfuse) which can break under minification; update the detection
in the provider registration block to prefer a public SDK query if available,
otherwise broaden the predicate to check for Langfuse-specific public members
(e.g., presence of a flush method or a langfuseClient property) on each
processor instead of relying solely on p.constructor?.name, and add a short
comment next to _registeredSpanProcessors/hasExistingLangfuse explaining the
fragility and why the fallback checks are needed; keep the existing skipLangfuse
and logging behavior unchanged and still gate adding langfuseProcessor via
provider.addSpanProcessor when appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md`:
- Around line 534-607: The saveToMemory background op using setImmediate in
neurolink.ts must both preserve AsyncLocalStorage and be bounded by a 30s
timeout: import runWithCurrentLangfuseContext and withTimeout (if missing), wrap
the async memory write callback (the block starting at setImmediate(async () =>
{ ... } that calls this.ensureMemoryReady() and performs memory writes) by first
wrapping the body with withTimeout(..., 30_000) and then wrap that with
runWithCurrentLangfuseContext, assign to a symbol like wrappedMemoryWrite, and
call setImmediate(wrappedMemoryWrite) instead of setImmediate(async () => ...);
ensure error handling inside remains unchanged and run pnpm run check to verify
zero errors.
- Around line 236-259: The new getContextAwareTimeout method is never invoked;
update call sites that currently use getTimeout to pass estimated token counts
into getContextAwareTimeout instead. Specifically, in provider entry points such
as BaseProvider.generate and BaseProvider.stream (and any provider-specific
overrides) replace calls like this.getTimeout(options) with
this.getContextAwareTimeout(options, estimatedTokens) where estimatedTokens is
the precomputed token count (e.g., from your context compaction/token estimation
flow or ContextCompactor/MessageTokenCounter results); also update any context
compaction flows that call getTimeout to use getContextAwareTimeout and thread
the estimatedTokens value through the call chain. Ensure all call signatures
supply the correct estimated token variable.
- Around line 29-88: The PR only clears the compaction watermark in
compactMCPConversationForBudget but misses two other failure paths; locate the
catch/throw sites in directProviderGeneration and createMCPStream where
ContextBudgetExceededError is thrown or re-thrown and delete the compaction
watermark there using the same symbol pattern (call
this.lastCompactionMessageCount.delete(compactionSessionId)) before re-throwing
so handleContextOverflow can retry compaction; ensure you reference
compactionSessionId and ContextBudgetExceededError in each fix and run pnpm run
check to verify no errors.

In `@src/lib/neurolink.ts`:
- Around line 1745-1749: withTimeout currently only races Promise.all(writeOps)
so client.add(...) calls keep running after the timeout; to fix this, make the
underlying write ops cancellable or use transport-level timeouts: update the
write flow that builds writeOps to use a cancellable pattern (e.g., an
AbortController/AbortSignal passed into each client.add call or a client.add
timeout option) and ensure when withTimeout triggers you call abort() on that
controller (or close/reset the Redis connection) so the in-flight client.add
operations are actually stopped; change the code that creates writeOps and the
client.add invocation to accept and honor the abort/timeout signal so the
background memory writes are truly bounded.

---

Duplicate comments:
In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md`:
- Around line 214-233: getTimeout currently hardcodes the "generate" operation
causing streaming paths to get wrong timeouts; change getTimeout signature to
accept an operation parameter (e.g., operation: "generate" | "stream") or infer
it from whether options is StreamOptions, then use that operation when calling
getDefaultTimeout(this.providerName, operation). Update all callers (streaming
methods like those in anthropic.ts, openAI.ts, openRouter.ts, mistral.ts and any
BaseProvider.stream/executeStream invocations) to pass "stream" for streaming
paths (and "generate" for non-streaming calls or keep default behavior),
ensuring TextGenerationOptions/StreamOptions callers are adjusted accordingly.

---

Nitpick comments:
In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md`:
- Around line 482-514: The current auto-detection uses the internal
_registeredSpanProcessors array and a fragile constructor name check
(hasExistingLangfuse) which can break under minification; update the detection
in the provider registration block to prefer a public SDK query if available,
otherwise broaden the predicate to check for Langfuse-specific public members
(e.g., presence of a flush method or a langfuseClient property) on each
processor instead of relying solely on p.constructor?.name, and add a short
comment next to _registeredSpanProcessors/hasExistingLangfuse explaining the
fragility and why the fallback checks are needed; keep the existing skipLangfuse
and logging behavior unchanged and still gate adding langfuseProcessor via
provider.addSpanProcessor when appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f397c672-7e40-4fc4-b24c-fab72829a63b

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7e87b and 042e341.

📒 Files selected for processing (8)
  • docs/superpowers/plans/2026-04-09-curator-production-fixes.md
  • src/lib/context/contextCompactor.ts
  • src/lib/context/stages/slidingWindowTruncator.ts
  • src/lib/core/modules/Utilities.ts
  • src/lib/mcp/externalServerManager.ts
  • src/lib/neurolink.ts
  • src/lib/services/server/ai/observability/instrumentation.ts
  • src/lib/types/externalMcp.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/lib/context/stages/slidingWindowTruncator.ts
  • src/lib/types/externalMcp.ts
  • src/lib/services/server/ai/observability/instrumentation.ts

Comment on lines +29 to +88
### Task 1: Context Compaction — Lower Prune Threshold and Clear Watermark on Failure [CRITICAL]

**Problem:** `pruneMinimumSavings: 20_000` means Stage 1 skips pruning unless it can save 20K+ tokens. In typical Curator conversations (no tool outputs, no file duplicates), stages 1-2 do nothing, stage 3 summarization fails silently, and stage 4 truncation may not be aggressive enough. The watermark check also prevents re-compaction on the `handleContextOverflow` recovery path since message count hasn't grown.

**Files:**

- Modify: `src/lib/context/contextCompactor.ts:47` (pruneMinimumSavings default)
- Modify: `src/lib/neurolink.ts:5342` (watermark set) and `~5401` (throw path)

- [ ] **Step 1: Lower pruneMinimumSavings from 20_000 to 500**

In `src/lib/context/contextCompactor.ts`, change line 47:

```typescript
// Before
pruneMinimumSavings: 20_000,

// After
pruneMinimumSavings: 500,
```

This allows Stage 1 to prune tool outputs even when savings are modest (500+ tokens). The 20K threshold was too conservative — in production, even small savings compound across the pipeline.

- [ ] **Step 2: Clear watermark when compaction fails to bring context under budget**

In `src/lib/neurolink.ts`, in `compactMCPConversationForBudget`, the watermark is set at line ~5342 regardless of whether compaction succeeds. When the final budget check fails and throws `ContextBudgetExceededError`, the watermark is stale — subsequent `handleContextOverflow` recovery calls see "already compacted this message count" and skip compaction.

Find the throw at line ~5402 (the `ContextBudgetExceededError` inside `compactMCPConversationForBudget`):

```typescript
if (!finalBudget.withinBudget) {
throw new ContextBudgetExceededError(
```

Add a watermark clear before the throw:

```typescript
if (!finalBudget.withinBudget) {
// Clear watermark so handleContextOverflow recovery can re-compact
this.lastCompactionMessageCount.delete(compactionSessionId);

throw new ContextBudgetExceededError(
```

- [ ] **Step 3: Verify build**

Run: `pnpm run check`
Expected: 0 errors

- [ ] **Step 4: Commit**

```bash
git add src/lib/context/contextCompactor.ts src/lib/neurolink.ts
git commit -m "fix(context): lower prune threshold and clear watermark on compaction failure

pruneMinimumSavings 20K→500 so Stage 1 prunes tool outputs with modest savings.
Clear compaction watermark when budget check fails, allowing handleContextOverflow
recovery to re-compact instead of skipping (stale watermark)."
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Task 1 incomplete — missing watermark clears in two other code paths.

The PR summary states the watermark must be cleared "in compactMCPConversationForBudget, directProviderGeneration, createMCPStream" (three locations), but Task 1 only shows the fix for compactMCPConversationForBudget. The other two failure paths (directProviderGeneration and createMCPStream) will still exhibit stale watermark behavior, leaving the production issue partially unresolved.

Add substeps to Task 1 covering:

  • directProviderGeneration: Find where it calls compaction and catches ContextBudgetExceededError, then clear the watermark before re-throwing.
  • createMCPStream: Similarly locate the compaction failure path and clear the watermark.

Each location should follow the same pattern as line 68:

this.lastCompactionMessageCount.delete(compactionSessionId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md` around lines
29 - 88, The PR only clears the compaction watermark in
compactMCPConversationForBudget but misses two other failure paths; locate the
catch/throw sites in directProviderGeneration and createMCPStream where
ContextBudgetExceededError is thrown or re-thrown and delete the compaction
watermark there using the same symbol pattern (call
this.lastCompactionMessageCount.delete(compactionSessionId)) before re-throwing
so handleContextOverflow can retry compaction; ensure you reference
compactionSessionId and ContextBudgetExceededError in each fix and run pnpm run
check to verify no errors.

Comment on lines +236 to +259
- [ ] **Step 3: Add dynamic timeout scaling for large contexts**

After the `getTimeout` method, add a new method for context-aware timeout:

```typescript
/**
* Get timeout scaled by estimated input token count.
* For large contexts (>100K tokens), increase timeout proportionally.
*/
getContextAwareTimeout(
options: TextGenerationOptions | StreamOptions,
estimatedTokens?: number,
): number {
const baseTimeout = this.getTimeout(options);

if (!estimatedTokens || estimatedTokens <= 100_000) {
return baseTimeout;
}

// Scale: >100K → 1.5x, >200K → 2x, >300K → 2.5x
const scale = 1 + Math.floor(estimatedTokens / 100_000) * 0.5;
return Math.round(baseTimeout * Math.min(scale, 4));
}
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Integration step missing: getContextAwareTimeout() is defined but never called.

Task 3 adds the getContextAwareTimeout() method, but the plan doesn't show where to integrate it. The method won't be used unless call sites are updated to invoke it instead of getTimeout().

Add a substep to Task 3 showing where to call getContextAwareTimeout(). Likely candidates:

  • Provider generate/stream methods where token counts are already estimated
  • Context compaction flows where message token counts are computed

For example, if BaseProvider has token estimates before calling the underlying API, replace:

-const timeout = this.utilities.getTimeout(options);
+const timeout = this.utilities.getContextAwareTimeout(options, estimatedTokens);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md` around lines
236 - 259, The new getContextAwareTimeout method is never invoked; update call
sites that currently use getTimeout to pass estimated token counts into
getContextAwareTimeout instead. Specifically, in provider entry points such as
BaseProvider.generate and BaseProvider.stream (and any provider-specific
overrides) replace calls like this.getTimeout(options) with
this.getContextAwareTimeout(options, estimatedTokens) where estimatedTokens is
the precomputed token count (e.g., from your context compaction/token estimation
flow or ContextCompactor/MessageTokenCounter results); also update any context
compaction flows that call getTimeout to use getContextAwareTimeout and thread
the estimatedTokens value through the call chain. Ensure all call signatures
supply the correct estimated token variable.

Comment on lines +534 to +607
### Task 6: AsyncLocalStorage Context Propagation [LOW]

**Problem:** `setImmediate()` at `neurolink.ts:1718` (memory write background op) loses AsyncLocalStorage context. The `runWithCurrentLangfuseContext` helper exists and is used in Redis/conversation memory managers but not here. Spans created inside this callback become orphaned Langfuse traces.

**Files:**

- Modify: `src/lib/neurolink.ts:1718` (setImmediate in saveToMemory)

- [ ] **Step 1: Import runWithCurrentLangfuseContext if not already imported**

In `src/lib/neurolink.ts`, check imports. If `runWithCurrentLangfuseContext` is not imported, add it to the existing observability import:

```typescript
import { runWithCurrentLangfuseContext } from "./services/server/ai/observability/instrumentation.js";
```

- [ ] **Step 2: Wrap setImmediate callback with context propagation**

In `src/lib/neurolink.ts`, find line 1718:

```typescript
setImmediate(async () => {
try {
const client = this.ensureMemoryReady();
```

Replace with:

```typescript
const wrappedMemoryWrite = runWithCurrentLangfuseContext(async () => {
try {
const client = this.ensureMemoryReady();
```

Then find the closing of this `setImmediate` callback (the matching `});`) and replace:

```typescript
// Before
});

// After
});
setImmediate(wrappedMemoryWrite);
```

The full pattern becomes:

```typescript
const wrappedMemoryWrite = runWithCurrentLangfuseContext(async () => {
try {
const client = this.ensureMemoryReady();
// ... rest of the async body unchanged ...
} catch (error) {
// ... error handling unchanged ...
}
});
setImmediate(wrappedMemoryWrite);
```

- [ ] **Step 3: Verify build**

Run: `pnpm run check`
Expected: 0 errors

- [ ] **Step 4: Commit**

```bash
git add src/lib/neurolink.ts
git commit -m "fix(langfuse): preserve AsyncLocalStorage context in memory write background op

Wrap setImmediate callback in runWithCurrentLangfuseContext to prevent orphaned
Langfuse traces from background memory operations."
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: Task 6 missing withTimeout wrapper for background memory writes.

The PR summary states "bounded background memory writes with ... withTimeout(..., 30_000)", the AI summary mentions "bounded background writes with a 30s timeout", and the verification checklist (line 658) checks for withTimeout.*writeOps, but Task 6's implementation steps (lines 550-591) only add runWithCurrentLangfuseContext() without the timeout wrapper.

Add missing step for withTimeout wrapper

After Step 2, add:

Step 2b: Add timeout wrapper to bound background writes

Import withTimeout if not already imported:

import { withTimeout } from "./utils/async.js"; // adjust path as needed

Wrap the async memory operation with a 30-second timeout:

const wrappedMemoryWrite = runWithCurrentLangfuseContext(async () => {
  return withTimeout(async () => {
    try {
      const client = this.ensureMemoryReady();
      // ... rest of memory write logic ...
    } catch (error) {
      // ... error handling ...
    }
  }, 30_000); // 30s timeout
});
setImmediate(wrappedMemoryWrite);

This prevents hung Redis writes from blocking indefinitely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-09-curator-production-fixes.md` around lines
534 - 607, The saveToMemory background op using setImmediate in neurolink.ts
must both preserve AsyncLocalStorage and be bounded by a 30s timeout: import
runWithCurrentLangfuseContext and withTimeout (if missing), wrap the async
memory write callback (the block starting at setImmediate(async () => { ... }
that calls this.ensureMemoryReady() and performs memory writes) by first
wrapping the body with withTimeout(..., 30_000) and then wrap that with
runWithCurrentLangfuseContext, assign to a symbol like wrappedMemoryWrite, and
call setImmediate(wrappedMemoryWrite) instead of setImmediate(async () => ...);
ensure error handling inside remains unchanged and run pnpm run check to verify
zero errors.

Comment thread src/lib/neurolink.ts
Comment on lines +1745 to +1749
await withTimeout(
Promise.all(writeOps),
30_000,
new Error("Background memory write timed out after 30s"),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

withTimeout here does not actually stop hung memory writes.

withTimeout() only races Promise.all(writeOps); the underlying client.add(...) calls keep running after 30s. That means this still logs a timeout without actually bounding the stuck Redis/I/O work the PR is trying to fix. The underlying write path needs cancellation or transport-level timeouts, not just a raced wrapper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/neurolink.ts` around lines 1745 - 1749, withTimeout currently only
races Promise.all(writeOps) so client.add(...) calls keep running after the
timeout; to fix this, make the underlying write ops cancellable or use
transport-level timeouts: update the write flow that builds writeOps to use a
cancellable pattern (e.g., an AbortController/AbortSignal passed into each
client.add call or a client.add timeout option) and ensure when withTimeout
triggers you call abort() on that controller (or close/reset the Redis
connection) so the in-flight client.add operations are actually stopped; change
the code that creates writeOps and the client.add invocation to accept and honor
the abort/timeout signal so the background memory writes are truly bounded.

@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore

murdore commented Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please verify each change freshly against the current code. Specifically check:

Context Compaction:

  1. pruneMinimumSavings: 500 in contextCompactor.ts
  2. maxIterations: 6 in contextCompactor.ts
  3. Escalation step +0.1 (not +0.25) in slidingWindowTruncator.ts
  4. Doc comment says "6 passes" (not "3 passes") in slidingWindowTruncator.ts
  5. lastCompactionMessageCount.delete() exists before ContextBudgetExceededError throw in ALL 3 paths: compactMCPConversationForBudget, directProviderGeneration, createMCPStream in neurolink.ts

Timeout:
6. getTimeout() in Utilities.ts uses options.timeout !== undefined && options.timeout !== null (not truthiness)
7. getTimeout() calls getDefaultTimeout(this.providerName, "generate") with comment explaining why always "generate"
8. getContextAwareTimeout() uses (estimatedTokens - 1) divisor so 100K stays 1x
9. ensureMCPGenerationBudget in neurolink.ts sets options.timeout for >100K token contexts

MCP Events:
10. serverName: string in all 7 ExternalMCPServerEvents types in externalMcp.ts
11. getServerName() is public (not private) in externalServerManager.ts
12. removeServer captures name BEFORE servers.delete() in externalServerManager.ts
13. removeExternalMCPServer captures name via getServerName() BEFORE removeServer() in neurolink.ts

Langfuse:
14. 3-tier detection in instrumentation.ts: instanceof → langfuseClient duck-type → constructor.name fallback
15. Fragility comment explains _registeredSpanProcessors is internal OTel API
16. storeMemoryInBackground uses runWithCurrentLangfuseContext wrapping in neurolink.ts
17. withTimeout(Promise.all(writeOps), 30_000) with comment about why underlying ops aren't cancelled

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== CHECK 1 & 2: pruneMinimumSavings and maxIterations in contextCompactor.ts ==="
grep -n "pruneMinimumSavings\|maxIterations" src/lib/context/contextCompactor.ts

echo ""
echo "=== CHECK 3 & 4: escalation step and doc comment in slidingWindowTruncator.ts ==="
grep -n "0\.1\|0\.25\|passes\|6\|3" src/lib/context/stages/slidingWindowTruncator.ts | head -30

echo ""
echo "=== CHECK 5: lastCompactionMessageCount.delete() in ALL 3 paths in neurolink.ts ==="
grep -n "lastCompactionMessageCount.delete\|ContextBudgetExceededError\|compactMCPConversationForBudget\|directProviderGeneration\|createMCPStream" src/lib/neurolink.ts | head -60

echo ""
echo "=== CHECK 6 & 7: getTimeout() implementation in Utilities.ts ==="
grep -n "getTimeout\|undefined\|null\|getDefaultTimeout\|generate\|providerName" src/lib/core/modules/Utilities.ts | head -40

echo ""
echo "=== CHECK 8: getContextAwareTimeout divisor in Utilities.ts ==="
grep -n "estimatedTokens\|divisor\|100_000\|100000\|1x\|- 1" src/lib/core/modules/Utilities.ts | head -20

echo ""
echo "=== CHECK 9: ensureMCPGenerationBudget sets options.timeout in neurolink.ts ==="
grep -n "ensureMCPGenerationBudget\|options\.timeout\|100_000\|100000" src/lib/neurolink.ts | head -20

echo ""
echo "=== CHECK 10: serverName in all 7 ExternalMCPServerEvents types ==="
grep -n "serverName\|statusChanged\|connected\|disconnected\|failed\|toolDiscovered\|toolRemoved\|healthCheck" src/lib/types/externalMcp.ts

echo ""
echo "=== CHECK 11: getServerName() is public in externalServerManager.ts ==="
grep -n "getServerName\|public\|private" src/lib/mcp/externalServerManager.ts | head -20

echo ""
echo "=== CHECK 12: removeServer captures name BEFORE servers.delete() ==="
grep -n "getServerName\|servers\.delete\|removeServer\|serverName" src/lib/mcp/externalServerManager.ts | head -30

echo ""
echo "=== CHECK 13: removeExternalMCPServer captures name BEFORE removeServer() in neurolink.ts ==="
grep -n "removeExternalMCPServer\|getServerName\|removeServer\|serverName" src/lib/neurolink.ts | head -30

echo ""
echo "=== CHECK 14: 3-tier detection in instrumentation.ts ==="
grep -n "instanceof\|langfuseClient\|constructor\.name\|LangfuseSpanProcessor\|hasExistingLangfuse" src/lib/services/server/ai/observability/instrumentation.ts | head -30

echo ""
echo "=== CHECK 15: Fragility comment about _registeredSpanProcessors ==="
grep -n "_registeredSpanProcessors\|internal\|fragil\|OTel\|private" src/lib/services/server/ai/observability/instrumentation.ts | head -20

echo ""
echo "=== CHECK 16 & 17: runWithCurrentLangfuseContext and withTimeout in neurolink.ts ==="
grep -n "runWithCurrentLangfuseContext\|storeMemoryInBackground\|withTimeout\|writeOps\|30_000\|cancelled\|cancel" src/lib/neurolink.ts | head -30

Length of output: 12991


@murdore: All 17 items verified against the current code. Here's the full result:


✅ Context Compaction (5/5)

# Check Location Status
1 pruneMinimumSavings: 500 contextCompactor.ts L47 ✅
2 maxIterations: 6 contextCompactor.ts L228 ✅
3 Escalation step +0.1 slidingWindowTruncator.ts L218 ✅
4 Doc comment says "6 passes" slidingWindowTruncator.ts L10 ✅
5 lastCompactionMessageCount.delete() before throw in all 3 paths neurolink.ts L5436→L5438 (compactMCPConversationForBudget), L5741→L5743 (directProviderGeneration), L7238→L7240 (createMCPStream) ✅

✅ Timeout (4/4)

# Check Location Status
6 options.timeout !== undefined && options.timeout !== null (strict null check) Utilities.ts L234 ✅
7 getDefaultTimeout(this.providerName, "generate") with explanatory comment Utilities.ts L242–248 ✅
8 (estimatedTokens - 1) divisor with comment explaining 100K stays 1× Utilities.ts L268–270 ✅
9 ensureMCPGenerationBudget sets options.timeout when === undefined and estimatedInputTokens > 100_000 neurolink.ts L5279–5289 ✅

✅ MCP Events (4/4)

# Check Location Status
10 serverName: string in all 7 ExternalMCPServerEvents types externalMcp.ts L309, 318, 326, 334, 342, 351, 359 ✅
11 getServerName() is public (no private modifier) externalServerManager.ts L306 ✅
12 removeServer captures serverName at L954 before servers.delete() at L960 externalServerManager.ts ✅
13 removeExternalMCPServer calls getServerName() at L10828 before removeServer() at L10830 neurolink.ts ✅

✅ Langfuse (4/4)

# Check Location Status
14 3-tier detection: instanceof LangfuseSpanProcessor (L694) → "langfuseClient" in p duck-type (L698) → constructor.name fallback (L704) instrumentation.ts ✅
15 Fragility comment about _registeredSpanProcessors being internal OTel API instrumentation.ts L681–682 ✅
16 storeMemoryInBackground wraps callback with runWithCurrentLangfuseContext(async () => { … }) neurolink.ts L1722 ✅
17 await withTimeout(Promise.all(writeOps), 30_000, …) with race/no-cancel comment neurolink.ts L1745–1752 ✅

All 17 checks pass. The code matches every specification exactly as described. 🎉


🧠 Learnings used
Learnt from: sudharsan-juspay
Repo: juspay/neurolink PR: 160
File: src/lib/providers/index.ts:43-44
Timestamp: 2025-09-10T08:22:11.910Z
Learning: In the Neurolink project, type deduplication across modules (like ProviderName definitions) should be handled as separate tasks rather than mixed with other refactoring efforts, as there are multiple such occurrences throughout the codebase that need systematic cleanup.

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2025-11-04T22:14:18.719Z
Learning: In the juspay/neurolink repository, do not flag existing type or interface definitions located outside src/lib/types/ - these are part of a phased migration plan and will be addressed in upcoming PRs. Only enforce type centralization rules on new code going forward.

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-02-22T12:42:53.802Z
Learning: In neurolink TypeScript code, when checking for optional config fields not declared on the interface (e.g., ConversationMemoryConfig.redis), prefer using the `'key' in obj` guard and then a narrowed/type-asserted access (e.g., (memConfig && "redis" in memConfig && !!(memConfig as { redis?: unknown }).redis)) instead of optional chaining on undeclared fields.

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.

Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T21:40:22.010Z
Learning: Applies to src/lib/types/conversation.ts : Context type changes should be made in `src/lib/types/conversation.ts` and must ensure backward compatibility

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-05T19:37:01.295Z
Learning: In the Neurolink codebase (`juspay/neurolink`), the MCP tool event payload emitted in `neurolink.ts` (around line 7804) uses `event.responseTime` (not `event.duration`) for tool execution timing. The `with-tools.ts` template also uses `event.responseTime`. Do not flag `event.responseTime` as incorrect in docs or templates.

… Langfuse

Fixes 6 production issues from Curator monitoring whose root cause is in the
NeuroLink SDK: token overflow compaction (prune threshold, watermark, graduated
truncation), Vertex timeout per-provider defaults, MCP event serverName field,
Langfuse duplicate span auto-detection, and AsyncLocalStorage context propagation.
@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore
murdore merged commit 220b582 into release Apr 9, 2026
16 checks passed
@murdore
murdore deleted the fix/curator-production-fixes branch April 9, 2026 20:02
@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 9.50.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

This branch was successfully deployed

1 active deployment
Preview — 1fce8515 Deployed Apr 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants