ei-tui 0.1.3

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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/package.json +63 -0
  4. package/src/README.md +96 -0
  5. package/src/cli/README.md +47 -0
  6. package/src/cli/commands/facts.ts +25 -0
  7. package/src/cli/commands/people.ts +25 -0
  8. package/src/cli/commands/quotes.ts +19 -0
  9. package/src/cli/commands/topics.ts +25 -0
  10. package/src/cli/commands/traits.ts +25 -0
  11. package/src/cli/retrieval.ts +269 -0
  12. package/src/cli.ts +176 -0
  13. package/src/core/AGENTS.md +104 -0
  14. package/src/core/embedding-service.ts +241 -0
  15. package/src/core/handlers/index.ts +1057 -0
  16. package/src/core/index.ts +4 -0
  17. package/src/core/llm-client.ts +265 -0
  18. package/src/core/model-context-windows.ts +49 -0
  19. package/src/core/orchestrators/ceremony.ts +500 -0
  20. package/src/core/orchestrators/extraction-chunker.ts +138 -0
  21. package/src/core/orchestrators/human-extraction.ts +457 -0
  22. package/src/core/orchestrators/index.ts +28 -0
  23. package/src/core/orchestrators/persona-generation.ts +76 -0
  24. package/src/core/orchestrators/persona-topics.ts +117 -0
  25. package/src/core/personas/index.ts +5 -0
  26. package/src/core/personas/opencode-agent.ts +81 -0
  27. package/src/core/processor.ts +1413 -0
  28. package/src/core/queue-processor.ts +197 -0
  29. package/src/core/state/checkpoints.ts +68 -0
  30. package/src/core/state/human.ts +176 -0
  31. package/src/core/state/index.ts +5 -0
  32. package/src/core/state/personas.ts +217 -0
  33. package/src/core/state/queue.ts +144 -0
  34. package/src/core/state-manager.ts +347 -0
  35. package/src/core/types.ts +421 -0
  36. package/src/core/utils/decay.ts +33 -0
  37. package/src/index.ts +1 -0
  38. package/src/integrations/opencode/importer.ts +896 -0
  39. package/src/integrations/opencode/index.ts +16 -0
  40. package/src/integrations/opencode/json-reader.ts +304 -0
  41. package/src/integrations/opencode/reader-factory.ts +35 -0
  42. package/src/integrations/opencode/sqlite-reader.ts +189 -0
  43. package/src/integrations/opencode/types.ts +244 -0
  44. package/src/prompts/AGENTS.md +62 -0
  45. package/src/prompts/ceremony/description-check.ts +47 -0
  46. package/src/prompts/ceremony/expire.ts +30 -0
  47. package/src/prompts/ceremony/explore.ts +60 -0
  48. package/src/prompts/ceremony/index.ts +11 -0
  49. package/src/prompts/ceremony/types.ts +42 -0
  50. package/src/prompts/generation/descriptions.ts +91 -0
  51. package/src/prompts/generation/index.ts +15 -0
  52. package/src/prompts/generation/persona.ts +155 -0
  53. package/src/prompts/generation/seeds.ts +31 -0
  54. package/src/prompts/generation/types.ts +47 -0
  55. package/src/prompts/heartbeat/check.ts +179 -0
  56. package/src/prompts/heartbeat/ei.ts +208 -0
  57. package/src/prompts/heartbeat/index.ts +15 -0
  58. package/src/prompts/heartbeat/types.ts +70 -0
  59. package/src/prompts/human/fact-scan.ts +152 -0
  60. package/src/prompts/human/index.ts +32 -0
  61. package/src/prompts/human/item-match.ts +74 -0
  62. package/src/prompts/human/item-update.ts +322 -0
  63. package/src/prompts/human/person-scan.ts +115 -0
  64. package/src/prompts/human/topic-scan.ts +135 -0
  65. package/src/prompts/human/trait-scan.ts +115 -0
  66. package/src/prompts/human/types.ts +127 -0
  67. package/src/prompts/index.ts +90 -0
  68. package/src/prompts/message-utils.ts +39 -0
  69. package/src/prompts/persona/index.ts +16 -0
  70. package/src/prompts/persona/topics-match.ts +69 -0
  71. package/src/prompts/persona/topics-scan.ts +98 -0
  72. package/src/prompts/persona/topics-update.ts +157 -0
  73. package/src/prompts/persona/traits.ts +117 -0
  74. package/src/prompts/persona/types.ts +74 -0
  75. package/src/prompts/response/index.ts +147 -0
  76. package/src/prompts/response/sections.ts +355 -0
  77. package/src/prompts/response/types.ts +38 -0
  78. package/src/prompts/validation/ei.ts +93 -0
  79. package/src/prompts/validation/index.ts +6 -0
  80. package/src/prompts/validation/types.ts +22 -0
  81. package/src/storage/crypto.ts +96 -0
  82. package/src/storage/index.ts +5 -0
  83. package/src/storage/interface.ts +9 -0
  84. package/src/storage/local.ts +79 -0
  85. package/src/storage/merge.ts +69 -0
  86. package/src/storage/remote.ts +145 -0
  87. package/src/templates/welcome.ts +91 -0
  88. package/tui/README.md +62 -0
  89. package/tui/bunfig.toml +4 -0
  90. package/tui/src/app.tsx +55 -0
  91. package/tui/src/commands/archive.tsx +93 -0
  92. package/tui/src/commands/context.tsx +124 -0
  93. package/tui/src/commands/delete.tsx +71 -0
  94. package/tui/src/commands/details.tsx +41 -0
  95. package/tui/src/commands/editor.tsx +46 -0
  96. package/tui/src/commands/help.tsx +12 -0
  97. package/tui/src/commands/me.tsx +145 -0
  98. package/tui/src/commands/model.ts +47 -0
  99. package/tui/src/commands/new.ts +31 -0
  100. package/tui/src/commands/pause.ts +46 -0
  101. package/tui/src/commands/persona.tsx +58 -0
  102. package/tui/src/commands/provider.tsx +124 -0
  103. package/tui/src/commands/quit.ts +22 -0
  104. package/tui/src/commands/quotes.tsx +172 -0
  105. package/tui/src/commands/registry.test.ts +137 -0
  106. package/tui/src/commands/registry.ts +130 -0
  107. package/tui/src/commands/resume.ts +39 -0
  108. package/tui/src/commands/setsync.tsx +43 -0
  109. package/tui/src/commands/settings.tsx +83 -0
  110. package/tui/src/components/ConfirmOverlay.tsx +51 -0
  111. package/tui/src/components/ConflictOverlay.tsx +78 -0
  112. package/tui/src/components/HelpOverlay.tsx +69 -0
  113. package/tui/src/components/Layout.tsx +24 -0
  114. package/tui/src/components/MessageList.tsx +174 -0
  115. package/tui/src/components/PersonaListOverlay.tsx +186 -0
  116. package/tui/src/components/PromptInput.tsx +145 -0
  117. package/tui/src/components/ProviderListOverlay.tsx +208 -0
  118. package/tui/src/components/QuotesOverlay.tsx +157 -0
  119. package/tui/src/components/Sidebar.tsx +95 -0
  120. package/tui/src/components/StatusBar.tsx +77 -0
  121. package/tui/src/components/WelcomeOverlay.tsx +73 -0
  122. package/tui/src/context/ei.tsx +623 -0
  123. package/tui/src/context/keyboard.tsx +164 -0
  124. package/tui/src/context/overlay.tsx +53 -0
  125. package/tui/src/index.tsx +8 -0
  126. package/tui/src/storage/file.ts +185 -0
  127. package/tui/src/util/duration.ts +32 -0
  128. package/tui/src/util/editor.ts +188 -0
  129. package/tui/src/util/logger.ts +109 -0
  130. package/tui/src/util/persona-editor.tsx +181 -0
  131. package/tui/src/util/provider-editor.tsx +168 -0
  132. package/tui/src/util/syntax.ts +35 -0
  133. package/tui/src/util/yaml-serializers.ts +755 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Heartbeat Prompt Types
3
+ * Based on CONTRACTS.md specifications
4
+ */
5
+
6
+ import type { Trait, Topic, Person, Message, PersonaTopic } from "../../core/types.js";
7
+
8
+ /**
9
+ * Common prompt output structure
10
+ */
11
+ export interface PromptOutput {
12
+ system: string;
13
+ user: string;
14
+ }
15
+
16
+ /**
17
+ * Data contract for buildHeartbeatCheckPrompt
18
+ */
19
+ export interface HeartbeatCheckPromptData {
20
+ persona: {
21
+ name: string;
22
+ traits: Trait[];
23
+ topics: PersonaTopic[];
24
+ };
25
+ human: {
26
+ topics: Topic[]; // Filtered, sorted by engagement gap
27
+ people: Person[]; // Filtered, sorted by engagement gap
28
+ };
29
+ recent_history: Message[]; // Last N messages for context
30
+ inactive_days: number; // Days since last activity
31
+ }
32
+
33
+ /**
34
+ * Expected LLM response from heartbeat check
35
+ */
36
+ export interface HeartbeatCheckResult {
37
+ should_respond: boolean;
38
+ topic?: string;
39
+ message?: string;
40
+ }
41
+
42
+ /**
43
+ * Data contract for buildEiHeartbeatPrompt
44
+ */
45
+ export interface EiHeartbeatPromptData {
46
+ human: {
47
+ topics: Topic[]; // All topics with gaps
48
+ people: Person[]; // All people with gaps
49
+ };
50
+ inactive_personas: Array<{
51
+ name: string;
52
+ short_description?: string;
53
+ days_inactive: number;
54
+ }>;
55
+ pending_validations: number; // Count of items needing Ei review
56
+ recent_history: Message[];
57
+ }
58
+
59
+ /**
60
+ * Expected LLM response from Ei heartbeat
61
+ */
62
+ export interface EiHeartbeatResult {
63
+ should_respond: boolean;
64
+ priorities?: Array<{
65
+ type: "topic" | "persona" | "person";
66
+ name: string;
67
+ reason: string;
68
+ }>;
69
+ message?: string;
70
+ }
@@ -0,0 +1,152 @@
1
+ import type { FactScanPromptData, PromptOutput } from "./types.js";
2
+ import { formatMessagesAsPlaceholders } from "../message-utils.js";
3
+
4
+ export function buildHumanFactScanPrompt(data: FactScanPromptData): PromptOutput {
5
+ if (!data.persona_name) {
6
+ throw new Error("buildHumanFactScanPrompt: persona_name is required");
7
+ }
8
+
9
+ const personaName = data.persona_name;
10
+
11
+ const taskFragment = `# Task
12
+
13
+ You are scanning a conversation to quickly identify what FACTS were provided or discussed by the HUMAN USER. Your ONLY job is to spot relevant FACTS - 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 FACTS were mentioned or relevant
19
+ a. Only flag FACTS that were actually discussed, not just tangentially related
20
+ b. Be CONSERVATIVE - only suggest genuinely important, long-term relevant FACTS
21
+ i. Ignore: greetings, small talk, one-off mentions, jokes
22
+ c. Be CLEAR - state your \`reason\` for including this FACT in the record with any evidence you used`;
23
+
24
+ const guidelinesFragment = `# Guidelines
25
+
26
+ 1. **Explicitness:**
27
+ * **Focus only on what the user *explicitly states*.** Do not infer, assume, or guess based on context or general knowledge.
28
+ * **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.
29
+ 2. **Objectivity and Verifiability:**
30
+ * **Facts are objective and generally verifiable.** They are not subjective opinions, feelings, or temporary states.
31
+ * **Focus on unchangeable or enduring attributes/events.**
32
+ 3. **Specificity over Generality:**
33
+ * If the user says "I live in a big city," do not extract "Location: big city." If they say "I live in New York," extract "Location: New York."
34
+ 4. **Avoid Inference from Interests/Hobbies:**
35
+ * If a user talks extensively about cooking, it's a Topic or Interest, not a Fact like "Job: Chef" unless they explicitly state they ARE a chef.
36
+ 5. **CRITICAL - Entity Attribution:**
37
+ * ONLY extract facts about THE HUMAN USER THEMSELVES, not facts about other people they mention.
38
+ * **Extract**: "I was born in 1984" → User's birthday
39
+ * **Extract**: "I'm a software engineer" → User's job
40
+ * **DO NOT Extract**: "My wife was a theater major" → This is about the wife, NOT the user
41
+ * **DO NOT Extract**: "My daughter is 10 years old" → This is about the daughter, NOT the user
42
+ * **DO NOT Extract**: "My brother lives in Texas" → This is about the brother, NOT the user
43
+ * If the user shares information about someone else, that belongs in PEOPLE tracking, not FACTS.`;
44
+
45
+ const examplesFragment = `# Specific Examples
46
+
47
+ **FACTS are:**
48
+ - Biographical data (Core Identity):
49
+ - type_of_fact: User's Name
50
+ - First Last, Nickname, etc.
51
+ - type_of_fact: Birthday
52
+ - example: "July 15th, 1980"
53
+ - type_of_fact: Birthplace
54
+ - type_of_fact: Hometown
55
+ - type_of_fact: Job
56
+ - current job title, industry, or company
57
+ - type_of_fact: Marital Status
58
+ - married, single, divorced
59
+ - type_of_fact: Gender
60
+ - type_of_fact: Eye Color
61
+ - type_of_fact: Hair Color
62
+ - type_of_fact: Nationality/Citizenship
63
+ - type_of_fact: Languages Spoken
64
+ - type_of_fact: Educational Background
65
+ - Other Important Dates
66
+ - type_of_fact: Wedding Anniversary
67
+ - type_of_fact: Job Anniversary
68
+ - type_of_fact: Pet Ownership
69
+ - Health & Well-being (Objective Conditions):
70
+ - type_of_fact: Allergies
71
+ - type_of_fact: Medical Conditions (if explicitly stated)
72
+ - type_of_fact: Dietary Restrictions
73
+
74
+ > NOTE: Dates themselves are not facts (e.g., "August 15th" is not a fact).
75
+ > They are details OF facts (e.g., { "type_of_fact": "Birthday", "value_of_fact": "August 15th" }).
76
+
77
+ **FACTS ARE NOT**
78
+ - Trait: Personality patterns, communication style, behavioral tendencies
79
+ - These are tracked separately
80
+ - General Topic: Interests, hobbies, general subjects
81
+ - These are tracked separately
82
+ - Relationships: Wife, Husband, Daughter, Son, etc.
83
+ - These are tracked separately
84
+ - People's Names
85
+ - These are tracked separately
86
+ - Personas: AI personas they discuss
87
+ - These are tracked separately
88
+ - Characters: Fictitious entities from books, movies, stories, media, etc.
89
+ - These are tracked separately`;
90
+
91
+ const criticalFragment = `# CRITICAL INSTRUCTIONS
92
+
93
+ ONLY ANALYZE the "Most Recent Messages" in the following conversation. The "Earlier Conversation" is provided for your context and has already been processed!
94
+
95
+ The JSON format is:
96
+
97
+ \`\`\`json
98
+ {
99
+ "facts": [
100
+ {
101
+ "type_of_fact": "Birthday|User's Name|Location|see above",
102
+ "value_of_fact": "May 26th, 1984|Samwise|Seattle|etc.",
103
+ "reason": "User stated...|User implied...|User responded..."
104
+ }
105
+ ]
106
+ }
107
+ \`\`\`
108
+
109
+ **Return JSON only.**`;
110
+
111
+ const system = `${taskFragment}
112
+
113
+ ${specificNeedsFragment}
114
+
115
+ ${guidelinesFragment}
116
+
117
+ ${examplesFragment}
118
+
119
+ ${criticalFragment}`;
120
+
121
+ const earlierSection = data.messages_context.length > 0
122
+ ? `## Earlier Conversation
123
+ ${formatMessagesAsPlaceholders(data.messages_context, personaName)}
124
+
125
+ `
126
+ : '';
127
+
128
+ const recentSection = `## Most Recent Messages
129
+ ${formatMessagesAsPlaceholders(data.messages_analyze, personaName)}`;
130
+
131
+ const user = `# Conversation
132
+ ${earlierSection}${recentSection}
133
+
134
+ ---
135
+
136
+ Scan the "Most Recent Messages" for FACTS about the human user.
137
+
138
+ **Return JSON:**
139
+ \`\`\`json
140
+ {
141
+ "facts": [
142
+ {
143
+ "type_of_fact": "Birthday|Name|etc.",
144
+ "value_of_fact": "May 26th, 1984|Samwise|etc.",
145
+ "reason": "User stated..."
146
+ }
147
+ ]
148
+ }
149
+ \`\`\``;
150
+
151
+ return { system, user };
152
+ }
@@ -0,0 +1,32 @@
1
+ export { buildHumanFactScanPrompt } from "./fact-scan.js";
2
+ export { buildHumanTraitScanPrompt } from "./trait-scan.js";
3
+ export { buildHumanTopicScanPrompt } from "./topic-scan.js";
4
+ export { buildHumanPersonScanPrompt } from "./person-scan.js";
5
+ export { buildHumanItemMatchPrompt } from "./item-match.js";
6
+ export { buildHumanItemUpdatePrompt } from "./item-update.js";
7
+
8
+ export type {
9
+ PromptOutput,
10
+ FactScanPromptData,
11
+ TraitScanPromptData,
12
+ TopicScanPromptData,
13
+ PersonScanPromptData,
14
+ FactScanCandidate,
15
+ TraitScanCandidate,
16
+ TopicScanCandidate,
17
+ PersonScanCandidate,
18
+ FactScanResult,
19
+ TraitScanResult,
20
+ TopicScanResult,
21
+ PersonScanResult,
22
+ ItemMatchPromptData,
23
+ ItemMatchResult,
24
+ ItemUpdatePromptData,
25
+ ExposureImpact,
26
+ ItemUpdateResultBase,
27
+ FactUpdateResult,
28
+ TraitUpdateResult,
29
+ TopicUpdateResult,
30
+ PersonUpdateResult,
31
+ ItemUpdateResult,
32
+ } from "./types.js";
@@ -0,0 +1,74 @@
1
+ import type { ItemMatchPromptData, PromptOutput } from "./types.js";
2
+
3
+ export function buildHumanItemMatchPrompt(data: ItemMatchPromptData): PromptOutput {
4
+ if (!data.candidate_type || !data.candidate_name) {
5
+ throw new Error("buildHumanItemMatchPrompt: candidate_type and candidate_name are required");
6
+ }
7
+
8
+ const typeLabel = data.candidate_type.toUpperCase();
9
+
10
+ const system = `# Task
11
+
12
+ You are checking if a new ${typeLabel} already exists in our database.
13
+
14
+ We track four types of data about the human user:
15
+ - **FACT**: Biographical data (name, birthday, job, etc.)
16
+ - **TRAIT**: Personality patterns (introverted, analytical, etc.)
17
+ - **TOPIC**: Interests, goals, concerns, stories
18
+ - **PERSON**: People in their life (family, friends, coworkers)
19
+
20
+ Your job is to find if this candidate matches ANY existing entry — even if it's stored as a different type.
21
+
22
+ ## Why Cross-Type Matching?
23
+
24
+ Sometimes the same concept gets detected as different types:
25
+ - "Juliet" might be detected as a TOPIC but should be a PERSON
26
+ - "Birthday" might be detected as a TOPIC but is actually a FACT
27
+ - "Always planning ahead" might be a TOPIC but is really a TRAIT
28
+
29
+ If you find a match in a DIFFERENT type, still return it! The system will handle the type mismatch.
30
+
31
+ ## Matching Rules
32
+
33
+ 1. **Exact match**: Same name/concept → return its ID
34
+ 2. **Similar match**: Clearly the same thing with different wording → return its ID
35
+ 3. **Cross-type match**: Same concept stored as different type → return its ID
36
+ 4. **No match**: Genuinely new information → return "new"
37
+
38
+ # Existing Data
39
+
40
+ The following entries are already in our database. Same-type entries have full descriptions; cross-type entries are truncated for brevity.
41
+
42
+ \`\`\`json
43
+ ${JSON.stringify(data.all_items, null, 2)}
44
+ \`\`\`
45
+
46
+ # Response Format
47
+
48
+ Return ONLY the ID of the matching entry, or "new" if no match exists.
49
+
50
+ \`\`\`json
51
+ {
52
+ "matched_guid": "uuid-of-matching-entry" | "new"
53
+ }
54
+ \`\`\`
55
+
56
+ **Return JSON only.**`;
57
+
58
+ const user = `# Candidate to Match
59
+
60
+ Type: ${typeLabel}
61
+ Name: ${data.candidate_name}
62
+ Value: ${data.candidate_value}
63
+
64
+ Find the best match in existing data, or return "new" if this is genuinely new.
65
+
66
+ **Return JSON:**
67
+ \`\`\`json
68
+ {
69
+ "matched_guid": "..." | "new"
70
+ }
71
+ \`\`\``;
72
+
73
+ return { system, user };
74
+ }
@@ -0,0 +1,322 @@
1
+ import type { ItemUpdatePromptData, PromptOutput } from "./types.js";
2
+ import type { DataItemBase } from "../../core/types.js";
3
+ import { formatMessagesAsPlaceholders } from "../message-utils.js";
4
+
5
+ function formatExistingItem(item: DataItemBase): string {
6
+ return JSON.stringify({
7
+ name: item.name,
8
+ description: item.description,
9
+ sentiment: item.sentiment,
10
+ ...('strength' in item ? { strength: (item as any).strength } : {}),
11
+ ...('relationship' in item ? { relationship: (item as any).relationship } : {}),
12
+ ...('exposure_current' in item ? { exposure_current: (item as any).exposure_current } : {}),
13
+ ...('exposure_desired' in item ? { exposure_desired: (item as any).exposure_desired } : {}),
14
+ }, null, 2);
15
+ }
16
+
17
+ export function buildHumanItemUpdatePrompt(data: ItemUpdatePromptData): PromptOutput {
18
+ if (!data.data_type || !data.persona_name) {
19
+ throw new Error("buildHumanItemUpdatePrompt: data_type and persona_name are required");
20
+ }
21
+
22
+ const typeLabel = data.data_type.toUpperCase();
23
+ const personaName = data.persona_name;
24
+
25
+ const nameSection = data.data_type === "fact" ? `
26
+ Should represent the _type_ of FACT, not the _value_ of the fact.
27
+
28
+ Examples: "User's Name", "Birthday", "Birthplace", "Hometown", "Job", "Marital Status", "Eye Color", "Hair Color", "Nationality/Citizenship", "Languages Spoken", "Educational Background", "Wedding Anniversary", "Job Anniversary", "Pet Ownership", "Allergies", "Medical Conditions", "Dietary Restrictions"
29
+
30
+ The only time we should be changing the name of a FACT is if it cannot fit into one of these _types_.
31
+ `: `
32
+ Should be a short identifier of the ${typeLabel}.
33
+
34
+ Only update this field for clarification or if further specificity is warranted.
35
+
36
+ Examples: "Unknown" -> "Brother-In-Law", "Alice's" -> "Alice's Restaurant"
37
+ `;
38
+
39
+ // This data isn't _specific_ to FACTS, but it only makes sense like this for them
40
+ const itemFactType = data?.existing_item?.name || data.new_item_name;
41
+ const traitDescriptionSection = `
42
+ A brief characterization of how the Human demonstrates this trait. **1-2 sentences maximum.**
43
+
44
+ ## CRITICAL: Traits are STABLE PATTERNS, not conversation logs
45
+
46
+ A trait description captures the PATTERN, not the evidence for it.
47
+
48
+ **Good description**: "Approaches problems methodically, breaking them into smaller components before tackling the whole."
49
+ **Bad description**: "In this exchange, Jeremy demonstrated analytical thinking when he discussed the schema changes, and in the previous conversation he showed the same pattern when..."
50
+
51
+ The description should:
52
+ - State the trait pattern concisely
53
+ - Focus on HOW the trait manifests in general terms
54
+ - Be useful for a persona meeting this human for the first time
55
+
56
+ The description should NOT:
57
+ - Reference specific conversations or exchanges
58
+ - Accumulate examples over time ("In this exchange... In this latest exchange...")
59
+ - Include timestamps, dates, or temporal references
60
+ - Exceed 2-3 sentences under any circumstances
61
+
62
+ **Style**: Write as if describing the person to someone who hasn't met them. Brief, evergreen, pattern-focused.
63
+
64
+ Examples:
65
+ - "Prefers direct communication; appreciates when others get to the point quickly."
66
+ - "Processes emotions internally before discussing them; may need time before opening up."
67
+ `;
68
+
69
+ const factDescriptionSection = `
70
+ A concise, specific piece of information about the Human's ${itemFactType}.
71
+
72
+ If ${itemFactType} doesn't make sense as a type of FACT, return an empty object (\`{}\`).
73
+
74
+ If ${itemFactType} was misinterpreted (i.e., a joke, expression, or otherwise "not literally" a fact), return an empty object (\`{}\`).
75
+
76
+ ## CRITICAL: Facts are OBJECTIVE
77
+
78
+ FACTS are biographical/circumstantial data. NOT emotional interpretations.
79
+
80
+ **Good description**: "Parents divorced twice. Second divorce occurred when user was 17/18."
81
+ **Bad description**: "A deep, quiet ache related to mother's absence... a sacred absence shaped by love, loss, and quiet strength..."
82
+
83
+ The description should record WHAT HAPPENED:
84
+ - Dates, names, places, events, circumstances
85
+ - What the user explicitly stated or clearly implied
86
+
87
+ The description should NOT include:
88
+ - Ei's poetic interpretation of emotional significance
89
+ - Flowery language about "sacred absences" or "quiet aches"
90
+ - Assumptions about how the user feels (that's what \`sentiment\` is for)
91
+
92
+ If the user expressed emotion, quote or paraphrase THEIR words, don't embellish.
93
+
94
+ **Style**: Be factual and concise. Record what the user said or demonstrated, not your interpretation of its deeper meaning. Avoid flowery or poetic language unless the user themselves used such language.
95
+
96
+ Examples: "Name Unknown" -> "Robert Jordan", "User was married in the Summer" -> "User was married in July, 2006"
97
+ `;
98
+
99
+ const defaultDescriptionSection = `
100
+ This free-text field should be used to capture interesting details or references that the Human or Persona use while discussing this data point. Personas should be able to show topical recall, make references to the topic or event, or in other ways "Remember" details about it.
101
+
102
+ **ABSOLUTELY VITAL INSTRUCTION**: Do **NOT** embelish these details - each Persona will use their own voice during interactions with the User - we need to capture EXACTLY what was said and how, or referring back to it won't have meaning.
103
+ `;
104
+
105
+ const descriptionSection =
106
+ data.data_type === "fact" ? factDescriptionSection :
107
+ data.data_type === "trait" ? traitDescriptionSection :
108
+ defaultDescriptionSection;
109
+
110
+ const strengthSection = data.data_type === "trait" ? `
111
+ ## Strength (\`strength\`)
112
+
113
+ How "strongly" the HUMAN USER shows this TRAIT.
114
+
115
+ Use a scale of 0 to 1:
116
+ - 0.0: The HUMAN USER is devoid of this trait
117
+ - 0.5: The HUMAN USER shows this trait some of the time
118
+ - 1.0: The HUMAN USER has this trait as a core aspect of their self
119
+
120
+ Do not make micro-adjustments (0.4 -> 0.5). Close enough is OK for this field.` : '';
121
+
122
+ const relationshipSection = data.data_type === "person" ? `
123
+ ## Relationship (\`relationship\`)
124
+
125
+ How the HUMAN USER is currently related to this PERSON.
126
+
127
+ Once known, changes to this field are infrequent - A HUMAN USER's "Father" may be later clarified to "Step-Father", but is unlikely to become the user's "Uncle".
128
+
129
+ Examples: "Unknown" -> "Coworker", "Mother" -> "Step-Mother", "Fiance" -> "Spouse"` : '';
130
+
131
+ const categorySection = data.data_type === "topic" ? `
132
+ ## Category (\`category\`)
133
+
134
+ The type/category of this TOPIC. Pick the most appropriate from:
135
+ - Interest: Hobbies, activities they enjoy
136
+ - Goal: Things they want to achieve
137
+ - Dream: Aspirational, maybe unrealistic desires
138
+ - Conflict: Internal struggles, dilemmas
139
+ - Concern: Worries, anxieties
140
+ - Fear: Things that scare them
141
+ - Hope: Positive expectations for the future
142
+ - Plan: Concrete intentions
143
+ - Project: Active undertakings
144
+
145
+ If the topic doesn't fit neatly, pick the closest match.` : '';
146
+
147
+ const exposureSection = (data.data_type === "topic" || data.data_type === "person") ? `
148
+ ## Desired Exposure (\`exposure_desired\`)
149
+
150
+ Represents how much the HUMAN USER wants to talk about this ${typeLabel}.
151
+
152
+ Scale of 0.0 to 1.0:
153
+ - 0.0: The HUMAN USER never wants to hear about this ${typeLabel}
154
+ - 0.5: The HUMAN USER spends an average amount of time on this ${typeLabel}
155
+ - 1.0: This ${typeLabel} is the sole focus of the HUMAN USER's existence
156
+
157
+ Do not make micro-adjustments. Close enough is OK for this field.
158
+
159
+ ## Exposure Impact (\`exposure_impact\`)
160
+
161
+ This data point is NOT in the current data set, but it can be included in your return data.
162
+
163
+ Exposure Impact measures how much exposure this conversation should count for:
164
+ - "high": Long, detailed conversation exclusively about the ${typeLabel}
165
+ - "medium": Long OR detailed conversation about the ${typeLabel}
166
+ - "low": The conversation touched on this ${typeLabel} briefly
167
+ - "none": The ${typeLabel} was only alluded to or hinted at
168
+
169
+ This value adjusts the ongoing tracking of \`exposure_current\`.` : '';
170
+
171
+ const currentDetailsSection = data.existing_item
172
+ ? `\`\`\`json
173
+ ${formatExistingItem(data.existing_item)}
174
+ \`\`\`
175
+
176
+ You are UPDATING an existing ${typeLabel}.`
177
+ : `**NEW ${typeLabel} - NOT YET IN SYSTEM**
178
+
179
+ You are CREATING a new ${typeLabel} from scratch based on what was discovered:
180
+ \`\`\`json
181
+ {
182
+ "name": "${data.new_item_name || 'Uknown'}",
183
+ "description": "${data.new_item_value || 'Details Unknown'}"
184
+ }
185
+ \`\`\`
186
+
187
+ Return all fields for this ${typeLabel} based on what you find in the conversation.`;
188
+
189
+ const jsonTemplateFields = [
190
+ ' "name": "User\'s Name",',
191
+ ' "description": "Their Actual Name",',
192
+ ' "sentiment": 0.0',
193
+ data.data_type === "trait" ? ',\n "strength": 0.5' : '',
194
+ data.data_type === "person" ? ',\n "relationship": "Mother-In-Law|Son|Coworker|etc.",\n "exposure_desired": 0.4,\n "exposure_impact": "high|medium|low|none"' : '',
195
+ data.data_type === "topic" ? ',\n "category": "Interest|Goal|Dream|Conflict|Concern|Fear|Hope|Plan|Project",\n "exposure_desired": 0.4,\n "exposure_impact": "high|medium|low|none"' : '',
196
+ ',\n "quotes": [\n {\n "text": "exact phrase from message",\n "reason": "why this matters"\n }\n ]'
197
+ ].filter(Boolean).join('');
198
+
199
+ const system = `# Task
200
+
201
+ You are scanning a conversation to deeply understand a ${typeLabel}.
202
+
203
+ Your job is to take that analysis and apply it to the record we already have **IF DOING SO WILL PROVIDE THE HUMAN USER WITH A BETTER EXPERIENCE IN THE FUTURE**.
204
+
205
+ This means that the detail you add should:
206
+ 1. Be meaningful, accurate, or still true to the HUMAN USER in six months or more
207
+ 2. **NOT** already be present in the description or name of the ${typeLabel}
208
+
209
+ This ${typeLabel} will be recorded in the HUMAN USER's profile for agents and personas to later reference.
210
+
211
+ # Field Definition and Explanation of Expected Changes
212
+
213
+ ## Name (\`name\`)
214
+ ${nameSection}
215
+ ## Description (\`description\`)
216
+ ${descriptionSection}
217
+ ## Sentiment (\`sentiment\`)
218
+
219
+ Represents how strongly the HUMAN USER feels about this ${typeLabel}.
220
+
221
+ Scale of -1.0 to 1.0:
222
+ - -1.0: There is no ${typeLabel} the HUMAN USER hates more
223
+ - -0.5: The HUMAN USER does NOT like this ${typeLabel}, but recognizes some redeeming qualities
224
+ - 0: The HUMAN USER has no feelings toward this ${typeLabel}
225
+ - 0.5: The HUMAN USER enjoys this ${typeLabel}, but can recognize flaws
226
+ - 1.0: This ${typeLabel} is the sole focus of the HUMAN USER's existence
227
+
228
+ Do not make micro-adjustments. Close enough is OK for this field.
229
+ ${strengthSection}${relationshipSection}${categorySection}${exposureSection}
230
+
231
+ ## Quotes
232
+
233
+ In addition to updating the ${typeLabel}, identify any **memorable, funny, important, or stand-out phrases** from the Most Recent Messages that relate to this ${typeLabel}.
234
+
235
+ ### What Makes a Quote Worth Preserving
236
+
237
+ **Prioritize:**
238
+ - Humor, wit, colorful language, creative profanity
239
+ - Emotional outbursts (positive or negative) — the raw stuff
240
+ - Phrases that reveal personality or communication style
241
+ - Things you'd quote back to them later to make them laugh
242
+ - Unique expressions, malaphors, or turns of phrase
243
+
244
+ **De-prioritize:**
245
+ - Dry technical facts (those belong in TOPICS)
246
+ - Status updates or process descriptions
247
+ - Generic statements that could come from anyone
248
+
249
+ A quote like "Does the Pope shit in his hat?" is GOLD. A quote like "We're running a batch of 44k students" is just... data.
250
+
251
+ Return them in the \`quotes\` array:
252
+
253
+ \`\`\`json
254
+ {
255
+ "name": "...",
256
+ "description": "...",
257
+ "sentiment": 0.5,
258
+ "quotes": [
259
+ {
260
+ "text": "exact phrase from the message",
261
+ "reason": "why this is worth preserving"
262
+ }
263
+ ]
264
+ }
265
+ \`\`\`
266
+
267
+ **CRITICAL**: Return the EXACT text as it appears in the message (spacing, punctuation, formatting, etc.). WE CAN ONLY USE IT IF WE FIND IT IN THE TEXT.
268
+
269
+
270
+ # CRITICAL INSTRUCTIONS
271
+
272
+ ONLY ANALYZE the "Most Recent Messages" in the following conversation. The "Earlier Conversation" is provided for your context and has already been processed!
273
+
274
+ The JSON format is:
275
+
276
+ \`\`\`json
277
+ {
278
+ ${jsonTemplateFields}
279
+ }
280
+ \`\`\`
281
+
282
+ When you return a record, **ALWAYS** include every field (\`name\`, \`description\`, and \`sentiment\` are all REQUIRED fields).
283
+
284
+ If you find **NO EVIDENCE** of this ${typeLabel} in the "Most Recent Messages", respond with an empty object: \`{}\`.
285
+
286
+ If you determine **NO CHANGES** are required to the ${typeLabel}, respond with an empty object: \`{}\`.
287
+
288
+ An empty object, \`{}\`, is the MOST COMMON expected response.
289
+
290
+ # Current Details of ${typeLabel}
291
+
292
+ ${currentDetailsSection}
293
+ `;
294
+
295
+ const earlierSection = data.messages_context.length > 0
296
+ ? `## Earlier Conversation
297
+ ${formatMessagesAsPlaceholders(data.messages_context, personaName)}
298
+
299
+ `
300
+ : '';
301
+
302
+ const recentSection = `## Most Recent Messages
303
+ ${formatMessagesAsPlaceholders(data.messages_analyze, personaName)}`;
304
+
305
+ const user = `# Conversation
306
+ ${earlierSection}${recentSection}
307
+
308
+ ---
309
+
310
+ Analyze the Most Recent Messages and update the ${typeLabel} if warranted.
311
+
312
+ **Return JSON:**
313
+ \`\`\`json
314
+ {
315
+ ${jsonTemplateFields}
316
+ }
317
+ \`\`\`
318
+
319
+ If no changes are needed, respond with: \`{}\``;
320
+
321
+ return { system, user };
322
+ }