Skip to content

Commit bac6c4e

Browse files
committed
feat: Implement Strategic Intelligence Engine (gravity and drift) (#9785) (#9786) (#9787)
- Added getNodeGravity natively to GraphService.mjs (#9785) - Enriched Node schema to persist strategic_weight and gravity_well (#9786) - Implemented Strategic Gap/Drift detection in Sandman to alert [ALIGNMENT_DRIFT] (#9787)
1 parent aed8feb commit bac6c4e

4 files changed

Lines changed: 61 additions & 5 deletions

File tree

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ Enforce this STRICT JSON schema:
223223
"description": "String",
224224
"logical_layer": "String (e.g. UI, State, Network, Build, Docs, Core, Unknown)",
225225
"stability": "String (EXPERIMENTAL, STABLE, DEPRECATED, UNKNOWN)",
226+
"gravity_well": "Boolean (Is this a long-term strategic anchor from roadmap/boardroom?)",
227+
"strategic_weight": 0.9,
226228
"confidence": 0.9,
227229
"tags": ["Array", "of", "Strings"]
228230
}
@@ -308,6 +310,8 @@ ${session.document}
308310
properties: {
309311
logical_layer: node.logical_layer || 'Unknown',
310312
stability: node.stability || 'UNKNOWN',
313+
gravity_well: node.gravity_well === true,
314+
strategic_weight: typeof node.strategic_weight === 'number' ? node.strategic_weight : (node.gravity_well ? 1.0 : 0.1),
311315
confidence: typeof node.confidence === 'number' ? node.confidence : 0.5,
312316
tags: Array.isArray(node.tags) ? node.tags : [],
313317
context_source: session.meta.sessionId
@@ -498,21 +502,23 @@ ${contextText}
498502
let nodeEdges = (payload.session_artifact.graph.edges || []).filter(e => e.source === node.id || e.target === node.id);
499503
let edgeSummary = nodeEdges.length ? nodeEdges.map(e => `[${e.relationship}] ${e.source} -> ${e.target} (Justification: ${e.justification || 'N/A'})`).join('\n') : '(No structural edges generated)';
500504

505+
const gravity = GraphService.getNodeGravity(node._resolvedId || node.id);
506+
501507
let messages = [
502508
{
503509
role: 'system',
504510
content: `You are the Neo.mjs Capability Gap Analyzer (REM).
505-
The underlying Agent just worked on a new feature/concept. You must detect topological conflicts, missing documentation, or coverage gaps based on the node's metadata and relational edges.
506-
We will provide you the Node Metadata, inferred Topological Edges (e.g. CAUSES_ISSUE, BLOCKS), and a FILTERED DIRECTORY TREE of relevant paths in 'docs/', 'learn/', 'test/', and 'src/'.
507-
Analyze if the architectural edges flag a conflict, or if stability implies missing test coverage.
511+
The underlying Agent just worked on a new feature/concept. You must detect topological conflicts, missing documentation, coverage gaps, or STRATEGIC ALIGNMENT DRIFT based on the node's metadata, relational edges, and structural gravity.
512+
We will provide you the Node Metadata, inferred Topological Edges (e.g. CAUSES_ISSUE, BLOCKS), Structural Gravity, and a FILTERED DIRECTORY TREE of relevant paths in 'docs/', 'learn/', 'test/', and 'src/'.
513+
Analyze if the architectural edges flag a conflict, if stability implies missing test coverage, or if there is a "Strategic Vacuum" (ALIGNMENT_DRIFT) where the technical cluster lacks a topological pathway to a strategic anchor or has anomalous gravitational weight.
508514
If you need to read a file to verify its contents, output JSON: {"action": "read_file", "path": "path/to/file.md"}.
509-
If you detect a true gap or topological blocker, output JSON: {"action": "alert", "message": "[CONFLICT|DOC_GAP|TEST_GAP] Detailed reason..."}.
515+
If you detect a true gap, topological blocker, or drift, output JSON: {"action": "alert", "message": "[CONFLICT|DOC_GAP|TEST_GAP|ALIGNMENT_DRIFT] Detailed reason..."}.
510516
If no issues are detected, output JSON: {"action": "pass"}.
511517
NEVER output raw markdown or conversational text. YOU MUST output EXACTLY ONE JSON OBJECT per turn.`
512518
},
513519
{
514520
role: 'user',
515-
content: `Node Type: ${node.type}\nNode Name: ${node.name}\nNode Description: ${node.description}\nLogical Layer: ${node.logical_layer || 'Unknown'}\nStability: ${node.stability || 'UNKNOWN'}\nTags: ${(node.tags || []).join(', ')}\n\n--- INFERRED TOPOLOGICAL EDGES ---\n${edgeSummary}\n\n--- FILTERED DIRECTORY TREE ---\n${relevantPaths || '(No relevant paths found based on heuristic)'}\n--- END DIRECTORY TREE ---`
521+
content: `Node Type: ${node.type}\nNode Name: ${node.name}\nNode Description: ${node.description}\nLogical Layer: ${node.logical_layer || 'Unknown'}\nStability: ${node.stability || 'UNKNOWN'}\nGravity Well: ${node.gravity_well === true ? 'YES' : 'NO'}\nStrategic Weight: ${typeof node.strategic_weight === 'number' ? node.strategic_weight : 0.1}\nStructural Gravity: Inbound ${gravity.in_degree}, Outbound ${gravity.out_degree}\nTags: ${(node.tags || []).join(', ')}\n\n--- INFERRED TOPOLOGICAL EDGES ---\n${edgeSummary}\n\n--- FILTERED DIRECTORY TREE ---\n${relevantPaths || '(No relevant paths found based on heuristic)'}\n--- END DIRECTORY TREE ---`
516522
}
517523
];
518524

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,29 @@ class GraphService extends Base {
317317
};
318318
}
319319

320+
/**
321+
* Dynamically computes the structural gravity (inbound/outbound edges) for a node natively via SQLite.
322+
* @param {String} id
323+
* @returns {Object} { in_degree, out_degree }
324+
*/
325+
getNodeGravity(id) {
326+
if (!this.db?.storage?.db) {
327+
return { in_degree: 0, out_degree: 0 };
328+
}
329+
330+
try {
331+
const inStmt = this.db.storage.db.prepare('SELECT count(*) as count FROM Edges WHERE target = ?');
332+
const inCount = inStmt.get(id).count || 0;
333+
334+
const outStmt = this.db.storage.db.prepare('SELECT count(*) as count FROM Edges WHERE source = ?');
335+
const outCount = outStmt.get(id).count || 0;
336+
337+
return { in_degree: inCount, out_degree: outCount };
338+
} catch (e) {
339+
return { in_degree: 0, out_degree: 0 };
340+
}
341+
}
342+
320343
/**
321344
* Retrieves adjacent connected nodes (neighbors) alongside relationship metadata.
322345
* @param {Object} data

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ test.describe('Neo.ai.mcp.server.memory-core.services.DreamService', () => {
161161
confidence : 0.9,
162162
logical_layer: 'UI Components',
163163
stability : 'EXPERIMENTAL',
164+
gravity_well : true,
165+
strategic_weight: 0.85,
164166
tags : ['Frontend', 'button']
165167
}],
166168
edges: [{
@@ -186,6 +188,9 @@ test.describe('Neo.ai.mcp.server.memory-core.services.DreamService', () => {
186188
expect(providerPrompt[1].content).toContain('src/button/Button.mjs');
187189
expect(providerPrompt[1].content).toContain('CAUSES_ISSUE');
188190
expect(providerPrompt[1].content).toContain('The button feature lacks mobile');
191+
expect(providerPrompt[1].content).toContain('Gravity Well: YES');
192+
expect(providerPrompt[1].content).toContain('Strategic Weight: 0.85');
193+
expect(providerPrompt[1].content).toContain('Structural Gravity: Inbound 0, Outbound 0');
189194

190195
// 5. Validate the Sandman interaction logic gracefully appended to the MD file (via proxy)
191196
console.log("APPENDED ARRAY:", appendedContent); expect(appendedContent.length).toBeGreaterThan(0);

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,28 @@ test.describe('Neo.ai.mcp.server.memory-core.services.GraphService', () => {
145145
expect(task2.weight).toBe(0.8);
146146
});
147147

148+
test('should dynamically compute getNodeGravity natively', async () => {
149+
GraphService.upsertNode({id: 'NodeA'});
150+
GraphService.upsertNode({id: 'NodeB'});
151+
GraphService.upsertNode({id: 'NodeC'});
152+
GraphService.upsertNode({id: 'NodeD'});
153+
154+
GraphService.linkNodes('NodeA', 'NodeB', 'DEPENDS_ON');
155+
GraphService.linkNodes('NodeA', 'NodeC', 'IMPLEMENTS');
156+
GraphService.linkNodes('NodeD', 'NodeA', 'RELATES_TO');
157+
158+
const gravityA = GraphService.getNodeGravity('NodeA');
159+
const gravityB = GraphService.getNodeGravity('NodeB');
160+
161+
// NodeA out:2 (NodeB, NodeC), in:1 (NodeD)
162+
expect(gravityA.out_degree).toBe(2);
163+
expect(gravityA.in_degree).toBe(1);
164+
165+
// NodeB out:0, in:1 (NodeA)
166+
expect(gravityB.out_degree).toBe(0);
167+
expect(gravityB.in_degree).toBe(1);
168+
});
169+
148170
test('should correctly expose getContextFrontier topology', async () => {
149171
GraphService.upsertNode({id: 'frontier', type: 'SYSTEM_ANCHOR'});
150172
GraphService.upsertNode({id: 'EpicB'});

0 commit comments

Comments
 (0)