agent-working-memory 0.8.8 → 0.9.0
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/README.md +165 -46
- package/dist/api/routes.js +7 -7
- package/dist/cli/migrate.js +29 -29
- package/dist/cli.js +104 -104
- package/dist/coordination/circuit-breaker.js +23 -23
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +17 -0
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/engine/activation.d.ts +28 -0
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +341 -11
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/connections.d.ts +12 -0
- package/dist/engine/connections.d.ts.map +1 -1
- package/dist/engine/connections.js +95 -0
- package/dist/engine/connections.js.map +1 -1
- package/dist/mcp.js +90 -90
- package/dist/storage/pglite-schema.js +143 -143
- package/dist/storage/pglite.js +138 -138
- package/dist/types/engram.d.ts +1 -0
- package/dist/types/engram.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/index.ts +3 -3
- package/src/cli/migrate.ts +307 -307
- package/src/coordination/circuit-breaker.ts +83 -83
- package/src/coordination/failure-modes.ts +50 -50
- package/src/core/decay.ts +63 -63
- package/src/core/embeddings.ts +110 -110
- package/src/core/index.ts +5 -5
- package/src/core/logger.ts +36 -36
- package/src/core/ml-worker-entry.ts +194 -194
- package/src/core/ml-worker.ts +281 -281
- package/src/core/query-expander.ts +122 -122
- package/src/core/reranker.ts +119 -119
- package/src/core/write-pipeline.ts +15 -0
- package/src/engine/activation.ts +328 -11
- package/src/engine/confidence.ts +120 -120
- package/src/engine/connections.ts +94 -0
- package/src/engine/consolidation-scheduler.ts +242 -242
- package/src/engine/eval.ts +102 -102
- package/src/engine/eviction.ts +101 -101
- package/src/engine/index.ts +8 -8
- package/src/engine/retraction.ts +366 -366
- package/src/engine/staging.ts +74 -74
- package/src/storage/factory.ts +147 -147
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +166 -166
- package/src/storage/pglite.ts +1363 -1363
- package/src/storage/store.ts +80 -80
- package/src/types/agent.ts +67 -67
- package/src/types/checkpoint.ts +46 -46
- package/src/types/engram.ts +1 -0
- package/src/types/eval.ts +100 -100
- package/src/types/index.ts +6 -6
package/src/engine/staging.ts
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
/**
|
|
4
|
-
* Staging Buffer — weak signal handler.
|
|
5
|
-
*
|
|
6
|
-
* Observations that don't meet the salience threshold for active memory
|
|
7
|
-
* go to staging. The staging buffer periodically:
|
|
8
|
-
* 1. Checks staged engrams against active memory for resonance
|
|
9
|
-
* 2. Promotes resonant engrams to active
|
|
10
|
-
* 3. Discards expired engrams that never resonated
|
|
11
|
-
*
|
|
12
|
-
* Modeled on hippocampal consolidation — provisional encoding
|
|
13
|
-
* that only persists if reactivated.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type { IEngramStore as EngramStore } from '../storage/store.js';
|
|
17
|
-
import type { ActivationEngine } from './activation.js';
|
|
18
|
-
|
|
19
|
-
export class StagingBuffer {
|
|
20
|
-
private store: EngramStore;
|
|
21
|
-
private engine: ActivationEngine;
|
|
22
|
-
private checkInterval: ReturnType<typeof setInterval> | null = null;
|
|
23
|
-
|
|
24
|
-
constructor(store: EngramStore, engine: ActivationEngine) {
|
|
25
|
-
this.store = store;
|
|
26
|
-
this.engine = engine;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Start the periodic staging check.
|
|
31
|
-
*/
|
|
32
|
-
start(intervalMs: number = 60_000): void {
|
|
33
|
-
this.checkInterval = setInterval(() => this.sweep(), intervalMs);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
stop(): void {
|
|
37
|
-
if (this.checkInterval) {
|
|
38
|
-
clearInterval(this.checkInterval);
|
|
39
|
-
this.checkInterval = null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Sweep staged engrams: promote or discard.
|
|
45
|
-
*/
|
|
46
|
-
async sweep(): Promise<{ promoted: string[]; discarded: string[] }> {
|
|
47
|
-
const promoted: string[] = [];
|
|
48
|
-
const discarded: string[] = [];
|
|
49
|
-
|
|
50
|
-
const expired = await this.store.getExpiredStaging();
|
|
51
|
-
for (const engram of expired) {
|
|
52
|
-
// Check if this engram resonates with active memory
|
|
53
|
-
const results = await this.engine.activate({
|
|
54
|
-
agentId: engram.agentId,
|
|
55
|
-
context: `${engram.concept} ${engram.content}`,
|
|
56
|
-
limit: 3,
|
|
57
|
-
minScore: 0.3,
|
|
58
|
-
internal: true,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (results.length > 0) {
|
|
62
|
-
// Resonance found — promote to active
|
|
63
|
-
await this.store.updateStage(engram.id, 'active');
|
|
64
|
-
promoted.push(engram.id);
|
|
65
|
-
} else {
|
|
66
|
-
// No resonance — discard
|
|
67
|
-
await this.store.deleteEngram(engram.id);
|
|
68
|
-
discarded.push(engram.id);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return { promoted, discarded };
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Staging Buffer — weak signal handler.
|
|
5
|
+
*
|
|
6
|
+
* Observations that don't meet the salience threshold for active memory
|
|
7
|
+
* go to staging. The staging buffer periodically:
|
|
8
|
+
* 1. Checks staged engrams against active memory for resonance
|
|
9
|
+
* 2. Promotes resonant engrams to active
|
|
10
|
+
* 3. Discards expired engrams that never resonated
|
|
11
|
+
*
|
|
12
|
+
* Modeled on hippocampal consolidation — provisional encoding
|
|
13
|
+
* that only persists if reactivated.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { IEngramStore as EngramStore } from '../storage/store.js';
|
|
17
|
+
import type { ActivationEngine } from './activation.js';
|
|
18
|
+
|
|
19
|
+
export class StagingBuffer {
|
|
20
|
+
private store: EngramStore;
|
|
21
|
+
private engine: ActivationEngine;
|
|
22
|
+
private checkInterval: ReturnType<typeof setInterval> | null = null;
|
|
23
|
+
|
|
24
|
+
constructor(store: EngramStore, engine: ActivationEngine) {
|
|
25
|
+
this.store = store;
|
|
26
|
+
this.engine = engine;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Start the periodic staging check.
|
|
31
|
+
*/
|
|
32
|
+
start(intervalMs: number = 60_000): void {
|
|
33
|
+
this.checkInterval = setInterval(() => this.sweep(), intervalMs);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
stop(): void {
|
|
37
|
+
if (this.checkInterval) {
|
|
38
|
+
clearInterval(this.checkInterval);
|
|
39
|
+
this.checkInterval = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sweep staged engrams: promote or discard.
|
|
45
|
+
*/
|
|
46
|
+
async sweep(): Promise<{ promoted: string[]; discarded: string[] }> {
|
|
47
|
+
const promoted: string[] = [];
|
|
48
|
+
const discarded: string[] = [];
|
|
49
|
+
|
|
50
|
+
const expired = await this.store.getExpiredStaging();
|
|
51
|
+
for (const engram of expired) {
|
|
52
|
+
// Check if this engram resonates with active memory
|
|
53
|
+
const results = await this.engine.activate({
|
|
54
|
+
agentId: engram.agentId,
|
|
55
|
+
context: `${engram.concept} ${engram.content}`,
|
|
56
|
+
limit: 3,
|
|
57
|
+
minScore: 0.3,
|
|
58
|
+
internal: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (results.length > 0) {
|
|
62
|
+
// Resonance found — promote to active
|
|
63
|
+
await this.store.updateStage(engram.id, 'active');
|
|
64
|
+
promoted.push(engram.id);
|
|
65
|
+
} else {
|
|
66
|
+
// No resonance — discard
|
|
67
|
+
await this.store.deleteEngram(engram.id);
|
|
68
|
+
discarded.push(engram.id);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { promoted, discarded };
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/storage/factory.ts
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
/**
|
|
4
|
-
* Storage backend factory.
|
|
5
|
-
*
|
|
6
|
-
* Picks between SQLite (mature, full features) and PGlite (portable, async,
|
|
7
|
-
* pgvector). Both backends satisfy the IEngramStore contract — the cognitive
|
|
8
|
-
* engines work with either through `await`.
|
|
9
|
-
*
|
|
10
|
-
* AWM_STORE_BACKEND=sqlite — better-sqlite3 + FTS5 + BLOB embeddings
|
|
11
|
-
* AWM_STORE_BACKEND=pglite — PGlite + pgvector + tsvector
|
|
12
|
-
*
|
|
13
|
-
* **Backend selection (v0.8.5):** auto-detect on disk when the env var is
|
|
14
|
-
* unset, so upgrading users on existing `memory.db` files keep working
|
|
15
|
-
* without setting anything. Order of precedence:
|
|
16
|
-
*
|
|
17
|
-
* 1. `AWM_STORE_BACKEND` env var (explicit override — always wins).
|
|
18
|
-
* 2. Auto-detect: if `memory-pglite/` exists on disk → pglite.
|
|
19
|
-
* 3. Auto-detect: else if `memory.db` exists on disk → sqlite.
|
|
20
|
-
* 4. Fall back to sqlite (the default for fresh installs).
|
|
21
|
-
*
|
|
22
|
-
* If `AWM_STORE_BACKEND` is set but the on-disk state disagrees (e.g. env
|
|
23
|
-
* says pglite but only `memory.db` is present), `openStore` prints a one-line
|
|
24
|
-
* warning suggesting `awm migrate`. The env var still wins — we never
|
|
25
|
-
* silently switch backends behind the user's back.
|
|
26
|
-
*
|
|
27
|
-
* The `AWM_DB_PATH` env var carries the file path (SQLite) or directory
|
|
28
|
-
* path (PGlite). When unset, default is `memory.db` (sqlite) or
|
|
29
|
-
* `memory-pglite/` (pglite).
|
|
30
|
-
*
|
|
31
|
-
* **Feature parity gaps** (see `docs/pglite-feature-parity.md`):
|
|
32
|
-
* a few legacy code paths reach for SQLite-specific methods
|
|
33
|
-
* (coordination plugin needs `store.getDb()`, slim-cache warming, hot
|
|
34
|
-
* backups). PGlite-backed AWM is fully functional for the cognitive
|
|
35
|
-
* engines (write, recall, consolidation, retraction, eviction). Opt-in
|
|
36
|
-
* `AWM_STORE_BACKEND=pglite` skips the SQLite-only extras.
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import { existsSync, statSync } from 'node:fs';
|
|
40
|
-
import type { IEngramStore } from './store.js';
|
|
41
|
-
|
|
42
|
-
export type StoreBackend = 'sqlite' | 'pglite';
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Auto-detect the backend from on-disk state. Used when `AWM_STORE_BACKEND`
|
|
46
|
-
* is unset. Looks in the current working directory; honors `AWM_DB_PATH`
|
|
47
|
-
* if it points at a known shape.
|
|
48
|
-
*/
|
|
49
|
-
function detectBackendFromDisk(): StoreBackend | null {
|
|
50
|
-
// If AWM_DB_PATH is set, infer from its shape.
|
|
51
|
-
const explicitPath = process.env.AWM_DB_PATH;
|
|
52
|
-
if (explicitPath && existsSync(explicitPath)) {
|
|
53
|
-
try {
|
|
54
|
-
const stat = statSync(explicitPath);
|
|
55
|
-
if (stat.isDirectory()) return 'pglite'; // PGlite uses a directory
|
|
56
|
-
if (stat.isFile()) return 'sqlite'; // SQLite is a single file
|
|
57
|
-
} catch { /* fall through */ }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Otherwise look for the conventional defaults in cwd.
|
|
61
|
-
// PGlite directory wins over SQLite file when both exist — assume the
|
|
62
|
-
// user actively migrated and forgot to set the env var. We warn below.
|
|
63
|
-
if (existsSync('memory-pglite')) return 'pglite';
|
|
64
|
-
if (existsSync('memory.db')) return 'sqlite';
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function getConfiguredBackend(): StoreBackend {
|
|
69
|
-
const raw = process.env.AWM_STORE_BACKEND;
|
|
70
|
-
if (raw !== undefined && raw !== '') {
|
|
71
|
-
const normalized = raw.toLowerCase();
|
|
72
|
-
if (normalized === 'pglite') return 'pglite';
|
|
73
|
-
if (normalized === 'sqlite') return 'sqlite';
|
|
74
|
-
console.warn(`Unknown AWM_STORE_BACKEND=${raw}; falling back to sqlite`);
|
|
75
|
-
return 'sqlite';
|
|
76
|
-
}
|
|
77
|
-
// Env unset → auto-detect from on-disk state.
|
|
78
|
-
const detected = detectBackendFromDisk();
|
|
79
|
-
return detected ?? 'sqlite';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function getConfiguredPath(): string {
|
|
83
|
-
if (process.env.AWM_DB_PATH) return process.env.AWM_DB_PATH;
|
|
84
|
-
return getConfiguredBackend() === 'pglite' ? 'memory-pglite' : 'memory.db';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Print a one-line warning to stderr when the configured backend disagrees
|
|
89
|
-
* with what's actually on disk. We never fail or silently switch backends
|
|
90
|
-
* — the explicit configuration always wins. The warning helps users notice
|
|
91
|
-
* that they may have stranded data on the other backend.
|
|
92
|
-
*/
|
|
93
|
-
function warnIfBackendDisagreesWithDisk(backend: StoreBackend, path: string): void {
|
|
94
|
-
if (process.env.AWM_SUPPRESS_BACKEND_WARNINGS === '1') return;
|
|
95
|
-
|
|
96
|
-
// Only warn when env var was explicit (auto-detect already follows disk).
|
|
97
|
-
if (!process.env.AWM_STORE_BACKEND) return;
|
|
98
|
-
|
|
99
|
-
const otherPath = backend === 'pglite' ? 'memory.db' : 'memory-pglite';
|
|
100
|
-
const otherBackend = backend === 'pglite' ? 'sqlite' : 'pglite';
|
|
101
|
-
|
|
102
|
-
// The "real" check: configured target doesn't exist yet (fresh / empty) AND
|
|
103
|
-
// the other backend's conventional file exists with data. Likely stranded.
|
|
104
|
-
const configuredExists = existsSync(path);
|
|
105
|
-
const otherExists = existsSync(otherPath);
|
|
106
|
-
|
|
107
|
-
if (!configuredExists && otherExists) {
|
|
108
|
-
console.warn(
|
|
109
|
-
`[awm] AWM_STORE_BACKEND=${backend} but no data at "${path}". ` +
|
|
110
|
-
`Existing ${otherBackend} data at "${otherPath}" — run \`awm migrate\` ` +
|
|
111
|
-
`to convert it to ${backend}, or unset AWM_STORE_BACKEND to use ${otherBackend}. ` +
|
|
112
|
-
`Suppress with AWM_SUPPRESS_BACKEND_WARNINGS=1.`,
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Open a store using the env-configured (or auto-detected) backend and path.
|
|
119
|
-
* Returns the concrete store class (cast to IEngramStore at call sites
|
|
120
|
-
* that need the async contract; SQLite callers can keep the concrete class
|
|
121
|
-
* to retain access to SQLite-specific methods like getDb()).
|
|
122
|
-
*
|
|
123
|
-
* Never fails on backend/disk mismatch — only warns. Existing users
|
|
124
|
-
* upgrading without setting env vars get auto-detect; users on the legacy
|
|
125
|
-
* `memory.db` keep working without touching anything.
|
|
126
|
-
*/
|
|
127
|
-
export async function openStore(): Promise<{
|
|
128
|
-
store: IEngramStore;
|
|
129
|
-
backend: StoreBackend;
|
|
130
|
-
path: string;
|
|
131
|
-
}> {
|
|
132
|
-
const backend = getConfiguredBackend();
|
|
133
|
-
const path = getConfiguredPath();
|
|
134
|
-
|
|
135
|
-
warnIfBackendDisagreesWithDisk(backend, path);
|
|
136
|
-
|
|
137
|
-
if (backend === 'pglite') {
|
|
138
|
-
const { PGliteEngramStore } = await import('./pglite.js');
|
|
139
|
-
const store = new PGliteEngramStore(path);
|
|
140
|
-
await store.ready();
|
|
141
|
-
return { store: store as unknown as IEngramStore, backend, path };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const { EngramStore } = await import('./sqlite.js');
|
|
145
|
-
const store = new EngramStore(path);
|
|
146
|
-
return { store: store as unknown as IEngramStore, backend, path };
|
|
147
|
-
}
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Storage backend factory.
|
|
5
|
+
*
|
|
6
|
+
* Picks between SQLite (mature, full features) and PGlite (portable, async,
|
|
7
|
+
* pgvector). Both backends satisfy the IEngramStore contract — the cognitive
|
|
8
|
+
* engines work with either through `await`.
|
|
9
|
+
*
|
|
10
|
+
* AWM_STORE_BACKEND=sqlite — better-sqlite3 + FTS5 + BLOB embeddings
|
|
11
|
+
* AWM_STORE_BACKEND=pglite — PGlite + pgvector + tsvector
|
|
12
|
+
*
|
|
13
|
+
* **Backend selection (v0.8.5):** auto-detect on disk when the env var is
|
|
14
|
+
* unset, so upgrading users on existing `memory.db` files keep working
|
|
15
|
+
* without setting anything. Order of precedence:
|
|
16
|
+
*
|
|
17
|
+
* 1. `AWM_STORE_BACKEND` env var (explicit override — always wins).
|
|
18
|
+
* 2. Auto-detect: if `memory-pglite/` exists on disk → pglite.
|
|
19
|
+
* 3. Auto-detect: else if `memory.db` exists on disk → sqlite.
|
|
20
|
+
* 4. Fall back to sqlite (the default for fresh installs).
|
|
21
|
+
*
|
|
22
|
+
* If `AWM_STORE_BACKEND` is set but the on-disk state disagrees (e.g. env
|
|
23
|
+
* says pglite but only `memory.db` is present), `openStore` prints a one-line
|
|
24
|
+
* warning suggesting `awm migrate`. The env var still wins — we never
|
|
25
|
+
* silently switch backends behind the user's back.
|
|
26
|
+
*
|
|
27
|
+
* The `AWM_DB_PATH` env var carries the file path (SQLite) or directory
|
|
28
|
+
* path (PGlite). When unset, default is `memory.db` (sqlite) or
|
|
29
|
+
* `memory-pglite/` (pglite).
|
|
30
|
+
*
|
|
31
|
+
* **Feature parity gaps** (see `docs/pglite-feature-parity.md`):
|
|
32
|
+
* a few legacy code paths reach for SQLite-specific methods
|
|
33
|
+
* (coordination plugin needs `store.getDb()`, slim-cache warming, hot
|
|
34
|
+
* backups). PGlite-backed AWM is fully functional for the cognitive
|
|
35
|
+
* engines (write, recall, consolidation, retraction, eviction). Opt-in
|
|
36
|
+
* `AWM_STORE_BACKEND=pglite` skips the SQLite-only extras.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { existsSync, statSync } from 'node:fs';
|
|
40
|
+
import type { IEngramStore } from './store.js';
|
|
41
|
+
|
|
42
|
+
export type StoreBackend = 'sqlite' | 'pglite';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Auto-detect the backend from on-disk state. Used when `AWM_STORE_BACKEND`
|
|
46
|
+
* is unset. Looks in the current working directory; honors `AWM_DB_PATH`
|
|
47
|
+
* if it points at a known shape.
|
|
48
|
+
*/
|
|
49
|
+
function detectBackendFromDisk(): StoreBackend | null {
|
|
50
|
+
// If AWM_DB_PATH is set, infer from its shape.
|
|
51
|
+
const explicitPath = process.env.AWM_DB_PATH;
|
|
52
|
+
if (explicitPath && existsSync(explicitPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const stat = statSync(explicitPath);
|
|
55
|
+
if (stat.isDirectory()) return 'pglite'; // PGlite uses a directory
|
|
56
|
+
if (stat.isFile()) return 'sqlite'; // SQLite is a single file
|
|
57
|
+
} catch { /* fall through */ }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Otherwise look for the conventional defaults in cwd.
|
|
61
|
+
// PGlite directory wins over SQLite file when both exist — assume the
|
|
62
|
+
// user actively migrated and forgot to set the env var. We warn below.
|
|
63
|
+
if (existsSync('memory-pglite')) return 'pglite';
|
|
64
|
+
if (existsSync('memory.db')) return 'sqlite';
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getConfiguredBackend(): StoreBackend {
|
|
69
|
+
const raw = process.env.AWM_STORE_BACKEND;
|
|
70
|
+
if (raw !== undefined && raw !== '') {
|
|
71
|
+
const normalized = raw.toLowerCase();
|
|
72
|
+
if (normalized === 'pglite') return 'pglite';
|
|
73
|
+
if (normalized === 'sqlite') return 'sqlite';
|
|
74
|
+
console.warn(`Unknown AWM_STORE_BACKEND=${raw}; falling back to sqlite`);
|
|
75
|
+
return 'sqlite';
|
|
76
|
+
}
|
|
77
|
+
// Env unset → auto-detect from on-disk state.
|
|
78
|
+
const detected = detectBackendFromDisk();
|
|
79
|
+
return detected ?? 'sqlite';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getConfiguredPath(): string {
|
|
83
|
+
if (process.env.AWM_DB_PATH) return process.env.AWM_DB_PATH;
|
|
84
|
+
return getConfiguredBackend() === 'pglite' ? 'memory-pglite' : 'memory.db';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Print a one-line warning to stderr when the configured backend disagrees
|
|
89
|
+
* with what's actually on disk. We never fail or silently switch backends
|
|
90
|
+
* — the explicit configuration always wins. The warning helps users notice
|
|
91
|
+
* that they may have stranded data on the other backend.
|
|
92
|
+
*/
|
|
93
|
+
function warnIfBackendDisagreesWithDisk(backend: StoreBackend, path: string): void {
|
|
94
|
+
if (process.env.AWM_SUPPRESS_BACKEND_WARNINGS === '1') return;
|
|
95
|
+
|
|
96
|
+
// Only warn when env var was explicit (auto-detect already follows disk).
|
|
97
|
+
if (!process.env.AWM_STORE_BACKEND) return;
|
|
98
|
+
|
|
99
|
+
const otherPath = backend === 'pglite' ? 'memory.db' : 'memory-pglite';
|
|
100
|
+
const otherBackend = backend === 'pglite' ? 'sqlite' : 'pglite';
|
|
101
|
+
|
|
102
|
+
// The "real" check: configured target doesn't exist yet (fresh / empty) AND
|
|
103
|
+
// the other backend's conventional file exists with data. Likely stranded.
|
|
104
|
+
const configuredExists = existsSync(path);
|
|
105
|
+
const otherExists = existsSync(otherPath);
|
|
106
|
+
|
|
107
|
+
if (!configuredExists && otherExists) {
|
|
108
|
+
console.warn(
|
|
109
|
+
`[awm] AWM_STORE_BACKEND=${backend} but no data at "${path}". ` +
|
|
110
|
+
`Existing ${otherBackend} data at "${otherPath}" — run \`awm migrate\` ` +
|
|
111
|
+
`to convert it to ${backend}, or unset AWM_STORE_BACKEND to use ${otherBackend}. ` +
|
|
112
|
+
`Suppress with AWM_SUPPRESS_BACKEND_WARNINGS=1.`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Open a store using the env-configured (or auto-detected) backend and path.
|
|
119
|
+
* Returns the concrete store class (cast to IEngramStore at call sites
|
|
120
|
+
* that need the async contract; SQLite callers can keep the concrete class
|
|
121
|
+
* to retain access to SQLite-specific methods like getDb()).
|
|
122
|
+
*
|
|
123
|
+
* Never fails on backend/disk mismatch — only warns. Existing users
|
|
124
|
+
* upgrading without setting env vars get auto-detect; users on the legacy
|
|
125
|
+
* `memory.db` keep working without touching anything.
|
|
126
|
+
*/
|
|
127
|
+
export async function openStore(): Promise<{
|
|
128
|
+
store: IEngramStore;
|
|
129
|
+
backend: StoreBackend;
|
|
130
|
+
path: string;
|
|
131
|
+
}> {
|
|
132
|
+
const backend = getConfiguredBackend();
|
|
133
|
+
const path = getConfiguredPath();
|
|
134
|
+
|
|
135
|
+
warnIfBackendDisagreesWithDisk(backend, path);
|
|
136
|
+
|
|
137
|
+
if (backend === 'pglite') {
|
|
138
|
+
const { PGliteEngramStore } = await import('./pglite.js');
|
|
139
|
+
const store = new PGliteEngramStore(path);
|
|
140
|
+
await store.ready();
|
|
141
|
+
return { store: store as unknown as IEngramStore, backend, path };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { EngramStore } = await import('./sqlite.js');
|
|
145
|
+
const store = new EngramStore(path);
|
|
146
|
+
return { store: store as unknown as IEngramStore, backend, path };
|
|
147
|
+
}
|
package/src/storage/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
export * from './sqlite.js';
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
export * from './sqlite.js';
|