import { frameworkLogger } from '../core/framework-logger.js'; import { getMemoryRoutingProvider } from '../memory-routing/index.js'; import { recallPlate } from '../memory-routing/plates.js'; import { LESSON_TEXT_CAP, type MemoryRoutingContext, type MemorySignalLessons, type MemoryTaskConfidence, } from '../memory-routing/types.js'; const TRAP_LANGUAGE = /TYPE:\s*ontological-trap|ontological[- ]trap/i; const DEFAULT_MIN_CONFIDENCE_GATE = 0.55; export interface ResearcherMemoryContext { providerId: string; confidence: MemoryTaskConfidence; matchedSignals: string[]; recommendedAgent: string | null; triggeredBy: 'trap-language' | 'high-confidence-primitives'; /** Graded lines for signals this task already matched. */ lessons?: MemorySignalLessons[]; /** Proposal text used to recall at most one pipeline plate. */ proposalText?: string; } export function shouldQueryRepertoireConfidence( description: string, proposalType = '', ): boolean { const text = `${description} ${proposalType}`.trim(); return TRAP_LANGUAGE.test(text); } export function hasHighConfidencePrimitiveMatch( routingContext: MemoryRoutingContext, minConfidence = DEFAULT_MIN_CONFIDENCE_GATE, ): boolean { const confidences = routingContext.signalConfidences ?? {}; return Object.values(confidences).some((confidence) => confidence >= minConfidence); } function buildTaskText( proposalTitle: string, proposalDescription: string, proposalType: string, ): string { return [proposalTitle, proposalDescription, proposalType].filter(Boolean).join('\n'); } /** * Resolve Repertoire task confidence for researcher proposal analysis. * Triggered by ontological-trap language or high-confidence primitive matches. */ export async function resolveResearcherMemoryContext(input: { proposalTitle?: string; proposalDescription?: string; proposalType?: string; }): Promise { const proposalTitle = input.proposalTitle ?? ''; const proposalDescription = input.proposalDescription ?? ''; const proposalType = input.proposalType ?? ''; const taskText = buildTaskText(proposalTitle, proposalDescription, proposalType); const provider = await getMemoryRoutingProvider(); if (provider.id === 'null' || !provider.getTaskConfidence) { return null; } const trapLanguageDetected = shouldQueryRepertoireConfidence( proposalDescription, proposalType, ); const routingContext = provider.buildRoutingContext(taskText); const primitiveMatchDetected = hasHighConfidencePrimitiveMatch(routingContext); if (!trapLanguageDetected && !primitiveMatchDetected) { return null; } const confidence = provider.getTaskConfidence({ id: 'researcher-proposal', description: taskText, type: proposalType || 'governance', }); const matchedSignals = confidence.matchedSignals.length > 0 ? confidence.matchedSignals : routingContext.matchedSignals; const lessons = lessonsForMatchedSignals(routingContext.lessons, matchedSignals); const context: ResearcherMemoryContext = { providerId: provider.id, confidence, matchedSignals, recommendedAgent: confidence.recommendedAgent, triggeredBy: trapLanguageDetected ? 'trap-language' : 'high-confidence-primitives', proposalText: taskText, ...(lessons.length > 0 ? { lessons } : {}), }; if (confidence.highConfidenceTrapPresent) { frameworkLogger.log('mcps/researcher', 'repertoire-trap-detected', 'info', { providerId: provider.id, matchedSignals, complexityBoost: confidence.complexityBoost, recommendedAgent: confidence.recommendedAgent, triggeredBy: context.triggeredBy, }); } return context; } export function buildMemoryRoutingEvidence( context: ResearcherMemoryContext, ): string[] { const { confidence, matchedSignals, recommendedAgent } = context; const signalSummary = confidence.signals .map((entry) => `${entry.name} (${entry.confidence.toFixed(2)})`) .join(', '); const lines = [ `Repertoire memory routing (${context.providerId}, trigger: ${context.triggeredBy})`, `Matched signals: ${matchedSignals.join(', ') || 'none'}`, `Signal confidences: ${signalSummary || 'none'}`, `High-confidence trap present: ${confidence.highConfidenceTrapPresent}`, `Complexity boost: ${confidence.complexityBoost}`, ]; if (recommendedAgent) { lines.push(`Recommended agent for trap handling: ${recommendedAgent}`); } for (const line of lessonSpeechLines(context.lessons, matchedSignals)) { lines.push(`Lesson: ${line}`); } appendRecalledPlate(lines, context.proposalText); return lines; } export function formatMemoryRoutingBlock(context: ResearcherMemoryContext): string { const { confidence, matchedSignals, recommendedAgent } = context; const lines = [ 'MEMORY_ROUTING:', ` provider: ${context.providerId}`, ` trigger: ${context.triggeredBy}`, ` matchedSignals: ${matchedSignals.join(', ') || 'none'}`, ` highConfidenceTrapPresent: ${confidence.highConfidenceTrapPresent}`, ` complexityBoost: ${confidence.complexityBoost}`, ]; if (recommendedAgent) { lines.push(` recommendedAgent: ${recommendedAgent}`); } for (const line of lessonSpeechLines(context.lessons, matchedSignals)) { lines.push(` lesson: ${line}`); } appendRecalledPlate(lines, context.proposalText); return lines.join('\n'); } function appendRecalledPlate(lines: string[], proposalText: string | undefined): void { const plate = recallPlate(proposalText); if (!plate) return; lines.push(`Plate: ${plate.id}`); lines.push(plate.body); } function lessonsForMatchedSignals( lessons: MemorySignalLessons[] | undefined, matchedSignals: string[], ): MemorySignalLessons[] { const matched = new Set(matchedSignals); return (lessons ?? []).filter((lesson) => matched.has(lesson.name)); } /** Stored lesson text for signals this task matched. Empty text is omitted. */ function lessonSpeechLines( lessons: MemorySignalLessons[] | undefined, matchedSignals: string[], ): string[] { const speech: string[] = []; for (const lesson of lessonsForMatchedSignals(lessons, matchedSignals)) { for (const line of lesson.lines) { const text = line.text.trim().slice(0, LESSON_TEXT_CAP); if (text.length === 0) continue; speech.push(`${lesson.name}: ${text}`); } } return speech; }