@nexus-cortex/core 4.84.0 → 4.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/adapters/GatewayTranslationLayer.d.ts.map +1 -1
  2. package/dist/adapters/GatewayTranslationLayer.js +11 -2
  3. package/dist/adapters/GatewayTranslationLayer.js.map +1 -1
  4. package/dist/models/cards/deepseek/deepseek-v4-flash-vision-exp.d.ts.map +1 -1
  5. package/dist/models/cards/deepseek/deepseek-v4-flash-vision-exp.js +5 -0
  6. package/dist/models/cards/deepseek/deepseek-v4-flash-vision-exp.js.map +1 -1
  7. package/dist/models/cards/deepseek/deepseek-v4-flash.d.ts.map +1 -1
  8. package/dist/models/cards/deepseek/deepseek-v4-flash.js +7 -0
  9. package/dist/models/cards/deepseek/deepseek-v4-flash.js.map +1 -1
  10. package/dist/models/cards/deepseek/deepseek-v4-pro.d.ts.map +1 -1
  11. package/dist/models/cards/deepseek/deepseek-v4-pro.js +9 -0
  12. package/dist/models/cards/deepseek/deepseek-v4-pro.js.map +1 -1
  13. package/dist/models/configurators/DeepSeekConfigurator.d.ts +8 -0
  14. package/dist/models/configurators/DeepSeekConfigurator.d.ts.map +1 -1
  15. package/dist/models/configurators/DeepSeekConfigurator.js +3 -0
  16. package/dist/models/configurators/DeepSeekConfigurator.js.map +1 -1
  17. package/dist/orchestrator/APIClient.d.ts.map +1 -1
  18. package/dist/orchestrator/APIClient.js +17 -1
  19. package/dist/orchestrator/APIClient.js.map +1 -1
  20. package/dist/orchestrator/CortexOrchestrator.d.ts +1 -2
  21. package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
  22. package/dist/orchestrator/CortexOrchestrator.js +48 -36
  23. package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
  24. package/dist/tools/ToolProfile.d.ts +9 -0
  25. package/dist/tools/ToolProfile.d.ts.map +1 -1
  26. package/dist/tools/ToolProfile.js +37 -0
  27. package/dist/tools/ToolProfile.js.map +1 -1
  28. package/package.json +3 -3
@@ -30,7 +30,7 @@ import { CacheMetricsAccumulator } from '../session/CacheMetricsAccumulator.js';
30
30
  import { SubAgentProcessManager, createSubAgentProcessManager } from './SubAgentProcessManager.js';
31
31
  // Phase 1: Tool Architecture Refactor - Unified Tool Registry
32
32
  import { toolFactory } from '../tools/ToolFactory.js';
33
- import { resolveToolProfile, resolveToolAnchor, resolveFrameProfile, isNarrowProfile, isToolAllowedByProfile, applyToolProfile } from '../tools/ToolProfile.js';
33
+ import { resolveToolProfile, resolveToolAnchor, resolveFrameProfile, resolveLiftNudge, resolveHeadlessDropAskUser, resolveDeferredLoading, isNarrowProfile, isToolAllowedByProfile, applyToolProfile } from '../tools/ToolProfile.js';
34
34
  import { readStagedDoctrine, applyCuratedDoctrine, runOrientForStaging, withTimeout } from './doctrineCuration.js';
35
35
  import { ExactRepeatTracker } from '../training/loopLadder.js';
36
36
  import { slashCommandRegistry } from '../commands/SlashCommandRegistry.js';
@@ -163,6 +163,7 @@ export class CortexOrchestrator {
163
163
  * exactly once, at the anchor-lift boundary. One-shot per orchestrator. */
164
164
  deferredCorpusDelivered = false;
165
165
  liftNudgeDelivered = false; // A′ proposal-1: lift-boundary SearchTools/AskForAdvice nudge, one-shot
166
+ effectiveDeferredLoading = true; // per-turn resolved deferred-loading (card > env > settings); set at assembly
166
167
  /**
167
168
  * Item 10 — helper-curated doctrine freshness (operator-designed; docs/
168
169
  * HARNESS_IMPROVEMENT_BACKLOG.md). SYNCHRONOUS-BY-BOUNDARY: under defer the
@@ -272,11 +273,6 @@ export class CortexOrchestrator {
272
273
  console.warn(`[Anchor] deferred corpus delivery failed (continuing without): ${e?.message ?? e}`);
273
274
  }
274
275
  }
275
- /** Truthy check for a raw env flag (mirrors the CORTEX_PROMPT_MASS parse idiom). */
276
- envOn(v) {
277
- const s = (v ?? '').trim().toLowerCase();
278
- return s === '1' || s === 'true' || s === 'yes' || s === 'on';
279
- }
280
276
  /** A′ proposal-2: in a NON-INTERACTIVE session (stateless /v1/messages, headless
281
277
  * API, piped oneshot) there is no human to answer AskUserQuestion — it becomes a
282
278
  * stall trap. When CORTEX_HEADLESS_DROP_ASKUSER is truthy AND the session is
@@ -284,9 +280,9 @@ export class CortexOrchestrator {
284
280
  * (deferred filter, anchor, lift rebuild, re-filter) excludes it. Kept in
285
281
  * interactive TUIs (a real user can answer). Headless swaps the human-ask for the
286
282
  * model-ask (AskForAdvice, which the lift nudge signposts). Gated + idempotent. */
287
- stripHeadlessOnlyTools(tools) {
288
- if (!this.envOn(process.env.CORTEX_HEADLESS_DROP_ASKUSER))
289
- return tools;
283
+ stripHeadlessOnlyTools(tools, model) {
284
+ if (!resolveHeadlessDropAskUser(process.env, model?.headlessDropAskUser))
285
+ return tools; // card > env > false
290
286
  if (!this.approvalMode.autoApproveActions)
291
287
  return tools; // interactive: keep it
292
288
  return tools.filter((t) => t.name !== 'AskUserQuestion');
@@ -299,12 +295,12 @@ export class CortexOrchestrator {
299
295
  * AFTER the model has committed to acting (post-lift is where prompt mass is safe —
300
296
  * this is a one-line signpost, NOT defer's full corpus). Fires at most once.
301
297
  * Mirrors deliverDeferredCorpusAtLift's append-to-last-tool_result mechanism. */
302
- deliverLiftNudge(allTools) {
298
+ deliverLiftNudge(allTools, model) {
303
299
  if (this.liftNudgeDelivered)
304
300
  return;
305
- if (!this.envOn(process.env.CORTEX_LIFT_NUDGE))
306
- return;
307
- if (!this.config.enableDeferredToolLoading)
301
+ if (!resolveLiftNudge(process.env, model?.liftNudge))
302
+ return; // card > env > false
303
+ if (!this.effectiveDeferredLoading)
308
304
  return; // deferred off ⇒ no hidden tools to point at
309
305
  this.liftNudgeDelivered = true; // one-shot even if assembly fails
310
306
  try {
@@ -441,7 +437,7 @@ export class CortexOrchestrator {
441
437
  // The closure captures `this` so it resolves the active model's naming
442
438
  // convention at call-time — names in SearchTools results match the tools
443
439
  // array the model sees (e.g. snake_case for XAI, PascalCase for Anthropic).
444
- if (this.config.enableDeferredToolLoading) {
440
+ if (this.config.enableDeferredToolLoading) { // constructor setup — base capability, not per-turn
445
441
  const searchExecutor = this.executorRegistry.getExecutor('SearchTools');
446
442
  if (searchExecutor && typeof searchExecutor.setToolProvider === 'function') {
447
443
  const namingHandler = new ToolNamingHandler();
@@ -480,7 +476,7 @@ export class CortexOrchestrator {
480
476
  console.log(`[Orchestrator] Dependency injection pattern - all components injected`);
481
477
  console.log(`[Orchestrator] ExecutorRegistry: ${this.executorRegistry.getExecutorCount()} executors`);
482
478
  console.log(`[Orchestrator] MCP enabled: ${mcpManager !== null}`);
483
- if (this.config.enableDeferredToolLoading) {
479
+ if (this.config.enableDeferredToolLoading) { // constructor debug log
484
480
  console.log(`[Orchestrator] Deferred tool loading: ENABLED`);
485
481
  }
486
482
  }
@@ -613,11 +609,16 @@ export class CortexOrchestrator {
613
609
  const mcpTools = this.mcpAutoInject && !bashOnlyProfile ? this.getMcpToolsAsCanonical() : [];
614
610
  const mcpManagementTools = bashOnlyProfile ? [] : this.getMcpManagementTools();
615
611
  const contextManagementTools = bashOnlyProfile ? [] : this.getContextManagementTools();
612
+ // Per-model deferred-loading resolved ONCE per turn (card > env > settings), so every per-request
613
+ // deferred gate below flips together — this is what makes the pro card's deferredToolLoading:false
614
+ // toggle (or a global env) coherent instead of split-brain. Constructor SearchTools wiring keeps
615
+ // this.config (base capability, pre-turn).
616
+ this.effectiveDeferredLoading = resolveDeferredLoading(process.env, model?.deferredToolLoading, this.config.enableDeferredToolLoading);
616
617
  // Include all default tools by default, or custom tools if provided
617
618
  // A′ proposal-2: strip AskUserQuestion in non-interactive sessions (gated) at the source.
618
619
  const allTools = this.stripHeadlessOnlyTools(options.tools !== undefined
619
620
  ? [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools, ...options.tools]
620
- : [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools]);
621
+ : [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools], model);
621
622
  // Phase 2.3: Inject server-side tools when enabled and model supports them
622
623
  // R20: provider-aware — XAI gets web/x/code; OpenAI gets web/code/file/image
623
624
  // (Responses API hosted tools); other providers stay client-only.
@@ -726,7 +727,7 @@ export class CortexOrchestrator {
726
727
  }
727
728
  // Deferred tool loading: categorized announcement with descriptions so
728
729
  // the model can match user intent to tool names without needing full schemas.
729
- if (this.config.enableDeferredToolLoading && Array.isArray(injectedContent)) {
730
+ if (this.effectiveDeferredLoading && Array.isArray(injectedContent)) {
730
731
  const convention = model.tools?.namingConvention || 'PascalCase';
731
732
  const announcement = this.buildDeferredToolAnnouncement(convention);
732
733
  if (announcement) {
@@ -850,7 +851,7 @@ export class CortexOrchestrator {
850
851
  // Check if PTC should be used for this request
851
852
  const isPTCEnabled = this.config.enablePTC && effectiveModel.supportsPTC && effectiveModel.provider === 'anthropic';
852
853
  // Deferred loading: filter tools for non-PTC paths (essential + recently-used only)
853
- if (!isPTCEnabled && this.config.enableDeferredToolLoading && toolsToUse && toolsToUse.length > 0) {
854
+ if (!isPTCEnabled && this.effectiveDeferredLoading && toolsToUse && toolsToUse.length > 0) {
854
855
  toolsToUse = this.toolFilter.getFilteredTools(toolsToUse);
855
856
  }
856
857
  // First-turn anchoring narrows further while armed (no-op once lifted).
@@ -861,7 +862,7 @@ export class CortexOrchestrator {
861
862
  // Thread the CARD's anchor to the executors (per-orchestrator config, by
862
863
  // reference) so framing-aware executor behavior (ShellTool steering
863
864
  // default) is model-card/registry-scoped, not process-global env.
864
- this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile });
865
+ this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions });
865
866
  if (toolsToUse && toolsToUse.length > 0) {
866
867
  toolsToUse = this.applyAnchorIfArmed(toolsToUse);
867
868
  }
@@ -886,7 +887,7 @@ export class CortexOrchestrator {
886
887
  // Reset sequential call counter at start of each user turn
887
888
  this.mentorshipMiddleware?.resetSequentialCalls(this.currentSessionId);
888
889
  // §13-B2: force AskForAdvice this turn on high-confidence thrash (mentor-force armed).
889
- const forcedMentorChoice = await this.resolveForcedMentorChoice(toolsToUse);
890
+ const forcedMentorChoice = await this.resolveForcedMentorChoice(toolsToUse, 0); // initial request: 0 tool calls yet
890
891
  const preparedRequest = this.gatewayTranslation.prepareRequest(canonicalHistory, toolsToUse, effectiveModel, {
891
892
  temperature: options.parameters?.temperature,
892
893
  maxTokens: options.parameters?.maxTokens,
@@ -2116,7 +2117,7 @@ export class CortexOrchestrator {
2116
2117
  // Mark this tool_use_id as processed
2117
2118
  processedToolUseIds.add(toolResult.tool_use_id);
2118
2119
  // Progressive filter: record tool use for deferred loading
2119
- if (this.config.enableDeferredToolLoading) {
2120
+ if (this.effectiveDeferredLoading) {
2120
2121
  this.toolFilter.recordToolUse(toolResult.tool_name);
2121
2122
  // Record discovered tools from SearchTools results
2122
2123
  if (toolResult.tool_name === 'SearchTools' && !toolResult.is_error) {
@@ -2307,7 +2308,7 @@ export class CortexOrchestrator {
2307
2308
  // surface — never lift, never deliver the deferred corpus.
2308
2309
  resolveFrameProfile(process.env, this.cardFrameProfile) !== 'persist') {
2309
2310
  this.anchorLifted = true;
2310
- if (!(this.config.enableDeferredToolLoading && !isPTCEnabled)) {
2311
+ if (!(this.effectiveDeferredLoading && !isPTCEnabled)) {
2311
2312
  toolsToUse = allTools;
2312
2313
  if (structuredOutputState) {
2313
2314
  toolsToUse = ensureStructuredOutputTool(toolsToUse, structuredOutputState);
@@ -2316,13 +2317,13 @@ export class CortexOrchestrator {
2316
2317
  // P6 deferral: the static corpus arrives at the same boundary as the
2317
2318
  // full tool catalog (no-op unless CORTEX_PROMPT_MASS=defer).
2318
2319
  await this.deliverDeferredCorpusAtLift(effectiveModel);
2319
- this.deliverLiftNudge(allTools); // A′ proposal-1: SearchTools/AskForAdvice signpost at lift
2320
+ this.deliverLiftNudge(allTools, effectiveModel); // A′ proposal-1: SearchTools/AskForAdvice signpost at lift
2320
2321
  if (this.config.debug)
2321
2322
  console.log('[Anchor] lifted at first tool_result boundary — session profile applies');
2322
2323
  }
2323
2324
  // Re-filter tools after SearchTools discovery so newly discovered tools
2324
2325
  // are included in the continuation request's tools array
2325
- if (this.config.enableDeferredToolLoading && !isPTCEnabled) {
2326
+ if (this.effectiveDeferredLoading && !isPTCEnabled) {
2326
2327
  const beforeCount = toolsToUse.length;
2327
2328
  toolsToUse = this.toolFilter.getFilteredTools(allTools);
2328
2329
  // StructuredOutput: the re-filter rebuilds from allTools (which never
@@ -2347,6 +2348,10 @@ export class CortexOrchestrator {
2347
2348
  ? this.messageHistory.slice(this.messageCountAtLastResponse)
2348
2349
  : this.messageHistory;
2349
2350
  const continuationCanonicalHistory = this.convertToCanonicalMessages([...continuationHistorySource]);
2351
+ // §13-B2: re-evaluate the forced mentor choice on THIS continuation — thrash develops HERE
2352
+ // (turnNumber ≥ 1 with accumulating failing outcomes), NOT on the initial request (turnNumber 0
2353
+ // where the window is empty). Attaching it only to the initial assembly made the force unreachable.
2354
+ const continuationForcedChoice = await this.resolveForcedMentorChoice(toolsToUse, toolCallIteration);
2350
2355
  const continuationRequest = this.gatewayTranslation.prepareRequest(continuationCanonicalHistory, toolsToUse, effectiveModel, {
2351
2356
  temperature: options.parameters?.temperature,
2352
2357
  maxTokens: options.parameters?.maxTokens,
@@ -2354,7 +2359,8 @@ export class CortexOrchestrator {
2354
2359
  reasoningEffort: options.parameters?.reasoningEffort, // GPT-5.1 reasoning level
2355
2360
  stream: options.streaming,
2356
2361
  staticSystemPrompt: this.currentStaticSystemPrompt, // R28
2357
- conversationId: this.currentConversationId // R28b
2362
+ conversationId: this.currentConversationId, // R28b
2363
+ toolChoice: continuationForcedChoice // §13-B2 forced mentor tool_choice (continuation)
2358
2364
  });
2359
2365
  // Stateful Responses API: chain with previous_response_id for server-side reasoning preservation
2360
2366
  if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
@@ -2919,10 +2925,12 @@ export class CortexOrchestrator {
2919
2925
  const mcpTools = this.mcpAutoInject && !bashOnlyProfile2 ? this.getMcpToolsAsCanonical() : [];
2920
2926
  const mcpManagementTools = bashOnlyProfile2 ? [] : this.getMcpManagementTools();
2921
2927
  const contextManagementTools = bashOnlyProfile2 ? [] : this.getContextManagementTools();
2928
+ // Per-model deferred-loading resolved once per turn (card > env > settings) — streaming mirror.
2929
+ this.effectiveDeferredLoading = resolveDeferredLoading(process.env, model?.deferredToolLoading, this.config.enableDeferredToolLoading);
2922
2930
  // A′ proposal-2: strip AskUserQuestion in non-interactive sessions (gated) at the source.
2923
2931
  const allTools = this.stripHeadlessOnlyTools(options.tools !== undefined
2924
2932
  ? [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools, ...options.tools]
2925
- : [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools]);
2933
+ : [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools], model);
2926
2934
  // Phase 2.3: Inject server-side tools when enabled and model supports them
2927
2935
  // R20: provider-aware (streaming path mirror of non-streaming injection above).
2928
2936
  if (isServerSideToolsEnabled() && modelSupportsServerSideTools(model)) {
@@ -3019,7 +3027,7 @@ export class CortexOrchestrator {
3019
3027
  }
3020
3028
  // Deferred tool loading: categorized announcement with descriptions so
3021
3029
  // the model can match user intent to tool names without needing full schemas.
3022
- if (this.config.enableDeferredToolLoading && Array.isArray(injectedContent)) {
3030
+ if (this.effectiveDeferredLoading && Array.isArray(injectedContent)) {
3023
3031
  const convention = model.tools?.namingConvention || 'PascalCase';
3024
3032
  const announcement = this.buildDeferredToolAnnouncement(convention);
3025
3033
  if (announcement) {
@@ -3097,7 +3105,7 @@ export class CortexOrchestrator {
3097
3105
  // Check if PTC should be used for this request (streaming)
3098
3106
  const isPTCEnabled = this.config.enablePTC && effectiveModel.supportsPTC && effectiveModel.provider === 'anthropic';
3099
3107
  // Deferred loading: filter tools for non-PTC paths (essential + recently-used only)
3100
- if (!isPTCEnabled && this.config.enableDeferredToolLoading && toolsToUse && toolsToUse.length > 0) {
3108
+ if (!isPTCEnabled && this.effectiveDeferredLoading && toolsToUse && toolsToUse.length > 0) {
3101
3109
  toolsToUse = this.toolFilter.getFilteredTools(toolsToUse);
3102
3110
  }
3103
3111
  // First-turn anchoring narrows further while armed (no-op once lifted).
@@ -3108,7 +3116,7 @@ export class CortexOrchestrator {
3108
3116
  // Thread the CARD's anchor to the executors (per-orchestrator config, by
3109
3117
  // reference) so framing-aware executor behavior (ShellTool steering
3110
3118
  // default) is model-card/registry-scoped, not process-global env.
3111
- this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile });
3119
+ this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions });
3112
3120
  if (toolsToUse && toolsToUse.length > 0) {
3113
3121
  toolsToUse = this.applyAnchorIfArmed(toolsToUse);
3114
3122
  }
@@ -3143,7 +3151,7 @@ export class CortexOrchestrator {
3143
3151
  console.log(`[Orchestrator Streaming] Input-sliced initial for cross-turn chain: sent ${initialHistoryForApiStreaming.length}/${messageHistoryForApi.length} messages`);
3144
3152
  }
3145
3153
  // §13-B2: force AskForAdvice this turn on high-confidence thrash (mentor-force armed).
3146
- const forcedMentorChoice = await this.resolveForcedMentorChoice(toolsToUse);
3154
+ const forcedMentorChoice = await this.resolveForcedMentorChoice(toolsToUse, 0); // initial request: 0 tool calls yet
3147
3155
  // Prepare request
3148
3156
  const preparedRequest = this.gatewayTranslation.prepareRequest(canonicalHistory, toolsToUse, effectiveModel, {
3149
3157
  temperature: options.parameters?.temperature,
@@ -3766,7 +3774,7 @@ export class CortexOrchestrator {
3766
3774
  // Mark this tool_use_id as processed to prevent duplicate in error handler
3767
3775
  processedToolUseIds.add(toolResult.tool_use_id);
3768
3776
  // Progressive filter: record tool use for deferred loading (streaming)
3769
- if (this.config.enableDeferredToolLoading) {
3777
+ if (this.effectiveDeferredLoading) {
3770
3778
  this.toolFilter.recordToolUse(toolResult.tool_name);
3771
3779
  if (toolResult.tool_name === 'SearchTools' && !toolResult.is_error) {
3772
3780
  const content = typeof toolResult.content === 'string'
@@ -3932,7 +3940,7 @@ export class CortexOrchestrator {
3932
3940
  // surface — never lift, never deliver the deferred corpus.
3933
3941
  resolveFrameProfile(process.env, this.cardFrameProfile) !== 'persist') {
3934
3942
  this.anchorLifted = true;
3935
- if (!(this.config.enableDeferredToolLoading && !isPTCEnabled)) {
3943
+ if (!(this.effectiveDeferredLoading && !isPTCEnabled)) {
3936
3944
  toolsToUse = allTools;
3937
3945
  if (structuredOutputState) {
3938
3946
  toolsToUse = ensureStructuredOutputTool(toolsToUse, structuredOutputState);
@@ -3940,12 +3948,12 @@ export class CortexOrchestrator {
3940
3948
  }
3941
3949
  // P6 deferral: same one-shot corpus delivery as the sendMessage path.
3942
3950
  await this.deliverDeferredCorpusAtLift(effectiveModel);
3943
- this.deliverLiftNudge(allTools); // A′ proposal-1: SearchTools/AskForAdvice signpost at lift
3951
+ this.deliverLiftNudge(allTools, effectiveModel); // A′ proposal-1: SearchTools/AskForAdvice signpost at lift
3944
3952
  if (this.config.debug)
3945
3953
  console.log('[Anchor] lifted at first tool_result boundary — session profile applies');
3946
3954
  }
3947
3955
  // Re-filter tools after SearchTools discovery (same as sendMessage path)
3948
- if (this.config.enableDeferredToolLoading && !isPTCEnabled) {
3956
+ if (this.effectiveDeferredLoading && !isPTCEnabled) {
3949
3957
  toolsToUse = this.toolFilter.getFilteredTools(allTools);
3950
3958
  // StructuredOutput: re-filter rebuilds from allTools — re-append the
3951
3959
  // request-scoped tool (same as the sendMessage continuation path).
@@ -3962,6 +3970,9 @@ export class CortexOrchestrator {
3962
3970
  ? this.messageHistory.slice(this.messageCountAtLastResponse)
3963
3971
  : this.messageHistory;
3964
3972
  const continuationCanonicalHistory = this.convertToCanonicalMessages([...streamContinuationHistorySource]);
3973
+ // §13-B2: re-evaluate the forced mentor choice on THIS streaming continuation — thrash
3974
+ // develops here, not on the initial request (mirror of the non-stream continuation fix).
3975
+ const continuationForcedChoice = await this.resolveForcedMentorChoice(toolsToUse, toolCallIteration);
3965
3976
  // Phase 2.8: Prepare continuation request
3966
3977
  // All providers maintain thinking throughout multi-turn execution
3967
3978
  const continuationRequest = this.gatewayTranslation.prepareRequest(continuationCanonicalHistory, toolsToUse, // Updated tools including SearchTools discoveries
@@ -3973,6 +3984,7 @@ export class CortexOrchestrator {
3973
3984
  stream: true, // STREAMING continuation
3974
3985
  staticSystemPrompt: this.currentStaticSystemPrompt, // R28
3975
3986
  conversationId: this.currentConversationId, // R28b
3987
+ toolChoice: continuationForcedChoice // §13-B2 forced mentor tool_choice (continuation)
3976
3988
  // Thinking enabled for all providers in continuations
3977
3989
  // This allows interleaved thinking during tool execution
3978
3990
  });
@@ -6812,7 +6824,7 @@ export class CortexOrchestrator {
6812
6824
  * of mostly-failures, currently failing). Returns the CANONICAL forced choice; the
6813
6825
  * gateway converts its name to the wire form. `tool_choice` is body-level → cache-safe.
6814
6826
  */
6815
- async resolveForcedMentorChoice(toolsToUse) {
6827
+ async resolveForcedMentorChoice(toolsToUse, turnCount) {
6816
6828
  if (!this.config.reactiveMentorship?.enabled)
6817
6829
  return undefined;
6818
6830
  if (process.env.CORTEX_MENTOR_FORCE !== 'true')
@@ -6827,7 +6839,7 @@ export class CortexOrchestrator {
6827
6839
  return undefined;
6828
6840
  try {
6829
6841
  const outcomes = await store.recentOutcomes(resolveThrashConfig().window);
6830
- return resolveThrashState(outcomes, this.turnNumber).thrashing
6842
+ return resolveThrashState(outcomes, turnCount).thrashing
6831
6843
  ? { type: 'tool', name: 'AskForAdvice' }
6832
6844
  : undefined;
6833
6845
  }