tidaldb/applications/iknowyou/lib/briefing.ts
jordan 98bdc18a49 feat: add iknowyou app + complete M8 replication extensions + Aeries agents/skills
- applications/iknowyou: new Next.js chat application with persona-aware conversations,
  briefing API, cohort logic, vLLM streaming, and sidebar navigation
- tidal M8: add replication control plane (control.rs), tenant migration state machine
  (migration.rs), tenant/upgrade coordinators, cluster/fault test harnesses
- tidal M8 tests: expand m8p2/m8p3/m8p4 test suites; add m8p5_multitenancy and m8_uat
- tidal db: split replication_ops out of db/mod.rs (was 647 lines, now 574)
- .claude: add kai-park, kaya-osei, mira-vasquez agents; add aeries-design-architect,
  aeries-fullstack-engineer, aeries-product-visionary skills
- docs: update ROADMAP.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 21:09:11 -07:00

388 lines
12 KiB
TypeScript

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<string, number>();
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;
// Override with recent signals (more granular)
let styleCount = 0;
let formalitySum = 0;
for (const mem of styleMemories) {
const parsed = parseStyleContent(mem.content);
if (!parsed) continue;
styleCount++;
formalitySum += parsed.formality;
jargon = parsed.jargon;
emoji = parsed.emoji;
structure = parsed.structure;
}
if (styleCount > 0) {
formality = formalitySum / styleCount;
}
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 first half to second half
let sentimentTrend: CommunicationBrief["patterns"]["sentimentTrend"] = "stable";
if (sentiments.length >= 4) {
const mid = Math.floor(sentiments.length / 2);
const firstHalf = sentiments.slice(0, mid).reduce((a, b) => a + b, 0) / mid;
const secondHalf =
sentiments.slice(mid).reduce((a, b) => a + b, 0) / (sentiments.length - mid);
const delta = secondHalf - firstHalf;
if (delta > 0.1) sentimentTrend = "warming";
else if (delta < -0.1) sentimentTrend = "cooling";
}
return { leadsConversation, deepensTopics, avgSentiment, sentimentTrend };
}
function buildObservationsSection(memories: SynapRecallMemory[]): string[] {
return memories
.map((m) => m.content)
.filter((c) => c.length > 0)
.slice(0, 5);
}
async function buildCohortSection(
profile: PersonProfile | null
): Promise<CommunicationBrief["cohortPriors"]> {
if (!profile || !profile.cohorts.length) {
return { active: false, weight: 0, priors: [] };
}
const weight = 1 / (1 + profile.interactionCount / 10);
if (weight < 0.1) {
return { active: false, weight, priors: [] };
}
const priors = await loadCohortPriors(
profile.cohorts,
profile.interactionCount
);
return { active: priors.length > 0, weight, priors };
}
// ---------------------------------------------------------------------------
// Main assembly
// ---------------------------------------------------------------------------
function flattenMemories(result: {
memories: {
vivid: SynapRecallMemory[];
associated: SynapRecallMemory[];
reconstructed: SynapRecallMemory[];
};
}): SynapRecallMemory[] {
return [
...result.memories.vivid,
...result.memories.associated,
];
}
export async function assembleBrief(
personId: string
): Promise<CommunicationBrief> {
const start = Date.now();
// 5 parallel queries
const [topicResult, styleResult, dynamicsResult, observationResult, profile] =
await Promise.all([
recallByTag(
"topics discussed",
["signal:topic", `person:${personId}`],
20,
0.2
).catch(() => null),
recallByTag(
"communication style",
["signal:style", `person:${personId}`],
10,
0.2
).catch(() => null),
recallByTag(
"conversation dynamics",
["signal:dynamics", `person:${personId}`],
10,
0.2
).catch(() => null),
recallByTag(
"communication patterns",
["observation", `person:${personId}`],
5,
0.3
).catch(() => null),
loadProfile(personId).catch(() => null),
]);
// Also fetch engagement signals for sentiment analysis (parallel with cohort)
const [engagementResult, cohortPriors] = await Promise.all([
recallByTag(
"engagement signals",
["signal:engagement", `person:${personId}`],
10,
0.2
).catch(() => null),
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;
}