memorix 1.9.1 → 1.9.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.
- package/CHANGELOG.md +22 -0
- package/README.md +17 -4
- package/README.zh-CN.md +8 -4
- package/dist/cli/index.js +448 -243
- package/dist/index.js +302 -165
- package/dist/maintenance-runner.js +194 -98
- package/dist/media-audio-runner.js +196 -100
- package/dist/media-video-runner.js +194 -98
- package/dist/memcode-runtime/CHANGELOG.md +22 -0
- package/dist/sdk.js +301 -164
- package/dist/vector-backfill-runner.js +194 -98
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +15 -0
- package/docs/ARCHITECTURE.md +23 -0
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/INTEGRATIONS.md +4 -0
- package/docs/PERFORMANCE.md +50 -0
- package/docs/README.md +1 -1
- package/docs/SETUP.md +5 -0
- package/package.json +1 -2
- package/plugins/antigravity/memorix/plugin.json +1 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/hermes/memorix/plugin.yaml +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/commands/doctor.ts +78 -17
- package/src/cli/commands/memory.ts +2 -1
- package/src/cli/default-mode.ts +15 -0
- package/src/cli/index.ts +10 -1
- package/src/codegraph/lite-provider.ts +10 -1
- package/src/dashboard/maintenance.ts +11 -2
- package/src/memory/consolidation.ts +15 -6
- package/src/store/mini-skill-store.ts +31 -22
- package/src/store/obs-store.ts +46 -30
- package/src/store/session-store.ts +35 -21
- package/src/store/sqlite-db.ts +16 -7
- package/src/store/sqlite-reliability.ts +87 -0
- package/src/store/sqlite-store.ts +4 -1
- package/assets/star-history-dark.svg +0 -30
- package/assets/star-history-light.svg +0 -30
- package/assets/star-history.json +0 -712
|
@@ -580,70 +580,79 @@ function getDatabase(dataDir) {
|
|
|
580
580
|
loadBetterSqlite3();
|
|
581
581
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
582
582
|
const dbPath = path3.join(dataDir, "memorix.db");
|
|
583
|
-
|
|
584
|
-
db2.pragma("journal_mode = WAL");
|
|
585
|
-
db2.pragma("busy_timeout = 5000");
|
|
586
|
-
db2.pragma("foreign_keys = ON");
|
|
587
|
-
db2.exec(CREATE_OBSERVATIONS_TABLE);
|
|
588
|
-
db2.exec(CREATE_MINI_SKILLS_TABLE);
|
|
589
|
-
db2.exec(CREATE_SESSIONS_TABLE);
|
|
590
|
-
db2.exec(CREATE_META_TABLE);
|
|
591
|
-
db2.exec(CREATE_TEAM_AGENTS_TABLE);
|
|
592
|
-
db2.exec(CREATE_TEAM_TASKS_TABLE);
|
|
593
|
-
db2.exec(CREATE_TEAM_TASK_DEPS_TABLE);
|
|
594
|
-
db2.exec(CREATE_TEAM_MESSAGES_TABLE);
|
|
595
|
-
db2.exec(CREATE_TEAM_LOCKS_TABLE);
|
|
596
|
-
db2.exec(CREATE_TEAM_ROLES_TABLE);
|
|
597
|
-
db2.exec(CREATE_GRAPH_ENTITIES_TABLE);
|
|
598
|
-
db2.exec(CREATE_GRAPH_RELATIONS_TABLE);
|
|
599
|
-
db2.exec(CREATE_CHAT_TRANSCRIPT_TABLE);
|
|
600
|
-
db2.exec(CREATE_CODE_FILES_TABLE);
|
|
601
|
-
db2.exec(CREATE_CODE_SYMBOLS_TABLE);
|
|
602
|
-
db2.exec(CREATE_CODE_EDGES_TABLE);
|
|
603
|
-
db2.exec(CREATE_OBSERVATION_CODE_REFS_TABLE);
|
|
604
|
-
db2.exec(CREATE_MAINTENANCE_JOBS_TABLE);
|
|
605
|
-
db2.exec(CREATE_MAINTENANCE_TARGETS_TABLE);
|
|
606
|
-
db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
|
|
583
|
+
let db2;
|
|
607
584
|
try {
|
|
608
|
-
db2
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
db2.exec(
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
db2.exec(
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
db2.exec(
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
db2.exec(
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
db2.exec(
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
585
|
+
db2 = createDatabase(dbPath);
|
|
586
|
+
db2.pragma("busy_timeout = 5000");
|
|
587
|
+
db2.pragma("journal_mode = WAL");
|
|
588
|
+
db2.pragma("foreign_keys = ON");
|
|
589
|
+
db2.exec(CREATE_OBSERVATIONS_TABLE);
|
|
590
|
+
db2.exec(CREATE_MINI_SKILLS_TABLE);
|
|
591
|
+
db2.exec(CREATE_SESSIONS_TABLE);
|
|
592
|
+
db2.exec(CREATE_META_TABLE);
|
|
593
|
+
db2.exec(CREATE_TEAM_AGENTS_TABLE);
|
|
594
|
+
db2.exec(CREATE_TEAM_TASKS_TABLE);
|
|
595
|
+
db2.exec(CREATE_TEAM_TASK_DEPS_TABLE);
|
|
596
|
+
db2.exec(CREATE_TEAM_MESSAGES_TABLE);
|
|
597
|
+
db2.exec(CREATE_TEAM_LOCKS_TABLE);
|
|
598
|
+
db2.exec(CREATE_TEAM_ROLES_TABLE);
|
|
599
|
+
db2.exec(CREATE_GRAPH_ENTITIES_TABLE);
|
|
600
|
+
db2.exec(CREATE_GRAPH_RELATIONS_TABLE);
|
|
601
|
+
db2.exec(CREATE_CHAT_TRANSCRIPT_TABLE);
|
|
602
|
+
db2.exec(CREATE_CODE_FILES_TABLE);
|
|
603
|
+
db2.exec(CREATE_CODE_SYMBOLS_TABLE);
|
|
604
|
+
db2.exec(CREATE_CODE_EDGES_TABLE);
|
|
605
|
+
db2.exec(CREATE_OBSERVATION_CODE_REFS_TABLE);
|
|
606
|
+
db2.exec(CREATE_MAINTENANCE_JOBS_TABLE);
|
|
607
|
+
db2.exec(CREATE_MAINTENANCE_TARGETS_TABLE);
|
|
608
|
+
db2.exec(CREATE_COMPACTION_CHECKPOINTS_TABLE);
|
|
609
|
+
try {
|
|
610
|
+
db2.exec(`ALTER TABLE mini_skills ADD COLUMN sourceSnapshot TEXT NOT NULL DEFAULT ''`);
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
db2.exec(`ALTER TABLE mini_skills ADD COLUMN updatedAt TEXT`);
|
|
615
|
+
} catch {
|
|
616
|
+
}
|
|
617
|
+
try {
|
|
618
|
+
db2.exec(`ALTER TABLE observations ADD COLUMN createdByAgentId TEXT`);
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
db2.exec(`ALTER TABLE observations ADD COLUMN writeGeneration INTEGER DEFAULT 0`);
|
|
623
|
+
} catch {
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
db2.exec(`ALTER TABLE team_tasks ADD COLUMN required_role TEXT`);
|
|
627
|
+
} catch {
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
db2.exec(`ALTER TABLE team_tasks ADD COLUMN preferred_role TEXT`);
|
|
631
|
+
} catch {
|
|
632
|
+
}
|
|
633
|
+
try {
|
|
634
|
+
db2.exec(`ALTER TABLE team_messages ADD COLUMN to_role TEXT`);
|
|
635
|
+
} catch {
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
db2.exec(`ALTER TABLE team_messages ADD COLUMN handoff_status TEXT`);
|
|
639
|
+
} catch {
|
|
640
|
+
}
|
|
641
|
+
applySchemaMigrations(db2);
|
|
642
|
+
db2.exec(CREATE_INDEXES);
|
|
643
|
+
initializeObservationLexicalIndex(db2);
|
|
644
|
+
db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('storage_generation', '0')`).run();
|
|
645
|
+
db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('next_id', '1')`).run();
|
|
646
|
+
db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('mini_skills_generation', '0')`).run();
|
|
647
|
+
_dbCache.set(normalized, db2);
|
|
648
|
+
return db2;
|
|
649
|
+
} catch (error) {
|
|
650
|
+
try {
|
|
651
|
+
db2?.close();
|
|
652
|
+
} catch {
|
|
653
|
+
}
|
|
654
|
+
throw error;
|
|
638
655
|
}
|
|
639
|
-
applySchemaMigrations(db2);
|
|
640
|
-
db2.exec(CREATE_INDEXES);
|
|
641
|
-
initializeObservationLexicalIndex(db2);
|
|
642
|
-
db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('storage_generation', '0')`).run();
|
|
643
|
-
db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('next_id', '1')`).run();
|
|
644
|
-
db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('mini_skills_generation', '0')`).run();
|
|
645
|
-
_dbCache.set(normalized, db2);
|
|
646
|
-
return db2;
|
|
647
656
|
}
|
|
648
657
|
function closeAllDatabases() {
|
|
649
658
|
for (const [key, db2] of _dbCache) {
|
|
@@ -1633,6 +1642,74 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
|
|
|
1633
1642
|
}
|
|
1634
1643
|
});
|
|
1635
1644
|
|
|
1645
|
+
// src/store/sqlite-reliability.ts
|
|
1646
|
+
function isTransientSqliteError(error) {
|
|
1647
|
+
const code = error?.code;
|
|
1648
|
+
if (typeof code === "string") {
|
|
1649
|
+
return /^SQLITE_BUSY(?:_|$)/i.test(code);
|
|
1650
|
+
}
|
|
1651
|
+
return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
|
|
1652
|
+
}
|
|
1653
|
+
function isSqliteUnavailableError(error) {
|
|
1654
|
+
return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
|
|
1655
|
+
}
|
|
1656
|
+
function degradedReadError(storeName) {
|
|
1657
|
+
return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
|
|
1658
|
+
}
|
|
1659
|
+
function degradedWriteError(storeName) {
|
|
1660
|
+
return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
|
|
1661
|
+
}
|
|
1662
|
+
function formatSqliteFailure(error) {
|
|
1663
|
+
return error instanceof Error && error.message ? error.message : String(error);
|
|
1664
|
+
}
|
|
1665
|
+
function delay(ms) {
|
|
1666
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1667
|
+
}
|
|
1668
|
+
async function retrySqliteBusy(operation, label) {
|
|
1669
|
+
for (let attempt = 0; ; attempt++) {
|
|
1670
|
+
try {
|
|
1671
|
+
return await operation();
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
|
|
1674
|
+
const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
|
|
1675
|
+
console.error(
|
|
1676
|
+
`[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
|
|
1677
|
+
);
|
|
1678
|
+
await delay(waitMs);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
async function initializeSqliteStore(factory, dataDir, label) {
|
|
1683
|
+
try {
|
|
1684
|
+
loadBetterSqlite3();
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
throw new SqliteUnavailableError(error);
|
|
1687
|
+
}
|
|
1688
|
+
return retrySqliteBusy(async () => {
|
|
1689
|
+
const store = factory();
|
|
1690
|
+
await store.init(dataDir);
|
|
1691
|
+
return store;
|
|
1692
|
+
}, label);
|
|
1693
|
+
}
|
|
1694
|
+
var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
|
|
1695
|
+
var init_sqlite_reliability = __esm({
|
|
1696
|
+
"src/store/sqlite-reliability.ts"() {
|
|
1697
|
+
"use strict";
|
|
1698
|
+
init_esm_shims();
|
|
1699
|
+
init_sqlite_db();
|
|
1700
|
+
SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
|
|
1701
|
+
SqliteUnavailableError = class extends Error {
|
|
1702
|
+
code = "MEMORIX_SQLITE_UNAVAILABLE";
|
|
1703
|
+
constructor(cause) {
|
|
1704
|
+
super(
|
|
1705
|
+
cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
|
|
1706
|
+
);
|
|
1707
|
+
this.name = "SqliteUnavailableError";
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1636
1713
|
// src/store/file-lock.ts
|
|
1637
1714
|
import { promises as fs2 } from "fs";
|
|
1638
1715
|
import { randomUUID } from "crypto";
|
|
@@ -3556,10 +3633,10 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
|
|
|
3556
3633
|
);
|
|
3557
3634
|
}
|
|
3558
3635
|
if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
|
|
3559
|
-
const
|
|
3636
|
+
const delay3 = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
3560
3637
|
const retryAfter = response.headers.get("retry-after");
|
|
3561
3638
|
const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
|
|
3562
|
-
const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter :
|
|
3639
|
+
const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay3;
|
|
3563
3640
|
console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
|
|
3564
3641
|
await waitForRetry(waitMs, options.signal);
|
|
3565
3642
|
return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
|
|
@@ -5591,6 +5668,7 @@ var init_sqlite_store = __esm({
|
|
|
5591
5668
|
"use strict";
|
|
5592
5669
|
init_esm_shims();
|
|
5593
5670
|
init_sqlite_db();
|
|
5671
|
+
init_sqlite_reliability();
|
|
5594
5672
|
init_sqlite_fts();
|
|
5595
5673
|
SqliteBackend = class {
|
|
5596
5674
|
db = null;
|
|
@@ -5868,7 +5946,9 @@ var init_sqlite_store = __esm({
|
|
|
5868
5946
|
// ── Compound atomic operation ────────────────────────────────────
|
|
5869
5947
|
async atomic(fn) {
|
|
5870
5948
|
const run = this._atomicQueue.catch(() => void 0).then(async () => {
|
|
5871
|
-
|
|
5949
|
+
await retrySqliteBusy(async () => {
|
|
5950
|
+
this.db.prepare("BEGIN IMMEDIATE").run();
|
|
5951
|
+
}, "Observation atomic write");
|
|
5872
5952
|
try {
|
|
5873
5953
|
const tx = {
|
|
5874
5954
|
loadAll: async () => this.rawLoadAll(),
|
|
@@ -5967,15 +6047,15 @@ function resetObservationStore() {
|
|
|
5967
6047
|
async function createObservationStore(dataDir) {
|
|
5968
6048
|
try {
|
|
5969
6049
|
const { SqliteBackend: SqliteBackend2 } = await Promise.resolve().then(() => (init_sqlite_store(), sqlite_store_exports));
|
|
5970
|
-
|
|
5971
|
-
await store2.init(dataDir);
|
|
5972
|
-
return store2;
|
|
6050
|
+
return await initializeSqliteStore(() => new SqliteBackend2(), dataDir, "Observation store");
|
|
5973
6051
|
} catch (err) {
|
|
5974
|
-
|
|
6052
|
+
if (!isSqliteUnavailableError(err)) throw err;
|
|
6053
|
+
const reason = formatSqliteFailure(err);
|
|
6054
|
+
console.error(`[memorix] SQLite module unavailable \u2014 degraded mode (reads/writes disabled): ${reason}`);
|
|
6055
|
+
const degraded = new DegradedBackend(reason);
|
|
6056
|
+
await degraded.init(dataDir);
|
|
6057
|
+
return degraded;
|
|
5975
6058
|
}
|
|
5976
|
-
const store = new DegradedBackend();
|
|
5977
|
-
await store.init(dataDir);
|
|
5978
|
-
return store;
|
|
5979
6059
|
}
|
|
5980
6060
|
async function initObservationStore(dataDir) {
|
|
5981
6061
|
if (_store2 && _storeDataDir === dataDir) {
|
|
@@ -5999,36 +6079,44 @@ var init_obs_store = __esm({
|
|
|
5999
6079
|
"src/store/obs-store.ts"() {
|
|
6000
6080
|
"use strict";
|
|
6001
6081
|
init_esm_shims();
|
|
6082
|
+
init_sqlite_reliability();
|
|
6002
6083
|
_store2 = null;
|
|
6003
6084
|
_storeDataDir = null;
|
|
6004
6085
|
DegradedBackend = class {
|
|
6086
|
+
constructor(reason = "SQLite runtime unavailable") {
|
|
6087
|
+
this.reason = reason;
|
|
6088
|
+
}
|
|
6089
|
+
reason;
|
|
6005
6090
|
dataDir = "";
|
|
6091
|
+
unavailableRead() {
|
|
6092
|
+
return degradedReadError("observations");
|
|
6093
|
+
}
|
|
6006
6094
|
async init(dataDir) {
|
|
6007
6095
|
this.dataDir = dataDir;
|
|
6008
6096
|
}
|
|
6009
6097
|
async loadAll() {
|
|
6010
|
-
|
|
6098
|
+
throw this.unavailableRead();
|
|
6011
6099
|
}
|
|
6012
6100
|
async loadByProject(_projectId, _options) {
|
|
6013
|
-
|
|
6101
|
+
throw this.unavailableRead();
|
|
6014
6102
|
}
|
|
6015
6103
|
async countByProject(_projectId, _options) {
|
|
6016
|
-
|
|
6104
|
+
throw this.unavailableRead();
|
|
6017
6105
|
}
|
|
6018
6106
|
async getById(_id) {
|
|
6019
|
-
|
|
6107
|
+
throw this.unavailableRead();
|
|
6020
6108
|
}
|
|
6021
6109
|
async loadIdCounter() {
|
|
6022
|
-
|
|
6110
|
+
throw this.unavailableRead();
|
|
6023
6111
|
}
|
|
6024
6112
|
async countAll() {
|
|
6025
|
-
|
|
6113
|
+
throw this.unavailableRead();
|
|
6026
6114
|
}
|
|
6027
6115
|
async listProjectIds() {
|
|
6028
|
-
|
|
6116
|
+
throw this.unavailableRead();
|
|
6029
6117
|
}
|
|
6030
6118
|
async searchLexical(_options) {
|
|
6031
|
-
|
|
6119
|
+
throw this.unavailableRead();
|
|
6032
6120
|
}
|
|
6033
6121
|
hasLexicalIndex() {
|
|
6034
6122
|
return false;
|
|
@@ -6037,25 +6125,25 @@ var init_obs_store = __esm({
|
|
|
6037
6125
|
return false;
|
|
6038
6126
|
}
|
|
6039
6127
|
async insert(_obs) {
|
|
6040
|
-
throw
|
|
6128
|
+
throw degradedWriteError("observations");
|
|
6041
6129
|
}
|
|
6042
6130
|
async update(_obs) {
|
|
6043
|
-
throw
|
|
6131
|
+
throw degradedWriteError("observations");
|
|
6044
6132
|
}
|
|
6045
6133
|
async remove(_id) {
|
|
6046
|
-
throw
|
|
6134
|
+
throw degradedWriteError("observations");
|
|
6047
6135
|
}
|
|
6048
6136
|
async bulkReplace(_obs) {
|
|
6049
|
-
throw
|
|
6137
|
+
throw degradedWriteError("observations");
|
|
6050
6138
|
}
|
|
6051
6139
|
async bulkRemoveByIds(_ids) {
|
|
6052
|
-
throw
|
|
6140
|
+
throw degradedWriteError("observations");
|
|
6053
6141
|
}
|
|
6054
6142
|
async saveIdCounter(_nextId) {
|
|
6055
|
-
throw
|
|
6143
|
+
throw degradedWriteError("observations");
|
|
6056
6144
|
}
|
|
6057
6145
|
async atomic(_fn) {
|
|
6058
|
-
throw
|
|
6146
|
+
throw degradedWriteError("observations");
|
|
6059
6147
|
}
|
|
6060
6148
|
async ensureFresh() {
|
|
6061
6149
|
return false;
|
|
@@ -6068,6 +6156,9 @@ var init_obs_store = __esm({
|
|
|
6068
6156
|
getBackendName() {
|
|
6069
6157
|
return "degraded";
|
|
6070
6158
|
}
|
|
6159
|
+
getBackendReason() {
|
|
6160
|
+
return this.reason;
|
|
6161
|
+
}
|
|
6071
6162
|
};
|
|
6072
6163
|
}
|
|
6073
6164
|
});
|
|
@@ -11716,7 +11807,8 @@ function clampCursor(afterId) {
|
|
|
11716
11807
|
}
|
|
11717
11808
|
async function loadConsolidationPage(projectId, options) {
|
|
11718
11809
|
const limit2 = clampBatchSize(options.limit);
|
|
11719
|
-
const
|
|
11810
|
+
const store = options.store ?? getObservationStore();
|
|
11811
|
+
const page = await store.loadByProject(projectId, {
|
|
11720
11812
|
status: "active",
|
|
11721
11813
|
afterId: clampCursor(options.afterId),
|
|
11722
11814
|
limit: limit2 + 1
|
|
@@ -11776,7 +11868,7 @@ async function findConsolidationCandidates(_projectDir, projectId, opts) {
|
|
|
11776
11868
|
return findClusters(page.observations, threshold);
|
|
11777
11869
|
}
|
|
11778
11870
|
async function executeConsolidation(_projectDir, projectId, opts) {
|
|
11779
|
-
const store = getObservationStore();
|
|
11871
|
+
const store = opts?.store ?? getObservationStore();
|
|
11780
11872
|
const page = await loadConsolidationPage(projectId, opts ?? {});
|
|
11781
11873
|
const clusters = findClusters(page.observations, opts?.threshold ?? DEFAULT_SIMILARITY_THRESHOLD);
|
|
11782
11874
|
if (clusters.length === 0) {
|
|
@@ -57602,10 +57694,10 @@ var require_retry3 = __commonJS({
|
|
|
57602
57694
|
if (!await shouldRetryFn(err)) {
|
|
57603
57695
|
return { shouldRetry: false, config: err.config };
|
|
57604
57696
|
}
|
|
57605
|
-
const
|
|
57697
|
+
const delay3 = getNextRetryDelay(config);
|
|
57606
57698
|
err.config.retryConfig.currentRetryAttempt += 1;
|
|
57607
|
-
const backoff = config.retryBackoff ? config.retryBackoff(err,
|
|
57608
|
-
setTimeout(resolve2,
|
|
57699
|
+
const backoff = config.retryBackoff ? config.retryBackoff(err, delay3) : new Promise((resolve2) => {
|
|
57700
|
+
setTimeout(resolve2, delay3);
|
|
57609
57701
|
});
|
|
57610
57702
|
if (config.onRetryAttempt) {
|
|
57611
57703
|
await config.onRetryAttempt(err);
|
|
@@ -99391,7 +99483,7 @@ async function retryBackoff(fn, strategy) {
|
|
|
99391
99483
|
retryInterval = initialInterval * Math.pow(x2, exponent) + Math.random() * 1e3;
|
|
99392
99484
|
}
|
|
99393
99485
|
const d = Math.min(retryInterval, maxInterval);
|
|
99394
|
-
await
|
|
99486
|
+
await delay2(d);
|
|
99395
99487
|
x2++;
|
|
99396
99488
|
}
|
|
99397
99489
|
}
|
|
@@ -99412,8 +99504,8 @@ function retryIntervalFromResponse(res) {
|
|
|
99412
99504
|
}
|
|
99413
99505
|
return 0;
|
|
99414
99506
|
}
|
|
99415
|
-
async function
|
|
99416
|
-
return new Promise((resolve2) => setTimeout(resolve2,
|
|
99507
|
+
async function delay2(delay3) {
|
|
99508
|
+
return new Promise((resolve2) => setTimeout(resolve2, delay3));
|
|
99417
99509
|
}
|
|
99418
99510
|
var defaultBackoff, PermanentError, TemporaryError, codeRangeRE2;
|
|
99419
99511
|
var init_retries = __esm({
|
|
@@ -148353,7 +148445,7 @@ __export(lite_provider_exports, {
|
|
|
148353
148445
|
refreshProjectLite: () => refreshProjectLite
|
|
148354
148446
|
});
|
|
148355
148447
|
import { createHash as createHash10 } from "crypto";
|
|
148356
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
148448
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
148357
148449
|
import { join as join4, relative as relative2 } from "path";
|
|
148358
148450
|
function hashText2(text2) {
|
|
148359
148451
|
return createHash10("sha256").update(text2).digest("hex");
|
|
@@ -148370,6 +148462,9 @@ function resolveMaxFileBytes(value) {
|
|
|
148370
148462
|
if (!Number.isFinite(value)) return DEFAULT_CODEGRAPH_MAX_FILE_BYTES;
|
|
148371
148463
|
return Math.max(1, Math.floor(value));
|
|
148372
148464
|
}
|
|
148465
|
+
function isPythonVirtualenvDir(dir) {
|
|
148466
|
+
return existsSync11(join4(dir, "pyvenv.cfg"));
|
|
148467
|
+
}
|
|
148373
148468
|
function walk(root, exclude, maxFiles) {
|
|
148374
148469
|
const out = [];
|
|
148375
148470
|
const visit = (dir) => {
|
|
@@ -148387,6 +148482,7 @@ function walk(root, exclude, maxFiles) {
|
|
|
148387
148482
|
if (entry.isDirectory()) {
|
|
148388
148483
|
if (entry.name === ".git" || entry.name === ".worktrees" || entry.name === ".tmp" || entry.name === "node_modules") continue;
|
|
148389
148484
|
if (rel === ".claude/worktrees" || rel.startsWith(".claude/worktrees/")) continue;
|
|
148485
|
+
if (isPythonVirtualenvDir(abs)) continue;
|
|
148390
148486
|
visit(abs);
|
|
148391
148487
|
if (out.length >= maxFiles) return;
|
|
148392
148488
|
continue;
|
|
@@ -151749,7 +151845,7 @@ __export(vector_backfill_runner_exports, {
|
|
|
151749
151845
|
import { spawn as spawn3 } from "child_process";
|
|
151750
151846
|
import {
|
|
151751
151847
|
closeSync,
|
|
151752
|
-
existsSync as
|
|
151848
|
+
existsSync as existsSync12,
|
|
151753
151849
|
mkdirSync as mkdirSync2,
|
|
151754
151850
|
openSync,
|
|
151755
151851
|
readFileSync as readFileSync9,
|
|
@@ -151877,7 +151973,7 @@ function releaseVectorBackfillLock(lockPath = process.env.MEMORIX_VECTOR_BACKFIL
|
|
|
151877
151973
|
}
|
|
151878
151974
|
function launchDetachedVectorBackfill(request, options = {}) {
|
|
151879
151975
|
const runnerPath = options.runnerPath ?? resolveVectorBackfillRunnerPath();
|
|
151880
|
-
const exists = options.exists ??
|
|
151976
|
+
const exists = options.exists ?? existsSync12;
|
|
151881
151977
|
if (!exists(runnerPath)) return false;
|
|
151882
151978
|
const lock = acquireVectorBackfillLock(request.dataDir, request.projectId, options.lockPath);
|
|
151883
151979
|
if (!lock) return false;
|
|
@@ -792,6 +792,21 @@ not a memory-loss condition. If Memorix instead reports that SQLite is
|
|
|
792
792
|
unavailable, confirm the installed Node version satisfies the package engine
|
|
793
793
|
and include the exact error when filing an issue.
|
|
794
794
|
|
|
795
|
+
## 10. Does `doctor` report degraded or unavailable storage?
|
|
796
|
+
|
|
797
|
+
Treat this as a real storage failure, not as an empty project. The active
|
|
798
|
+
SQLite backend is the source of truth for observations, sessions, and
|
|
799
|
+
mini-skills; derived search indexes may fall back, but they must not hide a
|
|
800
|
+
database failure.
|
|
801
|
+
|
|
802
|
+
Run `memorix doctor --json` and keep the reported error. A transient
|
|
803
|
+
cross-process `SQLITE_BUSY` is retried automatically. A persistent runtime
|
|
804
|
+
error is surfaced instead of returning empty memory. Stop or pause a long
|
|
805
|
+
maintenance operation and retry after the database is writable. For several
|
|
806
|
+
agent harnesses sharing one local store, prefer one `memorix background start`
|
|
807
|
+
control plane so writes are serialized by one owner; WAL does not allow
|
|
808
|
+
multiple simultaneous writers.
|
|
809
|
+
|
|
795
810
|
---
|
|
796
811
|
|
|
797
812
|
## 11. What Not to Do
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -150,6 +150,29 @@ Responsibilities:
|
|
|
150
150
|
- curate long-lived episodes, facts, and procedures with source evidence,
|
|
151
151
|
qualification, approval, archival, and supersession
|
|
152
152
|
|
|
153
|
+
### Persistence Contract
|
|
154
|
+
|
|
155
|
+
SQLite is the canonical local record for observations, sessions, mini-skills,
|
|
156
|
+
knowledge, coordination, and CodeGraph metadata. Search indexes and in-memory
|
|
157
|
+
structures are derived views and may be rebuilt; they must never decide that a
|
|
158
|
+
failed database is an empty project.
|
|
159
|
+
|
|
160
|
+
All SQLite-backed stores share the same startup semantics:
|
|
161
|
+
|
|
162
|
+
1. A missing SQLite runtime is reported as an explicit degraded backend.
|
|
163
|
+
2. Cross-process `SQLITE_BUSY` contention is retried with bounded backoff.
|
|
164
|
+
3. Persistent runtime failures are thrown and surfaced to the operator.
|
|
165
|
+
4. Degraded business reads and writes fail clearly instead of returning empty
|
|
166
|
+
data or a fake success.
|
|
167
|
+
5. A connection that fails during schema/index initialization is closed before
|
|
168
|
+
a retry, so a failed startup cannot retain a file lock.
|
|
169
|
+
|
|
170
|
+
WAL improves reader/writer overlap but still permits one writer per database
|
|
171
|
+
file. Long maintenance work is therefore isolated and paged, while write
|
|
172
|
+
transactions stay short. Multi-agent installations should prefer one local
|
|
173
|
+
HTTP control plane as the database owner; stdio remains useful for a single
|
|
174
|
+
agent or a deliberately isolated project store.
|
|
175
|
+
|
|
153
176
|
### Intelligence and Quality Layer
|
|
154
177
|
|
|
155
178
|
This layer improves memory quality and retrieval quality.
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -13,7 +13,7 @@ Memorix is a TypeScript project built around:
|
|
|
13
13
|
|
|
14
14
|
## Current Development Baseline
|
|
15
15
|
|
|
16
|
-
The current release line is **1.9.
|
|
16
|
+
The current release line is **1.9.3** and package metadata is kept synchronized
|
|
17
17
|
with the root package and MCP Registry manifest.
|
|
18
18
|
|
|
19
19
|
Contributors should assume the following areas are part of the current release line:
|
package/docs/INTEGRATIONS.md
CHANGED
|
@@ -122,6 +122,10 @@ If an agent only needs a raw stdio MCP entry:
|
|
|
122
122
|
|
|
123
123
|
Avoid `npx` in persistent MCP configs. Use the globally installed `memorix` binary so startup is predictable.
|
|
124
124
|
|
|
125
|
+
For npm-based MCP clients or registry testers, the complete package command is
|
|
126
|
+
`npx -y memorix serve`. Do not omit `serve`: bare `memorix` is the interactive
|
|
127
|
+
memcode entry in a human terminal.
|
|
128
|
+
|
|
125
129
|
For HTTP mode:
|
|
126
130
|
|
|
127
131
|
```bash
|
package/docs/PERFORMANCE.md
CHANGED
|
@@ -43,6 +43,34 @@ user is allowed to remember.
|
|
|
43
43
|
memory, and the V8 heap limit so a real saturation report can distinguish
|
|
44
44
|
JavaScript retention from native/model memory.
|
|
45
45
|
|
|
46
|
+
## SQLite Storage Reliability Contract
|
|
47
|
+
|
|
48
|
+
SQLite is the durable source of truth. FTS5, Orama, vector indexes, and other
|
|
49
|
+
search structures are derived working indexes; an index failure must never turn
|
|
50
|
+
the durable store into an empty project.
|
|
51
|
+
|
|
52
|
+
- A missing SQLite runtime may use the explicit read-only degraded backend.
|
|
53
|
+
- A runtime lock (`SQLITE_BUSY`) is retried with bounded backoff. A persistent
|
|
54
|
+
runtime error is surfaced to the caller; it is never converted into a fake
|
|
55
|
+
successful read or write.
|
|
56
|
+
- Observations, sessions, and mini-skills follow the same initialization rule.
|
|
57
|
+
Degraded writes fail loudly so an agent cannot believe a session or promotion
|
|
58
|
+
was saved when it was not.
|
|
59
|
+
- SQLite connections configure `busy_timeout` before WAL setup. A connection
|
|
60
|
+
that fails during schema or index initialization is closed before retrying,
|
|
61
|
+
so a failed startup cannot leave an orphan lock behind.
|
|
62
|
+
- WAL permits concurrent readers but still has one writer. Maintenance and
|
|
63
|
+
migration work must use short, bounded transactions and must not perform
|
|
64
|
+
network or LLM work while holding a write transaction. For several agent
|
|
65
|
+
harnesses, prefer one local `serve-http` control plane so writes are queued
|
|
66
|
+
by one owner instead of many stdio processes competing for one file.
|
|
67
|
+
- `memorix doctor` reports the active backend. It reports read-only or
|
|
68
|
+
unavailable storage as an error and never presents the backend's synthetic
|
|
69
|
+
zero count as an empty project.
|
|
70
|
+
|
|
71
|
+
This is an integrity boundary, not a corpus quota. The product does not lower
|
|
72
|
+
the number of durable memories to hide lock contention or resource problems.
|
|
73
|
+
|
|
46
74
|
## Retrieval Profiles
|
|
47
75
|
|
|
48
76
|
| Profile | Default? | Network work | Best fit |
|
|
@@ -163,6 +191,28 @@ Results are machine-specific and should be compared on the same OS and Node
|
|
|
163
191
|
version. The release gate is an integrity and regression check, not a public
|
|
164
192
|
latency guarantee.
|
|
165
193
|
|
|
194
|
+
### 1.9.3 Release Evidence (2026-09-14)
|
|
195
|
+
|
|
196
|
+
The 40,000-record gate passed on Windows with Node `v22.22.3`, SQLite-backed
|
|
197
|
+
storage, and embeddings disabled:
|
|
198
|
+
|
|
199
|
+
| Measurement | Result |
|
|
200
|
+
| --- | ---: |
|
|
201
|
+
| Seed and index | 48.9 s |
|
|
202
|
+
| Steady-state write p50 / p95 | 0.78 / 1.49 ms |
|
|
203
|
+
| SDK lexical search | 22.8 ms |
|
|
204
|
+
| HTTP ready | 1.87 s |
|
|
205
|
+
| MCP initialize / bind | 116 / 229 ms |
|
|
206
|
+
| MCP context / search / store | 605 / 322 / 595 ms |
|
|
207
|
+
| Hook write | 3.36 s |
|
|
208
|
+
| SDK reopen | 134 ms |
|
|
209
|
+
| Service peak RSS | 412.7 MiB |
|
|
210
|
+
| Durable records after MCP and hook writes | 40,002 |
|
|
211
|
+
|
|
212
|
+
The planted record was searchable, the MCP and hook writes were durable, and
|
|
213
|
+
the reopened SDK observed the project-visible records. These values are local
|
|
214
|
+
acceptance evidence, not cross-machine performance promises.
|
|
215
|
+
|
|
166
216
|
## 1.9.1 Large-Scale Retrieval Contract
|
|
167
217
|
|
|
168
218
|
The 1.9.1 line improves retrieval latency without shrinking the user's durable
|
package/docs/README.md
CHANGED
|
@@ -125,7 +125,7 @@ Historical/deep-reference documents may describe older designs. If they conflict
|
|
|
125
125
|
|
|
126
126
|
## Current Product Line
|
|
127
127
|
|
|
128
|
-
The current product line is **1.9.
|
|
128
|
+
The current product line is **1.9.3**. Multi-device store sync is opt-in and
|
|
129
129
|
keeps local SQLite canonical; existing users do not upload anything until they
|
|
130
130
|
configure a relay provider explicitly.
|
|
131
131
|
The authoritative acceptance contract for the current implementation is
|
package/docs/SETUP.md
CHANGED
|
@@ -170,6 +170,11 @@ Generic stdio MCP config:
|
|
|
170
170
|
|
|
171
171
|
Avoid `npx` in persistent MCP configs. Use the globally installed `memorix` binary so startup is predictable.
|
|
172
172
|
|
|
173
|
+
When an npm-based MCP client or registry tester must launch the package
|
|
174
|
+
directly, use `npx -y memorix serve`. The explicit `serve` argument selects
|
|
175
|
+
the stdio MCP server; a human running bare `memorix` in a terminal gets the
|
|
176
|
+
bundled memcode agent instead.
|
|
177
|
+
|
|
173
178
|
### Option C: memcode terminal agent
|
|
174
179
|
|
|
175
180
|
```bash
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memorix",
|
|
3
3
|
"mcpName": "io.github.AVIDS2/memorix",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.3",
|
|
5
5
|
"description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"benchmark:codegraph:scale": "node scripts/benchmark-codegraph-scale.mjs",
|
|
78
78
|
"benchmark:retrieval": "npm run build && node scripts/benchmark-retrieval.mjs",
|
|
79
79
|
"gate:large-store": "npm run build && node scripts/large-store-gate.mjs",
|
|
80
|
-
"generate:star-history": "node scripts/generate-star-history.mjs",
|
|
81
80
|
"sync:mcp-registry": "node scripts/sync-mcp-registry-manifest.mjs",
|
|
82
81
|
"check:mcp-registry": "node scripts/check-mcp-registry-manifest.mjs",
|
|
83
82
|
"sync:plugin-release": "node scripts/sync-plugin-release-manifests.mjs",
|