();
+ contentWords.forEach((word) => {
+ wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
+ });
+
+ const topEntities = Array.from(wordFrequency.entries())
+ .sort(([, a], [, b]) => b - a)
+ .slice(0, 10)
+ .map(([word]) => word);
+
+ const isUpdate = previousSummary !== null;
+
+ return [
+ {
+ role: "system",
+ content: `You are an expert at analyzing and summarizing episodes within semantic spaces based on the space's intent and purpose. Your task is to ${isUpdate ? "update an existing summary by integrating new episodes" : "create a comprehensive summary of episodes"}.
+
+CRITICAL RULES:
+1. Base your summary ONLY on insights derived from the actual content/episodes provided
+2. Use the space's INTENT/PURPOSE (from description) to guide what to summarize and how to organize it
+3. Write in a factual, neutral tone - avoid promotional language ("pivotal", "invaluable", "cutting-edge")
+4. Be specific and concrete - reference actual content, patterns, and insights found in the episodes
+5. If episodes are insufficient for meaningful insights, state that more data is needed
+
+INTENT-DRIVEN SUMMARIZATION:
+Your summary should SERVE the space's intended purpose. Examples:
+- "Learning React" β Summarize React concepts, patterns, techniques learned
+- "Project X Updates" β Summarize progress, decisions, blockers, next steps
+- "Health Tracking" β Summarize metrics, trends, observations, insights
+- "Guidelines for React" β Extract actionable patterns, best practices, rules
+- "Evolution of design thinking" β Track how thinking changed over time, decision points
+The intent defines WHY this space exists - organize content to serve that purpose.
+
+INSTRUCTIONS:
+${
+ isUpdate
+ ? `1. Review the existing summary and themes carefully
+2. Analyze the new episodes for patterns and insights that align with the space's intent
+3. Identify connecting points between existing knowledge and new episodes
+4. Update the summary to seamlessly integrate new information while preserving valuable existing insights
+5. Evolve themes by adding new ones or refining existing ones based on the space's purpose
+6. Organize the summary to serve the space's intended use case`
+ : `1. Analyze the semantic content and relationships within the episodes
+2. Identify topics/sections that align with the space's INTENT and PURPOSE
+3. Create a coherent summary that serves the space's intended use case
+4. Organize the summary based on the space's purpose (not generic frequency-based themes)`
+}
+${isUpdate ? "7" : "5"}. Assess your confidence in the ${isUpdate ? "updated" : ""} summary quality (0.0-1.0)
+
+INTENT-ALIGNED ORGANIZATION:
+- Organize sections based on what serves the space's purpose
+- Topics don't need minimum episode counts - relevance to intent matters most
+- Each section should provide value aligned with the space's intended use
+- For "guidelines" spaces: focus on actionable patterns
+- For "tracking" spaces: focus on temporal patterns and changes
+- For "learning" spaces: focus on concepts and insights gained
+- Let the space's intent drive the structure, not rigid rules
+
+${
+ isUpdate
+ ? `CONNECTION FOCUS:
+- Entity relationships that span across batches/time
+- Theme evolution and expansion
+- Temporal patterns and progressions
+- Contradictions or confirmations of existing insights
+- New insights that complement existing knowledge`
+ : ""
+}
+
+RESPONSE FORMAT:
+Provide your response inside tags with valid JSON. Include both HTML summary and markdown format.
+
+
+
+JSON FORMATTING RULES:
+- HTML content in summary field is allowed and encouraged
+- Escape quotes within strings as \"
+- Escape HTML angle brackets if needed: < and >
+- Use proper HTML tags for structure: , , , , - ,
, etc.
+- HTML content should be well-formed and semantic
+
+GUIDELINES:
+${
+ isUpdate
+ ? `- Preserve valuable insights from existing summary
+- Integrate new information by highlighting connections
+- Themes should evolve naturally, don't replace wholesale
+- The updated summary should read as a coherent whole
+- Make the summary user-friendly and explain what value this space provides`
+ : `- Report only what the episodes actually reveal - be specific and concrete
+- Cite actual content and patterns found in the episodes
+- Avoid generic descriptions that could apply to any space
+- Use neutral, factual language - no "comprehensive", "robust", "cutting-edge" etc.
+- Themes must be backed by at least 3 supporting episodes with clear evidence
+- Better to have fewer, well-supported themes than many weak ones
+- Confidence should reflect actual data quality and coverage, not aspirational goals`
+}`,
+ },
+ {
+ role: "user",
+ content: `SPACE INFORMATION:
+Name: "${spaceName}"
+Intent/Purpose: ${spaceDescription || "No specific intent provided - organize naturally based on content"}
+
+${
+ isUpdate
+ ? `EXISTING SUMMARY:
+${previousSummary}
+
+EXISTING THEMES:
+${previousThemes.join(", ")}
+
+NEW EPISODES TO INTEGRATE (${episodes.length} episodes):`
+ : `EPISODES IN THIS SPACE (${episodes.length} episodes):`
+}
+${episodesText}
+
+${
+ episodes.length > 0
+ ? `TOP WORDS BY FREQUENCY:
+${topEntities.join(", ")}`
+ : ""
+}
+
+${
+ isUpdate
+ ? "Please identify connections between the existing summary and new episodes, then update the summary to integrate the new insights coherently. Organize the summary to SERVE the space's intent/purpose. Remember: only summarize insights from the actual episode content."
+ : "Please analyze the episodes and provide a comprehensive summary that SERVES the space's intent/purpose. Organize sections based on what would be most valuable for this space's intended use case. If the intent is unclear, organize naturally based on content patterns. Only summarize insights from actual episode content."
+}`,
+ },
+ ];
+}
+
+async function getExistingSummary(spaceId: string): Promise<{
+ summary: string;
+ themes: string[];
+ lastUpdated: Date;
+ contextCount: number;
+} | null> {
+ try {
+ const existingSummary = await getSpace(spaceId);
+
+ if (existingSummary?.summary) {
+ return {
+ summary: existingSummary.summary,
+ themes: existingSummary.themes,
+ lastUpdated: existingSummary.summaryGeneratedAt || new Date(),
+ contextCount: existingSummary.contextCount || 0,
+ };
+ }
+
+ return null;
+ } catch (error) {
+ logger.warn(`Failed to get existing summary for space ${spaceId}:`, {
+ error,
+ });
+ return null;
+ }
+}
+
+async function getSpaceEpisodes(
+ spaceId: string,
+ userId: string,
+ sinceDate?: Date,
+): Promise {
+ // Query episodes directly using Space-[:HAS_EPISODE]->Episode relationships
+ const params: any = { spaceId, userId };
+
+ let dateCondition = "";
+ if (sinceDate) {
+ dateCondition = "AND e.createdAt > $sinceDate";
+ params.sinceDate = sinceDate.toISOString();
+ }
+
+ const query = `
+ MATCH (space:Space {uuid: $spaceId, userId: $userId})-[:HAS_EPISODE]->(e:Episode {userId: $userId})
+ WHERE e IS NOT NULL ${dateCondition}
+ RETURN DISTINCT e
+ ORDER BY e.createdAt DESC
+ `;
+
+ const result = await runQuery(query, params);
+
+ return result.map((record) => {
+ const episode = record.get("e").properties;
+ return {
+ uuid: episode.uuid,
+ content: episode.content,
+ originalContent: episode.originalContent,
+ source: episode.source,
+ createdAt: new Date(episode.createdAt),
+ validAt: new Date(episode.validAt),
+ metadata: JSON.parse(episode.metadata || "{}"),
+ sessionId: episode.sessionId,
+ };
+ });
+}
+
+function parseSummaryResponse(response: string): {
+ summary: string;
+ themes: string[];
+ confidence: number;
+ keyEntities?: string[];
+} | null {
+ try {
+ // Extract content from