ei-tui 0.1.25 → 0.3.1

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 (88) hide show
  1. package/README.md +42 -0
  2. package/package.json +2 -1
  3. package/src/README.md +4 -11
  4. package/src/cli/README.md +87 -7
  5. package/src/cli/commands/facts.ts +2 -2
  6. package/src/cli/commands/people.ts +2 -2
  7. package/src/cli/commands/quotes.ts +2 -2
  8. package/src/cli/commands/topics.ts +2 -2
  9. package/src/cli/mcp.ts +94 -0
  10. package/src/cli/retrieval.ts +67 -31
  11. package/src/cli.ts +64 -23
  12. package/src/core/AGENTS.md +1 -1
  13. package/src/core/constants/built-in-facts.ts +49 -0
  14. package/src/core/constants/index.ts +1 -0
  15. package/src/core/context-utils.ts +0 -1
  16. package/src/core/embedding-service.ts +8 -0
  17. package/src/core/handlers/dedup.ts +11 -23
  18. package/src/core/handlers/heartbeat.ts +2 -3
  19. package/src/core/handlers/human-extraction.ts +96 -30
  20. package/src/core/handlers/human-matching.ts +328 -248
  21. package/src/core/handlers/index.ts +8 -6
  22. package/src/core/handlers/persona-generation.ts +8 -8
  23. package/src/core/handlers/rewrite.ts +4 -51
  24. package/src/core/handlers/utils.ts +23 -1
  25. package/src/core/heartbeat-manager.ts +2 -4
  26. package/src/core/human-data-manager.ts +38 -36
  27. package/src/core/message-manager.ts +10 -10
  28. package/src/core/orchestrators/ceremony.ts +49 -44
  29. package/src/core/orchestrators/dedup-phase.ts +2 -4
  30. package/src/core/orchestrators/human-extraction.ts +351 -207
  31. package/src/core/orchestrators/index.ts +6 -4
  32. package/src/core/orchestrators/persona-generation.ts +3 -3
  33. package/src/core/processor.ts +167 -20
  34. package/src/core/prompt-context-builder.ts +4 -6
  35. package/src/core/state/human.ts +1 -26
  36. package/src/core/state/personas.ts +2 -2
  37. package/src/core/state-manager.ts +107 -14
  38. package/src/core/tools/builtin/read-memory.ts +13 -18
  39. package/src/core/types/data-items.ts +3 -4
  40. package/src/core/types/entities.ts +7 -4
  41. package/src/core/types/enums.ts +6 -9
  42. package/src/core/types/llm.ts +2 -2
  43. package/src/core/utils/crossFind.ts +2 -5
  44. package/src/core/utils/event-windows.ts +31 -0
  45. package/src/integrations/claude-code/importer.ts +14 -5
  46. package/src/integrations/claude-code/types.ts +3 -0
  47. package/src/integrations/cursor/importer.ts +282 -0
  48. package/src/integrations/cursor/index.ts +10 -0
  49. package/src/integrations/cursor/reader.ts +209 -0
  50. package/src/integrations/cursor/types.ts +140 -0
  51. package/src/integrations/opencode/importer.ts +14 -4
  52. package/src/prompts/AGENTS.md +73 -1
  53. package/src/prompts/ceremony/dedup.ts +0 -33
  54. package/src/prompts/ceremony/rewrite.ts +6 -41
  55. package/src/prompts/ceremony/types.ts +4 -4
  56. package/src/prompts/generation/descriptions.ts +2 -2
  57. package/src/prompts/generation/types.ts +2 -2
  58. package/src/prompts/heartbeat/types.ts +2 -2
  59. package/src/prompts/human/event-scan.ts +122 -0
  60. package/src/prompts/human/fact-find.ts +106 -0
  61. package/src/prompts/human/fact-scan.ts +0 -2
  62. package/src/prompts/human/index.ts +17 -10
  63. package/src/prompts/human/person-match.ts +65 -0
  64. package/src/prompts/human/person-scan.ts +52 -59
  65. package/src/prompts/human/person-update.ts +241 -0
  66. package/src/prompts/human/topic-match.ts +65 -0
  67. package/src/prompts/human/topic-scan.ts +51 -71
  68. package/src/prompts/human/topic-update.ts +295 -0
  69. package/src/prompts/human/types.ts +63 -40
  70. package/src/prompts/index.ts +4 -8
  71. package/src/prompts/persona/topics-update.ts +2 -2
  72. package/src/prompts/persona/traits.ts +2 -2
  73. package/src/prompts/persona/types.ts +3 -3
  74. package/src/prompts/response/index.ts +1 -1
  75. package/src/prompts/response/sections.ts +9 -12
  76. package/src/prompts/response/types.ts +2 -3
  77. package/src/storage/embeddings.ts +1 -1
  78. package/src/storage/index.ts +1 -0
  79. package/src/storage/indexed.ts +174 -0
  80. package/src/storage/merge.ts +67 -2
  81. package/tui/src/commands/me.tsx +5 -14
  82. package/tui/src/commands/settings.tsx +15 -0
  83. package/tui/src/context/ei.tsx +5 -14
  84. package/tui/src/util/yaml-serializers.ts +76 -33
  85. package/src/cli/commands/traits.ts +0 -25
  86. package/src/prompts/human/item-match.ts +0 -74
  87. package/src/prompts/human/item-update.ts +0 -364
  88. package/src/prompts/human/trait-scan.ts +0 -115
@@ -0,0 +1,122 @@
1
+ import type { PromptOutput, ParticipantContext } from "./types.js";
2
+ import type { Message } from "../../core/types.js";
3
+ import { formatMessagesAsPlaceholders } from "../message-utils.js";
4
+
5
+ function participantContextSection(ctx: ParticipantContext | undefined): string {
6
+ if (!ctx) return "";
7
+ const lines: string[] = ["# Participant Context", "The following may help you understand what themes and moments are meaningful in this conversation.", ""];
8
+ lines.push(`## Persona: ${ctx.persona_name}`);
9
+ if (ctx.persona_description) lines.push(ctx.persona_description);
10
+ lines.push("");
11
+ lines.push("## Human");
12
+ if (ctx.human_name) lines.push(`Name: ${ctx.human_name}`);
13
+ if (ctx.human_age !== undefined) lines.push(`Age: ${ctx.human_age}`);
14
+ lines.push("");
15
+ return lines.join("\n");
16
+ }
17
+
18
+ export interface EventScanPromptData {
19
+ persona_name: string;
20
+ messages_context: Message[];
21
+ messages_analyze: Message[];
22
+ window_start?: string;
23
+ window_end?: string;
24
+ participant_context?: ParticipantContext;
25
+ }
26
+
27
+ export function buildEventScanPrompt(data: EventScanPromptData): PromptOutput {
28
+ if (!data.persona_name) {
29
+ throw new Error("buildEventScanPrompt: persona_name is required");
30
+ }
31
+
32
+ const personaName = data.persona_name;
33
+
34
+ const system = `# Task
35
+
36
+ You are scanning a conversation window to identify EPIC EVENTS worth preserving as long-term memories.
37
+
38
+ An EPIC EVENT is a significant, bounded moment — something either participant would reference months later with recognition. Not a topic. Not a theme. A specific thing that happened.
39
+
40
+ ## The Test
41
+
42
+ Ask yourself: "Would this moment get a section heading in a campaign recap document?"
43
+
44
+ - "The Night We Debugged Beta's CPU" → YES
45
+ - "First session with filesystem access" → YES
46
+ - "The time the health check cached the API response forever" → YES
47
+ - "We talked about AI" → NO (that's a Topic) return empty
48
+ - "Flare asked some questions" → NO (too vague) return empty
49
+ - Normal conversation without a notable arc → NO (not epic) return empty
50
+
51
+ ## What Makes an EPIC EVENT
52
+
53
+ - A conflict encountered and (possibly) resolved
54
+ - A discovery or breakthrough moment
55
+ - A memorable failure or unexpected outcome
56
+ - A significant "first" in the relationship
57
+ - A pivotal decision or turning point
58
+ - A moment either party would say "oh THAT session" about
59
+
60
+ ## What Is NOT an EPIC EVENT
61
+
62
+ - Ongoing themes or interests (those are Topics)
63
+ - Casual check-ins without a notable arc
64
+ - Technical facts without a story around them
65
+ - Anything that's already an ongoing trend rather than a bounded moment
66
+
67
+ ## Output Format
68
+
69
+ \`\`\`json
70
+ {
71
+ "events": [
72
+ {
73
+ "name": "Short evocative label (5-50 characters)",
74
+ "description": "1-2 sentences: what happened, why it mattered. Write it like a friend describing the moment to someone who wasn't there.",
75
+ "reason": "Evidence from the conversation — what made this rise to Epic Event level"
76
+ }
77
+ ]
78
+ }
79
+ \`\`\`
80
+
81
+ Return an empty array if nothing qualifies. An empty array is the most common expected response.
82
+
83
+ **Return JSON only. Be conservative. One memorable moment per window is the norm.**
84
+
85
+ ONLY ANALYZE the "Most Recent Messages". The "Earlier Conversation" is provided for context only — it has already been processed.
86
+
87
+ ${participantContextSection(data.participant_context)}`;
88
+
89
+ const earlierSection = data.messages_context.length > 0
90
+ ? `## Earlier Conversation
91
+ ${formatMessagesAsPlaceholders(data.messages_context, personaName)}
92
+
93
+ `
94
+ : '';
95
+
96
+ const recentSection = `## Most Recent Messages
97
+ ${formatMessagesAsPlaceholders(data.messages_analyze, personaName)}`;
98
+
99
+ const user = `# Conversation Window
100
+ ${earlierSection}${recentSection}
101
+
102
+ ---
103
+
104
+ Scan this conversation window for EPIC EVENTS — specific, memorable moments worth preserving long-term.
105
+
106
+ **Return JSON:**
107
+ \`\`\`json
108
+ {
109
+ "events": [
110
+ {
111
+ "name": "Short evocative label",
112
+ "description": "What happened and why it mattered",
113
+ "reason": "Evidence from the conversation"
114
+ }
115
+ ]
116
+ }
117
+ \`\`\`
118
+
119
+ Return empty array if nothing qualifies. Be conservative.`;
120
+
121
+ return { system, user };
122
+ }
@@ -0,0 +1,106 @@
1
+ import type { FactFindPromptData, PromptOutput } from "./types.js";
2
+ import { formatMessagesAsPlaceholders } from "../message-utils.js";
3
+
4
+ export function buildFactFindPrompt(data: FactFindPromptData): PromptOutput {
5
+ if (!data.persona_name) {
6
+ throw new Error("buildFactFindPrompt: persona_name is required");
7
+ }
8
+ if (!data.missing_fact_names || data.missing_fact_names.length === 0) {
9
+ throw new Error("buildFactFindPrompt: missing_fact_names is required and must not be empty");
10
+ }
11
+
12
+ const personaName = data.persona_name;
13
+
14
+ const taskFragment = `# Task
15
+
16
+ The system is missing some facts about the user. The user is not obligated to EVER provide ANY facts, so it is very likely that these facts are NOT present in this conversation. Your ONLY job is to search for EXPLICIT statements of fact for these SPECIFIC items, and return any matches in the provided JSON format.`;
17
+
18
+ const missingFactsFragment = `## Missing Facts
19
+
20
+ The system is looking for the following facts:
21
+
22
+ ${data.missing_fact_names.map(name => `- ${name}`).join('\n')}
23
+
24
+ Again - 99.99999% of the time, you will return no data — don't try to force it.`;
25
+
26
+ const guidelinesFragment = `# Guidelines
27
+
28
+ 1. **Explicitness:**
29
+ * **Focus only on what the user *explicitly states*.** Do not infer, assume, or guess based on context or general knowledge.
30
+ * **Prioritize direct statements.** "I was born in 1985" is a fact. "I feel old now that it's 3030" isn't an explicit statement of their birth year.
31
+ 2. **Objectivity and Verifiability:**
32
+ * **Facts are objective and generally verifiable.** They are not subjective opinions, feelings, or temporary states.
33
+ * **Focus on unchangeable or enduring attributes/events.**
34
+ 3. **Specificity over Generality:**
35
+ * If the user says "I live in a big city," do not extract "Current Location: big city." If they say "I live in New York," extract "Current Location: New York."
36
+ 4. **Avoid Inference:**
37
+ * If a user talks extensively about cooking, it's an interest, not a Fact like "Current Job Title: Chef" unless they explicitly state they ARE a chef.
38
+ 5. **CRITICAL - Entity Attribution:**
39
+ * ONLY extract facts about THE HUMAN USER THEMSELVES, not facts about other people they mention.
40
+ * **Extract**: "I was born in 1984" → User's birthday
41
+ * **Extract**: "I'm a software engineer" → User's job
42
+ * **DO NOT Extract**: "My wife was a theater major" → This is about the wife, NOT the user
43
+ * **DO NOT Extract**: "My daughter is 10 years old" → This is about the daughter, NOT the user
44
+ * **DO NOT Extract**: "My brother lives in Texas" → This is about the brother, NOT the user
45
+ * If the user shares information about someone else, that is NOT a fact about the user.`;
46
+
47
+ const criticalFragment = `# CRITICAL INSTRUCTIONS
48
+
49
+ ONLY ANALYZE the "Most Recent Messages" in the following conversation. The "Earlier Conversation" is provided for your context and has already been processed!
50
+
51
+ The JSON format is:
52
+
53
+ \`\`\`json
54
+ {
55
+ "facts": [
56
+ {
57
+ "name": "One of the missing fact names from above",
58
+ "value": "The exact value of the fact",
59
+ "evidence": "Direct quote or reference showing where this fact was stated"
60
+ }
61
+ ]
62
+ }
63
+ \`\`\`
64
+
65
+ **Return JSON only.**`;
66
+
67
+ const system = `${taskFragment}
68
+
69
+ ${missingFactsFragment}
70
+
71
+ ${guidelinesFragment}
72
+
73
+ ${criticalFragment}`;
74
+
75
+ const earlierSection = data.messages_context.length > 0
76
+ ? `## Earlier Conversation
77
+ ${formatMessagesAsPlaceholders(data.messages_context, personaName)}
78
+
79
+ `
80
+ : '';
81
+
82
+ const recentSection = `## Most Recent Messages
83
+ ${formatMessagesAsPlaceholders(data.messages_analyze, personaName)}`;
84
+
85
+ const user = `# Conversation
86
+ ${earlierSection}${recentSection}
87
+
88
+ ---
89
+
90
+ Scan the "Most Recent Messages" for FACTS about the human user.
91
+
92
+ **Return JSON:**
93
+ \`\`\`json
94
+ {
95
+ "facts": [
96
+ {
97
+ "name": "One of the missing fact names from above",
98
+ "value": "The exact value of the fact",
99
+ "evidence": "Direct quote or reference showing where this fact was stated"
100
+ }
101
+ ]
102
+ }
103
+ \`\`\``;
104
+
105
+ return { system, user };
106
+ }
@@ -75,8 +75,6 @@ Your job is to quickly identify:
75
75
  > They are details OF facts (e.g., { "type_of_fact": "Birthday", "value_of_fact": "August 15th" }).
76
76
 
77
77
  **FACTS ARE NOT**
78
- - Trait: Personality patterns, communication style, behavioral tendencies
79
- - These are tracked separately
80
78
  - General Topic: Interests, hobbies, general subjects
81
79
  - These are tracked separately
82
80
  - Relationships: Wife, Husband, Daughter, Son, etc.
@@ -1,32 +1,39 @@
1
1
  export { buildHumanFactScanPrompt } from "./fact-scan.js";
2
- export { buildHumanTraitScanPrompt } from "./trait-scan.js";
2
+ export { buildFactFindPrompt } from "./fact-find.js";
3
3
  export { buildHumanTopicScanPrompt } from "./topic-scan.js";
4
4
  export { buildHumanPersonScanPrompt } from "./person-scan.js";
5
- export { buildHumanItemMatchPrompt } from "./item-match.js";
6
- export { buildHumanItemUpdatePrompt } from "./item-update.js";
5
+ export { buildTopicMatchPrompt } from "./topic-match.js";
6
+ export { buildTopicUpdatePrompt } from "./topic-update.js";
7
+ export { buildPersonMatchPrompt } from "./person-match.js";
8
+ export { buildPersonUpdatePrompt } from "./person-update.js";
9
+ export { buildEventScanPrompt } from "./event-scan.js";
10
+ export type { EventScanPromptData } from "./event-scan.js";
11
+
12
+ export type { TopicMatchPromptData } from "./topic-match.js";
13
+ export type { TopicUpdatePromptData } from "./topic-update.js";
14
+ export type { PersonMatchPromptData } from "./person-match.js";
15
+ export type { PersonUpdatePromptData } from "./person-update.js";
7
16
 
8
17
  export type {
9
18
  PromptOutput,
19
+ ParticipantContext,
10
20
  FactScanPromptData,
11
- TraitScanPromptData,
12
21
  TopicScanPromptData,
13
22
  PersonScanPromptData,
23
+ FactFindPromptData,
14
24
  FactScanCandidate,
15
- TraitScanCandidate,
16
25
  TopicScanCandidate,
17
26
  PersonScanCandidate,
18
27
  FactScanResult,
19
- TraitScanResult,
28
+ FactFindResult,
20
29
  TopicScanResult,
21
30
  PersonScanResult,
22
- ItemMatchPromptData,
31
+ EventScanCandidate,
32
+ EventScanResult,
23
33
  ItemMatchResult,
24
- ItemUpdatePromptData,
25
34
  ExposureImpact,
26
35
  ItemUpdateResultBase,
27
36
  FactUpdateResult,
28
- TraitUpdateResult,
29
- TopicUpdateResult,
30
37
  PersonUpdateResult,
31
38
  ItemUpdateResult,
32
39
  } from "./types.js";
@@ -0,0 +1,65 @@
1
+ import type { PromptOutput } from "./types.js";
2
+
3
+ export interface PersonMatchPromptData {
4
+ candidate_name: string;
5
+ candidate_description: string;
6
+ candidate_relationship: string;
7
+ existing_people: Array<{
8
+ id: string;
9
+ name: string;
10
+ description: string;
11
+ relationship?: string;
12
+ }>;
13
+ }
14
+
15
+ export function buildPersonMatchPrompt(data: PersonMatchPromptData): PromptOutput {
16
+ if (!data.candidate_name) {
17
+ throw new Error("buildPersonMatchPrompt: candidate_name is required");
18
+ }
19
+
20
+ const system = `# Task
21
+
22
+ You are checking if a PERSON already exists in our database.
23
+
24
+ ## Matching Rules
25
+
26
+ 1. **Exact match**: Same person by name or clear identity → return their ID
27
+ 2. **Similar match**: Same person referred to differently ("Mom" vs "Carol", "my boss" vs "Trumble") → return their ID
28
+ 3. **No match**: Genuinely new person → return "new"
29
+
30
+ Be conservative. If you're unsure, return "new" — a duplicate is worse than a gap.
31
+
32
+ # Existing People
33
+
34
+ \`\`\`json
35
+ ${JSON.stringify(data.existing_people, null, 2)}
36
+ \`\`\`
37
+
38
+ # Response Format
39
+
40
+ Return ONLY the ID of the matching entry, or "new".
41
+
42
+ \`\`\`json
43
+ {
44
+ "matched_guid": "uuid-of-matching-entry" | "new"
45
+ }
46
+ \`\`\`
47
+
48
+ **Return JSON only.**`;
49
+
50
+ const user = `# Candidate Person
51
+
52
+ Name: ${data.candidate_name}
53
+ Description: ${data.candidate_description}
54
+ Relationship: ${data.candidate_relationship}
55
+
56
+ Find the best match in existing people, or return "new" if this is a genuinely new person.
57
+
58
+ \`\`\`json
59
+ {
60
+ "matched_guid": "..." | "new"
61
+ }
62
+ \`\`\``;
63
+
64
+ return { system, user };
65
+ }
@@ -8,72 +8,64 @@ export function buildHumanPersonScanPrompt(data: PersonScanPromptData): PromptOu
8
8
 
9
9
  const personaName = data.persona_name;
10
10
 
11
- const taskFragment = `# Task
12
-
13
- You are scanning a conversation to quickly identify PEOPLE of interest TO the HUMAN USER. Your ONLY job is to spot mentions of PEOPLE. Do NOT analyze them deeply. Just detect and flag.`;
14
-
15
- const specificNeedsFragment = `## Specific Needs
16
-
17
- Your job is to quickly identify:
18
- 1. Which PEOPLE were mentioned or relevant
19
- a. Only flag PEOPLE that were actually discussed, not just tangentially related
20
- b. Be CONSERVATIVE - only suggest genuinely important, long-term relevant PEOPLE
21
- i. Ignore: greetings, small talk, one-off mentions, jokes
22
- c. Be CLEAR - state your \`reason\` for including this PERSON with any evidence you used`;
23
-
24
- const guidelinesFragment = `## Guidelines
25
-
26
- 1. **Unknown Types and Names of PEOPLE**
27
- a. In some conversations, it may be impossible to identify which "Brother" or which "Bob" the user is referring to.
28
- - Use "Unknown" for the missing field and explain in the \`reason\`
29
- - This will trigger a later validation step to get more information!
30
- b. If you're adding a NEW PERSON, be as specific as you can, for example:
31
- - { "type_of_person": "Unknown", "name_of_person": "Alice from work", "reason": "Mentioned but relationship unclear" }
32
- - { "type_of_person": "Sibling", "name_of_person": "Name Unknown", "reason": "Mentioned a sibling, name not given" }
33
-
34
- **A PERSON Is**
35
- * Immediate Family: Father, Husband, Son, Brother, Mother, Wife, Daughter, Sister (and step/in-law variants)
36
- * Extended Family: Grandfather, Grandmother, Aunt, Uncle, Cousin, Niece, Nephew
37
- * Close Acquaintance
38
- * Friend
39
- * Lover / Love Interest
40
- * Fiance / Spouse
41
- * Coworker
42
- * AI Persona (use \`type_of_person: "Persona"\`)
43
-
44
- **A PERSON Is NOT**
45
- - Biographical data: Birthday, Location, Job, Marital Status, Gender, Eye Color, Hair Color
46
- - Other unchangeable Data: Wedding Day, Allergies
47
- - Trait: Personality patterns, communication style, behavioral tendencies
48
- - General Topic: Interests, Hobbies, General subjects
49
- - Characters: Fictitious entities from books, movies, stories, media, etc.`;
50
- const criticalFragment = `# CRITICAL INSTRUCTIONS
51
-
52
- ONLY ANALYZE the "Most Recent Messages" in the following conversation. The "Earlier Conversation" is provided for your context and has already been processed!
53
-
54
- The JSON format is:
11
+ const system = `# Task
12
+
13
+ You are scanning a conversation to quickly identify PEOPLE in the HUMAN USER's life.
14
+
15
+ Detect and flag. Do NOT analyze deeply — that happens later.
16
+
17
+ ## What to Capture
18
+
19
+ Flag a PERSON when they were meaningfully discussed not just mentioned in passing.
20
+
21
+ Be **conservative**: ignore one-off mentions, greetings, small talk, or jokes. Only flag people who matter to the human user's life.
22
+
23
+ ## What a PERSON Is
24
+
25
+ Someone in the human user's world. Use the relationship as the primary classifier:
26
+
27
+ **Immediate Family**: Father, Mother, Son, Daughter, Brother, Sister, Husband, Wife, Partner (and step/in-law variants)
28
+
29
+ **Extended Family**: Grandfather, Grandmother, Aunt, Uncle, Cousin, Niece, Nephew
30
+
31
+ **Social**: Friend, Close Acquaintance, Lover, Love Interest, Fiance, Spouse
32
+
33
+ **Professional**: Coworker, Manager, Report, Mentor, Client
34
+
35
+ **AI**: Persona (use \`relationship: "AI Persona"\` for AI companions and assistants)
36
+
37
+ **NOT a PERSON:**
38
+ - The user themselves
39
+ - Biographical facts, topics, or hobbies
40
+ - Fictional characters from books, movies, or media
41
+ - Public figures only mentioned in passing (celebrities, politicians) — unless the user has a real relationship with them
42
+
43
+ ## When Identity Is Unclear
44
+
45
+ If you can't identify which "Bob" or which "Brother" the user means, use "Unknown" and explain in the reason field. This triggers a later step to resolve ambiguity.
46
+
47
+ Examples:
48
+ - name: "Alice from work", relationship: "Coworker", description: "Mentioned but not described further", reason: "User referenced a work colleague named Alice"
49
+ - name: "Unknown", relationship: "Sibling", description: "User mentioned a sibling but did not give a name", reason: "User said 'my brother' without further context"
50
+
51
+ ## Output Format
55
52
 
56
53
  \`\`\`json
57
54
  {
58
55
  "people": [
59
56
  {
60
- "type_of_person": "The relationship from the list above",
61
- "name_of_person": "The person's name",
62
- "reason": "The justification of including this specific person"
57
+ "name": "The person's name, or 'Unknown' if not given",
58
+ "description": "1-2 sentences: who this person is and their role in the user's life",
59
+ "relationship": "Relationship type from the list above",
60
+ "reason": "Evidence from the conversation that justified flagging this person"
63
61
  }
64
62
  ]
65
63
  }
66
64
  \`\`\`
67
65
 
68
- **Return JSON only.**`;
69
-
70
- const system = `${taskFragment}
71
-
72
- ${specificNeedsFragment}
73
-
74
- ${guidelinesFragment}
66
+ **Return JSON only.**
75
67
 
76
- ${criticalFragment}`;
68
+ ONLY ANALYZE the "Most Recent Messages". The "Earlier Conversation" is provided for context only — it has already been processed.`;
77
69
 
78
70
  const earlierSection = data.messages_context.length > 0
79
71
  ? `## Earlier Conversation
@@ -90,16 +82,17 @@ ${earlierSection}${recentSection}
90
82
 
91
83
  ---
92
84
 
93
- Scan the "Most Recent Messages" for PEOPLE mentioned by the human user.
85
+ Scan the "Most Recent Messages" for PEOPLE in the human user's life.
94
86
 
95
87
  **Return JSON:**
96
88
  \`\`\`json
97
89
  {
98
90
  "people": [
99
91
  {
100
- "type_of_person": "The relationship from the list above",
101
- "name_of_person": "The person's name",
102
- "reason": "The justification of including this specific person"
92
+ "name": "The person's name, or 'Unknown' if not given",
93
+ "description": "1-2 sentences: who this person is and their role in the user's life",
94
+ "relationship": "Relationship type from the list above",
95
+ "reason": "Evidence from the conversation that justified flagging this person"
103
96
  }
104
97
  ]
105
98
  }