Skip to content

Commit cf1c704

Browse files
feat(memory-core): GraphService boot-time identity self-seed (#10232) (#10236)
* feat(memory-core): GraphService boot-time identity self-seed (#10232) * revert(data): remove optional chaining from Neo.get in RecordFactory * fix(memory-core): resolve test cache race for GraphService identity bootstrapping (#10236) * chore(memory-core): cleanup stray patch artifacts from commit and update gitignore --------- Co-authored-by: tobiu <tobiasuhlig78@gmail.com>
1 parent d63af76 commit cf1c704

5 files changed

Lines changed: 135 additions & 56 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,6 @@ test-results/
119119

120120
# Autonomous AI Output files
121121
resources/content/sandman_handoff.md
122+
123+
*.orig
124+
*.patch

ai/graph/identityRoots.mjs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @summary Central definition of AgentIdentity and BroadcastSentinel root nodes for the Memory Core Graph.
3+
*
4+
* This shared list provides the definitive addressable identity surface for the A2A Mailbox
5+
* substrate (#10139).
6+
*
7+
* It is used for both:
8+
* 1. Boot-time self-seeding in `GraphService.initAsync` (#10232)
9+
* 2. Explicit manual recovery via `ai/scripts/seedAgentIdentities.mjs`
10+
*/
11+
12+
export const IDENTITIES = [
13+
{
14+
id: '@neo-opus-4-7',
15+
type: 'AgentIdentity',
16+
name: 'Claude Opus 4.7',
17+
description: 'Anthropic Claude Opus version 4.7 Agent Identity',
18+
properties: {
19+
githubLogin: '@neo-opus-4-7',
20+
displayName: 'Claude Opus 4.7',
21+
modelFamily: 'claude',
22+
accountType: 'agent',
23+
createdAt: new Date().toISOString()
24+
}
25+
},
26+
{
27+
id: '@neo-gemini-3-1-pro',
28+
type: 'AgentIdentity',
29+
name: 'Gemini 3.1 Pro',
30+
description: 'Google Gemini 3.1 Pro Agent Identity',
31+
properties: {
32+
githubLogin: '@neo-gemini-3-1-pro',
33+
displayName: 'Gemini 3.1 Pro',
34+
modelFamily: 'gemini',
35+
accountType: 'agent',
36+
createdAt: new Date().toISOString()
37+
}
38+
},
39+
{
40+
id: '@tobiu',
41+
type: 'AgentIdentity',
42+
name: 'Tobias Uhlig',
43+
description: 'Human Owner',
44+
properties: {
45+
githubLogin: '@tobiu',
46+
displayName: 'Tobias Uhlig',
47+
modelFamily: null,
48+
accountType: 'human',
49+
createdAt: new Date().toISOString()
50+
}
51+
},
52+
{
53+
id: 'AGENT:*',
54+
type: 'BroadcastSentinel',
55+
name: 'Broadcast',
56+
description: 'Mailbox broadcast sentinel. `SENT_TO` edges targeting this node fan out to all authenticated recipients per MailboxService.listMessages visibility rules. Must exist as a real graph node so GraphService.linkNodes FK-style guard does not cull broadcast edges — see #10174.',
57+
properties: {
58+
githubLogin: null,
59+
displayName: 'Broadcast',
60+
modelFamily: null,
61+
accountType: 'sentinel',
62+
createdAt: new Date().toISOString()
63+
}
64+
}
65+
];

ai/mcp/server/memory-core/services/GraphService.mjs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import logger from '../logger.mjs';
44
import Base from '../../../../../src/core/Base.mjs';
55
import CoreDatabase from '../../../../../ai/graph/Database.mjs';
66
import SQLite from '../../../../../ai/graph/storage/SQLite.mjs';
7+
import { IDENTITIES } from '../../../../../ai/graph/identityRoots.mjs';
78

89
/**
910
* @summary Service that manages the SQLite Knowledge Graph (Nodes and Edges).
@@ -88,7 +89,32 @@ class GraphService extends Base {
8889
}
8990
this.linkNodes('frontier', 'Neo-Master-Architecture', 'SYSTEM_TENET', 1.0);
9091

91-
// --- 2. FILE SYSTEM INGESTION (Differential Sync) ---
92+
// --- 2. AGENT IDENTITY SUBSTRATE SEEDING (#10232) ---
93+
// Eliminates the "seed → restart-again" recovery loop documented across sessions
94+
// 0327771f, 15852d91, 8968b9f6, 24aa1fa1 (see #10232 body).
95+
// Auto-provision identity roots at boot so bindAgentIdentity doesn't throw on fresh setups.
96+
for (const identity of IDENTITIES) {
97+
// We use getAdjacentNodes as a trigger for lazy-loading into the cache
98+
this.db.getAdjacentNodes(identity.id, 'both');
99+
100+
if (!this.db.nodes.has(identity.id)) {
101+
this.upsertNode(identity);
102+
} else {
103+
// Defensive createdAt retention: Peek SQLite to preserve original timestamp
104+
const row = storage.db.prepare('SELECT data FROM Nodes WHERE id = ?').get(identity.id);
105+
if (row) {
106+
const rawData = JSON.parse(row.data);
107+
if (rawData.properties?.createdAt) {
108+
const preserved = {...identity, properties: {...identity.properties, createdAt: rawData.properties.createdAt}};
109+
this.upsertNode(preserved);
110+
continue;
111+
}
112+
}
113+
this.upsertNode(identity);
114+
}
115+
}
116+
117+
// --- 3. FILE SYSTEM INGESTION (Differential Sync) ---
92118
if (aiConfig.data.autoIngestFileSystem) {
93119
logger.log('[GraphService] Bootstrapping FileSystem Ingestion Native Sync...');
94120
const FileSystemIngestor = (await import('./FileSystemIngestor.mjs')).default;

ai/scripts/seedAgentIdentities.mjs

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -33,61 +33,7 @@
3333
*/
3434

3535
import { Memory_GraphService } from '../services.mjs';
36-
37-
const IDENTITIES = [
38-
{
39-
id: '@neo-opus-4-7',
40-
type: 'AgentIdentity',
41-
name: 'Claude Opus 4.7',
42-
description: 'Anthropic Claude Opus version 4.7 Agent Identity',
43-
properties: {
44-
githubLogin: '@neo-opus-4-7',
45-
displayName: 'Claude Opus 4.7',
46-
modelFamily: 'claude',
47-
accountType: 'agent',
48-
createdAt: new Date().toISOString()
49-
}
50-
},
51-
{
52-
id: '@neo-gemini-3-1-pro',
53-
type: 'AgentIdentity',
54-
name: 'Gemini 3.1 Pro',
55-
description: 'Google Gemini 3.1 Pro Agent Identity',
56-
properties: {
57-
githubLogin: '@neo-gemini-3-1-pro',
58-
displayName: 'Gemini 3.1 Pro',
59-
modelFamily: 'gemini',
60-
accountType: 'agent',
61-
createdAt: new Date().toISOString()
62-
}
63-
},
64-
{
65-
id: '@tobiu',
66-
type: 'AgentIdentity',
67-
name: 'Tobias Uhlig',
68-
description: 'Human Owner',
69-
properties: {
70-
githubLogin: '@tobiu',
71-
displayName: 'Tobias Uhlig',
72-
modelFamily: null,
73-
accountType: 'human',
74-
createdAt: new Date().toISOString()
75-
}
76-
},
77-
{
78-
id: 'AGENT:*',
79-
type: 'BroadcastSentinel',
80-
name: 'Broadcast',
81-
description: 'Mailbox broadcast sentinel. `SENT_TO` edges targeting this node fan out to all authenticated recipients per MailboxService.listMessages visibility rules. Must exist as a real graph node so GraphService.linkNodes FK-style guard does not cull broadcast edges — see #10174.',
82-
properties: {
83-
githubLogin: null,
84-
displayName: 'Broadcast',
85-
modelFamily: null,
86-
accountType: 'sentinel',
87-
createdAt: new Date().toISOString()
88-
}
89-
}
90-
];
36+
import { IDENTITIES } from '../graph/identityRoots.mjs';
9137

9238
async function seed() {
9339
console.log('Bootstrapping Memory Graph Service...');

test/playwright/unit/ai/mcp/server/memory-core/services/GraphService.spec.mjs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,4 +536,43 @@ test.describe('Neo.ai.mcp.server.memory-core.services.GraphService', () => {
536536

537537
expect(ready).toBe(true);
538538
});
539+
540+
test('should auto-provision all identity roots at boot via initAsync', async () => {
541+
// Guarantee pristine isolated boundary baseline natively
542+
if (GraphService.db) {
543+
GraphService.db.nodes.clear();
544+
GraphService.db.edges.clear();
545+
GraphService.db.vicinityLoadedNodes.clear();
546+
547+
if (GraphService.db.storage?.db) {
548+
await GraphService.db.storage.clear();
549+
GraphService.db.storage.db.exec('DELETE FROM GraphLog');
550+
GraphService.db.lastSyncId = 0;
551+
}
552+
}
553+
554+
// Wipe the init promise and db to force complete re-initialization
555+
GraphService._initPromise = null;
556+
GraphService.db = null;
557+
558+
// Run the boot sequence
559+
await GraphService.initAsync();
560+
561+
// Assert all four identities are present with correct types
562+
const geminiPro = GraphService.getNode({id: '@neo-gemini-3-1-pro'});
563+
expect(geminiPro).toBeTruthy();
564+
expect(geminiPro.type).toBe('AgentIdentity');
565+
566+
const claudeOpus = GraphService.getNode({id: '@neo-opus-4-7'});
567+
expect(claudeOpus).toBeTruthy();
568+
expect(claudeOpus.type).toBe('AgentIdentity');
569+
570+
const tobiu = GraphService.getNode({id: '@tobiu'});
571+
expect(tobiu).toBeTruthy();
572+
expect(tobiu.type).toBe('AgentIdentity');
573+
574+
const broadcast = GraphService.getNode({id: 'AGENT:*'});
575+
expect(broadcast).toBeTruthy();
576+
expect(broadcast.type).toBe('BroadcastSentinel');
577+
});
539578
});

0 commit comments

Comments
 (0)