agent-working-memory 0.10.0 → 0.12.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 +118 -19
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +63 -3
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +329 -302
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +31 -8
- package/dist/api/routes.js.map +1 -1
- package/dist/cli/migrate.js +29 -29
- package/dist/cli.js +82 -2
- package/dist/cli.js.map +1 -1
- package/dist/coordination/circuit-breaker.js +23 -23
- package/dist/coordination/routes.d.ts.map +1 -1
- package/dist/coordination/routes.js +174 -170
- package/dist/coordination/routes.js.map +1 -1
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +3 -0
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/entity-extract.d.ts +3 -0
- package/dist/core/entity-extract.d.ts.map +1 -0
- package/dist/core/entity-extract.js +47 -0
- package/dist/core/entity-extract.js.map +1 -0
- package/dist/core/salience.d.ts.map +1 -1
- package/dist/core/salience.js +14 -2
- package/dist/core/salience.js.map +1 -1
- package/dist/core/whoami.d.ts +24 -0
- package/dist/core/whoami.d.ts.map +1 -0
- package/dist/core/whoami.js +66 -0
- package/dist/core/whoami.js.map +1 -0
- package/dist/core/write-pipeline.d.ts +9 -0
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +109 -68
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/core/write-telemetry.d.ts +33 -0
- package/dist/core/write-telemetry.d.ts.map +1 -0
- package/dist/core/write-telemetry.js +110 -0
- package/dist/core/write-telemetry.js.map +1 -0
- package/dist/engine/activation.d.ts +22 -12
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +133 -17
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/consolidation-scheduler.d.ts +1 -1
- package/dist/engine/consolidation-scheduler.js +1 -1
- package/dist/engine/consolidation.d.ts +1 -0
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +18 -0
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/engine/eval.d.ts.map +1 -1
- package/dist/engine/eval.js +5 -1
- package/dist/engine/eval.js.map +1 -1
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +212 -97
- package/dist/mcp.js.map +1 -1
- package/dist/onboard/index.d.ts +68 -0
- package/dist/onboard/index.d.ts.map +1 -0
- package/dist/onboard/index.js +265 -0
- package/dist/onboard/index.js.map +1 -0
- package/dist/recipes/index.d.ts +57 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +81 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/storage/pglite-schema.d.ts.map +1 -1
- package/dist/storage/pglite-schema.js +170 -143
- package/dist/storage/pglite-schema.js.map +1 -1
- package/dist/storage/pglite.d.ts +5 -0
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +180 -138
- package/dist/storage/pglite.js.map +1 -1
- package/dist/storage/postgres.d.ts +5 -0
- package/dist/storage/postgres.d.ts.map +1 -1
- package/dist/storage/postgres.js +44 -2
- package/dist/storage/postgres.js.map +1 -1
- package/dist/storage/sqlite.d.ts +9 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +394 -326
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types/engram.d.ts +14 -0
- package/dist/types/engram.d.ts.map +1 -1
- package/dist/types/engram.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +27 -0
- package/dist/version.js.map +1 -0
- package/package.json +9 -1
- package/src/adapters/claude-code.ts +66 -3
- package/src/adapters/common.ts +538 -511
- package/src/api/index.ts +3 -3
- package/src/api/routes.ts +999 -970
- package/src/cli/migrate.ts +307 -307
- package/src/cli.ts +77 -2
- package/src/coordination/circuit-breaker.ts +83 -83
- package/src/coordination/failure-modes.ts +50 -50
- package/src/coordination/routes.ts +2155 -2150
- package/src/core/decay.ts +63 -63
- package/src/core/embeddings.ts +113 -110
- package/src/core/entity-extract.ts +47 -0
- 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/salience.ts +529 -514
- package/src/core/whoami.ts +92 -0
- package/src/core/write-pipeline.ts +60 -8
- package/src/core/write-telemetry.ts +131 -0
- package/src/engine/activation.ts +1468 -1369
- package/src/engine/confidence.ts +120 -120
- package/src/engine/consolidation-scheduler.ts +242 -242
- package/src/engine/consolidation.ts +887 -869
- package/src/engine/eval.ts +107 -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/index.ts +248 -226
- package/src/mcp.ts +1341 -1211
- package/src/onboard/index.ts +298 -0
- package/src/recipes/index.ts +125 -0
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +193 -166
- package/src/storage/pglite.ts +1420 -1372
- package/src/storage/postgres.ts +50 -2
- package/src/storage/sqlite.ts +1936 -1861
- 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 +22 -0
- package/src/types/eval.ts +100 -100
- package/src/types/index.ts +6 -6
- package/src/version.ts +26 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Instance identity (D3, 2026-07-30).
|
|
6
|
+
*
|
|
7
|
+
* Answers "which AWM am I talking to?" — the recurring confusion class where
|
|
8
|
+
* sessions mix up the hosted multi-agent connector with the local instance,
|
|
9
|
+
* or run blind to sibling agent spaces in the same store (an evaluation once
|
|
10
|
+
* analyzed this project without seeing the `personal` space that held its
|
|
11
|
+
* design decisions).
|
|
12
|
+
*
|
|
13
|
+
* Exposed as the `memory_whoami` MCP tool and the `GET /whoami` HTTP route.
|
|
14
|
+
* Read-only; additive module.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { VERSION } from '../version.js';
|
|
20
|
+
import { getConfiguredBackend, getConfiguredPath } from '../storage/factory.js';
|
|
21
|
+
|
|
22
|
+
export interface WhoamiInfo {
|
|
23
|
+
agentId: string;
|
|
24
|
+
workspace: string | null;
|
|
25
|
+
surface: 'mcp' | 'http';
|
|
26
|
+
mode: 'standalone' | 'hive';
|
|
27
|
+
backend: string;
|
|
28
|
+
storePath: string;
|
|
29
|
+
version: string;
|
|
30
|
+
codePath: string;
|
|
31
|
+
pid: number;
|
|
32
|
+
ports: { http: number | null; hookSidecar: number | null };
|
|
33
|
+
siblingAgents: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Store subset whoami needs — every backend implements listAgentIds. */
|
|
37
|
+
export interface WhoamiStore {
|
|
38
|
+
listAgentIds(): string[] | Promise<string[]>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function packageRoot(): string {
|
|
42
|
+
// src/core/whoami.ts (dev) and dist/core/whoami.js (build) both sit two
|
|
43
|
+
// levels below the package root.
|
|
44
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
return resolve(join(here, '..', '..'));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function buildWhoami(
|
|
49
|
+
store: WhoamiStore,
|
|
50
|
+
agentId: string,
|
|
51
|
+
surface: 'mcp' | 'http',
|
|
52
|
+
): Promise<WhoamiInfo> {
|
|
53
|
+
const coordination = process.env.AWM_COORDINATION === 'true' || process.env.AWM_COORDINATION === '1';
|
|
54
|
+
let siblings: string[] = [];
|
|
55
|
+
try {
|
|
56
|
+
const all = await store.listAgentIds();
|
|
57
|
+
siblings = all.filter(a => a !== agentId).sort();
|
|
58
|
+
} catch { /* identity must never fail on a listing error */ }
|
|
59
|
+
const httpPort = Number(process.env.AWM_PORT ?? '');
|
|
60
|
+
const hookPort = Number(process.env.AWM_HOOK_PORT ?? '');
|
|
61
|
+
return {
|
|
62
|
+
agentId,
|
|
63
|
+
workspace: process.env.AWM_WORKSPACE ?? null,
|
|
64
|
+
surface,
|
|
65
|
+
mode: coordination ? 'hive' : 'standalone',
|
|
66
|
+
backend: getConfiguredBackend(),
|
|
67
|
+
storePath: getConfiguredPath(),
|
|
68
|
+
version: VERSION,
|
|
69
|
+
codePath: packageRoot(),
|
|
70
|
+
pid: process.pid,
|
|
71
|
+
ports: {
|
|
72
|
+
http: Number.isFinite(httpPort) && httpPort > 0 ? httpPort : null,
|
|
73
|
+
hookSidecar: Number.isFinite(hookPort) && hookPort > 0 ? hookPort : null,
|
|
74
|
+
},
|
|
75
|
+
siblingAgents: siblings,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Human-readable rendering for the MCP tool response. */
|
|
80
|
+
export function formatWhoami(w: WhoamiInfo): string {
|
|
81
|
+
return [
|
|
82
|
+
`Agent: ${w.agentId}${w.workspace ? ` (workspace: ${w.workspace})` : ''}`,
|
|
83
|
+
`Mode: ${w.mode} · Surface: ${w.surface} · Version: ${w.version}`,
|
|
84
|
+
`Backend: ${w.backend}`,
|
|
85
|
+
`Store: ${w.storePath}`,
|
|
86
|
+
`Code: ${w.codePath} (pid ${w.pid})`,
|
|
87
|
+
`Ports: http=${w.ports.http ?? 'off'} hookSidecar=${w.ports.hookSidecar ?? 'off'}`,
|
|
88
|
+
w.siblingAgents.length
|
|
89
|
+
? `Sibling agent spaces in this store: ${w.siblingAgents.join(', ')} — recall is scoped to '${w.agentId}'; other spaces need workspace recall, \`awm export --agent <id>\`, or a session configured for that agent.`
|
|
90
|
+
: 'Sibling agent spaces in this store: none',
|
|
91
|
+
].join('\n');
|
|
92
|
+
}
|
|
@@ -48,6 +48,8 @@ import {
|
|
|
48
48
|
} from './salience.js';
|
|
49
49
|
import { embed } from './embeddings.js';
|
|
50
50
|
import { extractMetaTags } from './auto-tagger.js';
|
|
51
|
+
import { extractEntitiesFromTags } from './entity-extract.js';
|
|
52
|
+
import { reportWrite } from './write-telemetry.js';
|
|
51
53
|
import { DEFAULT_AGENT_CONFIG } from '../types/agent.js';
|
|
52
54
|
|
|
53
55
|
/** Confidence floor below which a matched engram is treated as "decaying out". */
|
|
@@ -98,6 +100,15 @@ export interface WriteInput {
|
|
|
98
100
|
* because it's deterministically retrieved; opt in if a caller wants
|
|
99
101
|
* cognitive recall over a structural engram. (0.8 Cluster A) */
|
|
100
102
|
embed?: boolean;
|
|
103
|
+
/** Provenance (D5, log-only): 'user-stated' | 'tool-output' | 'inference' | 'recipe'. */
|
|
104
|
+
originClass?: string;
|
|
105
|
+
/** Provenance (D5): session/conversation id performing the write. */
|
|
106
|
+
writerSession?: string;
|
|
107
|
+
/** Provenance (D5): cognition-recipe id+version when originClass is 'recipe'. */
|
|
108
|
+
recipeId?: string;
|
|
109
|
+
/** Temporal validity (D8): ISO bounds on when the FACT holds. */
|
|
110
|
+
validFrom?: string;
|
|
111
|
+
validTo?: string;
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
export interface WriteResult {
|
|
@@ -163,7 +174,10 @@ export async function performWrite(
|
|
|
163
174
|
// Profiling: AWM_PROFILE_WRITE=1 logs ms-per-phase to stderr (v0.8.2).
|
|
164
175
|
// Off by default; zero cost when unset.
|
|
165
176
|
const profile = process.env.AWM_PROFILE_WRITE === '1';
|
|
166
|
-
|
|
177
|
+
// Timings are collected unconditionally since D1 (2026-07-30): a handful of
|
|
178
|
+
// performance.now() calls are free, and the always-on slow-write telemetry
|
|
179
|
+
// (write-telemetry.ts) needs them to explain stalls in production.
|
|
180
|
+
const startTotal = performance.now();
|
|
167
181
|
let tNovelty = 0, tCreate = 0, tEmbed = 0;
|
|
168
182
|
|
|
169
183
|
// Pre-embed once (v0.8.5): the embedding is needed for cosine-based novelty
|
|
@@ -177,22 +191,22 @@ export async function performWrite(
|
|
|
177
191
|
// hook below will retry).
|
|
178
192
|
// Disable via AWM_NOVELTY_EMBED=0 to keep writes ultra-fast at the cost
|
|
179
193
|
// of cross-backend novelty consistency.
|
|
180
|
-
const tStartEmbed =
|
|
194
|
+
const tStartEmbed = performance.now();
|
|
181
195
|
let prewriteEmbedding: number[] | null = null;
|
|
182
196
|
if (process.env.AWM_NOVELTY_EMBED !== '0') {
|
|
183
197
|
try {
|
|
184
198
|
prewriteEmbedding = await embed(`${input.concept} ${input.content}`);
|
|
185
199
|
} catch { /* fall through — async embed will retry later */ }
|
|
186
200
|
}
|
|
187
|
-
|
|
201
|
+
tEmbed = performance.now() - tStartEmbed;
|
|
188
202
|
|
|
189
|
-
const tStartNovelty =
|
|
203
|
+
const tStartNovelty = performance.now();
|
|
190
204
|
const noveltyResult = await computeNoveltyWithMatch(
|
|
191
205
|
store, input.agentId, input.concept, input.content,
|
|
192
206
|
input.workspace ?? null,
|
|
193
207
|
prewriteEmbedding,
|
|
194
208
|
);
|
|
195
|
-
|
|
209
|
+
tNovelty = performance.now() - tStartNovelty;
|
|
196
210
|
|
|
197
211
|
// Effective event type — auto-promote user-feedback content.
|
|
198
212
|
const effectiveEventType: SalienceEventType =
|
|
@@ -216,8 +230,10 @@ export async function performWrite(
|
|
|
216
230
|
});
|
|
217
231
|
|
|
218
232
|
// -- Reinforce / Supersede branching --
|
|
219
|
-
const tStartCreate =
|
|
233
|
+
const tStartCreate = performance.now();
|
|
234
|
+
let busyHit = false;
|
|
220
235
|
let result: WriteResult | null = null;
|
|
236
|
+
try {
|
|
221
237
|
if (enableReinforcement && noveltyResult.matchedEngramId) {
|
|
222
238
|
const matched = await store.getEngram(noveltyResult.matchedEngramId);
|
|
223
239
|
// Agent-scope guard: NEVER reinforce/supersede an engram that belongs to a DIFFERENT agent. A
|
|
@@ -285,10 +301,34 @@ export async function performWrite(
|
|
|
285
301
|
supersedesId: input.supersedes,
|
|
286
302
|
}, prewriteEmbedding);
|
|
287
303
|
}
|
|
288
|
-
|
|
304
|
+
} catch (e) {
|
|
305
|
+
const es = `${(e as { code?: unknown })?.code ?? ''} ${(e as Error)?.message ?? ''}`;
|
|
306
|
+
busyHit = /busy/i.test(es);
|
|
307
|
+
reportWrite({
|
|
308
|
+
totalMs: performance.now() - startTotal,
|
|
309
|
+
embedMs: tEmbed,
|
|
310
|
+
noveltyMs: tNovelty,
|
|
311
|
+
persistMs: performance.now() - tStartCreate,
|
|
312
|
+
action: 'error',
|
|
313
|
+
agentId: input.agentId,
|
|
314
|
+
busyHit,
|
|
315
|
+
});
|
|
316
|
+
throw e;
|
|
317
|
+
}
|
|
318
|
+
tCreate = performance.now() - tStartCreate;
|
|
319
|
+
|
|
320
|
+
const total = performance.now() - startTotal;
|
|
321
|
+
reportWrite({
|
|
322
|
+
totalMs: total,
|
|
323
|
+
embedMs: tEmbed,
|
|
324
|
+
noveltyMs: tNovelty,
|
|
325
|
+
persistMs: tCreate,
|
|
326
|
+
action: result.action,
|
|
327
|
+
agentId: input.agentId,
|
|
328
|
+
busyHit,
|
|
329
|
+
});
|
|
289
330
|
|
|
290
331
|
if (profile) {
|
|
291
|
-
const total = performance.now() - startTotal;
|
|
292
332
|
// Single-line stderr log; cheap to grep, easy to disable.
|
|
293
333
|
// Format: [write] action=create novelty=42.1ms create=18.3ms total=60.4ms agent=x id=y
|
|
294
334
|
// eslint-disable-next-line no-console
|
|
@@ -478,6 +518,11 @@ async function createNewEngram(
|
|
|
478
518
|
supersedes: meta.supersedesId,
|
|
479
519
|
sequence: input.sequence,
|
|
480
520
|
references: input.references,
|
|
521
|
+
originClass: input.originClass,
|
|
522
|
+
writerSession: input.writerSession,
|
|
523
|
+
recipeId: input.recipeId,
|
|
524
|
+
validFrom: input.validFrom,
|
|
525
|
+
validTo: input.validTo,
|
|
481
526
|
embedding: prewriteEmbedding && prewriteEmbedding.length > 0
|
|
482
527
|
? prewriteEmbedding
|
|
483
528
|
: undefined,
|
|
@@ -487,6 +532,13 @@ async function createNewEngram(
|
|
|
487
532
|
await store.updateStage(engram.id, 'staging');
|
|
488
533
|
}
|
|
489
534
|
|
|
535
|
+
// Entity inverted index (D9): index structured entities from tags at write
|
|
536
|
+
// time. Best-effort — retrieval does not depend on it yet (D11).
|
|
537
|
+
try {
|
|
538
|
+
const entities = extractEntitiesFromTags(tags);
|
|
539
|
+
if (entities.length > 0) await store.recordEntityMentions(engram.id, input.agentId, entities);
|
|
540
|
+
} catch { /* non-fatal */ }
|
|
541
|
+
|
|
490
542
|
// Supersession side-effects: mark the old engram, add causal edge.
|
|
491
543
|
if (meta.supersedesId) {
|
|
492
544
|
try {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Write-path telemetry (D1, 2026-07-30).
|
|
6
|
+
*
|
|
7
|
+
* Purpose: prove the mechanism behind slow writes before building the
|
|
8
|
+
* serialized write service. Captures, per write:
|
|
9
|
+
* - phase wall times (embed / novelty / persist)
|
|
10
|
+
* - event-loop lag at write completion (was this process's loop blocked?)
|
|
11
|
+
* - whether a consolidation cycle was running IN THIS PROCESS
|
|
12
|
+
* - whether SQLITE_BUSY was hit
|
|
13
|
+
* - embed-model cold-load duration (first inference)
|
|
14
|
+
*
|
|
15
|
+
* Always-on slow-write warning: any write slower than AWM_SLOW_WRITE_MS
|
|
16
|
+
* (default 250ms; set 0 to disable) emits ONE structured stderr line.
|
|
17
|
+
* AWM_PROFILE_WRITE=1 continues to log every write as before.
|
|
18
|
+
*
|
|
19
|
+
* Design notes: additive module — no existing export changes (MWA vendors
|
|
20
|
+
* dist internals; nothing here alters existing paths). All timers are
|
|
21
|
+
* unref'd so they never hold the process open.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const SLOW_WRITE_MS = (() => {
|
|
25
|
+
const v = Number(process.env.AWM_SLOW_WRITE_MS ?? '250');
|
|
26
|
+
return Number.isFinite(v) ? v : 250;
|
|
27
|
+
})();
|
|
28
|
+
|
|
29
|
+
// ---------- Event-loop lag monitor ----------
|
|
30
|
+
|
|
31
|
+
const TICK_MS = 500;
|
|
32
|
+
let lastTick = 0;
|
|
33
|
+
let maxLagSinceRead = 0;
|
|
34
|
+
let monitorStarted = false;
|
|
35
|
+
|
|
36
|
+
/** Start the loop-lag heartbeat. Idempotent; call once at process boot. */
|
|
37
|
+
export function startLoopLagMonitor(): void {
|
|
38
|
+
if (monitorStarted) return;
|
|
39
|
+
monitorStarted = true;
|
|
40
|
+
lastTick = performance.now();
|
|
41
|
+
const t = setInterval(() => {
|
|
42
|
+
const now = performance.now();
|
|
43
|
+
const lag = now - lastTick - TICK_MS;
|
|
44
|
+
if (lag > maxLagSinceRead) maxLagSinceRead = lag;
|
|
45
|
+
lastTick = now;
|
|
46
|
+
}, TICK_MS);
|
|
47
|
+
if (typeof t.unref === 'function') t.unref();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Max observed loop lag (ms beyond the expected tick) since the last read.
|
|
52
|
+
* Reading resets the max. A large value means this process's event loop was
|
|
53
|
+
* blocked (in-process ML, consolidation, sync SQL) during the window.
|
|
54
|
+
*/
|
|
55
|
+
export function readMaxLoopLag(): number {
|
|
56
|
+
const v = maxLagSinceRead;
|
|
57
|
+
maxLagSinceRead = 0;
|
|
58
|
+
return Math.max(0, Math.round(v));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------- In-process consolidation state ----------
|
|
62
|
+
|
|
63
|
+
let consolidationActive = false;
|
|
64
|
+
let consolidationAgent: string | null = null;
|
|
65
|
+
let consolidationStartedAt = 0;
|
|
66
|
+
|
|
67
|
+
export function setConsolidationActive(active: boolean, agentId?: string): void {
|
|
68
|
+
consolidationActive = active;
|
|
69
|
+
consolidationAgent = active ? (agentId ?? null) : null;
|
|
70
|
+
consolidationStartedAt = active ? performance.now() : 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getConsolidationState(): { active: boolean; agentId: string | null; runningMs: number } {
|
|
74
|
+
return {
|
|
75
|
+
active: consolidationActive,
|
|
76
|
+
agentId: consolidationAgent,
|
|
77
|
+
runningMs: consolidationActive ? Math.round(performance.now() - consolidationStartedAt) : 0,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------- Model cold-load record ----------
|
|
82
|
+
|
|
83
|
+
let modelColdLoadMs: number | null = null;
|
|
84
|
+
|
|
85
|
+
export function noteModelLoad(ms: number): void {
|
|
86
|
+
if (modelColdLoadMs === null) {
|
|
87
|
+
modelColdLoadMs = Math.round(ms);
|
|
88
|
+
process.stderr.write(`[awm] embed model cold load: ${modelColdLoadMs}ms\n`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getModelColdLoadMs(): number | null {
|
|
93
|
+
return modelColdLoadMs;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------- Slow-write reporter ----------
|
|
97
|
+
|
|
98
|
+
export interface WriteTimings {
|
|
99
|
+
totalMs: number;
|
|
100
|
+
embedMs: number;
|
|
101
|
+
noveltyMs: number;
|
|
102
|
+
persistMs: number;
|
|
103
|
+
action: string; // created | reinforced | superseded | discarded | ...
|
|
104
|
+
agentId: string;
|
|
105
|
+
busyHit: boolean; // a SQLITE_BUSY was caught (and rethrown) during persist
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Report a completed write. Emits one structured stderr line when the write
|
|
110
|
+
* exceeded the slow threshold (always-on) — including loop lag and in-process
|
|
111
|
+
* consolidation state, the two signals that distinguish "DB lock wait" from
|
|
112
|
+
* "my own event loop was blocked".
|
|
113
|
+
*/
|
|
114
|
+
export function reportWrite(t: WriteTimings): void {
|
|
115
|
+
if (SLOW_WRITE_MS <= 0) return;
|
|
116
|
+
if (t.totalMs < SLOW_WRITE_MS) return;
|
|
117
|
+
const consol = getConsolidationState();
|
|
118
|
+
const parts = [
|
|
119
|
+
`[awm] SLOW WRITE ${Math.round(t.totalMs)}ms`,
|
|
120
|
+
`agent=${t.agentId}`,
|
|
121
|
+
`action=${t.action}`,
|
|
122
|
+
`embed=${Math.round(t.embedMs)}ms`,
|
|
123
|
+
`novelty=${Math.round(t.noveltyMs)}ms`,
|
|
124
|
+
`persist=${Math.round(t.persistMs)}ms`,
|
|
125
|
+
`loopLagMax=${readMaxLoopLag()}ms`,
|
|
126
|
+
`consolidating=${consol.active ? `yes(${consol.agentId ?? '?'},${consol.runningMs}ms)` : 'no'}`,
|
|
127
|
+
`busy=${t.busyHit ? 'yes' : 'no'}`,
|
|
128
|
+
modelColdLoadMs !== null ? `modelColdLoad=${modelColdLoadMs}ms` : 'modelColdLoad=none',
|
|
129
|
+
];
|
|
130
|
+
process.stderr.write(parts.join(' ') + '\n');
|
|
131
|
+
}
|