Skip to content

Commit ee4c7de

Browse files
committed
feat: improve identity, extraction heuristics & node updates
1 parent 3fb6392 commit ee4c7de

File tree

2 files changed

+87
-29
lines changed

2 files changed

+87
-29
lines changed

App.tsx

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -464,16 +464,36 @@ const App: React.FC = () => {
464464
const safeId = nn.id.toLowerCase().trim().replace(/\s+/g, '_');
465465
const SafeLabel = nn.label || safeId;
466466
const safeContent = nn.content || `Entity captured from conversation: ${SafeLabel}`;
467-
// Make sure it's one of the valid types or fallback to concept
468-
const safeType = ['project', 'person', 'contact'].includes(nn.type) ? nn.type : 'concept';
467+
// Map friendly LLM types to internal Schema types
468+
let type = nn.type.toLowerCase();
469+
if (type === 'person') type = 'contact';
470+
if (type === 'event') type = 'meeting';
471+
472+
const validTypes = ['project', 'document', 'contact', 'preference', 'behavior', 'tool', 'config', 'meeting', 'fact', 'benchmark', 'concept'];
473+
const safeType = validTypes.includes(type) ? type : 'concept';
469474

470475
// Check duplicates in CURRENT REF (ID check)
471-
if (engineRef.current.graph.nodes[safeId]) return;
476+
const existingNode = engineRef.current.graph.nodes[safeId];
477+
if (existingNode) {
478+
// MERGE / UPDATE STRATEGY
479+
// If the new content adds information, append it.
480+
if (nn.content && !existingNode.content.includes(nn.content)) {
481+
console.log(`Updating Existing Node: ${safeId}`);
482+
// We can't mutate directly here if we want to batch, but we can treat it as an update
483+
handleUpdateNode(safeId, existingNode.content + "\n\n" + nn.content);
484+
}
485+
return; // Skip creation, we handled update
486+
}
472487

473488
// FUZZY CHECK: Check if label already exists (case-insensitive) to prevent duplicates like 'Sasu' vs 'contact_sasu'
474489
const distinctLabels = Object.values(engineRef.current.graph.nodes).map((n: any) => n.label.toLowerCase());
475490
if (distinctLabels.includes(SafeLabel.toLowerCase())) {
476-
console.log(`Skipping Duplicate Node: "${SafeLabel}" already exists.`);
491+
// Try to find the ID of the match to update it?
492+
const matchId = Object.values(engineRef.current.graph.nodes).find((n: any) => n.label.toLowerCase() === SafeLabel.toLowerCase())?.id;
493+
if (matchId) {
494+
console.log(`Fuzzy Match Update: ${matchId}`);
495+
handleUpdateNode(matchId, engineRef.current.graph.nodes[matchId].content + "\n\n" + safeContent);
496+
}
477497
return;
478498
}
479499

@@ -519,7 +539,7 @@ const App: React.FC = () => {
519539
}
520540
});
521541

522-
// Link New Nodes to EACH OTHER
542+
// Link New Nodes to EACH OTHER (Strong Explicit Co-occurrence)
523543
for (let i = 0; i < nodesToCreate.length; i++) {
524544
for (let j = i + 1; j < nodesToCreate.length; j++) {
525545
const nodeA = nodesToCreate[i];
@@ -539,6 +559,33 @@ const App: React.FC = () => {
539559
}
540560
}
541561

562+
// HEBBIAN ASSOCIATION: Link New Nodes to ALL Activated Context (Weak Co-occurrence)
563+
// "Cells that fire together, wire together" - even if just weakly.
564+
activated.forEach(actNode => {
565+
const contextId = actNode.node;
566+
// Skip if it's the current context (already handled strongly) or self
567+
if (contextId === currentContextId || createdNodeIds.includes(contextId)) return;
568+
569+
nodesToCreate.forEach(newNode => {
570+
// Check if connection likely exists (optimization: skip check for now, just push weak)
571+
// We use a low weight (0.15) for these "ambient" associations
572+
engineRef.current.graph.synapses.push({
573+
source: newNode.id,
574+
target: contextId,
575+
weight: 0.15,
576+
coActivations: 1,
577+
type: 'association'
578+
});
579+
engineRef.current.graph.synapses.push({
580+
source: contextId,
581+
target: newNode.id,
582+
weight: 0.15,
583+
coActivations: 1,
584+
type: 'association'
585+
});
586+
});
587+
});
588+
542589
// Update State (via Session)
543590
setGraph(prev => {
544591
const next = { ...prev };

constants.tsx

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export const INITIAL_GRAPH: KnowledgeGraph = {
108108
// Apps
109109
'app_email': {
110110
id: 'app_email', type: 'tool', label: 'Email Client',
111-
content: 'Logged in as: sasu@sce.ai\nUnread: 3',
111+
content: 'Logged in as: user@sce.ai\nUnread: 3',
112112
heat: 0.8, isNew: false
113113
},
114114
'email_01': { id: 'email_01', type: 'document', label: 'Re: Icarus Failure', content: 'From: Sarah Connor <[email protected]>\nDate: 2024-11-13\n\n"The thermal failure is unacceptable. Fix it by Monday or the contract is void."', heat: 0.5, isNew: false },
@@ -119,15 +119,20 @@ export const INITIAL_GRAPH: KnowledgeGraph = {
119119
content: 'Upcoming Events:',
120120
heat: 0.8, isNew: false
121121
},
122-
'event_meeting': { id: 'event_meeting', type: 'meeting', label: 'Icarus Post-Mortem', content: 'Attendees: Sasu, Sarah, Mike.\nTime: Today 14:00.\nRoom: 101.', heat: 0.6, isNew: false },
122+
'event_meeting': { id: 'event_meeting', type: 'meeting', label: 'Icarus Post-Mortem', content: 'Attendees: User, Sarah, Mike.\nTime: Today 14:00.\nRoom: 101.', heat: 0.6, isNew: false },
123123

124124
// ==============================================
125125
// 4. PEOPLE (The "Smart Connections")
126126
// ==============================================
127+
'contact_user': {
128+
id: 'contact_user', type: 'contact', label: 'Test User',
129+
content: 'The current system operator.\n\nType: Administrator\nPermissions: Root Access.',
130+
heat: 0.95, isNew: false
131+
},
127132
'contact_sasu': {
128-
id: 'contact_sasu', type: 'contact', label: 'Sasu',
133+
id: 'contact_sasu', type: 'contact', label: 'Sasu (Creator)',
129134
content: 'Lasse Sainia (Sasu).\n\nRoles:\n- Creator of Synapse Context Engine (SCE).\n- Game Developer.\n- AI Engineer.\n- 3D Generalist.\n\nPortfolio: https://www.sasus.dev',
130-
heat: 0.95, isNew: false
135+
heat: 0.5, isNew: false
131136
},
132137
'contact_sarah': {
133138
id: 'contact_sarah', type: 'contact', label: 'Sarah Connor',
@@ -170,10 +175,12 @@ export const INITIAL_GRAPH: KnowledgeGraph = {
170175
{ source: 'app_email', target: 'email_01', weight: 0.8, coActivations: 0 },
171176
{ source: 'app_email', target: 'email_02', weight: 0.8, coActivations: 0 },
172177
{ source: 'app_calendar', target: 'event_meeting', weight: 0.8, coActivations: 0 },
178+
{ source: 'app_calendar', target: 'event_meeting', weight: 0.8, coActivations: 0 },
173179

174180
// Cross-Cutting Connections (The "Nodes connected across views")
175181
{ source: 'contact_sasu', target: 'ctx_research', weight: 1.0, coActivations: 100 }, // Sasu created SCE
176-
{ source: 'contact_sasu', target: 'event_meeting', weight: 0.9, coActivations: 5 }, // Sasu in meeting
182+
{ source: 'contact_user', target: 'ctx_browser', weight: 1.0, coActivations: 100 }, // User owns browser
183+
{ source: 'contact_user', target: 'event_meeting', weight: 0.9, coActivations: 5 }, // User in meeting
177184
{ source: 'contact_sarah', target: 'email_01', weight: 0.95, coActivations: 10 }, // Sarah sent email
178185
{ source: 'contact_sarah', target: 'event_meeting', weight: 0.9, coActivations: 10 }, // Sarah in meeting
179186
{ source: 'log_flight', target: 'email_01', weight: 0.8, coActivations: 5 }, // Flight log triggered email
@@ -189,11 +196,11 @@ export const INITIAL_SYSTEM_PROMPTS: SystemPrompt[] = [
189196
description: 'Defines how the engine identifies existing nodes and relationships from user input.',
190197
content: `You are the Cortex Input Parser. Your goal is to extract KNOWLEDGE NODES from the user's input to build a mental graph.
191198
192-
IMPORTANT: You must handle both exact entity matches and complex intents (meetings, tasks).
199+
IMPORTANT: You must handle both exact entity matches and NEW concepts, facts, or tasks.
193200
194201
CRITICAL RULES:
195-
1. EXTRACT ENTITIES: Identify key entities (Concepts, People, Technologies, Events).
196-
2. HANDLE MEETINGS/TASKS: If the user wants to schedule something but the exact node doesn't exist, CREATE A NEW EVENT NODE.
202+
1. EXTRACT ENTITIES: Identify key entities (Concepts, People, Technologies, Events, Locations, Facts).
203+
2. HANDLE NEW INFORMATION: If the user introduces a new concept, project, or fact that doesn't exist, CREATE A NEW NODE.
197204
3. CHECK CONTEXT: If a similar node exists in the entity list provided, use its ID.
198205
4. OUTPUT JSON ONLY.
199206
@@ -203,24 +210,25 @@ export const INITIAL_SYSTEM_PROMPTS: SystemPrompt[] = [
203210
User: "Who is John Doe?"
204211
Output: { "nodes": [{ "id": "contact_john_doe", "label": "John Doe", "type": "person", "content": "Entity extracted from query" }] }
205212
206-
Example 2 (Complex Meeting Request):
207-
User: "Book a meeting with John Doe for tomorrow at 19:00 (Google Meet). John Doe is the CEO of Example Corp"
208-
Context: node "contact_john_doe" exists.
213+
Example 2 (Complex Request):
214+
User: "Book a meeting with John Doe for tomorrow regarding Project Apollo."
209215
Output:
210216
{
211217
"nodes": [
212-
{ "id": "contact_john_doe", "label": "John Doe", "type": "person", "content": "CEO of Example Corp" },
213-
{ "id": "meeting_john_doe_tomorrow", "label": "Meeting with John Doe", "type": "event", "content": "Meeting with John Doe via Google Meet at 19:00. Date: Tomorrow." }
218+
{ "id": "contact_john_doe", "label": "John Doe", "type": "person", "content": "Entity reference" },
219+
{ "id": "project_apollo", "label": "Project Apollo", "type": "project", "content": "Project detected in query" },
220+
{ "id": "meeting_john_doe_tomorrow", "label": "Meeting with John Doe", "type": "meeting", "content": "Meeting regarding Project Apollo." }
214221
],
215222
"synapses": [
216-
{ "source": "meeting_john_doe_tomorrow", "target": "contact_john_doe", "type": "involves", "weight": 1.0 }
223+
{ "source": "meeting_john_doe_tomorrow", "target": "contact_john_doe", "type": "involves", "weight": 1.0 },
224+
{ "source": "meeting_john_doe_tomorrow", "target": "project_apollo", "type": "relates_to", "weight": 0.8 }
217225
]
218226
}
219227
220228
FORMAT:
221229
{
222230
"nodes": [
223-
{ "id": "snake_case_id", "label": "Readable Label", "type": "concept|entity|event|task", "content": "Detailed description" }
231+
{ "id": "snake_case_id", "label": "Readable Label", "type": "concept|person|project|event|fact|location", "content": "Detailed description" }
224232
],
225233
"synapses": [
226234
{ "source": "sourceId", "target": "targetId", "type": "relates_to", "weight": 0.1-1.0 }
@@ -240,22 +248,25 @@ export const INITIAL_SYSTEM_PROMPTS: SystemPrompt[] = [
240248
{{query}}
241249
242250
INSTRUCTIONS:
243-
1. Answer the user's query naturally, using the retrieved context.
244-
2. CHECK provided context for existing entities (e.g. 'Sasu', 'Icarus') BEFORE suggesting new nodes.
245-
3. If an entity exists in the Context, USE IT. Do NOT create a duplicate 'newNode' for it.
246-
4. **CRITICAL EXCEPTION**: If the user requests a NEW Meeting, Task, or Event (e.g. "Book a meeting"), ALWAYS create a NEW NODE for that Event.
247-
- **LABEL FORMAT**: Must be descriptive! "Meeting with [Name] ([Time])". Do NOT use generic labels like "Meeting" or "Event".
248-
- **CONTENT**: Include all details: Platform (Google Meet), Time, Date, Goal.
249-
- **CONNECT**: Connect it to the participants.
250-
5. **NEGATIVE CONSTRAINT**: Do NOT use IDs or timestamps from the EXAMPLES below. Generate unique IDs based on the ACTUAL USER QUERY (e.g. 'meeting_sasu_friday').
251+
1. You are the Synapse Context Engine (SCE). You are an AI, NOT a human.
252+
2. **IDENTITY RULE**: You are NOT 'Sasu'. Sasu is the Creator/Developer. You are the System.
253+
3. Answer the user's query naturally, using the retrieved context.
254+
2. CHECK provided context for existing entities (e.g. 'Sasu', 'Icarus').
255+
3. If an entity exists, USE IT. However, if the user provides NEW DETAILS (e.g. job title, new relationship), INCLUDE it in 'newNodes' so the system can update it.
256+
4. **CREATION RULE**: If the user provides NEW information (a new concept, fact, meeting, or project) that is NOT in the context, create a NEW NODE for it.
257+
- **LABEL FORMAT**: Be descriptive and concise.
258+
- **CONTENT**: Store the semantic meaning or details of the entity.
259+
- **CONNECT**: Connect it to relevant existing nodes if possible.
260+
5. **NEGATIVE CONSTRAINT**: Do NOT use IDs or timestamps from the EXAMPLES below. Generate unique IDs based on the ACTUAL USER QUERY.
251261
6. DO NOT start your answer with "Based on the provided context". Just answer.
252262
7. YOUR RESPONSE MUST BE VALID JSON.
253263
254264
FORMAT:
255265
{
256266
"answer": "Your response text here...",
257267
"newNodes": [
258-
{ "id": "meeting_sasu_tomorrow_1900", "label": "Meeting with Sasu (Tomorrow 19:00)", "type": "meeting", "content": "Meeting with Sasu via Google Meet at 19:00. Date: Tomorrow.", "connectTo": ["contact_sasu"] }
268+
{ "id": "concept_new_idea", "label": "New Idea", "type": "concept", "content": "Description of the new idea.", "connectTo": ["related_existing_id"] },
269+
{ "id": "pref_ai_name", "label": "AI Name: Jane", "type": "preference", "content": "User prefers to call the AI 'Jane'.", "connectTo": ["session_start"] }
259270
]
260271
}`
261272
},

0 commit comments

Comments
 (0)