import { recallByTag, type SynapRecallMemory } from "./synap"; import { loadProfile, loadCohortPriors } from "./cohorts"; import type { CommunicationBrief, TopicEntry, PersonProfile, } from "./types"; // --------------------------------------------------------------------------- // Signal content parsers — match exact formats from observer.ts // --------------------------------------------------------------------------- interface ParsedTopic { topic: string; domain: string; specificity: "surface" | "intermediate" | "expert"; deepened: boolean; continued: boolean; } /** Parse: "topic: distributed systems (engineering, expert) [deepened] [continued]" */ function parseTopicContent(content: string): ParsedTopic | null { const match = content.match( /^topic:\s*(.+?)\s*\((\w+),\s*(surface|intermediate|expert)\)/ ); if (!match) return null; return { topic: match[1].trim(), domain: match[2].trim(), specificity: match[3] as ParsedTopic["specificity"], deepened: content.includes("[deepened]"), continued: content.includes("[continued]"), }; } interface ParsedStyle { formality: number; lowercase: boolean; jargon: boolean; structure: string; emoji: boolean; } /** Parse: "formality: 0.25, lowercase: true, jargon: false, structure: stream_of_thought, emoji: false" */ function parseStyleContent(content: string): ParsedStyle | null { const formality = content.match(/formality:\s*([\d.]+)/); if (!formality) return null; return { formality: parseFloat(formality[1]), lowercase: content.includes("lowercase: true"), jargon: content.includes("jargon: true"), structure: content.match(/structure:\s*(\w+)/)?.[1] ?? "stream_of_thought", emoji: content.includes("emoji: true"), }; } interface ParsedDynamics { leading: "person" | "system"; builtOnPrevious: boolean; redirected: string | null; correctedSystem: boolean; } /** Parse: "leading: person, built_on_previous: true, redirected: X, corrected system" */ function parseDynamicsContent(content: string): ParsedDynamics | null { const leading = content.match(/leading:\s*(person|system)/); if (!leading) return null; const redirectMatch = content.match(/redirected:\s*(.+?)(?:,|$)/); return { leading: leading[1] as "person" | "system", builtOnPrevious: content.includes("built_on_previous: true"), redirected: redirectMatch ? redirectMatch[1].trim() : null, correctedSystem: content.includes("corrected system"), }; } interface ParsedEngagement { sentiment: number; direction: string; substantive: boolean; words: number; } /** Parse: "sentiment: 0.78 (positive), substantive: true, words: 42" */ function parseEngagementContent(content: string): ParsedEngagement | null { const sentiment = content.match(/sentiment:\s*([\d.]+)\s*\((\w+)\)/); if (!sentiment) return null; const words = content.match(/words:\s*(\d+)/); return { sentiment: parseFloat(sentiment[1]), direction: sentiment[2], substantive: content.includes("substantive: true"), words: words ? parseInt(words[1], 10) : 0, }; } // --------------------------------------------------------------------------- // Section builders // --------------------------------------------------------------------------- function buildTopicsSection( memories: SynapRecallMemory[] ): CommunicationBrief["topics"] { const topicMap = new Map< string, { domain: string; specificity: "surface" | "intermediate" | "expert"; deepened: boolean; count: number; lastSeen: number; } >(); for (let i = 0; i < memories.length; i++) { const parsed = parseTopicContent(memories[i].content); if (!parsed) continue; const key = parsed.topic.toLowerCase(); const existing = topicMap.get(key); if (existing) { existing.count++; existing.lastSeen = i; if (parsed.deepened) existing.deepened = true; // Keep highest specificity const specOrder = { surface: 0, intermediate: 1, expert: 2 }; if (specOrder[parsed.specificity] > specOrder[existing.specificity]) { existing.specificity = parsed.specificity; } } else { topicMap.set(key, { domain: parsed.domain, specificity: parsed.specificity, deepened: parsed.deepened, count: 1, lastSeen: i, }); } } // Sort by recency * frequency (higher = hotter) const entries = [...topicMap.entries()] .map(([topic, data]) => ({ topic, ...data, score: data.count * (1 + data.lastSeen / memories.length), })) .sort((a, b) => b.score - a.score); const hot: TopicEntry[] = entries.slice(0, 5).map((e) => ({ topic: e.topic, domain: e.domain, frequency: e.count, specificity: e.specificity, deepened: e.deepened, })); // Cold: known topics not in the hot list, sorted by count descending const cold: TopicEntry[] = entries.slice(5, 8).map((e) => ({ topic: e.topic, domain: e.domain, frequency: e.count, specificity: e.specificity, deepened: e.deepened, })); // Domains by frequency const domainCount = new Map(); for (const e of entries) { domainCount.set(e.domain, (domainCount.get(e.domain) ?? 0) + e.count); } const domains = [...domainCount.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([d]) => d); return { hot, cold, domains }; } function buildStyleSection( profile: PersonProfile | null, styleMemories: SynapRecallMemory[] ): CommunicationBrief["style"] { // Default from profile let formality = profile?.avgFormality ?? 0.5; let jargon = (profile?.jargonRate ?? 0) > 0.5; let emoji = false; let structure = "stream_of_thought"; let avgWords = profile?.avgWordCount ?? 20; // Aggregate from recent signals — average numerics, majority-vote booleans/strings let styleCount = 0; let formalitySum = 0; let jargonTrue = 0; let emojiTrue = 0; const structureCounts = new Map(); for (const mem of styleMemories) { const parsed = parseStyleContent(mem.content); if (!parsed) continue; styleCount++; formalitySum += parsed.formality; if (parsed.jargon) jargonTrue++; if (parsed.emoji) emojiTrue++; structureCounts.set(parsed.structure, (structureCounts.get(parsed.structure) ?? 0) + 1); } if (styleCount > 0) { formality = formalitySum / styleCount; jargon = jargonTrue / styleCount > 0.5; emoji = emojiTrue / styleCount > 0.5; // Most frequent structure wins let maxCount = 0; for (const [s, count] of structureCounts) { if (count > maxCount) { maxCount = count; structure = s; } } } const formalityBand: CommunicationBrief["style"]["formality"] = formality < 0.35 ? "casual" : formality > 0.65 ? "formal" : "moderate"; const lengthBand: CommunicationBrief["style"]["length"] = avgWords < 15 ? "terse" : avgWords > 40 ? "verbose" : "moderate"; return { formality: formalityBand, length: lengthBand, structure, usesJargon: jargon, usesEmoji: emoji }; } function buildPatternsSection( profile: PersonProfile | null, dynamicsMemories: SynapRecallMemory[], engagementMemories: SynapRecallMemory[] ): CommunicationBrief["patterns"] { const leadsConversation = (profile?.leaderRate ?? 0.5) > 0.5; // Deepens topics: check if any dynamics show built_on_previous let deepensCount = 0; let dynamicsTotal = 0; for (const mem of dynamicsMemories) { const parsed = parseDynamicsContent(mem.content); if (!parsed) continue; dynamicsTotal++; if (parsed.builtOnPrevious) deepensCount++; } const deepensTopics = dynamicsTotal > 0 ? deepensCount / dynamicsTotal > 0.4 : false; // Sentiment from engagement signals const sentiments: number[] = []; for (const mem of engagementMemories) { const parsed = parseEngagementContent(mem.content); if (parsed) sentiments.push(parsed.sentiment); } const avgSentiment = sentiments.length > 0 ? sentiments.reduce((a, b) => a + b, 0) / sentiments.length : profile?.avgSentiment ?? 0.5; // Trend: compare recent batch sentiment against profile's running average let sentimentTrend: CommunicationBrief["patterns"]["sentimentTrend"] = "stable"; if (sentiments.length >= 3 && profile?.avgSentiment !== undefined) { const delta = avgSentiment - profile.avgSentiment; if (delta > 0.1) sentimentTrend = "warming"; else if (delta < -0.1) sentimentTrend = "cooling"; } return { leadsConversation, deepensTopics, avgSentiment, sentimentTrend }; } /** Raw signal patterns that should never appear in observation content. */ const RAW_SIGNAL_PATTERNS = [ /^formality:\s/, /^topic:\s/, /^leading:\s/, /^sentiment:\s[\d.]+\s*\(/, /^response latency:/, ]; function buildObservationsSection(memories: SynapRecallMemory[]): string[] { return memories .map((m) => m.content) .filter((c) => c.length > 0 && !RAW_SIGNAL_PATTERNS.some((p) => p.test(c))) .slice(0, 5); } async function buildCohortSection( profile: PersonProfile | null ): Promise { if (!profile || !profile.cohorts.length || profile.interactionCount >= 30) { return { active: false, weight: 0, priors: [] }; } const weight = 1 / (1 + profile.interactionCount / 10); const priors = await loadCohortPriors( profile.cohorts, profile.interactionCount ); return { active: priors.length > 0, weight, priors }; } // --------------------------------------------------------------------------- // Main assembly // --------------------------------------------------------------------------- /** Flatten vivid + associated tiers (reconstructed excluded — lower confidence). */ function flattenMemories(result: { memories: { vivid: SynapRecallMemory[]; associated: SynapRecallMemory[]; reconstructed: SynapRecallMemory[]; }; }): SynapRecallMemory[] { const vivid = result.memories?.vivid ?? []; const associated = result.memories?.associated ?? []; return [...vivid, ...associated]; } export async function assembleBrief( personId: string ): Promise { const start = Date.now(); // 6 parallel Synap queries + profile load — single wave const catchSynap = (label: string) => (err: unknown) => { const msg = err instanceof Error ? err.message : String(err); console.error(`[brief] ${label} failed for ${personId.slice(0, 8)}…: ${msg}`); return null; }; const [topicResult, styleResult, dynamicsResult, observationResult, engagementResult, profile] = await Promise.all([ recallByTag("topics discussed", ["signal:topic", `person:${personId}`], 20, 0.2).catch(catchSynap("topic recall")), recallByTag("communication style", ["signal:style", `person:${personId}`], 10, 0.2).catch(catchSynap("style recall")), recallByTag("conversation dynamics", ["signal:dynamics", `person:${personId}`], 10, 0.2).catch(catchSynap("dynamics recall")), recallByTag("communication patterns", ["observation", `person:${personId}`], 5, 0.3).catch(catchSynap("observation recall")), recallByTag("engagement signals", ["signal:engagement", `person:${personId}`], 10, 0.2).catch(catchSynap("engagement recall")), loadProfile(personId).catch(catchSynap("profile load")), ]); // Cohort priors chain off profile (needs interactionCount) const cohortPriors = await buildCohortSection(profile); const topicMemories = topicResult ? flattenMemories(topicResult) : []; const styleMemories = styleResult ? flattenMemories(styleResult) : []; const dynamicsMemories = dynamicsResult ? flattenMemories(dynamicsResult) : []; const observationMemories = observationResult ? flattenMemories(observationResult) : []; const engagementMemories = engagementResult ? flattenMemories(engagementResult) : []; const brief: CommunicationBrief = { personId, interactionCount: profile?.interactionCount ?? 0, assembledAt: Date.now(), topics: buildTopicsSection(topicMemories), style: buildStyleSection(profile, styleMemories), patterns: buildPatternsSection(profile, dynamicsMemories, engagementMemories), observations: buildObservationsSection(observationMemories), cohortPriors, assemblyMs: Date.now() - start, }; console.log( `[brief] assembled for ${personId.slice(0, 8)}… in ${brief.assemblyMs}ms: ` + `${brief.topics.hot.length} hot topics, ` + `style=${brief.style.formality}, ` + `${brief.observations.length} observations, ` + `cohort=${brief.cohortPriors.active ? "active" : "inactive"}` ); return brief; }