Skip to content

Commit 0d66876

Browse files
authored
refactor(ai): read Memory Core path leaves directly (#12438) (#12562)
1 parent 27eebac commit 0d66876

6 files changed

Lines changed: 62 additions & 33 deletions

File tree

ai/daemons/bridge/daemon.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import Neo from '../../../src/Neo.mjs';
3131
import * as core from '../../../src/core/_export.mjs';
3232
import InstanceManager from '../../../src/manager/Instance.mjs';
3333
import AiConfig from '../../config.mjs';
34+
import memoryCoreConfig from '../../mcp/server/memory-core/config.mjs';
3435

3536
import fs from 'fs-extra';
3637
import path from 'path';
@@ -53,8 +54,8 @@ import {
5354
import {applyHarnessMetadataDefaults} from '../../scripts/lifecycle/harnessRouting.mjs';
5455
import {getDefaultInstancePid, getInstancePid} from './instanceResolver.mjs';
5556

56-
const DB_PATH = process.env.NEO_AI_DB_PATH || '.neo-ai-data/sqlite/memory-core-graph.sqlite';
57-
const DAEMON_DATA_DIR = process.env.NEO_AI_DAEMON_DIR || '.neo-ai-data/wake-daemon';
57+
const DB_PATH = memoryCoreConfig.storagePaths.graph;
58+
const DAEMON_DATA_DIR = memoryCoreConfig.wakeDaemon.dataDir;
5859
const STATE_FILE = path.join(DAEMON_DATA_DIR, 'lastSyncId');
5960
const LOG_FILE = path.join(DAEMON_DATA_DIR, 'bridge.log');
6061
const LOG_RETENTION_DAYS = 30;

ai/scripts/diagnostics/analyzeNlTelemetry.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const __dirname = path.dirname(__filename);
2020
const ROOT_DIR = path.resolve(__dirname, '../../../');
2121
import aiConfig from '../../mcp/server/memory-core/config.mjs';
2222

23-
const DB_PATH = process.env.NEO_MEMORY_DB_PATH || aiConfig.storagePaths.graph;
23+
// The Memory Core config leaf owns the NEO_MEMORY_DB_PATH env override.
24+
const DB_PATH = aiConfig.storagePaths.graph;
2425
const RLAIF_PATH = aiConfig.datasets.rlaif.trajectories;
2526

2627
const sessionId = process.argv[2];

ai/services/memory-core/HealthService.mjs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ import {readRecentRemRunStates} from './helpers/RemRunStateStore.mjs';
1919
const __dirname = path.dirname(fileURLToPath(import.meta.url));
2020

2121
/**
22-
* Heartbeat-liveness file path resolution. Mirrors `wakeSafetyGate.gateFilePath()` env-override
23-
* pattern so parallel test specs can isolate from the canonical on-disk path. Production
24-
* deployments leave `NEO_HEARTBEAT_ALIVE_PATH` unset; the canonical path under `.neo-ai-data/wake-daemon/`
25-
* applies. Counterpart producer: `SwarmHeartbeatService.touchLivenessFile()`, called once per
26-
* `pulse()` by the Orchestrator's swarm-heartbeat lane.
22+
* @summary Heartbeat-liveness file path resolution shared with `SwarmHeartbeatService`.
23+
*
24+
* The Tier-1 config leaf owns `NEO_HEARTBEAT_ALIVE_PATH` env resolution, keeping the
25+
* producer and consumer on one resolved path.
26+
*
27+
* @returns {String}
2728
*/
28-
function heartbeatAlivePath() {
29-
return process.env.NEO_HEARTBEAT_ALIVE_PATH || path.resolve(__dirname, '../../../.neo-ai-data/wake-daemon/heartbeat.alive');
29+
export function heartbeatAlivePath() {
30+
return aiConfig.wakeDaemonHeartbeatAlivePath;
3031
}
3132

3233
/**

test/playwright/unit/ai/daemons/bridge/daemon.spec.mjs

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ test.describe('Bridge Daemon', () => {
122122
if (fs.existsSync(DB_PATH)) fs.unlinkSync(DB_PATH);
123123
if (fs.existsSync(`${DB_PATH}-wal`)) fs.unlinkSync(`${DB_PATH}-wal`);
124124
if (fs.existsSync(`${DB_PATH}-shm`)) fs.unlinkSync(`${DB_PATH}-shm`);
125+
process.env.NEO_MEMORY_DB_PATH_TEST = DB_PATH;
125126

126127
db = new Database(DB_PATH);
127128
db.pragma('journal_mode = WAL');
@@ -163,6 +164,7 @@ test.describe('Bridge Daemon', () => {
163164
if (fs.existsSync(`${DB_PATH}-wal`)) fs.unlinkSync(`${DB_PATH}-wal`);
164165
if (fs.existsSync(`${DB_PATH}-shm`)) fs.unlinkSync(`${DB_PATH}-shm`);
165166
fs.removeSync(DAEMON_DIR);
167+
delete process.env.NEO_MEMORY_DB_PATH_TEST;
166168
});
167169

168170
test('detects and delivers wake events via test adapter', async () => {
@@ -195,7 +197,7 @@ test.describe('Bridge Daemon', () => {
195197
// Start the daemon with environment overrides
196198
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
197199
stdio: 'pipe',
198-
env: { ...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
200+
env: { ...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
199201
});
200202

201203
const deliveryPromise = new Promise((resolve, reject) => {
@@ -286,7 +288,7 @@ test.describe('Bridge Daemon', () => {
286288

287289
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
288290
stdio: 'pipe',
289-
env: { ...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
291+
env: { ...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
290292
});
291293

292294
const deliveryPromise = new Promise((resolve, reject) => {
@@ -343,7 +345,7 @@ test.describe('Bridge Daemon', () => {
343345

344346
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
345347
stdio: 'pipe',
346-
env: { ...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
348+
env: { ...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
347349
});
348350

349351
const deliveryPromise = new Promise((resolve, reject) => {
@@ -400,7 +402,7 @@ test.describe('Bridge Daemon', () => {
400402

401403
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
402404
stdio: 'pipe',
403-
env: { ...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
405+
env: { ...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
404406
});
405407

406408
let deliveryCount = 0;
@@ -476,7 +478,7 @@ test.describe('Bridge Daemon', () => {
476478

477479
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
478480
stdio: 'pipe',
479-
env: { ...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
481+
env: { ...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
480482
});
481483

482484
let deliveryCount = 0;
@@ -570,7 +572,7 @@ test.describe('Bridge Daemon', () => {
570572

571573
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
572574
stdio: 'pipe',
573-
env: { ...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
575+
env: { ...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
574576
});
575577

576578
const deliveryPromise = new Promise((resolve, reject) => {
@@ -668,7 +670,7 @@ test.describe('Bridge Daemon', () => {
668670

669671
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
670672
stdio: 'pipe',
671-
env: { ...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
673+
env: { ...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
672674
});
673675

674676
const errorLogPromise = new Promise((resolve, reject) => {
@@ -747,7 +749,7 @@ test.describe('Bridge Daemon', () => {
747749

748750
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
749751
stdio: 'pipe',
750-
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
752+
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
751753
});
752754

753755
// We know bridge-daemon will log INFO when it finishes osascript
@@ -836,7 +838,7 @@ test.describe('Bridge Daemon', () => {
836838

837839
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
838840
stdio: 'pipe',
839-
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
841+
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
840842
});
841843

842844
const deliveryPromise = new Promise((resolve, reject) => {
@@ -936,7 +938,7 @@ test.describe('Bridge Daemon', () => {
936938

937939
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
938940
stdio: 'pipe',
939-
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
941+
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
940942
});
941943

942944
const deliveryPromise = new Promise((resolve, reject) => {
@@ -991,7 +993,7 @@ test.describe('Bridge Daemon', () => {
991993

992994
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
993995
stdio: 'pipe',
994-
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
996+
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
995997
});
996998

997999
const refusalPromise = new Promise((resolve, reject) => {
@@ -1203,7 +1205,7 @@ test.describe('Bridge Daemon', () => {
12031205

12041206
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
12051207
stdio: 'pipe',
1206-
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
1208+
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
12071209
});
12081210

12091211
const deliveryPromise = new Promise((resolve, reject) => {
@@ -1260,7 +1262,7 @@ test.describe('Bridge Daemon', () => {
12601262

12611263
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
12621264
stdio: 'pipe',
1263-
env: { ...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
1265+
env: { ...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
12641266
});
12651267

12661268
setTimeout(() => insertMessageWake(db, {agentId, subject: 'Webhook Address Wake'}), 1000);
@@ -1340,7 +1342,7 @@ test.describe('Bridge Daemon', () => {
13401342

13411343
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
13421344
stdio: 'pipe',
1343-
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
1345+
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
13441346
});
13451347

13461348
// Wait for the fail-closed warning log line (proxy for the deliver-or-refuse decision).
@@ -1433,7 +1435,7 @@ test.describe('Bridge Daemon', () => {
14331435

14341436
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
14351437
stdio: 'pipe',
1436-
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
1438+
env: { ...process.env, PATH: `${path.resolve(binDir)}:${process.env.PATH}`, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR }
14371439
});
14381440

14391441
const deliveryPromise = new Promise((resolve, reject) => {
@@ -1605,7 +1607,7 @@ test.describe('Bridge Daemon', () => {
16051607

16061608
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
16071609
stdio: 'pipe',
1608-
env : {...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR}
1610+
env : {...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR}
16091611
});
16101612

16111613
let senderDeliveryCount = 0;
@@ -1676,7 +1678,7 @@ test.describe('Bridge Daemon', () => {
16761678

16771679
daemonProcess = spawn('node', ['ai/daemons/bridge/daemon.mjs'], {
16781680
stdio: 'pipe',
1679-
env : {...process.env, NEO_AI_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR}
1681+
env : {...process.env, NEO_MEMORY_DB_PATH: DB_PATH, NEO_AI_DAEMON_DIR: DAEMON_DIR}
16801682
});
16811683

16821684
const deliveryPromise = new Promise((resolve, reject) => {

test/playwright/unit/ai/scripts/diagnostics/analyzeNlTelemetry.spec.mjs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ test.describe('Neo.ai.scripts.analyzeNlTelemetry', () => {
9191
test('should extract Neural Link trajectories without mutating physical databases', () => {
9292
const env = {
9393
...process.env,
94-
NEO_MEMORY_DB_PATH: testDbPath,
94+
NEO_MEMORY_DB_PATH_TEST: testDbPath,
9595
NEO_RLAIF_PATH: testRlaifPath
9696
};
9797

@@ -104,7 +104,7 @@ test.describe('Neo.ai.scripts.analyzeNlTelemetry', () => {
104104
test('should gracefully exit if no trajectories are found', () => {
105105
const env = {
106106
...process.env,
107-
NEO_MEMORY_DB_PATH: testDbPath,
107+
NEO_MEMORY_DB_PATH_TEST: testDbPath,
108108
NEO_RLAIF_PATH: testRlaifPath
109109
};
110110

@@ -115,7 +115,7 @@ test.describe('Neo.ai.scripts.analyzeNlTelemetry', () => {
115115
test('should save to custom RLAIF path when --save is provided', () => {
116116
const env = {
117117
...process.env,
118-
NEO_MEMORY_DB_PATH: testDbPath,
118+
NEO_MEMORY_DB_PATH_TEST: testDbPath,
119119
NEO_RLAIF_PATH: testRlaifPath
120120
};
121121

@@ -142,4 +142,12 @@ test.describe('Neo.ai.scripts.analyzeNlTelemetry', () => {
142142
expect(diagnosticsSource).not.toContain(retiredPath);
143143
expect(maintenanceSource).not.toContain(retiredPath);
144144
});
145+
146+
test('reads the resolved Memory Core graph leaf without a duplicate env fallback (#12438)', () => {
147+
const diagnosticsSource = fs.readFileSync(scriptPath, 'utf8');
148+
149+
expect(diagnosticsSource).toContain('const DB_PATH = aiConfig.storagePaths.graph;');
150+
expect(diagnosticsSource).not.toContain('process.env.NEO_MEMORY_DB_PATH ||');
151+
expect(diagnosticsSource).not.toContain('process.env.NEO_MEMORY_DB_PATH ??');
152+
});
145153
});

test/playwright/unit/ai/services/memory-core/HealthService.spec.mjs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {test, expect} from '@playwright/test';
1717
import Neo from '../../../../../../src/Neo.mjs';
1818
import * as core from '../../../../../../src/core/_export.mjs';
1919
import InstanceManager from '../../../../../../src/manager/Instance.mjs';
20+
import AiConfig from '../../../../../../ai/config.mjs';
2021
import ChromaManager from '../../../../../../ai/services/memory-core/managers/ChromaManager.mjs';
2122
import StorageRouter from '../../../../../../ai/services/memory-core/managers/StorageRouter.mjs';
2223
import ChromaLifecycleService from '../../../../../../ai/services/memory-core/lifecycle/ChromaLifecycleService.mjs';
@@ -1028,19 +1029,22 @@ test.describe('HealthService #10844 — buildBackupStateBlock', () => {
10281029
* missing / malformed) × (liveness-file: fresh / stalled / missing) per AC5, plus the fully-degraded
10291030
* defensive case.
10301031
*
1031-
* Test isolation: each test writes to a unique temp directory + sets `WAKE_GATE_FILE_PATH` and
1032-
* `NEO_HEARTBEAT_ALIVE_PATH` env vars before importing the block; restores after. Mirrors the
1033-
* `wakeSafetyGate` env-override pattern.
1032+
* Test isolation: each test writes to a unique temp directory, points `WAKE_GATE_FILE_PATH`
1033+
* at that fixture, and applies `NEO_HEARTBEAT_ALIVE_PATH` through the AiConfig env-override seam.
10341034
*
10351035
* @see Neo.ai.services.memory-core.HealthService#buildWakeFeaturesBlock
10361036
*/
10371037
test.describe('HealthService #10783 — buildWakeFeaturesBlock', () => {
10381038
let buildWakeFeaturesBlock;
1039+
let heartbeatAlivePath;
1040+
let originalHeartbeatAlivePath;
10391041
let tmpDir;
10401042

10411043
test.beforeAll(async () => {
10421044
const mod = await import('../../../../../../ai/services/memory-core/HealthService.mjs');
10431045
buildWakeFeaturesBlock = mod.buildWakeFeaturesBlock;
1046+
heartbeatAlivePath = mod.heartbeatAlivePath;
1047+
originalHeartbeatAlivePath = AiConfig.wakeDaemonHeartbeatAlivePath;
10441048

10451049
const os = await import('os');
10461050
const path = await import('path');
@@ -1062,6 +1066,7 @@ test.describe('HealthService #10783 — buildWakeFeaturesBlock', () => {
10621066

10631067
process.env.WAKE_GATE_FILE_PATH = path.join(tmpDir, `gate-${Date.now()}.json`);
10641068
process.env.NEO_HEARTBEAT_ALIVE_PATH = path.join(tmpDir, `alive-${Date.now()}`);
1069+
AiConfig.setEnvOverride('NEO_HEARTBEAT_ALIVE_PATH', process.env.NEO_HEARTBEAT_ALIVE_PATH);
10651070

10661071
// Ensure clean slate between tests (no carryover from prior writes)
10671072
await fs.rm(process.env.WAKE_GATE_FILE_PATH, {force: true}).catch(() => {});
@@ -1071,6 +1076,17 @@ test.describe('HealthService #10783 — buildWakeFeaturesBlock', () => {
10711076
test.afterEach(() => {
10721077
delete process.env.WAKE_GATE_FILE_PATH;
10731078
delete process.env.NEO_HEARTBEAT_ALIVE_PATH;
1079+
AiConfig.setEnvOverride('NEO_HEARTBEAT_ALIVE_PATH', originalHeartbeatAlivePath);
1080+
});
1081+
1082+
test('heartbeatAlivePath() reads the resolved AiConfig leaf (#12438)', async () => {
1083+
const path = await import('path');
1084+
const overridePath = path.join(tmpDir, `alive-helper-${Date.now()}`);
1085+
1086+
AiConfig.setEnvOverride('NEO_HEARTBEAT_ALIVE_PATH', overridePath);
1087+
1088+
expect(heartbeatAlivePath()).toBe(AiConfig.wakeDaemonHeartbeatAlivePath);
1089+
expect(heartbeatAlivePath()).toBe(overridePath);
10741090
});
10751091

10761092
test('gate enabled + liveness fresh → daemonRunning true, gateState enabled', async () => {

0 commit comments

Comments
 (0)