@clude/sdk 3.2.0 → 3.3.1
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 +47 -446
- package/README.npm.md +115 -0
- package/dist/cli/index.js +2108 -487
- package/dist/mcp/local-store.d.ts +74 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +1941 -326
- package/dist/sdk/cortex-v2.d.ts +120 -0
- package/dist/sdk/cortex.d.ts +63 -0
- package/dist/sdk/http-transport.d.ts +14 -0
- package/dist/sdk/index.d.ts +4 -0
- package/dist/sdk/index.js +2005 -378
- package/dist/sdk/memory-types.d.ts +321 -0
- package/dist/sdk/sdk-mode.d.ts +1 -0
- package/dist/sdk/shared-constants.d.ts +53 -0
- package/dist/sdk/types.d.ts +47 -0
- package/package.json +18 -23
- package/supabase-schema.sql +169 -12
package/dist/sdk/index.js
CHANGED
|
@@ -67,7 +67,20 @@ var init_config = __esm({
|
|
|
67
67
|
accessToken: requiredUnlessSiteOnly("X_ACCESS_TOKEN"),
|
|
68
68
|
accessSecret: requiredUnlessSiteOnly("X_ACCESS_SECRET"),
|
|
69
69
|
botUserId: requiredUnlessSiteOnly("X_BOT_USER_ID"),
|
|
70
|
-
creatorUserId: optional("CREATOR_USER_ID", "")
|
|
70
|
+
creatorUserId: optional("CREATOR_USER_ID", ""),
|
|
71
|
+
/**
|
|
72
|
+
* Bearer token for the public "$ANSEM LIVE" feed (GET /api/ansem/feed).
|
|
73
|
+
* Reads-only X app bearer — used server-side for GET /2/tweets/search/recent.
|
|
74
|
+
* Empty → the feed endpoint returns { enabled:false } and the frontend panel hides.
|
|
75
|
+
* NEVER sent to the browser.
|
|
76
|
+
*/
|
|
77
|
+
searchBearer: optional("X_SEARCH_BEARER", ""),
|
|
78
|
+
/** Min poll gap for the on-demand $ANSEM feed (ms). Idle → no polling → $0. */
|
|
79
|
+
ansemFeedIntervalMs: parseInt(optional("ANSEM_FEED_INTERVAL_MS", "90000"), 10),
|
|
80
|
+
/** Posts older than ~15min must clear this like floor (fresh posts are exempt). */
|
|
81
|
+
ansemFeedMinLikes: parseInt(optional("ANSEM_FEED_MIN_LIKES", "3"), 10),
|
|
82
|
+
/** Hard daily read-cap: once exceeded, serve the stale buffer (cost stop-loss). */
|
|
83
|
+
ansemFeedDailyReadCap: parseInt(optional("ANSEM_FEED_DAILY_READ_CAP", "40000"), 10)
|
|
71
84
|
},
|
|
72
85
|
supabase: {
|
|
73
86
|
url: requiredUnlessSiteOnly("SUPABASE_URL"),
|
|
@@ -85,7 +98,9 @@ var init_config = __esm({
|
|
|
85
98
|
optional("SOLANA_NETWORK", "mainnet-beta") === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com"
|
|
86
99
|
),
|
|
87
100
|
botWalletPrivateKey: optional("BOT_WALLET_PRIVATE_KEY", ""),
|
|
88
|
-
cludeTokenMint: optional("CLUUDE_TOKEN_MINT", "")
|
|
101
|
+
cludeTokenMint: optional("CLUUDE_TOKEN_MINT", ""),
|
|
102
|
+
/** Deployed memory-registry Anchor program ID. Empty → on-chain registration disabled. */
|
|
103
|
+
memoryRegistryProgramId: optional("CLUDE_PROGRAM_ID", "")
|
|
89
104
|
},
|
|
90
105
|
server: {
|
|
91
106
|
port: parseInt(optional("PORT", "3000"), 10),
|
|
@@ -123,6 +138,69 @@ var init_config = __esm({
|
|
|
123
138
|
freePromoCreditUsdc: parseFloat(optional("FREE_PROMO_CREDIT_USDC", "1")),
|
|
124
139
|
freePromoExpiry: optional("FREE_PROMO_EXPIRY", "")
|
|
125
140
|
},
|
|
141
|
+
memory: {
|
|
142
|
+
/**
|
|
143
|
+
* Memory 3.0 C2: route storeMemory enrichment (embed/link/extract) through the durable
|
|
144
|
+
* memory_write_jobs outbox instead of fire-and-forget. Default OFF — fire-and-forget stays
|
|
145
|
+
* the default until the worker is proven. Requires migrations 044 + 045; degrades gracefully
|
|
146
|
+
* (falls back to fire-and-forget) if unapplied.
|
|
147
|
+
*/
|
|
148
|
+
outboxEnabled: optional("MEMORY_OUTBOX", "false") === "true",
|
|
149
|
+
/**
|
|
150
|
+
* Memory 3.0 C1: write-time reconciliation, SHADOW slice (LLM-free). When on, each write records
|
|
151
|
+
* a PROPOSED reconcile op (add / needs_router / skip) into memory_reconciliation_log WITHOUT
|
|
152
|
+
* applying it — the labeled sample the enforce path must earn its turn-on from. Default OFF;
|
|
153
|
+
* runs fully detached after embedMemory (zero write latency); degrades gracefully if migration
|
|
154
|
+
* 046 is unapplied. Disabled under BENCH_MODE. The router is a later slice; only the cosine gate
|
|
155
|
+
* runs here.
|
|
156
|
+
*/
|
|
157
|
+
reconcileEnabled: optional("MEMORY_RECONCILE", "false") === "true",
|
|
158
|
+
/** Screen floor passed to match_memories_temporal — LOW so max_cosine is captured for below-LO
|
|
159
|
+
* writes (LO is tuned from the shadow data, not fixed up-front). */
|
|
160
|
+
reconcileFloor: Number(optional("MEMORY_RECONCILE_FLOOR", "0.5")),
|
|
161
|
+
/** "similar enough to reconcile" boundary → needs_router (vs add). A starting probe, not gospel. */
|
|
162
|
+
reconcileLo: Number(optional("MEMORY_RECONCILE_LO", "0.85")),
|
|
163
|
+
/** hi/mid band LABEL boundary for analysis (not a decision boundary). */
|
|
164
|
+
reconcileHi: Number(optional("MEMORY_RECONCILE_HI", "0.95")),
|
|
165
|
+
/**
|
|
166
|
+
* C1 slice 1.5: the LLM router. Its own sub-flag (default OFF, independent of reconcileEnabled)
|
|
167
|
+
* so gate-only instrumentation runs first and LO is calibrated from that data before any LLM
|
|
168
|
+
* spend. When on, a >= LO write is classified add/update/noop by reconcileModel (still SHADOW —
|
|
169
|
+
* the op is logged, never applied). Requires OpenRouter configured.
|
|
170
|
+
*/
|
|
171
|
+
reconcileRouter: optional("MEMORY_RECONCILE_ROUTER", "false") === "true",
|
|
172
|
+
/** Router model — an explicit id (NEVER a cognitiveFunction, which would silently override it
|
|
173
|
+
* with the fast llama slot). Haiku-class default: capable enough for dup-vs-update, cheap. */
|
|
174
|
+
reconcileModel: optional("MEMORY_RECONCILE_MODEL", "anthropic/claude-haiku-4.5"),
|
|
175
|
+
/** Per-owner soft daily cap on router calls (approximate, per-process) — bounds LLM spend. */
|
|
176
|
+
reconcileBudget: Number(optional("MEMORY_RECONCILE_BUDGET", "200"))
|
|
177
|
+
},
|
|
178
|
+
oauth: {
|
|
179
|
+
/** HMAC secret for signing OAuth access-token JWTs. Empty disables the OAuth AS — bearer API-key auth still works. */
|
|
180
|
+
signingSecret: optional("OAUTH_SIGNING_SECRET", ""),
|
|
181
|
+
/** Issuer/audience identifier baked into tokens. Falls back to the request origin when empty. */
|
|
182
|
+
issuer: optional("OAUTH_ISSUER", ""),
|
|
183
|
+
/** Access-token lifetime in seconds (default 1h). */
|
|
184
|
+
accessTtlSec: parseInt(optional("OAUTH_ACCESS_TTL_SEC", "3600"), 10),
|
|
185
|
+
/** Refresh-token lifetime in seconds (default 30d). */
|
|
186
|
+
refreshTtlSec: parseInt(optional("OAUTH_REFRESH_TTL_SEC", "2592000"), 10),
|
|
187
|
+
/** Authorization-code lifetime in seconds (default 60s). */
|
|
188
|
+
codeTtlSec: parseInt(optional("OAUTH_CODE_TTL_SEC", "60"), 10)
|
|
189
|
+
},
|
|
190
|
+
stripe: {
|
|
191
|
+
/**
|
|
192
|
+
* Stripe secret API key (sk_live_… / sk_test_…). Empty disables the
|
|
193
|
+
* marketplace Stripe rail — the orchestrator/webhook fail closed without it.
|
|
194
|
+
* SDK/MCP consumers never need this, so it stays optional() (not required()).
|
|
195
|
+
*/
|
|
196
|
+
secretKey: optional("STRIPE_SECRET_KEY", ""),
|
|
197
|
+
/**
|
|
198
|
+
* Stripe webhook signing secret (whsec_…) used by stripe.webhooks.constructEvent
|
|
199
|
+
* to verify the raw body BEFORE any DB write (Risk R6). Empty ⇒ every webhook is
|
|
200
|
+
* rejected, so a misconfigured deploy can never process an unverified event.
|
|
201
|
+
*/
|
|
202
|
+
webhookSecret: optional("STRIPE_WEBHOOK_SECRET", "")
|
|
203
|
+
},
|
|
126
204
|
campaign: {
|
|
127
205
|
startDate: optional("CAMPAIGN_START", "")
|
|
128
206
|
},
|
|
@@ -136,6 +214,51 @@ var init_config = __esm({
|
|
|
136
214
|
queryApiKey: optional("EMBEDDING_QUERY_API_KEY", ""),
|
|
137
215
|
queryModel: optional("EMBEDDING_QUERY_MODEL", "")
|
|
138
216
|
},
|
|
217
|
+
migration: {
|
|
218
|
+
/**
|
|
219
|
+
* Database backend that getDb() targets during the GCP parallel-run.
|
|
220
|
+
* 'supabase' (default, current live infra) | 'cloudsql' (PostgREST /
|
|
221
|
+
* self-hosted Supabase in front of Cloud SQL). Flip per-layer for the
|
|
222
|
+
* shadow-stack cutover; Supabase stays the source of truth until sunset.
|
|
223
|
+
*/
|
|
224
|
+
dbTarget: optional("DB_TARGET", "supabase"),
|
|
225
|
+
/** PostgREST endpoint for the Cloud SQL backend (used only when DB_TARGET=cloudsql). */
|
|
226
|
+
cloudsqlUrl: optional("CLOUDSQL_PGREST_URL", ""),
|
|
227
|
+
/** Service key for the Cloud SQL PostgREST backend (used only when DB_TARGET=cloudsql). */
|
|
228
|
+
cloudsqlServiceKey: optional("CLOUDSQL_SERVICE_KEY", ""),
|
|
229
|
+
/**
|
|
230
|
+
* Active embedding vector space for ingest + recall. 'voyage' (default,
|
|
231
|
+
* current corpus) | 'vertex' (shadow column, only after the LongMemEval gate
|
|
232
|
+
* passes). Separate from EMBEDDING_PROVIDER so the swap is A/B, not one-way.
|
|
233
|
+
*/
|
|
234
|
+
embeddingActive: optional("EMBEDDING_ACTIVE", "voyage"),
|
|
235
|
+
/**
|
|
236
|
+
* Whether THIS process runs the in-server singleton timers (recall canary,
|
|
237
|
+
* marketplace delivery poller, title-mint reconciliation). Default true =
|
|
238
|
+
* current Railway behavior. Set false on the Cloud Run server so exactly one
|
|
239
|
+
* owner (the worker) runs them and autoscaling never duplicates them.
|
|
240
|
+
*/
|
|
241
|
+
runInProcessTimers: optional("RUN_INPROCESS_TIMERS", "true") === "true"
|
|
242
|
+
},
|
|
243
|
+
vertex: {
|
|
244
|
+
/**
|
|
245
|
+
* Vertex AI embedding backend (the GCP replacement for Voyage), used only when
|
|
246
|
+
* ingest/backfill/recall target the 'vertex' space (EMBEDDING_ACTIVE=vertex or an
|
|
247
|
+
* explicit generateEmbeddingForSpace('vertex') call). Separate from config.embedding
|
|
248
|
+
* so the Vertex space can be backfilled + A/B-gated while Voyage stays live.
|
|
249
|
+
*/
|
|
250
|
+
project: optional("VERTEX_PROJECT", optional("GCP_PROJECT", "")),
|
|
251
|
+
location: optional("VERTEX_LOCATION", "us-central1"),
|
|
252
|
+
model: optional("VERTEX_EMBEDDING_MODEL", "gemini-embedding-001"),
|
|
253
|
+
/** Output dims — MRL-truncated to 1024 so vector(1024) columns + HNSW + match_* RPCs are unchanged. */
|
|
254
|
+
dimensions: parseInt(optional("VERTEX_EMBEDDING_DIMENSIONS", "1024"), 10),
|
|
255
|
+
/**
|
|
256
|
+
* Optional static OAuth token override (from `gcloud auth print-access-token`) for
|
|
257
|
+
* local dev / smoke tests. Empty in prod: Cloud Run mints a token from the attached
|
|
258
|
+
* service account via the metadata server (SDK-free), no static key stored.
|
|
259
|
+
*/
|
|
260
|
+
accessToken: optional("VERTEX_ACCESS_TOKEN", "")
|
|
261
|
+
},
|
|
139
262
|
openrouter: {
|
|
140
263
|
apiKey: optional("OPENROUTER_API_KEY", ""),
|
|
141
264
|
model: optional("OPENROUTER_MODEL", "meta-llama/llama-3.3-70b-instruct")
|
|
@@ -149,6 +272,35 @@ var init_config = __esm({
|
|
|
149
272
|
tavily: {
|
|
150
273
|
apiKey: optional("TAVILY_API_KEY", "")
|
|
151
274
|
},
|
|
275
|
+
higgsfield: {
|
|
276
|
+
/** Higgsfield API key ID — first half of the V2 `Authorization: Key <id>:<secret>` pair (server-only). Empty → /api/ansem/speak returns 501. */
|
|
277
|
+
apiKey: optional("HIGGSFIELD_API_KEY", ""),
|
|
278
|
+
/** Higgsfield API key SECRET — second half of the V2 `Key <id>:<secret>` pair. */
|
|
279
|
+
apiSecret: optional("HIGGSFIELD_API_SECRET", ""),
|
|
280
|
+
/** REST base URL (Higgsfield V2 API). */
|
|
281
|
+
apiBase: optional("HIGGSFIELD_API_BASE", "https://platform.higgsfield.ai"),
|
|
282
|
+
/**
|
|
283
|
+
* V2 model path for the seed_audio TTS model: create is POST {apiBase}/{ttsEndpoint}.
|
|
284
|
+
* Verified live against the Higgsfield V2 API — bytedance/seed-audio-1.0.
|
|
285
|
+
*/
|
|
286
|
+
ttsEndpoint: optional("HIGGSFIELD_TTS_ENDPOINT", "bytedance/seed-audio-1.0"),
|
|
287
|
+
/** Ansem voice — Higgsfield "Sterling" preset (founder's choice). */
|
|
288
|
+
voiceId: optional("ANSEM_VOICE_ID", "dc382508-c8bd-443c-8cb2-46e57b8d2e6f"),
|
|
289
|
+
voiceType: optional("ANSEM_VOICE_TYPE", "preset"),
|
|
290
|
+
/** Deep-voice tuning: seed_audio pitch_rate / speech_rate (integers, default 0). */
|
|
291
|
+
voicePitch: parseInt(optional("ANSEM_VOICE_PITCH", "-9"), 10),
|
|
292
|
+
voiceSpeechRate: parseInt(optional("ANSEM_VOICE_SPEECH_RATE", "-12"), 10),
|
|
293
|
+
/** Output audio format (seed_audio supports wav|mp3|pcm|ogg_opus). */
|
|
294
|
+
audioFormat: optional("ANSEM_VOICE_FORMAT", "mp3")
|
|
295
|
+
},
|
|
296
|
+
elevenlabs: {
|
|
297
|
+
/** ElevenLabs API key (server-only). Set → PRIMARY TTS (~1-3s, no lag); empty → falls back to Higgsfield. */
|
|
298
|
+
apiKey: optional("ELEVENLABS_API_KEY", ""),
|
|
299
|
+
/** Voice id — default "Brian" (deep, resonant, american). Swap via ELEVENLABS_VOICE_ID. */
|
|
300
|
+
voiceId: optional("ELEVENLABS_VOICE_ID", "nPczCjzI2devNBz1zQrb"),
|
|
301
|
+
/** Model — turbo for lowest latency. */
|
|
302
|
+
model: optional("ELEVENLABS_MODEL", "eleven_turbo_v2_5")
|
|
303
|
+
},
|
|
152
304
|
privy: {
|
|
153
305
|
appId: optional("PRIVY_APP_ID", ""),
|
|
154
306
|
appSecret: optional("PRIVY_APP_SECRET", ""),
|
|
@@ -230,28 +382,70 @@ var init_logger = __esm({
|
|
|
230
382
|
}
|
|
231
383
|
});
|
|
232
384
|
|
|
385
|
+
// packages/shared/src/core/migration-profile.ts
|
|
386
|
+
function defaultSupabaseConnection() {
|
|
387
|
+
return { url: config.supabase.url, serviceKey: config.supabase.serviceKey };
|
|
388
|
+
}
|
|
389
|
+
function resolveDbConnection(profile = config.migration, supabase2 = defaultSupabaseConnection()) {
|
|
390
|
+
if (!DB_TARGETS.includes(profile.dbTarget)) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`Invalid DB_TARGET '${profile.dbTarget}' (expected 'supabase' | 'cloudsql')`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
if (profile.dbTarget === "cloudsql") {
|
|
396
|
+
if (!profile.cloudsqlUrl || !profile.cloudsqlServiceKey) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
"DB_TARGET=cloudsql requires CLOUDSQL_PGREST_URL and CLOUDSQL_SERVICE_KEY"
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return { url: profile.cloudsqlUrl, serviceKey: profile.cloudsqlServiceKey };
|
|
402
|
+
}
|
|
403
|
+
return { url: supabase2.url, serviceKey: supabase2.serviceKey };
|
|
404
|
+
}
|
|
405
|
+
function activeEmbeddingSpace(profile = config.migration) {
|
|
406
|
+
if (!EMBEDDING_SPACES.includes(profile.embeddingActive)) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`Invalid EMBEDDING_ACTIVE '${profile.embeddingActive}' (expected 'voyage' | 'vertex')`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
return profile.embeddingActive;
|
|
412
|
+
}
|
|
413
|
+
function vectorRpcName(baseRpc, profile = config.migration) {
|
|
414
|
+
return activeEmbeddingSpace(profile) === "vertex" ? `${baseRpc}_vertex` : baseRpc;
|
|
415
|
+
}
|
|
416
|
+
var DB_TARGETS, EMBEDDING_SPACES;
|
|
417
|
+
var init_migration_profile = __esm({
|
|
418
|
+
"packages/shared/src/core/migration-profile.ts"() {
|
|
419
|
+
"use strict";
|
|
420
|
+
init_config();
|
|
421
|
+
DB_TARGETS = ["supabase", "cloudsql"];
|
|
422
|
+
EMBEDDING_SPACES = ["voyage", "vertex"];
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
233
426
|
// packages/shared/src/core/database.ts
|
|
234
427
|
var database_exports = {};
|
|
235
428
|
__export(database_exports, {
|
|
236
429
|
_setDb: () => _setDb,
|
|
237
430
|
claimForProcessing: () => claimForProcessing,
|
|
238
431
|
getDb: () => getDb,
|
|
432
|
+
getSchemaDriftReport: () => getSchemaDriftReport,
|
|
239
433
|
initDatabase: () => initDatabase,
|
|
240
434
|
isAlreadyProcessed: () => isAlreadyProcessed,
|
|
241
435
|
markProcessed: () => markProcessed
|
|
242
436
|
});
|
|
243
437
|
function getDb() {
|
|
244
438
|
if (!supabase) {
|
|
245
|
-
|
|
246
|
-
|
|
439
|
+
const conn = resolveDbConnection();
|
|
440
|
+
supabase = (0, import_supabase_js.createClient)(conn.url, conn.serviceKey);
|
|
441
|
+
log.info({ dbTarget: config.migration.dbTarget }, "Database client initialized");
|
|
247
442
|
}
|
|
248
443
|
return supabase;
|
|
249
444
|
}
|
|
250
445
|
function _setDb(client2) {
|
|
251
446
|
supabase = client2;
|
|
252
447
|
}
|
|
253
|
-
async function
|
|
254
|
-
const db = getDb();
|
|
448
|
+
async function runBootDdl(db) {
|
|
255
449
|
try {
|
|
256
450
|
const { error } = await db.rpc("exec_sql", {
|
|
257
451
|
query: `
|
|
@@ -323,6 +517,7 @@ async function initDatabase() {
|
|
|
323
517
|
input_memory_ids BIGINT[] DEFAULT '{}',
|
|
324
518
|
output TEXT NOT NULL,
|
|
325
519
|
new_memories_created BIGINT[] DEFAULT '{}',
|
|
520
|
+
owner_wallet TEXT,
|
|
326
521
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
327
522
|
);
|
|
328
523
|
|
|
@@ -407,7 +602,7 @@ async function initDatabase() {
|
|
|
407
602
|
target_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
408
603
|
link_type TEXT NOT NULL CHECK (link_type IN (
|
|
409
604
|
'supports', 'contradicts', 'elaborates', 'causes', 'follows', 'relates', 'resolves',
|
|
410
|
-
'happens_before', 'happens_after', 'concurrent_with'
|
|
605
|
+
'supersedes', 'happens_before', 'happens_after', 'concurrent_with'
|
|
411
606
|
)),
|
|
412
607
|
strength REAL DEFAULT 0.5,
|
|
413
608
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
@@ -442,7 +637,11 @@ async function initDatabase() {
|
|
|
442
637
|
WHERE ml.source_id = ANY(seed_ids)
|
|
443
638
|
AND ml.target_id != ALL(seed_ids)
|
|
444
639
|
AND ml.strength >= min_strength
|
|
445
|
-
AND (
|
|
640
|
+
AND (
|
|
641
|
+
filter_owner IS NULL
|
|
642
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
643
|
+
OR m.owner_wallet = filter_owner
|
|
644
|
+
)
|
|
446
645
|
UNION
|
|
447
646
|
SELECT DISTINCT ON (ml.source_id, ml.link_type)
|
|
448
647
|
ml.source_id AS memory_id,
|
|
@@ -454,7 +653,11 @@ async function initDatabase() {
|
|
|
454
653
|
WHERE ml.target_id = ANY(seed_ids)
|
|
455
654
|
AND ml.source_id != ALL(seed_ids)
|
|
456
655
|
AND ml.strength >= min_strength
|
|
457
|
-
AND (
|
|
656
|
+
AND (
|
|
657
|
+
filter_owner IS NULL
|
|
658
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
659
|
+
OR m.owner_wallet = filter_owner
|
|
660
|
+
)
|
|
458
661
|
ORDER BY strength DESC
|
|
459
662
|
LIMIT max_results;
|
|
460
663
|
$$;
|
|
@@ -482,6 +685,10 @@ async function initDatabase() {
|
|
|
482
685
|
ALTER TABLE dream_logs ADD CONSTRAINT dream_logs_session_type_check
|
|
483
686
|
CHECK (session_type IN ('consolidation', 'reflection', 'emergence', 'compaction', 'decay', 'contradiction_resolution'));
|
|
484
687
|
|
|
688
|
+
-- Migration 021: per-owner attribution for dream_logs (fixes totalDreamSessions leak)
|
|
689
|
+
ALTER TABLE dream_logs ADD COLUMN IF NOT EXISTS owner_wallet TEXT;
|
|
690
|
+
CREATE INDEX IF NOT EXISTS idx_dream_logs_owner ON dream_logs(owner_wallet);
|
|
691
|
+
|
|
485
692
|
-- Migration: add 'resolves' + temporal link types
|
|
486
693
|
ALTER TABLE memory_links DROP CONSTRAINT IF EXISTS memory_links_link_type_check;
|
|
487
694
|
ALTER TABLE memory_links ADD CONSTRAINT memory_links_link_type_check
|
|
@@ -663,6 +870,170 @@ async function initDatabase() {
|
|
|
663
870
|
CREATE INDEX IF NOT EXISTS idx_memories_event_date ON memories(event_date)
|
|
664
871
|
WHERE event_date IS NOT NULL;
|
|
665
872
|
|
|
873
|
+
-- Lexical index for keyword/BM25 search (encryption \xA79). content_tokens is
|
|
874
|
+
-- app-maintained; ts_summary (summary-only) is added on fresh deploys that lack it.
|
|
875
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS content_tokens tsvector;
|
|
876
|
+
CREATE INDEX IF NOT EXISTS idx_memories_content_tokens ON memories USING GIN(content_tokens);
|
|
877
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS provider_delegated BOOLEAN DEFAULT TRUE;
|
|
878
|
+
CREATE INDEX IF NOT EXISTS idx_memories_delegated ON memories(provider_delegated) WHERE encrypted = TRUE;
|
|
879
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS summary_ciphertext TEXT;
|
|
880
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS embedding_ciphertext TEXT;
|
|
881
|
+
|
|
882
|
+
-- Memory 3.0 Phase 1 (migration 044): bi-temporal validity + provenance (additive/nullable, inert until MEMORY_RECONCILE)
|
|
883
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS valid_from TIMESTAMPTZ;
|
|
884
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS invalid_at TIMESTAMPTZ;
|
|
885
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS superseded_by TEXT;
|
|
886
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS fact_key TEXT;
|
|
887
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS extractor_version TEXT;
|
|
888
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS extraction_confidence REAL;
|
|
889
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS source_turn_ref JSONB;
|
|
890
|
+
ALTER TABLE memories ADD COLUMN IF NOT EXISTS hash_id_v2 TEXT;
|
|
891
|
+
CREATE INDEX IF NOT EXISTS idx_memories_valid ON memories(id) WHERE invalid_at IS NULL;
|
|
892
|
+
CREATE INDEX IF NOT EXISTS idx_memories_fact_key ON memories(fact_key) WHERE fact_key IS NOT NULL;
|
|
893
|
+
CREATE INDEX IF NOT EXISTS idx_memories_hash_id_v2 ON memories(hash_id_v2) WHERE hash_id_v2 IS NOT NULL;
|
|
894
|
+
|
|
895
|
+
-- Memory 3.0 Phase 1 (migration 044): the C2 durable write outbox.
|
|
896
|
+
CREATE TABLE IF NOT EXISTS memory_write_jobs (
|
|
897
|
+
id BIGSERIAL PRIMARY KEY,
|
|
898
|
+
memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
899
|
+
job_type TEXT NOT NULL CHECK (job_type IN ('enrich', 'embed', 'link', 'extract', 'reconcile', 'backfill_v2')),
|
|
900
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'done', 'failed')),
|
|
901
|
+
attempts INT NOT NULL DEFAULT 0,
|
|
902
|
+
next_retry_at TIMESTAMPTZ DEFAULT NOW(),
|
|
903
|
+
last_error TEXT,
|
|
904
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
905
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
906
|
+
);
|
|
907
|
+
CREATE INDEX IF NOT EXISTS idx_write_jobs_claim ON memory_write_jobs(status, next_retry_at) WHERE status IN ('pending', 'failed');
|
|
908
|
+
CREATE INDEX IF NOT EXISTS idx_write_jobs_memory ON memory_write_jobs(memory_id);
|
|
909
|
+
CREATE INDEX IF NOT EXISTS idx_write_jobs_running ON memory_write_jobs(updated_at) WHERE status = 'running';
|
|
910
|
+
|
|
911
|
+
-- Memory 3.0 C2 outbox \u2014 MIRROR of migration 045 (byte-equivalent). The table above is in
|
|
912
|
+
-- the boot blob, so the claim RPC + idempotency objects MUST be too: otherwise a
|
|
913
|
+
-- boot-provisioned box gets the table but not the RPC and the worker silently never drains
|
|
914
|
+
-- (the migration-028 class). Keep in sync with 045.
|
|
915
|
+
DO $do$
|
|
916
|
+
BEGIN
|
|
917
|
+
ALTER TABLE memory_write_jobs DROP CONSTRAINT IF EXISTS memory_write_jobs_job_type_check;
|
|
918
|
+
ALTER TABLE memory_write_jobs ADD CONSTRAINT memory_write_jobs_job_type_check
|
|
919
|
+
CHECK (job_type IN ('enrich', 'embed', 'link', 'extract', 'reconcile', 'backfill_v2'));
|
|
920
|
+
EXCEPTION WHEN undefined_table THEN NULL; END $do$;
|
|
921
|
+
|
|
922
|
+
CREATE OR REPLACE FUNCTION claim_memory_write_jobs(
|
|
923
|
+
p_limit INT DEFAULT 20, p_stale_running INTERVAL DEFAULT INTERVAL '15 minutes', p_owner TEXT DEFAULT NULL
|
|
924
|
+
)
|
|
925
|
+
RETURNS TABLE (id BIGINT, memory_id BIGINT, job_type TEXT, attempts INT, owner_wallet TEXT)
|
|
926
|
+
LANGUAGE plpgsql AS $claimfn$
|
|
927
|
+
BEGIN
|
|
928
|
+
RETURN QUERY
|
|
929
|
+
UPDATE memory_write_jobs j
|
|
930
|
+
SET status = 'running', attempts = j.attempts + 1, updated_at = NOW()
|
|
931
|
+
FROM (
|
|
932
|
+
SELECT jj.id FROM memory_write_jobs jj JOIN memories m ON m.id = jj.memory_id
|
|
933
|
+
WHERE ((jj.status IN ('pending','failed') AND jj.next_retry_at IS NOT NULL AND jj.next_retry_at <= NOW())
|
|
934
|
+
OR (jj.status = 'running' AND jj.updated_at < NOW() - p_stale_running))
|
|
935
|
+
AND (p_owner IS NULL OR m.owner_wallet = p_owner OR (p_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL))
|
|
936
|
+
ORDER BY jj.next_retry_at ASC NULLS LAST LIMIT p_limit FOR UPDATE SKIP LOCKED
|
|
937
|
+
) claimed
|
|
938
|
+
WHERE j.id = claimed.id
|
|
939
|
+
RETURNING j.id, j.memory_id, j.job_type, j.attempts,
|
|
940
|
+
(SELECT mm.owner_wallet FROM memories mm WHERE mm.id = j.memory_id);
|
|
941
|
+
END; $claimfn$;
|
|
942
|
+
|
|
943
|
+
DO $do$
|
|
944
|
+
BEGIN
|
|
945
|
+
DELETE FROM memory_links a USING memory_links b
|
|
946
|
+
WHERE a.ctid < b.ctid AND a.source_id = b.source_id AND a.target_id = b.target_id AND a.link_type = b.link_type;
|
|
947
|
+
ALTER TABLE memory_links DROP CONSTRAINT IF EXISTS memory_links_unique_edge;
|
|
948
|
+
ALTER TABLE memory_links ADD CONSTRAINT memory_links_unique_edge UNIQUE (source_id, target_id, link_type);
|
|
949
|
+
EXCEPTION WHEN undefined_table THEN NULL; END $do$;
|
|
950
|
+
|
|
951
|
+
CREATE OR REPLACE FUNCTION upsert_entity_relation(
|
|
952
|
+
p_src BIGINT, p_tgt BIGINT, p_type TEXT, p_evidence BIGINT DEFAULT NULL, p_strength REAL DEFAULT 0.5
|
|
953
|
+
)
|
|
954
|
+
RETURNS VOID LANGUAGE sql AS $uerfn$
|
|
955
|
+
INSERT INTO entity_relations (source_entity_id, target_entity_id, relation_type, strength, evidence_memory_ids)
|
|
956
|
+
VALUES (p_src, p_tgt, p_type, LEAST(1.0, p_strength),
|
|
957
|
+
CASE WHEN p_evidence IS NULL THEN '{}'::bigint[] ELSE ARRAY[p_evidence] END)
|
|
958
|
+
ON CONFLICT (source_entity_id, target_entity_id, relation_type) DO UPDATE
|
|
959
|
+
SET strength = LEAST(1.0, entity_relations.strength +
|
|
960
|
+
CASE WHEN p_evidence IS NULL OR p_evidence = ANY(entity_relations.evidence_memory_ids) THEN 0.0 ELSE 0.1 END),
|
|
961
|
+
evidence_memory_ids = (SELECT ARRAY(SELECT DISTINCT e FROM unnest(entity_relations.evidence_memory_ids || EXCLUDED.evidence_memory_ids) AS e));
|
|
962
|
+
$uerfn$;
|
|
963
|
+
|
|
964
|
+
-- Memory 3.0 C1 (migration 046): reconciliation SHADOW decision log. MIRROR \u2014 byte-equivalent
|
|
965
|
+
-- to migration 046. Records a PROPOSED reconcile op per write without applying it, so enforce
|
|
966
|
+
-- can be greenlit from a labeled sample. Deliberately NOT added to CORE_TABLES (dormant,
|
|
967
|
+
-- default-off; must not trip SCHEMA DRIFT at boot on an un-migrated prod box \u2014 C2 precedent).
|
|
968
|
+
CREATE TABLE IF NOT EXISTS memory_reconciliation_log (
|
|
969
|
+
id BIGSERIAL PRIMARY KEY,
|
|
970
|
+
memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
971
|
+
owner_wallet TEXT,
|
|
972
|
+
mode TEXT NOT NULL DEFAULT 'shadow' CHECK (mode IN ('shadow','enforce')),
|
|
973
|
+
proposed_op TEXT NOT NULL CHECK (proposed_op IN ('add','update','noop','needs_router','skip')),
|
|
974
|
+
target_memory_id BIGINT,
|
|
975
|
+
max_cosine REAL,
|
|
976
|
+
band TEXT CHECK (band IN ('hi','mid','lo','none')),
|
|
977
|
+
router_used BOOLEAN NOT NULL DEFAULT false,
|
|
978
|
+
router_model TEXT,
|
|
979
|
+
fact_key TEXT,
|
|
980
|
+
reason TEXT,
|
|
981
|
+
label TEXT,
|
|
982
|
+
labeled_at TIMESTAMPTZ,
|
|
983
|
+
gate_version TEXT,
|
|
984
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
985
|
+
);
|
|
986
|
+
CREATE INDEX IF NOT EXISTS idx_reconcile_log_owner_created ON memory_reconciliation_log(owner_wallet, created_at);
|
|
987
|
+
CREATE INDEX IF NOT EXISTS idx_reconcile_log_op ON memory_reconciliation_log(proposed_op);
|
|
988
|
+
CREATE INDEX IF NOT EXISTS idx_reconcile_log_router ON memory_reconciliation_log(mode, router_used);
|
|
989
|
+
|
|
990
|
+
DO $do$
|
|
991
|
+
BEGIN
|
|
992
|
+
IF NOT EXISTS (
|
|
993
|
+
SELECT 1 FROM information_schema.columns
|
|
994
|
+
WHERE table_name = 'memories' AND column_name = 'ts_summary'
|
|
995
|
+
) THEN
|
|
996
|
+
ALTER TABLE memories ADD COLUMN ts_summary tsvector GENERATED ALWAYS AS (
|
|
997
|
+
setweight(to_tsvector('english', COALESCE(summary, '')), 'A')
|
|
998
|
+
) STORED;
|
|
999
|
+
CREATE INDEX IF NOT EXISTS idx_memories_ts_summary ON memories USING GIN(ts_summary);
|
|
1000
|
+
END IF;
|
|
1001
|
+
END $do$;
|
|
1002
|
+
|
|
1003
|
+
-- Semantic search across memory-level embeddings with metadata filtering (the always-on
|
|
1004
|
+
-- recall vector lane). MIRROR of migration 028 / supabase-schema.sql \u2014 8-arg filter_tags form.
|
|
1005
|
+
CREATE OR REPLACE FUNCTION match_memories(
|
|
1006
|
+
query_embedding vector(1024),
|
|
1007
|
+
match_threshold float DEFAULT 0.3,
|
|
1008
|
+
match_count int DEFAULT 10,
|
|
1009
|
+
filter_types text[] DEFAULT NULL,
|
|
1010
|
+
filter_user text DEFAULT NULL,
|
|
1011
|
+
min_decay float DEFAULT 0.1,
|
|
1012
|
+
filter_owner text DEFAULT NULL,
|
|
1013
|
+
filter_tags text[] DEFAULT NULL
|
|
1014
|
+
)
|
|
1015
|
+
RETURNS TABLE (id bigint, similarity float)
|
|
1016
|
+
LANGUAGE plpgsql AS $$
|
|
1017
|
+
BEGIN
|
|
1018
|
+
RETURN QUERY
|
|
1019
|
+
SELECT m.id, (1 - (m.embedding <=> query_embedding))::float AS similarity
|
|
1020
|
+
FROM memories m
|
|
1021
|
+
WHERE m.embedding IS NOT NULL
|
|
1022
|
+
AND m.decay_factor >= min_decay
|
|
1023
|
+
AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
|
|
1024
|
+
AND (filter_user IS NULL OR m.related_user = filter_user)
|
|
1025
|
+
AND (
|
|
1026
|
+
filter_owner IS NULL
|
|
1027
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
1028
|
+
OR m.owner_wallet = filter_owner
|
|
1029
|
+
)
|
|
1030
|
+
AND (filter_tags IS NULL OR m.tags && filter_tags)
|
|
1031
|
+
AND (1 - (m.embedding <=> query_embedding)) > match_threshold
|
|
1032
|
+
ORDER BY m.embedding <=> query_embedding
|
|
1033
|
+
LIMIT match_count;
|
|
1034
|
+
END;
|
|
1035
|
+
$$;
|
|
1036
|
+
|
|
666
1037
|
-- Temporal-aware semantic search RPC (Exp 9)
|
|
667
1038
|
CREATE OR REPLACE FUNCTION match_memories_temporal(
|
|
668
1039
|
query_embedding vector(1024),
|
|
@@ -686,7 +1057,11 @@ async function initDatabase() {
|
|
|
686
1057
|
AND m.decay_factor >= min_decay
|
|
687
1058
|
AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
|
|
688
1059
|
AND (filter_user IS NULL OR m.related_user = filter_user)
|
|
689
|
-
AND (
|
|
1060
|
+
AND (
|
|
1061
|
+
filter_owner IS NULL
|
|
1062
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
1063
|
+
OR m.owner_wallet = filter_owner
|
|
1064
|
+
)
|
|
690
1065
|
AND (filter_tags IS NULL OR m.tags && filter_tags)
|
|
691
1066
|
AND (1 - (m.embedding <=> query_embedding)) > match_threshold
|
|
692
1067
|
AND (start_date IS NULL OR COALESCE(m.event_date, m.created_at) >= start_date)
|
|
@@ -696,8 +1071,124 @@ async function initDatabase() {
|
|
|
696
1071
|
END;
|
|
697
1072
|
$$;
|
|
698
1073
|
|
|
699
|
-
--
|
|
700
|
-
--
|
|
1074
|
+
-- Fragment-level semantic search with deduplication to parent memory. Returns the highest
|
|
1075
|
+
-- similarity fragment per memory (the non-skipExpansion recall lane). MIRROR of
|
|
1076
|
+
-- supabase-schema.sql \u2014 6-arg migration-043 form; the 4-arg (migration 009) call still
|
|
1077
|
+
-- resolves against it via parameter defaults, so a boot-provisioned box is never left on a
|
|
1078
|
+
-- stale fragment signature.
|
|
1079
|
+
CREATE OR REPLACE FUNCTION match_memory_fragments(
|
|
1080
|
+
query_embedding vector(1024),
|
|
1081
|
+
match_threshold float DEFAULT 0.3,
|
|
1082
|
+
match_count int DEFAULT 10,
|
|
1083
|
+
filter_owner text DEFAULT NULL,
|
|
1084
|
+
min_decay float DEFAULT 0.0,
|
|
1085
|
+
filter_types text[] DEFAULT NULL
|
|
1086
|
+
)
|
|
1087
|
+
RETURNS TABLE (memory_id bigint, max_similarity float)
|
|
1088
|
+
LANGUAGE plpgsql AS $$
|
|
1089
|
+
BEGIN
|
|
1090
|
+
RETURN QUERY
|
|
1091
|
+
SELECT f.memory_id, MAX((1 - (f.embedding <=> query_embedding))::float) AS max_similarity
|
|
1092
|
+
FROM memory_fragments f
|
|
1093
|
+
JOIN memories m ON m.id = f.memory_id
|
|
1094
|
+
WHERE f.embedding IS NOT NULL
|
|
1095
|
+
AND (1 - (f.embedding <=> query_embedding)) > match_threshold
|
|
1096
|
+
AND m.decay_factor >= min_decay
|
|
1097
|
+
AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
|
|
1098
|
+
AND (
|
|
1099
|
+
filter_owner IS NULL
|
|
1100
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
1101
|
+
OR m.owner_wallet = filter_owner
|
|
1102
|
+
)
|
|
1103
|
+
GROUP BY f.memory_id
|
|
1104
|
+
ORDER BY max_similarity DESC
|
|
1105
|
+
LIMIT match_count;
|
|
1106
|
+
END;
|
|
1107
|
+
$$;
|
|
1108
|
+
|
|
1109
|
+
-- Encryption: owner key registry + per-memory wrapped DEKs (encryption \xA79, Plan 1 sync).
|
|
1110
|
+
CREATE TABLE IF NOT EXISTS encryption_keys (
|
|
1111
|
+
owner_wallet TEXT PRIMARY KEY,
|
|
1112
|
+
x25519_pubkey TEXT NOT NULL,
|
|
1113
|
+
verifier_ct TEXT NOT NULL,
|
|
1114
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
1115
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
1116
|
+
);
|
|
1117
|
+
CREATE TABLE IF NOT EXISTS memory_dek_wraps (
|
|
1118
|
+
memory_id BIGINT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
1119
|
+
recipient TEXT NOT NULL,
|
|
1120
|
+
wrapped_dek TEXT NOT NULL,
|
|
1121
|
+
wrap_pubkey TEXT NOT NULL,
|
|
1122
|
+
holder_wallet TEXT, -- (034) names the title_holder; NULL for owner/provider
|
|
1123
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
1124
|
+
CONSTRAINT memory_dek_wraps_recipient_chk CHECK (recipient IN ('owner', 'provider', 'title_holder')),
|
|
1125
|
+
CONSTRAINT memory_dek_wraps_holder_chk CHECK ((recipient = 'title_holder') = (holder_wallet IS NOT NULL))
|
|
1126
|
+
);
|
|
1127
|
+
-- (036) holder-aware uniqueness: one owner + one provider per memory (NULLS NOT DISTINCT),
|
|
1128
|
+
-- and one title_holder per (memory, holder) so a sale's seller + buyer wraps coexist (RT7).
|
|
1129
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_dek_wraps_identity
|
|
1130
|
+
ON memory_dek_wraps (memory_id, recipient, holder_wallet) NULLS NOT DISTINCT;
|
|
1131
|
+
CREATE INDEX IF NOT EXISTS idx_dek_wraps_memory ON memory_dek_wraps(memory_id);
|
|
1132
|
+
|
|
1133
|
+
-- Populate content_tokens from a transient plaintext arg (PostgREST can't express to_tsvector inline).
|
|
1134
|
+
-- setweight 'B' mirrors the old combined ts_summary weighting (summary 'A' > content 'B').
|
|
1135
|
+
CREATE OR REPLACE FUNCTION set_memory_content_tokens(p_memory_id bigint, p_text text)
|
|
1136
|
+
RETURNS void LANGUAGE sql AS $fn$
|
|
1137
|
+
UPDATE memories
|
|
1138
|
+
SET content_tokens = CASE
|
|
1139
|
+
WHEN p_text IS NULL OR p_text = '' THEN NULL
|
|
1140
|
+
ELSE setweight(to_tsvector('english', p_text), 'B')
|
|
1141
|
+
END
|
|
1142
|
+
WHERE id = p_memory_id;
|
|
1143
|
+
$fn$;
|
|
1144
|
+
|
|
1145
|
+
-- Atomic revoke: clear plaintext + drop the provider wrap in one transaction (encryption \xA77).
|
|
1146
|
+
CREATE OR REPLACE FUNCTION revoke_memory(p_memory_id bigint, p_summary_ct text, p_embedding_ct text)
|
|
1147
|
+
RETURNS void LANGUAGE plpgsql AS $rev$
|
|
1148
|
+
BEGIN
|
|
1149
|
+
UPDATE memories SET
|
|
1150
|
+
summary = '',
|
|
1151
|
+
summary_ciphertext = p_summary_ct,
|
|
1152
|
+
embedding = NULL,
|
|
1153
|
+
embedding_ciphertext = NULLIF(p_embedding_ct, ''),
|
|
1154
|
+
content_tokens = NULL,
|
|
1155
|
+
provider_delegated = false
|
|
1156
|
+
WHERE id = p_memory_id;
|
|
1157
|
+
DELETE FROM memory_dek_wraps WHERE memory_id = p_memory_id AND recipient = 'provider';
|
|
1158
|
+
-- Fragment parity (migration 043): fragments hold plaintext + live embeddings.
|
|
1159
|
+
DELETE FROM memory_fragments WHERE memory_id = p_memory_id;
|
|
1160
|
+
END;
|
|
1161
|
+
$rev$;
|
|
1162
|
+
|
|
1163
|
+
-- Atomic re-delegate (encryption \xA77) \u2014 inverse of revoke_memory. Restores plaintext
|
|
1164
|
+
-- summary/embedding, rebuilds content_tokens via the canonical builder, clears ciphertext
|
|
1165
|
+
-- cols, sets provider_delegated=true, (re-)inserts the provider wrap.
|
|
1166
|
+
CREATE OR REPLACE FUNCTION redelegate_memory(
|
|
1167
|
+
p_memory_id bigint,
|
|
1168
|
+
p_summary text,
|
|
1169
|
+
p_embedding text,
|
|
1170
|
+
p_content text,
|
|
1171
|
+
p_wrapped_dek text,
|
|
1172
|
+
p_wrap_pubkey text
|
|
1173
|
+
)
|
|
1174
|
+
RETURNS void LANGUAGE plpgsql AS $redel$
|
|
1175
|
+
BEGIN
|
|
1176
|
+
UPDATE memories SET
|
|
1177
|
+
summary = p_summary,
|
|
1178
|
+
summary_ciphertext = NULL,
|
|
1179
|
+
embedding = NULLIF(p_embedding, '')::vector,
|
|
1180
|
+
embedding_ciphertext = NULL,
|
|
1181
|
+
provider_delegated = true
|
|
1182
|
+
WHERE id = p_memory_id;
|
|
1183
|
+
PERFORM set_memory_content_tokens(p_memory_id, p_content);
|
|
1184
|
+
INSERT INTO memory_dek_wraps (memory_id, recipient, wrapped_dek, wrap_pubkey)
|
|
1185
|
+
VALUES (p_memory_id, 'provider', p_wrapped_dek, p_wrap_pubkey)
|
|
1186
|
+
ON CONFLICT (memory_id, recipient) DO UPDATE
|
|
1187
|
+
SET wrapped_dek = EXCLUDED.wrapped_dek, wrap_pubkey = EXCLUDED.wrap_pubkey;
|
|
1188
|
+
END;
|
|
1189
|
+
$redel$;
|
|
1190
|
+
|
|
1191
|
+
-- BM25-ranked full-text search RPC (Exp 8) \u2014 dual-column (ts_summary + content_tokens)
|
|
701
1192
|
CREATE OR REPLACE FUNCTION bm25_search_memories(
|
|
702
1193
|
search_query text,
|
|
703
1194
|
match_count int DEFAULT 20,
|
|
@@ -716,11 +1207,17 @@ async function initDatabase() {
|
|
|
716
1207
|
RETURN;
|
|
717
1208
|
END IF;
|
|
718
1209
|
RETURN QUERY
|
|
719
|
-
SELECT m.id,
|
|
1210
|
+
SELECT m.id,
|
|
1211
|
+
(ts_rank_cd(COALESCE(m.ts_summary, ''::tsvector), tsquery_val, 32)
|
|
1212
|
+
+ ts_rank_cd(COALESCE(m.content_tokens, ''::tsvector), tsquery_val, 32))::float AS rank
|
|
720
1213
|
FROM memories m
|
|
721
|
-
WHERE m.ts_summary @@ tsquery_val
|
|
1214
|
+
WHERE (m.ts_summary @@ tsquery_val OR m.content_tokens @@ tsquery_val)
|
|
722
1215
|
AND m.decay_factor >= min_decay
|
|
723
|
-
AND (
|
|
1216
|
+
AND (
|
|
1217
|
+
filter_owner IS NULL
|
|
1218
|
+
OR (filter_owner = '__BOT_OWN__' AND m.owner_wallet IS NULL)
|
|
1219
|
+
OR m.owner_wallet = filter_owner
|
|
1220
|
+
)
|
|
724
1221
|
AND (filter_types IS NULL OR m.memory_type = ANY(filter_types))
|
|
725
1222
|
AND (filter_tags IS NULL OR m.tags && filter_tags)
|
|
726
1223
|
ORDER BY rank DESC
|
|
@@ -749,9 +1246,31 @@ async function initDatabase() {
|
|
|
749
1246
|
tokens_prompt INTEGER,
|
|
750
1247
|
tokens_completion INTEGER,
|
|
751
1248
|
memory_ids INTEGER[],
|
|
1249
|
+
frontier_tokens INTEGER,
|
|
1250
|
+
memories_used INTEGER,
|
|
1251
|
+
tokens_saved INTEGER,
|
|
752
1252
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
753
1253
|
);
|
|
754
1254
|
CREATE INDEX IF NOT EXISTS idx_chat_msg_conv ON chat_messages(conversation_id, created_at);
|
|
1255
|
+
CREATE INDEX IF NOT EXISTS idx_chat_msg_saved ON chat_messages(created_at) WHERE tokens_saved IS NOT NULL;
|
|
1256
|
+
-- Proof counter totals. "today" is UTC-based (resets 00:00 UTC). Full-table aggregate:
|
|
1257
|
+
-- callers MUST use a server-side TTL cache (see proof.routes.ts). Mirror of migration 026.
|
|
1258
|
+
CREATE OR REPLACE FUNCTION proof_tokens_saved_totals()
|
|
1259
|
+
RETURNS TABLE(
|
|
1260
|
+
measured_saved bigint,
|
|
1261
|
+
measured_today bigint,
|
|
1262
|
+
measured_frontier bigint,
|
|
1263
|
+
historical_prompt_sum bigint,
|
|
1264
|
+
n bigint
|
|
1265
|
+
) LANGUAGE sql STABLE AS $$
|
|
1266
|
+
SELECT
|
|
1267
|
+
COALESCE(SUM(tokens_saved) FILTER (WHERE tokens_saved IS NOT NULL), 0)::bigint,
|
|
1268
|
+
COALESCE(SUM(tokens_saved) FILTER (WHERE tokens_saved IS NOT NULL AND created_at >= date_trunc('day', now())), 0)::bigint,
|
|
1269
|
+
COALESCE(SUM(frontier_tokens) FILTER (WHERE tokens_saved IS NOT NULL), 0)::bigint,
|
|
1270
|
+
COALESCE(SUM(tokens_prompt) FILTER (WHERE tokens_saved IS NULL), 0)::bigint,
|
|
1271
|
+
COUNT(*) FILTER (WHERE tokens_saved IS NOT NULL)::bigint
|
|
1272
|
+
FROM chat_messages;
|
|
1273
|
+
$$;
|
|
755
1274
|
|
|
756
1275
|
-- Chat billing: balances, top-ups, and per-message usage
|
|
757
1276
|
CREATE TABLE IF NOT EXISTS chat_balances (
|
|
@@ -806,6 +1325,50 @@ async function initDatabase() {
|
|
|
806
1325
|
);
|
|
807
1326
|
CREATE INDEX IF NOT EXISTS idx_wiki_pack_installations_owner
|
|
808
1327
|
ON wiki_pack_installations(owner_wallet);
|
|
1328
|
+
|
|
1329
|
+
-- Migration 019: corrected access-boost RPC (no importance mutation on read).
|
|
1330
|
+
-- Re-asserted on every boot so the read->rank->read feedback loop can't return.
|
|
1331
|
+
-- importance_boosts is kept for call-site signature compatibility and ignored.
|
|
1332
|
+
-- Drop the stale single-arg overload (migration 003) so the two-arg version is
|
|
1333
|
+
-- unambiguous and the lockdown below can't be bypassed via an unrevoked overload.
|
|
1334
|
+
DROP FUNCTION IF EXISTS batch_boost_memory_access(bigint[]);
|
|
1335
|
+
CREATE OR REPLACE FUNCTION batch_boost_memory_access(
|
|
1336
|
+
memory_ids BIGINT[],
|
|
1337
|
+
importance_boosts DOUBLE PRECISION[] DEFAULT NULL
|
|
1338
|
+
) RETURNS void LANGUAGE plpgsql AS $fn$
|
|
1339
|
+
BEGIN
|
|
1340
|
+
UPDATE memories
|
|
1341
|
+
SET access_count = access_count + 1,
|
|
1342
|
+
last_accessed = NOW(),
|
|
1343
|
+
decay_factor = LEAST(1.0, decay_factor + 0.1)
|
|
1344
|
+
WHERE id = ANY(memory_ids);
|
|
1345
|
+
END;
|
|
1346
|
+
$fn$;
|
|
1347
|
+
|
|
1348
|
+
-- Migration 020: lock down dangerous SECURITY DEFINER RPCs to service_role only.
|
|
1349
|
+
-- exec_sql/boost RPCs must never be callable by anon/authenticated (the publishable
|
|
1350
|
+
-- key ships in client apps). Wrapped so a not-yet-created function never aborts boot.
|
|
1351
|
+
DO $do$ BEGIN
|
|
1352
|
+
REVOKE EXECUTE ON FUNCTION exec_sql(text) FROM PUBLIC, anon, authenticated;
|
|
1353
|
+
GRANT EXECUTE ON FUNCTION exec_sql(text) TO service_role;
|
|
1354
|
+
EXCEPTION WHEN undefined_function THEN NULL; END $do$;
|
|
1355
|
+
|
|
1356
|
+
DO $do$ BEGIN
|
|
1357
|
+
REVOKE EXECUTE ON FUNCTION batch_boost_memory_access(bigint[], double precision[]) FROM PUBLIC, anon, authenticated;
|
|
1358
|
+
GRANT EXECUTE ON FUNCTION batch_boost_memory_access(bigint[], double precision[]) TO service_role;
|
|
1359
|
+
EXCEPTION WHEN undefined_function THEN NULL; END $do$;
|
|
1360
|
+
|
|
1361
|
+
DO $do$ BEGIN
|
|
1362
|
+
REVOKE EXECUTE ON FUNCTION boost_memory_importance(bigint, double precision, double precision) FROM PUBLIC, anon, authenticated;
|
|
1363
|
+
GRANT EXECUTE ON FUNCTION boost_memory_importance(bigint, double precision, double precision) TO service_role;
|
|
1364
|
+
EXCEPTION WHEN undefined_function THEN NULL; END $do$;
|
|
1365
|
+
|
|
1366
|
+
-- Migration 022 (guard portion): reject empty content on NEW writes in every env.
|
|
1367
|
+
-- NOT VALID enforces on new INSERT/UPDATE without scanning existing rows; the
|
|
1368
|
+
-- one-time cleanup of legacy blank rows + VALIDATE lives in the manual 022 file.
|
|
1369
|
+
DO $do$ BEGIN
|
|
1370
|
+
ALTER TABLE memories ADD CONSTRAINT memories_content_nonempty CHECK (length(btrim(content)) > 0) NOT VALID;
|
|
1371
|
+
EXCEPTION WHEN duplicate_object THEN NULL; END $do$;
|
|
809
1372
|
`
|
|
810
1373
|
});
|
|
811
1374
|
if (error) {
|
|
@@ -814,7 +1377,75 @@ async function initDatabase() {
|
|
|
814
1377
|
} catch {
|
|
815
1378
|
log.warn("rpc exec_sql not available. Create tables via Supabase SQL editor.");
|
|
816
1379
|
}
|
|
817
|
-
|
|
1380
|
+
}
|
|
1381
|
+
function getSchemaDriftReport() {
|
|
1382
|
+
return lastSchemaReport;
|
|
1383
|
+
}
|
|
1384
|
+
async function verifyCoreSchema(db) {
|
|
1385
|
+
const missingTables = [];
|
|
1386
|
+
const probeErrors = [];
|
|
1387
|
+
for (const table of CORE_TABLES) {
|
|
1388
|
+
try {
|
|
1389
|
+
const { error } = await db.from(table).select("id", { head: true, count: "exact" }).limit(1);
|
|
1390
|
+
if (error) {
|
|
1391
|
+
if (MISSING_RE.test(error.message ?? "")) missingTables.push(table);
|
|
1392
|
+
else probeErrors.push(`${table}: ${error.message}`);
|
|
1393
|
+
}
|
|
1394
|
+
} catch (err) {
|
|
1395
|
+
probeErrors.push(`${table}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
const brokenRpcs = [];
|
|
1399
|
+
for (const probe of CORE_RPC_PROBES) {
|
|
1400
|
+
try {
|
|
1401
|
+
const { error } = await db.rpc(probe.name, probe.args);
|
|
1402
|
+
if (error && MISSING_RE.test(error.message ?? "")) brokenRpcs.push(probe.name);
|
|
1403
|
+
} catch (err) {
|
|
1404
|
+
probeErrors.push(`${probe.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return { missingTables, brokenRpcs, probeErrors };
|
|
1408
|
+
}
|
|
1409
|
+
async function initDatabase(dbOverride) {
|
|
1410
|
+
const db = dbOverride ?? getDb();
|
|
1411
|
+
const mode = process.env.INIT_DB_MODE ?? "auto";
|
|
1412
|
+
if (mode === "replay") {
|
|
1413
|
+
await runBootDdl(db);
|
|
1414
|
+
lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: "replayed", missingTables: [], brokenRpcs: [] };
|
|
1415
|
+
log.info("Database initialized (legacy replay mode)");
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
let { missingTables, brokenRpcs, probeErrors } = await verifyCoreSchema(db);
|
|
1419
|
+
if (mode === "auto" && missingTables.includes("memories")) {
|
|
1420
|
+
log.info("Fresh database detected (no memories table) \u2014 running boot DDL");
|
|
1421
|
+
await runBootDdl(db);
|
|
1422
|
+
({ missingTables, brokenRpcs, probeErrors } = await verifyCoreSchema(db));
|
|
1423
|
+
const healthy2 = missingTables.length === 0 && brokenRpcs.length === 0;
|
|
1424
|
+
lastSchemaReport = {
|
|
1425
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1426
|
+
status: healthy2 ? "fresh-bootstrapped" : "drift",
|
|
1427
|
+
missingTables,
|
|
1428
|
+
brokenRpcs
|
|
1429
|
+
};
|
|
1430
|
+
if (healthy2) log.info("Database bootstrapped from boot DDL");
|
|
1431
|
+
else log.error({ missingTables, brokenRpcs }, "SCHEMA DRIFT after fresh bootstrap \u2014 check exec_sql availability");
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
if (probeErrors.length > 0 && missingTables.length === 0 && brokenRpcs.length === 0) {
|
|
1435
|
+
lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: "unknown", missingTables, brokenRpcs };
|
|
1436
|
+
log.warn({ probeErrors: probeErrors.slice(0, 3) }, "Schema verification inconclusive (probe errors); skipping");
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
const healthy = missingTables.length === 0 && brokenRpcs.length === 0;
|
|
1440
|
+
lastSchemaReport = { at: (/* @__PURE__ */ new Date()).toISOString(), status: healthy ? "ok" : "drift", missingTables, brokenRpcs };
|
|
1441
|
+
if (healthy) {
|
|
1442
|
+
log.info("Database schema verified (boot blob frozen; migrations are the single schema writer)");
|
|
1443
|
+
} else {
|
|
1444
|
+
log.error(
|
|
1445
|
+
{ missingTables, brokenRpcs },
|
|
1446
|
+
"SCHEMA DRIFT \u2014 core objects missing on an established database; apply the corresponding migration by hand (the boot blob no longer auto-repairs)"
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
818
1449
|
}
|
|
819
1450
|
async function isAlreadyProcessed(tweetId) {
|
|
820
1451
|
const db = getDb();
|
|
@@ -847,14 +1478,44 @@ async function markProcessed(tweetId, feature, responseTweetId, extra) {
|
|
|
847
1478
|
processed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
848
1479
|
});
|
|
849
1480
|
}
|
|
850
|
-
var import_supabase_js, log, supabase;
|
|
1481
|
+
var import_supabase_js, log, supabase, CORE_TABLES, CORE_RPC_PROBES, lastSchemaReport, MISSING_RE;
|
|
851
1482
|
var init_database = __esm({
|
|
852
1483
|
"packages/shared/src/core/database.ts"() {
|
|
853
1484
|
"use strict";
|
|
854
1485
|
import_supabase_js = require("@supabase/supabase-js");
|
|
855
1486
|
init_config();
|
|
856
1487
|
init_logger();
|
|
1488
|
+
init_migration_profile();
|
|
857
1489
|
log = createChildLogger("database");
|
|
1490
|
+
CORE_TABLES = [
|
|
1491
|
+
"memories",
|
|
1492
|
+
"memory_fragments",
|
|
1493
|
+
"memory_links",
|
|
1494
|
+
"agent_keys",
|
|
1495
|
+
"dream_logs",
|
|
1496
|
+
"rate_limits",
|
|
1497
|
+
"chat_conversations",
|
|
1498
|
+
"chat_messages"
|
|
1499
|
+
];
|
|
1500
|
+
CORE_RPC_PROBES = [
|
|
1501
|
+
{ name: "get_linked_memories", args: { seed_ids: [], min_strength: 0.1, max_results: 1, filter_owner: null } },
|
|
1502
|
+
{ name: "bm25_search_memories", args: { search_query: "", match_count: 1, min_decay: 0.1, filter_owner: null } },
|
|
1503
|
+
// Vector recall lanes (memory.ts recall). match_memories is the always-on memory-level lane;
|
|
1504
|
+
// match_memory_fragments is the fragment lane (non-skipExpansion path). Both are silently absent
|
|
1505
|
+
// from a stale boot blob, so probe them so the gap surfaces as drift instead of a dead lane.
|
|
1506
|
+
// Vector-safe args (null embedding + match_count 0) make each a no-op read. filter_tags pins the
|
|
1507
|
+
// migration-028 8-arg match_memories overload recall calls unconditionally (catches a pre-028 box
|
|
1508
|
+
// where recall would break); the fragment probe stays the 4/6-arg common subset (min_decay +
|
|
1509
|
+
// filter_types are opt-in via MEMORY_FRAGMENT_FILTERS) so it never false-drifts a pre-043 box
|
|
1510
|
+
// still serving the migration-009 4-arg form.
|
|
1511
|
+
{ name: "match_memories", args: { query_embedding: null, match_count: 0, filter_owner: null, filter_tags: null } },
|
|
1512
|
+
{ name: "match_memory_fragments", args: { query_embedding: null, match_count: 0, filter_owner: null } },
|
|
1513
|
+
// Memory 3.0 C2: probe the outbox claim RPC so a table-without-RPC state (the §6 hazard) is
|
|
1514
|
+
// caught at boot as drift rather than silently never draining.
|
|
1515
|
+
{ name: "claim_memory_write_jobs", args: { p_limit: 0 } }
|
|
1516
|
+
];
|
|
1517
|
+
lastSchemaReport = null;
|
|
1518
|
+
MISSING_RE = /does not exist|could not find|schema cache/i;
|
|
858
1519
|
}
|
|
859
1520
|
});
|
|
860
1521
|
|
|
@@ -967,6 +1628,17 @@ var init_guardrails = __esm({
|
|
|
967
1628
|
}
|
|
968
1629
|
});
|
|
969
1630
|
|
|
1631
|
+
// packages/shared/src/core/model-capabilities.ts
|
|
1632
|
+
function modelRejectsSamplingParams(model) {
|
|
1633
|
+
if (!model) return false;
|
|
1634
|
+
return /opus[-_.]?4[-_.]?[789]\b|opus[-_.]?[5-9]\b/i.test(model);
|
|
1635
|
+
}
|
|
1636
|
+
var init_model_capabilities = __esm({
|
|
1637
|
+
"packages/shared/src/core/model-capabilities.ts"() {
|
|
1638
|
+
"use strict";
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
|
|
970
1642
|
// packages/shared/src/core/openrouter-client.ts
|
|
971
1643
|
function getModelForFunction(fn) {
|
|
972
1644
|
return COGNITIVE_MODEL_MAP[fn] || OPENROUTER_MODELS["llama-70b"];
|
|
@@ -1033,7 +1705,8 @@ async function generateOpenRouterResponse(opts) {
|
|
|
1033
1705
|
model,
|
|
1034
1706
|
messages,
|
|
1035
1707
|
max_tokens: maxTokens,
|
|
1036
|
-
|
|
1708
|
+
// Opus 4.7+ via OpenRouter rejects temperature/top_p — strip for those models.
|
|
1709
|
+
...modelRejectsSamplingParams(model) ? {} : { temperature: opts.temperature ?? 0.7 }
|
|
1037
1710
|
})
|
|
1038
1711
|
});
|
|
1039
1712
|
if (!response.ok) {
|
|
@@ -1064,6 +1737,7 @@ var init_openrouter_client = __esm({
|
|
|
1064
1737
|
"packages/shared/src/core/openrouter-client.ts"() {
|
|
1065
1738
|
"use strict";
|
|
1066
1739
|
init_logger();
|
|
1740
|
+
init_model_capabilities();
|
|
1067
1741
|
log3 = createChildLogger("openrouter");
|
|
1068
1742
|
OPENROUTER_API_URL = "https://openrouter.ai/api/v1";
|
|
1069
1743
|
OPENROUTER_MODELS = {
|
|
@@ -1126,7 +1800,7 @@ var init_openrouter_client = __esm({
|
|
|
1126
1800
|
});
|
|
1127
1801
|
|
|
1128
1802
|
// packages/shared/src/utils/constants.ts
|
|
1129
|
-
var MEMO_PROGRAM_ID, SOLSCAN_TX_BASE_URL, TWEET_MAX_LENGTH, BOT_X_HANDLE, MEMORY_MIN_DECAY, MEMORY_MAX_CONTENT_LENGTH, MEMORY_MAX_SUMMARY_LENGTH, DECAY_RATES, RECENCY_DECAY_BASE, RETRIEVAL_WEIGHT_RECENCY, RETRIEVAL_WEIGHT_RELEVANCE, RETRIEVAL_WEIGHT_IMPORTANCE, RETRIEVAL_WEIGHT_VECTOR, KNOWLEDGE_TYPE_BOOST, VECTOR_MATCH_THRESHOLD, BOND_TYPE_WEIGHTS, RETRIEVAL_WEIGHT_GRAPH, RETRIEVAL_WEIGHT_COOCCURRENCE, LINK_SIMILARITY_THRESHOLD, MAX_AUTO_LINKS, LINK_CO_RETRIEVAL_BOOST, INTERNAL_MEMORY_SOURCES,
|
|
1803
|
+
var MEMO_PROGRAM_ID, SOLSCAN_TX_BASE_URL, TWEET_MAX_LENGTH, BOT_X_HANDLE, MEMORY_MIN_DECAY, MEMORY_MAX_CONTENT_LENGTH, MEMORY_MAX_SUMMARY_LENGTH, DECAY_RATES, RECENCY_DECAY_BASE, RETRIEVAL_WEIGHT_RECENCY, RETRIEVAL_WEIGHT_RELEVANCE, RETRIEVAL_WEIGHT_IMPORTANCE, RETRIEVAL_WEIGHT_VECTOR, RETRIEVAL_WEIGHT_BM25, KNOWLEDGE_TYPE_BOOST, VECTOR_MATCH_THRESHOLD, BOND_TYPE_WEIGHTS, RETRIEVAL_WEIGHT_GRAPH, RETRIEVAL_WEIGHT_COOCCURRENCE, LINK_SIMILARITY_THRESHOLD, MAX_AUTO_LINKS, LINK_CO_RETRIEVAL_BOOST, INTERNAL_MEMORY_SOURCES, EMBEDDING_FRAGMENT_MAX_LENGTH, REFLECTION_IMPORTANCE_THRESHOLD, REFLECTION_MIN_INTERVAL_MS, WHALE_SELL_COOLDOWN_MS, MEMO_MAX_LENGTH;
|
|
1130
1804
|
var init_constants = __esm({
|
|
1131
1805
|
"packages/shared/src/utils/constants.ts"() {
|
|
1132
1806
|
"use strict";
|
|
@@ -1150,10 +1824,11 @@ var init_constants = __esm({
|
|
|
1150
1824
|
// Journal entries persist like knowledge
|
|
1151
1825
|
};
|
|
1152
1826
|
RECENCY_DECAY_BASE = 0.995;
|
|
1153
|
-
RETRIEVAL_WEIGHT_RECENCY = 1;
|
|
1154
|
-
RETRIEVAL_WEIGHT_RELEVANCE = 2;
|
|
1155
|
-
RETRIEVAL_WEIGHT_IMPORTANCE = 2;
|
|
1156
|
-
RETRIEVAL_WEIGHT_VECTOR = 4;
|
|
1827
|
+
RETRIEVAL_WEIGHT_RECENCY = Number(process.env.RETRIEVAL_WEIGHT_RECENCY ?? "1.0");
|
|
1828
|
+
RETRIEVAL_WEIGHT_RELEVANCE = Number(process.env.RETRIEVAL_WEIGHT_RELEVANCE ?? "2.0");
|
|
1829
|
+
RETRIEVAL_WEIGHT_IMPORTANCE = Number(process.env.RETRIEVAL_WEIGHT_IMPORTANCE ?? "2.0");
|
|
1830
|
+
RETRIEVAL_WEIGHT_VECTOR = Number(process.env.RETRIEVAL_WEIGHT_VECTOR ?? "4.0");
|
|
1831
|
+
RETRIEVAL_WEIGHT_BM25 = Number(process.env.RETRIEVAL_WEIGHT_BM25 ?? "0.15");
|
|
1157
1832
|
KNOWLEDGE_TYPE_BOOST = {
|
|
1158
1833
|
semantic: 0.15,
|
|
1159
1834
|
// Distilled knowledge gets meaningful boost
|
|
@@ -1170,6 +1845,8 @@ var init_constants = __esm({
|
|
|
1170
1845
|
BOND_TYPE_WEIGHTS = {
|
|
1171
1846
|
causes: 1,
|
|
1172
1847
|
supports: 0.9,
|
|
1848
|
+
supersedes: 0.85,
|
|
1849
|
+
// decisive replacement: new fact overrides an older one (Memory 3.0 C1)
|
|
1173
1850
|
concurrent_with: 0.8,
|
|
1174
1851
|
resolves: 0.8,
|
|
1175
1852
|
happens_before: 0.7,
|
|
@@ -1194,7 +1871,7 @@ var init_constants = __esm({
|
|
|
1194
1871
|
"dream-cycle",
|
|
1195
1872
|
"meditation"
|
|
1196
1873
|
]);
|
|
1197
|
-
|
|
1874
|
+
EMBEDDING_FRAGMENT_MAX_LENGTH = 2e3;
|
|
1198
1875
|
REFLECTION_IMPORTANCE_THRESHOLD = 2;
|
|
1199
1876
|
REFLECTION_MIN_INTERVAL_MS = 30 * 60 * 1e3;
|
|
1200
1877
|
WHALE_SELL_COOLDOWN_MS = 30 * 60 * 1e3;
|
|
@@ -1291,10 +1968,12 @@ ${userContent || "(no message, just a mention)"}`;
|
|
|
1291
1968
|
cognitiveFunction: options.cognitiveFunction
|
|
1292
1969
|
});
|
|
1293
1970
|
} else {
|
|
1971
|
+
const directModel = getModel();
|
|
1294
1972
|
const response = await getClient().messages.create({
|
|
1295
|
-
model:
|
|
1973
|
+
model: directModel,
|
|
1296
1974
|
max_tokens: options.maxTokens || 1024,
|
|
1297
|
-
temperature
|
|
1975
|
+
// Opus 4.7+ rejects temperature/top_p (HTTP 400) — strip for those models.
|
|
1976
|
+
...modelRejectsSamplingParams(directModel) ? {} : { temperature: 0.9 },
|
|
1298
1977
|
system: systemPrompt,
|
|
1299
1978
|
messages: [{ role: "user", content: userContent }]
|
|
1300
1979
|
});
|
|
@@ -1321,10 +2000,11 @@ ${userContent || "(no message, just a mention)"}`;
|
|
|
1321
2000
|
}
|
|
1322
2001
|
async function generateImportanceScore(description) {
|
|
1323
2002
|
const importancePrompt = process.env.CLUDE_IMPORTANCE_PROMPT || "You rate the importance of events for an AI agent. Respond with ONLY a single integer from 1 to 10. 1 = purely mundane. 5 = moderately important. 10 = extremely significant.";
|
|
2003
|
+
const scoreModel = getModel();
|
|
1324
2004
|
const response = await getClient().messages.create({
|
|
1325
|
-
model:
|
|
2005
|
+
model: scoreModel,
|
|
1326
2006
|
max_tokens: 10,
|
|
1327
|
-
temperature: 0,
|
|
2007
|
+
...modelRejectsSamplingParams(scoreModel) ? {} : { temperature: 0 },
|
|
1328
2008
|
system: importancePrompt,
|
|
1329
2009
|
messages: [{
|
|
1330
2010
|
role: "user",
|
|
@@ -1353,6 +2033,7 @@ var init_claude_client = __esm({
|
|
|
1353
2033
|
init_guardrails();
|
|
1354
2034
|
init_openrouter_client();
|
|
1355
2035
|
init_constants();
|
|
2036
|
+
init_model_capabilities();
|
|
1356
2037
|
log4 = createChildLogger("claude-client");
|
|
1357
2038
|
_systemPromptProvider = () => "You are an AI assistant with persistent memory.";
|
|
1358
2039
|
_responsePostProcessor = (t) => t;
|
|
@@ -1373,11 +2054,18 @@ var init_claude_client = __esm({
|
|
|
1373
2054
|
var embeddings_exports = {};
|
|
1374
2055
|
__export(embeddings_exports, {
|
|
1375
2056
|
_configureEmbeddings: () => _configureEmbeddings,
|
|
2057
|
+
_fetchMetadataToken: () => _fetchMetadataToken,
|
|
2058
|
+
_resetVertexAuthCache: () => _resetVertexAuthCache,
|
|
1376
2059
|
generateEmbedding: () => generateEmbedding,
|
|
2060
|
+
generateEmbeddingForSpace: () => generateEmbeddingForSpace,
|
|
1377
2061
|
generateEmbeddings: () => generateEmbeddings,
|
|
1378
2062
|
generateQueryEmbedding: () => generateQueryEmbedding,
|
|
2063
|
+
generateQueryEmbeddingForSpace: () => generateQueryEmbeddingForSpace,
|
|
2064
|
+
generateVertexEmbedding: () => generateVertexEmbedding,
|
|
2065
|
+
generateVertexEmbeddings: () => generateVertexEmbeddings,
|
|
1379
2066
|
getCachedEmbedding: () => getCachedEmbedding,
|
|
1380
2067
|
isEmbeddingEnabled: () => isEmbeddingEnabled,
|
|
2068
|
+
isVertexConfigured: () => isVertexConfigured,
|
|
1381
2069
|
setCachedEmbedding: () => setCachedEmbedding
|
|
1382
2070
|
});
|
|
1383
2071
|
function getCachedEmbedding(text) {
|
|
@@ -1521,7 +2209,80 @@ async function generateEmbeddings(texts) {
|
|
|
1521
2209
|
return texts.map(() => null);
|
|
1522
2210
|
}
|
|
1523
2211
|
}
|
|
1524
|
-
|
|
2212
|
+
function _resetVertexAuthCache() {
|
|
2213
|
+
_vertexToken = null;
|
|
2214
|
+
}
|
|
2215
|
+
async function _fetchMetadataToken() {
|
|
2216
|
+
const res = await fetch(METADATA_TOKEN_URL, { headers: { "Metadata-Flavor": "Google" } });
|
|
2217
|
+
if (!res.ok) throw new Error(`Vertex metadata token fetch failed: ${res.status}`);
|
|
2218
|
+
const j = await res.json();
|
|
2219
|
+
return { token: j.access_token, expiresInSec: j.expires_in };
|
|
2220
|
+
}
|
|
2221
|
+
async function getVertexAccessToken() {
|
|
2222
|
+
const override = config.vertex.accessToken;
|
|
2223
|
+
if (override) return override;
|
|
2224
|
+
const now = Date.now();
|
|
2225
|
+
if (_vertexToken && _vertexToken.expiresAtMs > now + 6e4) return _vertexToken.token;
|
|
2226
|
+
const { token, expiresInSec } = await _fetchMetadataToken();
|
|
2227
|
+
_vertexToken = { token, expiresAtMs: now + expiresInSec * 1e3 };
|
|
2228
|
+
return token;
|
|
2229
|
+
}
|
|
2230
|
+
function vertexPredictUrl(model) {
|
|
2231
|
+
const { project, location } = config.vertex;
|
|
2232
|
+
return `https://${location}-aiplatform.googleapis.com/v1/projects/${project}/locations/${location}/publishers/google/models/${model}:predict`;
|
|
2233
|
+
}
|
|
2234
|
+
function isVertexConfigured() {
|
|
2235
|
+
return !!config.vertex.project;
|
|
2236
|
+
}
|
|
2237
|
+
async function generateVertexEmbeddings(texts, dimensions) {
|
|
2238
|
+
if (texts.length === 0) return [];
|
|
2239
|
+
const { project, model, dimensions: defaultDims } = config.vertex;
|
|
2240
|
+
if (!project) {
|
|
2241
|
+
log5.warn("Vertex embeddings requested but VERTEX_PROJECT is unset");
|
|
2242
|
+
return texts.map(() => null);
|
|
2243
|
+
}
|
|
2244
|
+
const outputDimensionality = dimensions ?? defaultDims;
|
|
2245
|
+
const startMs = Date.now();
|
|
2246
|
+
try {
|
|
2247
|
+
const token = await getVertexAccessToken();
|
|
2248
|
+
const res = await fetch(vertexPredictUrl(model), {
|
|
2249
|
+
method: "POST",
|
|
2250
|
+
headers: {
|
|
2251
|
+
"Content-Type": "application/json",
|
|
2252
|
+
"Authorization": `Bearer ${token}`
|
|
2253
|
+
},
|
|
2254
|
+
body: JSON.stringify({
|
|
2255
|
+
instances: texts.map((t) => ({ content: t.slice(0, 8e3) })),
|
|
2256
|
+
parameters: { outputDimensionality }
|
|
2257
|
+
})
|
|
2258
|
+
});
|
|
2259
|
+
if (!res.ok) {
|
|
2260
|
+
const errText = await res.text();
|
|
2261
|
+
log5.error({ status: res.status, body: errText.slice(0, 200), provider: "vertex" }, "Vertex embedding API error");
|
|
2262
|
+
return texts.map(() => null);
|
|
2263
|
+
}
|
|
2264
|
+
const data = await res.json();
|
|
2265
|
+
const preds = data.predictions ?? [];
|
|
2266
|
+
log5.debug({ provider: "vertex", model, count: texts.length, elapsed: Date.now() - startMs }, "Vertex embeddings generated");
|
|
2267
|
+
return texts.map((_, i) => preds[i]?.embeddings?.values ?? null);
|
|
2268
|
+
} catch (err) {
|
|
2269
|
+
log5.error({ err, provider: "vertex" }, "Vertex embedding generation failed");
|
|
2270
|
+
return texts.map(() => null);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
async function generateVertexEmbedding(text, dimensions) {
|
|
2274
|
+
const [vec] = await generateVertexEmbeddings([text], dimensions);
|
|
2275
|
+
return vec ?? null;
|
|
2276
|
+
}
|
|
2277
|
+
async function generateEmbeddingForSpace(space, text) {
|
|
2278
|
+
if (space === "vertex") return generateVertexEmbedding(text);
|
|
2279
|
+
return generateEmbedding(text);
|
|
2280
|
+
}
|
|
2281
|
+
async function generateQueryEmbeddingForSpace(space, text) {
|
|
2282
|
+
if (space === "vertex") return generateVertexEmbedding(text);
|
|
2283
|
+
return generateQueryEmbedding(text);
|
|
2284
|
+
}
|
|
2285
|
+
var log5, PROVIDERS, _enabled, _overrideConfig, EMBEDDING_CACHE_MAX, EMBEDDING_CACHE_TTL_MS, _embeddingCache, _vertexToken, METADATA_TOKEN_URL;
|
|
1525
2286
|
var init_embeddings = __esm({
|
|
1526
2287
|
"packages/shared/src/core/embeddings.ts"() {
|
|
1527
2288
|
"use strict";
|
|
@@ -1554,6 +2315,8 @@ var init_embeddings = __esm({
|
|
|
1554
2315
|
EMBEDDING_CACHE_MAX = 200;
|
|
1555
2316
|
EMBEDDING_CACHE_TTL_MS = 30 * 60 * 1e3;
|
|
1556
2317
|
_embeddingCache = /* @__PURE__ */ new Map();
|
|
2318
|
+
_vertexToken = null;
|
|
2319
|
+
METADATA_TOKEN_URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
|
|
1557
2320
|
}
|
|
1558
2321
|
});
|
|
1559
2322
|
|
|
@@ -1860,6 +2623,39 @@ var init_solana_client = __esm({
|
|
|
1860
2623
|
}
|
|
1861
2624
|
});
|
|
1862
2625
|
|
|
2626
|
+
// packages/shared/src/core/memory-grounding.ts
|
|
2627
|
+
function byMemoryDateAsc(a, b) {
|
|
2628
|
+
const ta = a.created_at ? Date.parse(a.created_at) : NaN;
|
|
2629
|
+
const tb = b.created_at ? Date.parse(b.created_at) : NaN;
|
|
2630
|
+
if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
|
|
2631
|
+
if (Number.isNaN(ta)) return 1;
|
|
2632
|
+
if (Number.isNaN(tb)) return -1;
|
|
2633
|
+
return ta - tb;
|
|
2634
|
+
}
|
|
2635
|
+
function renderGroundedLine(m, suffix = "") {
|
|
2636
|
+
const date = m.created_at ? new Date(m.created_at).toISOString().slice(0, 10) : "";
|
|
2637
|
+
const stamp = date ? `[${date}] ` : "";
|
|
2638
|
+
const summary = (m.summary || "").trim();
|
|
2639
|
+
const content = (m.content || "").trim();
|
|
2640
|
+
let body;
|
|
2641
|
+
if (!content || content === summary) {
|
|
2642
|
+
body = summary || content;
|
|
2643
|
+
} else if (content.length <= GROUNDED_CONTENT_MAX) {
|
|
2644
|
+
body = summary ? `${summary} \u2014 ${content}` : content;
|
|
2645
|
+
} else {
|
|
2646
|
+
const slice = content.slice(0, GROUNDED_CONTENT_MAX);
|
|
2647
|
+
body = summary ? `${summary} \u2014 ${slice}\u2026` : `${slice}\u2026`;
|
|
2648
|
+
}
|
|
2649
|
+
return `- ${stamp}${body}${suffix}`;
|
|
2650
|
+
}
|
|
2651
|
+
var GROUNDED_CONTENT_MAX;
|
|
2652
|
+
var init_memory_grounding = __esm({
|
|
2653
|
+
"packages/shared/src/core/memory-grounding.ts"() {
|
|
2654
|
+
"use strict";
|
|
2655
|
+
GROUNDED_CONTENT_MAX = 600;
|
|
2656
|
+
}
|
|
2657
|
+
});
|
|
2658
|
+
|
|
1863
2659
|
// packages/shared/src/utils/format.ts
|
|
1864
2660
|
function timeAgo(dateStr) {
|
|
1865
2661
|
const ms = Date.now() - new Date(dateStr).getTime();
|
|
@@ -1921,7 +2717,7 @@ function stableStringify(value) {
|
|
|
1921
2717
|
}
|
|
1922
2718
|
if (typeof value === "object") {
|
|
1923
2719
|
const obj = value;
|
|
1924
|
-
const keys = Object.keys(obj).sort();
|
|
2720
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
|
|
1925
2721
|
const pairs = keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k]));
|
|
1926
2722
|
return "{" + pairs.join(",") + "}";
|
|
1927
2723
|
}
|
|
@@ -1964,9 +2760,12 @@ var init_content_hash = __esm({
|
|
|
1964
2760
|
});
|
|
1965
2761
|
|
|
1966
2762
|
// packages/tokenization/src/pack-merkle.ts
|
|
2763
|
+
var LEAF_PREFIX, INNER_PREFIX;
|
|
1967
2764
|
var init_pack_merkle = __esm({
|
|
1968
2765
|
"packages/tokenization/src/pack-merkle.ts"() {
|
|
1969
2766
|
"use strict";
|
|
2767
|
+
LEAF_PREFIX = Buffer.from([0]);
|
|
2768
|
+
INNER_PREFIX = Buffer.from([1]);
|
|
1970
2769
|
}
|
|
1971
2770
|
});
|
|
1972
2771
|
|
|
@@ -2324,6 +3123,489 @@ var init_bm25_search = __esm({
|
|
|
2324
3123
|
}
|
|
2325
3124
|
});
|
|
2326
3125
|
|
|
3126
|
+
// packages/brain/src/memory/content-tokens.ts
|
|
3127
|
+
async function setContentTokens(db, memoryId, plaintext) {
|
|
3128
|
+
try {
|
|
3129
|
+
const { error } = await db.rpc("set_memory_content_tokens", {
|
|
3130
|
+
p_memory_id: memoryId,
|
|
3131
|
+
p_text: plaintext
|
|
3132
|
+
});
|
|
3133
|
+
if (error) {
|
|
3134
|
+
log8.warn({ memoryId, error: error.message }, "set_memory_content_tokens failed (keyword recall degraded)");
|
|
3135
|
+
}
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
log8.warn({ memoryId, err }, "set_memory_content_tokens threw (keyword recall degraded)");
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
var log8;
|
|
3141
|
+
var init_content_tokens = __esm({
|
|
3142
|
+
"packages/brain/src/memory/content-tokens.ts"() {
|
|
3143
|
+
"use strict";
|
|
3144
|
+
init_logger();
|
|
3145
|
+
log8 = createChildLogger("content-tokens");
|
|
3146
|
+
}
|
|
3147
|
+
});
|
|
3148
|
+
|
|
3149
|
+
// packages/brain/src/memory/reconcile-router.ts
|
|
3150
|
+
function shortReason(x) {
|
|
3151
|
+
return typeof x === "string" ? x.slice(0, 200) : "";
|
|
3152
|
+
}
|
|
3153
|
+
function parseRouterReply(raw, candidateIds, model) {
|
|
3154
|
+
const fail = (reason) => ({ op: "add", targetId: null, reason, model });
|
|
3155
|
+
let obj;
|
|
3156
|
+
try {
|
|
3157
|
+
obj = JSON.parse(raw);
|
|
3158
|
+
} catch {
|
|
3159
|
+
const m = raw.match(/\{[\s\S]*\}/);
|
|
3160
|
+
if (!m) return fail("router_parse_error");
|
|
3161
|
+
try {
|
|
3162
|
+
obj = JSON.parse(m[0]);
|
|
3163
|
+
} catch {
|
|
3164
|
+
return fail("router_parse_error");
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return fail("router_invalid_shape");
|
|
3168
|
+
const rec = obj;
|
|
3169
|
+
const op = rec.op;
|
|
3170
|
+
if (op !== "add" && op !== "update" && op !== "noop") return fail("router_invalid_op");
|
|
3171
|
+
if (op === "add") return { op: "add", targetId: null, reason: shortReason(rec.reason), model };
|
|
3172
|
+
const rawTarget = rec.targetId ?? rec.target_id;
|
|
3173
|
+
const targetId = typeof rawTarget === "number" ? rawTarget : Number(rawTarget);
|
|
3174
|
+
if (!Number.isInteger(targetId) || !candidateIds.has(targetId)) return fail("router_invalid_target");
|
|
3175
|
+
return { op, targetId, reason: shortReason(rec.reason), model };
|
|
3176
|
+
}
|
|
3177
|
+
function buildPayload(newFact, candidates) {
|
|
3178
|
+
const lines = candidates.map(
|
|
3179
|
+
(c) => `[id ${c.id}] date: ${c.eventDate ?? "unknown"} \u2014 ${c.summary}`
|
|
3180
|
+
);
|
|
3181
|
+
return `NEW fact:
|
|
3182
|
+
date: ${newFact.eventDate ?? "unknown"} \u2014 ${newFact.summary}
|
|
3183
|
+
|
|
3184
|
+
Candidate facts:
|
|
3185
|
+
${lines.join("\n")}`;
|
|
3186
|
+
}
|
|
3187
|
+
function isRouterConfigured() {
|
|
3188
|
+
return config.memory.reconcileRouter && !!config.memory.reconcileModel && isOpenRouterEnabled();
|
|
3189
|
+
}
|
|
3190
|
+
function utcDay() {
|
|
3191
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3192
|
+
}
|
|
3193
|
+
function rollDay() {
|
|
3194
|
+
const d = utcDay();
|
|
3195
|
+
if (d !== budgetDay) {
|
|
3196
|
+
budgetDay = d;
|
|
3197
|
+
budgetByOwner.clear();
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
function budgetKey(owner) {
|
|
3201
|
+
return owner ?? "__BOT_OWN__";
|
|
3202
|
+
}
|
|
3203
|
+
function routerBudgetAvailable(owner) {
|
|
3204
|
+
rollDay();
|
|
3205
|
+
return (budgetByOwner.get(budgetKey(owner)) ?? 0) < config.memory.reconcileBudget;
|
|
3206
|
+
}
|
|
3207
|
+
function noteRouterCall(owner) {
|
|
3208
|
+
rollDay();
|
|
3209
|
+
const k = budgetKey(owner);
|
|
3210
|
+
budgetByOwner.set(k, (budgetByOwner.get(k) ?? 0) + 1);
|
|
3211
|
+
}
|
|
3212
|
+
async function routeReconcile(newFact, candidates) {
|
|
3213
|
+
const model = config.memory.reconcileModel;
|
|
3214
|
+
const candidateIds = new Set(candidates.map((c) => c.id));
|
|
3215
|
+
try {
|
|
3216
|
+
const raw = await generateOpenRouterResponse({
|
|
3217
|
+
systemPrompt: ROUTER_SYSTEM,
|
|
3218
|
+
messages: [{ role: "user", content: buildPayload(newFact, candidates) }],
|
|
3219
|
+
model,
|
|
3220
|
+
// explicit — never cognitiveFunction (which overrides model with the fast llama slot)
|
|
3221
|
+
maxTokens: 150,
|
|
3222
|
+
temperature: 0
|
|
3223
|
+
});
|
|
3224
|
+
return parseRouterReply(raw, candidateIds, model);
|
|
3225
|
+
} catch (err) {
|
|
3226
|
+
log9.warn({ err: err?.message }, "reconcile router call failed \u2014 defaulting to add");
|
|
3227
|
+
return { op: "add", targetId: null, reason: "router_error", model };
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
var log9, ROUTER_SYSTEM, budgetDay, budgetByOwner;
|
|
3231
|
+
var init_reconcile_router = __esm({
|
|
3232
|
+
"packages/brain/src/memory/reconcile-router.ts"() {
|
|
3233
|
+
"use strict";
|
|
3234
|
+
init_config();
|
|
3235
|
+
init_logger();
|
|
3236
|
+
init_openrouter_client();
|
|
3237
|
+
log9 = createChildLogger("reconcile-router");
|
|
3238
|
+
ROUTER_SYSTEM = 'You are a memory reconciliation classifier. Given a NEW fact and a list of EXISTING candidate facts that all belong to the SAME user, decide the relationship of the NEW fact to the candidates. Respond with ONLY a single JSON object, no prose, no markdown.\nFormat: {"op":"add"|"update"|"noop","targetId":<candidate id or null>,"reason":"<short>"}\n- "noop": NEW states the SAME thing as candidate <targetId> (a duplicate). Set targetId.\n- "update": NEW corrects or SUPERSEDES candidate <targetId> \u2014 same underlying fact, changed value or a newer date. Set targetId to that candidate id.\n- "add": NEW is distinct from every candidate. targetId must be null.\nWhen unsure, choose "add". NEVER invent a targetId that is not in the candidate list.';
|
|
3239
|
+
budgetDay = "";
|
|
3240
|
+
budgetByOwner = /* @__PURE__ */ new Map();
|
|
3241
|
+
}
|
|
3242
|
+
});
|
|
3243
|
+
|
|
3244
|
+
// packages/brain/src/memory/reconcile-shadow.ts
|
|
3245
|
+
function logReconcileDegradeOnce(err, memoryId) {
|
|
3246
|
+
if (reconcileDegradeLogged) return;
|
|
3247
|
+
reconcileDegradeLogged = true;
|
|
3248
|
+
const e = err;
|
|
3249
|
+
log10.warn(
|
|
3250
|
+
{ code: e?.code, message: e?.message, memoryId },
|
|
3251
|
+
"Reconcile shadow degraded (migration 046 unapplied?) \u2014 skipping; write unaffected"
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
function gateVersion() {
|
|
3255
|
+
const { reconcileFloor: f, reconcileLo: lo, reconcileHi: hi } = config.memory;
|
|
3256
|
+
return `c1-shadow-v1:floor=${f}:lo=${lo}:hi=${hi}`;
|
|
3257
|
+
}
|
|
3258
|
+
async function insertShadowRow(row) {
|
|
3259
|
+
try {
|
|
3260
|
+
const { error } = await getDb().from("memory_reconciliation_log").insert(row);
|
|
3261
|
+
if (error) logReconcileDegradeOnce(error, row.memory_id);
|
|
3262
|
+
} catch (err) {
|
|
3263
|
+
logReconcileDegradeOnce(err, row.memory_id);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
async function runReconcileShadow(memoryId, scope) {
|
|
3267
|
+
if (process.env.BENCH_MODE === "true") return;
|
|
3268
|
+
const ownerWalletForRow = scope === SCOPE_BOT_OWN ? null : scope;
|
|
3269
|
+
const gv = gateVersion();
|
|
3270
|
+
const db = getDb();
|
|
3271
|
+
const skip = (reason) => insertShadowRow({
|
|
3272
|
+
memory_id: memoryId,
|
|
3273
|
+
owner_wallet: ownerWalletForRow,
|
|
3274
|
+
mode: "shadow",
|
|
3275
|
+
proposed_op: "skip",
|
|
3276
|
+
target_memory_id: null,
|
|
3277
|
+
max_cosine: null,
|
|
3278
|
+
band: "none",
|
|
3279
|
+
router_used: false,
|
|
3280
|
+
fact_key: null,
|
|
3281
|
+
reason,
|
|
3282
|
+
gate_version: gv
|
|
3283
|
+
});
|
|
3284
|
+
const add = (reason, maxCosine2, band2) => insertShadowRow({
|
|
3285
|
+
memory_id: memoryId,
|
|
3286
|
+
owner_wallet: ownerWalletForRow,
|
|
3287
|
+
mode: "shadow",
|
|
3288
|
+
proposed_op: "add",
|
|
3289
|
+
target_memory_id: null,
|
|
3290
|
+
max_cosine: maxCosine2,
|
|
3291
|
+
band: band2,
|
|
3292
|
+
router_used: false,
|
|
3293
|
+
fact_key: null,
|
|
3294
|
+
reason,
|
|
3295
|
+
gate_version: gv
|
|
3296
|
+
});
|
|
3297
|
+
let embedding;
|
|
3298
|
+
let newSummary = "";
|
|
3299
|
+
let newEventDate = null;
|
|
3300
|
+
try {
|
|
3301
|
+
const { data, error } = await db.from("memories").select("embedding, summary, event_date, created_at").eq("id", memoryId).maybeSingle();
|
|
3302
|
+
if (error) {
|
|
3303
|
+
logReconcileDegradeOnce(error, memoryId);
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
3306
|
+
const row = data;
|
|
3307
|
+
embedding = row?.embedding ?? null;
|
|
3308
|
+
newSummary = row?.summary ?? "";
|
|
3309
|
+
newEventDate = row?.event_date ?? row?.created_at ?? null;
|
|
3310
|
+
} catch (err) {
|
|
3311
|
+
logReconcileDegradeOnce(err, memoryId);
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
if (!embedding) {
|
|
3315
|
+
await skip("no_embedding");
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
const queryEmbedding = typeof embedding === "string" ? embedding : JSON.stringify(embedding);
|
|
3319
|
+
let candidates;
|
|
3320
|
+
try {
|
|
3321
|
+
const { data, error } = await db.rpc("match_memories_temporal", {
|
|
3322
|
+
query_embedding: queryEmbedding,
|
|
3323
|
+
match_threshold: config.memory.reconcileFloor,
|
|
3324
|
+
match_count: K_CANDIDATES,
|
|
3325
|
+
start_date: null,
|
|
3326
|
+
end_date: null,
|
|
3327
|
+
filter_types: null,
|
|
3328
|
+
filter_user: null,
|
|
3329
|
+
min_decay: 0,
|
|
3330
|
+
filter_owner: scope,
|
|
3331
|
+
filter_tags: null
|
|
3332
|
+
});
|
|
3333
|
+
if (error) {
|
|
3334
|
+
logReconcileDegradeOnce(error, memoryId);
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
candidates = data ?? [];
|
|
3338
|
+
} catch (err) {
|
|
3339
|
+
logReconcileDegradeOnce(err, memoryId);
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
candidates = candidates.filter((c) => c.id !== memoryId);
|
|
3343
|
+
if (candidates.length === 0) {
|
|
3344
|
+
await add("no_candidate", null, "none");
|
|
3345
|
+
return;
|
|
3346
|
+
}
|
|
3347
|
+
const ids = candidates.map((c) => c.id);
|
|
3348
|
+
let validIds;
|
|
3349
|
+
const hydratedById = /* @__PURE__ */ new Map();
|
|
3350
|
+
try {
|
|
3351
|
+
const base = db.from("memories").select("id, owner_wallet, invalid_at, summary, event_date, created_at").in("id", ids);
|
|
3352
|
+
const scoped = scope === SCOPE_BOT_OWN ? base.is("owner_wallet", null) : base.eq("owner_wallet", scope);
|
|
3353
|
+
const { data, error } = await scoped;
|
|
3354
|
+
if (error) {
|
|
3355
|
+
logReconcileDegradeOnce(error, memoryId);
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
const rows = data ?? [];
|
|
3359
|
+
const valid = rows.filter((r) => r.invalid_at == null).filter((r) => scope === SCOPE_BOT_OWN ? r.owner_wallet == null : r.owner_wallet === scope);
|
|
3360
|
+
validIds = new Set(valid.map((r) => r.id));
|
|
3361
|
+
for (const r of valid) hydratedById.set(r.id, { summary: r.summary ?? "", eventDate: r.event_date ?? r.created_at ?? null });
|
|
3362
|
+
} catch (err) {
|
|
3363
|
+
logReconcileDegradeOnce(err, memoryId);
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
const surviving = candidates.filter((c) => validIds.has(c.id));
|
|
3367
|
+
if (surviving.length === 0) {
|
|
3368
|
+
await add("no_valid_candidate", null, "none");
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
const maxCosine = surviving[0].similarity;
|
|
3372
|
+
const { reconcileLo: LO, reconcileHi: HI } = config.memory;
|
|
3373
|
+
if (maxCosine < LO) {
|
|
3374
|
+
await add("below_lo", maxCosine, "lo");
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
const band = maxCosine >= HI ? "hi" : "mid";
|
|
3378
|
+
const top = surviving[0];
|
|
3379
|
+
if (isRouterConfigured() && routerBudgetAvailable(ownerWalletForRow)) {
|
|
3380
|
+
noteRouterCall(ownerWalletForRow);
|
|
3381
|
+
const routerCandidates = surviving.map((c) => ({
|
|
3382
|
+
id: c.id,
|
|
3383
|
+
summary: hydratedById.get(c.id)?.summary ?? "",
|
|
3384
|
+
eventDate: hydratedById.get(c.id)?.eventDate ?? null
|
|
3385
|
+
}));
|
|
3386
|
+
const decision = await routeReconcile({ summary: newSummary, eventDate: newEventDate }, routerCandidates);
|
|
3387
|
+
await insertShadowRow({
|
|
3388
|
+
memory_id: memoryId,
|
|
3389
|
+
owner_wallet: ownerWalletForRow,
|
|
3390
|
+
mode: "shadow",
|
|
3391
|
+
proposed_op: decision.op,
|
|
3392
|
+
target_memory_id: decision.op === "add" ? null : decision.targetId,
|
|
3393
|
+
max_cosine: maxCosine,
|
|
3394
|
+
band,
|
|
3395
|
+
router_used: true,
|
|
3396
|
+
router_model: decision.model,
|
|
3397
|
+
fact_key: null,
|
|
3398
|
+
reason: decision.reason || "router",
|
|
3399
|
+
gate_version: gv
|
|
3400
|
+
});
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
await insertShadowRow({
|
|
3404
|
+
memory_id: memoryId,
|
|
3405
|
+
owner_wallet: ownerWalletForRow,
|
|
3406
|
+
mode: "shadow",
|
|
3407
|
+
proposed_op: "needs_router",
|
|
3408
|
+
target_memory_id: top.id,
|
|
3409
|
+
max_cosine: maxCosine,
|
|
3410
|
+
band,
|
|
3411
|
+
router_used: false,
|
|
3412
|
+
fact_key: null,
|
|
3413
|
+
reason: "similar_prior_fact",
|
|
3414
|
+
gate_version: gv
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
var log10, SCOPE_BOT_OWN, K_CANDIDATES, reconcileDegradeLogged;
|
|
3418
|
+
var init_reconcile_shadow = __esm({
|
|
3419
|
+
"packages/brain/src/memory/reconcile-shadow.ts"() {
|
|
3420
|
+
"use strict";
|
|
3421
|
+
init_database();
|
|
3422
|
+
init_config();
|
|
3423
|
+
init_logger();
|
|
3424
|
+
init_reconcile_router();
|
|
3425
|
+
log10 = createChildLogger("reconcile-shadow");
|
|
3426
|
+
SCOPE_BOT_OWN = "__BOT_OWN__";
|
|
3427
|
+
K_CANDIDATES = 6;
|
|
3428
|
+
reconcileDegradeLogged = false;
|
|
3429
|
+
}
|
|
3430
|
+
});
|
|
3431
|
+
|
|
3432
|
+
// packages/shared/src/core/owner-key-constants.ts
|
|
3433
|
+
var HKDF_SALT, HKDF_INFO;
|
|
3434
|
+
var init_owner_key_constants = __esm({
|
|
3435
|
+
"packages/shared/src/core/owner-key-constants.ts"() {
|
|
3436
|
+
"use strict";
|
|
3437
|
+
HKDF_SALT = "clude-cortex-v1";
|
|
3438
|
+
HKDF_INFO = "memory-encryption-x25519-v2";
|
|
3439
|
+
}
|
|
3440
|
+
});
|
|
3441
|
+
|
|
3442
|
+
// packages/shared/src/core/memory-envelope.ts
|
|
3443
|
+
function generateDek() {
|
|
3444
|
+
return import_tweetnacl2.default.randomBytes(import_tweetnacl2.default.secretbox.keyLength);
|
|
3445
|
+
}
|
|
3446
|
+
function encryptField(plaintext, dek) {
|
|
3447
|
+
const nonce = import_tweetnacl2.default.randomBytes(NONCE_LENGTH);
|
|
3448
|
+
const message = new TextEncoder().encode(plaintext);
|
|
3449
|
+
const ct = import_tweetnacl2.default.secretbox(message, nonce, dek);
|
|
3450
|
+
const combined = new Uint8Array(NONCE_LENGTH + ct.length);
|
|
3451
|
+
combined.set(nonce, 0);
|
|
3452
|
+
combined.set(ct, NONCE_LENGTH);
|
|
3453
|
+
return Buffer.from(combined).toString("base64");
|
|
3454
|
+
}
|
|
3455
|
+
function decryptField(encrypted, dek) {
|
|
3456
|
+
try {
|
|
3457
|
+
const combined = Buffer.from(encrypted, "base64");
|
|
3458
|
+
if (combined.length < NONCE_LENGTH + 1) return null;
|
|
3459
|
+
const nonce = combined.subarray(0, NONCE_LENGTH);
|
|
3460
|
+
const ct = combined.subarray(NONCE_LENGTH);
|
|
3461
|
+
const pt = import_tweetnacl2.default.secretbox.open(ct, nonce, dek);
|
|
3462
|
+
if (!pt) return null;
|
|
3463
|
+
return new TextDecoder().decode(pt);
|
|
3464
|
+
} catch {
|
|
3465
|
+
return null;
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
function wrapDek(dek, recipientPubkey) {
|
|
3469
|
+
const ephemeral = import_tweetnacl2.default.box.keyPair();
|
|
3470
|
+
const nonce = import_tweetnacl2.default.randomBytes(BOX_NONCE_LENGTH);
|
|
3471
|
+
const boxed = import_tweetnacl2.default.box(dek, nonce, recipientPubkey, ephemeral.secretKey);
|
|
3472
|
+
const combined = new Uint8Array(BOX_NONCE_LENGTH + boxed.length);
|
|
3473
|
+
combined.set(nonce, 0);
|
|
3474
|
+
combined.set(boxed, BOX_NONCE_LENGTH);
|
|
3475
|
+
return {
|
|
3476
|
+
wrapped: Buffer.from(combined).toString("base64"),
|
|
3477
|
+
wrapPubkey: Buffer.from(ephemeral.publicKey).toString("base64")
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
function unwrapDek(wrapped, wrapPubkey, recipientSecretKey) {
|
|
3481
|
+
try {
|
|
3482
|
+
const combined = Buffer.from(wrapped, "base64");
|
|
3483
|
+
if (combined.length < BOX_NONCE_LENGTH + 1) return null;
|
|
3484
|
+
const nonce = combined.subarray(0, BOX_NONCE_LENGTH);
|
|
3485
|
+
const boxed = combined.subarray(BOX_NONCE_LENGTH);
|
|
3486
|
+
const ephemeralPub = Buffer.from(wrapPubkey, "base64");
|
|
3487
|
+
if (ephemeralPub.length !== import_tweetnacl2.default.box.publicKeyLength) return null;
|
|
3488
|
+
const out = import_tweetnacl2.default.box.open(boxed, nonce, ephemeralPub, recipientSecretKey);
|
|
3489
|
+
return out ?? null;
|
|
3490
|
+
} catch {
|
|
3491
|
+
return null;
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
async function deriveOwnerEncryptionKeypair(signature) {
|
|
3495
|
+
const seed = await hkdfAsync("sha256", signature, HKDF_SALT, HKDF_INFO, 32);
|
|
3496
|
+
return import_tweetnacl2.default.box.keyPair.fromSecretKey(new Uint8Array(seed));
|
|
3497
|
+
}
|
|
3498
|
+
var import_tweetnacl2, import_crypto, import_util, hkdfAsync, NONCE_LENGTH, BOX_NONCE_LENGTH;
|
|
3499
|
+
var init_memory_envelope = __esm({
|
|
3500
|
+
"packages/shared/src/core/memory-envelope.ts"() {
|
|
3501
|
+
"use strict";
|
|
3502
|
+
import_tweetnacl2 = __toESM(require("tweetnacl"));
|
|
3503
|
+
import_crypto = require("crypto");
|
|
3504
|
+
import_util = require("util");
|
|
3505
|
+
init_owner_key_constants();
|
|
3506
|
+
hkdfAsync = (0, import_util.promisify)(import_crypto.hkdf);
|
|
3507
|
+
NONCE_LENGTH = import_tweetnacl2.default.secretbox.nonceLength;
|
|
3508
|
+
BOX_NONCE_LENGTH = import_tweetnacl2.default.box.nonceLength;
|
|
3509
|
+
}
|
|
3510
|
+
});
|
|
3511
|
+
|
|
3512
|
+
// packages/shared/src/core/encryption-keys.ts
|
|
3513
|
+
function loadProviderKeypair() {
|
|
3514
|
+
if (providerKeypair) return providerKeypair;
|
|
3515
|
+
const b64 = process.env.PMP_PROVIDER_ENC_SECRET;
|
|
3516
|
+
if (!b64) throw new Error("PMP_PROVIDER_ENC_SECRET is not set");
|
|
3517
|
+
const secret = Buffer.from(b64, "base64");
|
|
3518
|
+
if (secret.length !== import_tweetnacl3.default.box.secretKeyLength) {
|
|
3519
|
+
throw new Error(`PMP_PROVIDER_ENC_SECRET must decode to 32 bytes (got ${secret.length})`);
|
|
3520
|
+
}
|
|
3521
|
+
providerKeypair = import_tweetnacl3.default.box.keyPair.fromSecretKey(new Uint8Array(secret));
|
|
3522
|
+
log11.info("Provider encryption keypair loaded");
|
|
3523
|
+
return providerKeypair;
|
|
3524
|
+
}
|
|
3525
|
+
async function getOwnerEncryptionKey(ownerWallet) {
|
|
3526
|
+
const { data, error } = await getDb().from("encryption_keys").select("x25519_pubkey, verifier_ct").eq("owner_wallet", ownerWallet).maybeSingle();
|
|
3527
|
+
if (error) throw new Error(`getOwnerEncryptionKey failed: ${error.message}`);
|
|
3528
|
+
if (!data) return null;
|
|
3529
|
+
return { x25519_pubkey: data.x25519_pubkey, verifier_ct: data.verifier_ct };
|
|
3530
|
+
}
|
|
3531
|
+
var import_tweetnacl3, log11, providerKeypair;
|
|
3532
|
+
var init_encryption_keys = __esm({
|
|
3533
|
+
"packages/shared/src/core/encryption-keys.ts"() {
|
|
3534
|
+
"use strict";
|
|
3535
|
+
import_tweetnacl3 = __toESM(require("tweetnacl"));
|
|
3536
|
+
init_database();
|
|
3537
|
+
init_logger();
|
|
3538
|
+
log11 = createChildLogger("encryption-keys");
|
|
3539
|
+
providerKeypair = null;
|
|
3540
|
+
}
|
|
3541
|
+
});
|
|
3542
|
+
|
|
3543
|
+
// packages/brain/src/memory/memory-encryption.ts
|
|
3544
|
+
function isMemoryEncryptionEnabled() {
|
|
3545
|
+
if (process.env.PMP_ENCRYPTION_ENABLED !== "true") return false;
|
|
3546
|
+
try {
|
|
3547
|
+
loadProviderKeypair();
|
|
3548
|
+
return true;
|
|
3549
|
+
} catch {
|
|
3550
|
+
return false;
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
async function getBotOwnerKeypair() {
|
|
3554
|
+
if (botKeypair) return botKeypair;
|
|
3555
|
+
const wallet = getBotWallet();
|
|
3556
|
+
if (!wallet) return null;
|
|
3557
|
+
const sig = import_tweetnacl4.default.sign.detached(new TextEncoder().encode(DERIVE_MESSAGE), wallet.secretKey);
|
|
3558
|
+
botKeypair = await deriveOwnerEncryptionKeypair(sig);
|
|
3559
|
+
return botKeypair;
|
|
3560
|
+
}
|
|
3561
|
+
async function resolveOwnerPublicKey(ownerWallet) {
|
|
3562
|
+
const botWallet2 = getBotWallet();
|
|
3563
|
+
const botAddr = botWallet2?.publicKey.toBase58();
|
|
3564
|
+
if (!ownerWallet || botAddr && ownerWallet === botAddr) {
|
|
3565
|
+
const kp = await getBotOwnerKeypair();
|
|
3566
|
+
return kp ? Buffer.from(kp.publicKey).toString("base64") : null;
|
|
3567
|
+
}
|
|
3568
|
+
const row = await getOwnerEncryptionKey(ownerWallet);
|
|
3569
|
+
return row?.x25519_pubkey ?? null;
|
|
3570
|
+
}
|
|
3571
|
+
async function encryptForStorage(plaintext, ownerWallet) {
|
|
3572
|
+
if (!isMemoryEncryptionEnabled()) return null;
|
|
3573
|
+
const ownerPubkey = await resolveOwnerPublicKey(ownerWallet);
|
|
3574
|
+
if (!ownerPubkey) {
|
|
3575
|
+
log12.warn("No owner encryption key resolvable \u2014 storing plaintext (encrypted:false)");
|
|
3576
|
+
return null;
|
|
3577
|
+
}
|
|
3578
|
+
const dek = generateDek();
|
|
3579
|
+
const ciphertext = encryptField(plaintext, dek);
|
|
3580
|
+
const ownerWrap = wrapDek(dek, Buffer.from(ownerPubkey, "base64"));
|
|
3581
|
+
const provWrap = wrapDek(dek, loadProviderKeypair().publicKey);
|
|
3582
|
+
return {
|
|
3583
|
+
ciphertext,
|
|
3584
|
+
ownerPubkey,
|
|
3585
|
+
wraps: [
|
|
3586
|
+
{ recipient: "owner", wrapped_dek: ownerWrap.wrapped, wrap_pubkey: ownerWrap.wrapPubkey },
|
|
3587
|
+
{ recipient: "provider", wrapped_dek: provWrap.wrapped, wrap_pubkey: provWrap.wrapPubkey }
|
|
3588
|
+
]
|
|
3589
|
+
};
|
|
3590
|
+
}
|
|
3591
|
+
function delegationStateForWrite(hasEnvelope) {
|
|
3592
|
+
return hasEnvelope ? true : null;
|
|
3593
|
+
}
|
|
3594
|
+
var import_tweetnacl4, log12, DERIVE_MESSAGE, botKeypair;
|
|
3595
|
+
var init_memory_encryption = __esm({
|
|
3596
|
+
"packages/brain/src/memory/memory-encryption.ts"() {
|
|
3597
|
+
"use strict";
|
|
3598
|
+
import_tweetnacl4 = __toESM(require("tweetnacl"));
|
|
3599
|
+
init_memory_envelope();
|
|
3600
|
+
init_encryption_keys();
|
|
3601
|
+
init_solana_client();
|
|
3602
|
+
init_logger();
|
|
3603
|
+
log12 = createChildLogger("memory-encryption");
|
|
3604
|
+
DERIVE_MESSAGE = "clude-memory-encryption-v1";
|
|
3605
|
+
botKeypair = null;
|
|
3606
|
+
}
|
|
3607
|
+
});
|
|
3608
|
+
|
|
2327
3609
|
// packages/shared/src/core/encryption.ts
|
|
2328
3610
|
var encryption_exports = {};
|
|
2329
3611
|
__export(encryption_exports, {
|
|
@@ -2339,11 +3621,11 @@ async function configureEncryption(solanaSecretKey) {
|
|
|
2339
3621
|
throw new Error("Encryption requires a 64-byte Ed25519 secret key");
|
|
2340
3622
|
}
|
|
2341
3623
|
const seed = solanaSecretKey.slice(0, 32);
|
|
2342
|
-
const derived = await
|
|
3624
|
+
const derived = await hkdfAsync2("sha256", seed, HKDF_SALT2, HKDF_INFO2, 32);
|
|
2343
3625
|
encryptionKey = new Uint8Array(derived);
|
|
2344
|
-
const keypair =
|
|
3626
|
+
const keypair = import_tweetnacl5.default.sign.keyPair.fromSecretKey(solanaSecretKey);
|
|
2345
3627
|
encryptionPubkey = Buffer.from(keypair.publicKey).toString("base64");
|
|
2346
|
-
|
|
3628
|
+
log13.info({ pubkey: encryptionPubkey.slice(0, 12) + "..." }, "Encryption configured");
|
|
2347
3629
|
}
|
|
2348
3630
|
function isEncryptionEnabled() {
|
|
2349
3631
|
return encryptionKey !== null;
|
|
@@ -2355,12 +3637,12 @@ function encryptContent(plaintext) {
|
|
|
2355
3637
|
if (!encryptionKey) {
|
|
2356
3638
|
throw new Error("Encryption not configured. Call configureEncryption() first.");
|
|
2357
3639
|
}
|
|
2358
|
-
const nonce =
|
|
3640
|
+
const nonce = import_tweetnacl5.default.randomBytes(NONCE_LENGTH2);
|
|
2359
3641
|
const messageBytes = new TextEncoder().encode(plaintext);
|
|
2360
|
-
const ciphertext =
|
|
2361
|
-
const combined = new Uint8Array(
|
|
3642
|
+
const ciphertext = import_tweetnacl5.default.secretbox(messageBytes, nonce, encryptionKey);
|
|
3643
|
+
const combined = new Uint8Array(NONCE_LENGTH2 + ciphertext.length);
|
|
2362
3644
|
combined.set(nonce, 0);
|
|
2363
|
-
combined.set(ciphertext,
|
|
3645
|
+
combined.set(ciphertext, NONCE_LENGTH2);
|
|
2364
3646
|
return Buffer.from(combined).toString("base64");
|
|
2365
3647
|
}
|
|
2366
3648
|
function decryptContent(encrypted) {
|
|
@@ -2369,12 +3651,12 @@ function decryptContent(encrypted) {
|
|
|
2369
3651
|
}
|
|
2370
3652
|
try {
|
|
2371
3653
|
const combined = Buffer.from(encrypted, "base64");
|
|
2372
|
-
if (combined.length <
|
|
3654
|
+
if (combined.length < NONCE_LENGTH2 + 1) {
|
|
2373
3655
|
return null;
|
|
2374
3656
|
}
|
|
2375
|
-
const nonce = combined.subarray(0,
|
|
2376
|
-
const ciphertext = combined.subarray(
|
|
2377
|
-
const plaintext =
|
|
3657
|
+
const nonce = combined.subarray(0, NONCE_LENGTH2);
|
|
3658
|
+
const ciphertext = combined.subarray(NONCE_LENGTH2);
|
|
3659
|
+
const plaintext = import_tweetnacl5.default.secretbox.open(ciphertext, nonce, encryptionKey);
|
|
2378
3660
|
if (!plaintext) {
|
|
2379
3661
|
return null;
|
|
2380
3662
|
}
|
|
@@ -2388,36 +3670,94 @@ function decryptMemoryBatch(memories) {
|
|
|
2388
3670
|
for (const mem of memories) {
|
|
2389
3671
|
if (!mem.encrypted) continue;
|
|
2390
3672
|
if (mem.encryption_pubkey && mem.encryption_pubkey !== encryptionPubkey) {
|
|
2391
|
-
|
|
3673
|
+
log13.debug({ memPubkey: mem.encryption_pubkey?.slice(0, 12) }, "Skipping memory encrypted by different key");
|
|
2392
3674
|
continue;
|
|
2393
3675
|
}
|
|
2394
3676
|
const decrypted = decryptContent(mem.content);
|
|
2395
3677
|
if (decrypted !== null) {
|
|
2396
3678
|
mem.content = decrypted;
|
|
2397
3679
|
} else {
|
|
2398
|
-
|
|
3680
|
+
log13.warn("Failed to decrypt memory content \u2014 wrong key or corrupted data");
|
|
2399
3681
|
}
|
|
2400
3682
|
}
|
|
2401
3683
|
return memories;
|
|
2402
3684
|
}
|
|
2403
|
-
var
|
|
3685
|
+
var import_tweetnacl5, import_crypto2, import_util2, log13, hkdfAsync2, HKDF_SALT2, HKDF_INFO2, NONCE_LENGTH2, encryptionKey, encryptionPubkey;
|
|
2404
3686
|
var init_encryption = __esm({
|
|
2405
3687
|
"packages/shared/src/core/encryption.ts"() {
|
|
2406
3688
|
"use strict";
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
3689
|
+
import_tweetnacl5 = __toESM(require("tweetnacl"));
|
|
3690
|
+
import_crypto2 = require("crypto");
|
|
3691
|
+
import_util2 = require("util");
|
|
2410
3692
|
init_logger();
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
3693
|
+
log13 = createChildLogger("encryption");
|
|
3694
|
+
hkdfAsync2 = (0, import_util2.promisify)(import_crypto2.hkdf);
|
|
3695
|
+
HKDF_SALT2 = "clude-cortex-v1";
|
|
3696
|
+
HKDF_INFO2 = "memory-encryption";
|
|
3697
|
+
NONCE_LENGTH2 = import_tweetnacl5.default.secretbox.nonceLength;
|
|
2416
3698
|
encryptionKey = null;
|
|
2417
3699
|
encryptionPubkey = null;
|
|
2418
3700
|
}
|
|
2419
3701
|
});
|
|
2420
3702
|
|
|
3703
|
+
// packages/brain/src/memory/memory-decryption.ts
|
|
3704
|
+
async function fetchProviderWraps(ids) {
|
|
3705
|
+
const map = /* @__PURE__ */ new Map();
|
|
3706
|
+
if (ids.length === 0) return map;
|
|
3707
|
+
const { data, error } = await getDb().from("memory_dek_wraps").select("memory_id, wrapped_dek, wrap_pubkey").eq("recipient", "provider").in("memory_id", ids);
|
|
3708
|
+
if (error || !data) return map;
|
|
3709
|
+
for (const r of data) {
|
|
3710
|
+
map.set(r.memory_id, { wrapped_dek: r.wrapped_dek, wrap_pubkey: r.wrap_pubkey });
|
|
3711
|
+
}
|
|
3712
|
+
return map;
|
|
3713
|
+
}
|
|
3714
|
+
async function decryptMemories(memories) {
|
|
3715
|
+
if (!memories || memories.length === 0) return memories;
|
|
3716
|
+
const encryptedRows = memories.filter((m) => m.encrypted === true);
|
|
3717
|
+
if (encryptedRows.length === 0) return memories;
|
|
3718
|
+
let providerSecret = null;
|
|
3719
|
+
try {
|
|
3720
|
+
providerSecret = loadProviderKeypair().secretKey;
|
|
3721
|
+
} catch {
|
|
3722
|
+
providerSecret = null;
|
|
3723
|
+
}
|
|
3724
|
+
const wraps = providerSecret ? await fetchProviderWraps(encryptedRows.map((m) => m.id)) : /* @__PURE__ */ new Map();
|
|
3725
|
+
const legacyRows = [];
|
|
3726
|
+
for (const mem of encryptedRows) {
|
|
3727
|
+
const wrap = wraps.get(mem.id);
|
|
3728
|
+
if (providerSecret && wrap) {
|
|
3729
|
+
const dek = unwrapDek(wrap.wrapped_dek, wrap.wrap_pubkey, providerSecret);
|
|
3730
|
+
const plain = dek ? decryptField(mem.content, dek) : null;
|
|
3731
|
+
if (plain !== null) {
|
|
3732
|
+
mem.content = plain;
|
|
3733
|
+
continue;
|
|
3734
|
+
}
|
|
3735
|
+
log14.debug({ id: mem.id }, "Envelope decrypt failed \u2014 leaving ciphertext");
|
|
3736
|
+
continue;
|
|
3737
|
+
}
|
|
3738
|
+
legacyRows.push(mem);
|
|
3739
|
+
}
|
|
3740
|
+
if (legacyRows.length > 0) decryptMemoryBatch(legacyRows);
|
|
3741
|
+
return memories;
|
|
3742
|
+
}
|
|
3743
|
+
async function decryptOneContent(row) {
|
|
3744
|
+
if (row.encrypted !== true) return row.content;
|
|
3745
|
+
const [out] = await decryptMemories([{ ...row }]);
|
|
3746
|
+
return out.content !== row.content ? out.content : null;
|
|
3747
|
+
}
|
|
3748
|
+
var log14;
|
|
3749
|
+
var init_memory_decryption = __esm({
|
|
3750
|
+
"packages/brain/src/memory/memory-decryption.ts"() {
|
|
3751
|
+
"use strict";
|
|
3752
|
+
init_database();
|
|
3753
|
+
init_memory_envelope();
|
|
3754
|
+
init_encryption_keys();
|
|
3755
|
+
init_encryption();
|
|
3756
|
+
init_logger();
|
|
3757
|
+
log14 = createChildLogger("memory-decryption");
|
|
3758
|
+
}
|
|
3759
|
+
});
|
|
3760
|
+
|
|
2421
3761
|
// packages/brain/src/events/event-bus.ts
|
|
2422
3762
|
var event_bus_exports = {};
|
|
2423
3763
|
__export(event_bus_exports, {
|
|
@@ -2470,16 +3810,16 @@ async function findOrCreateEntity(name, entityType, opts) {
|
|
|
2470
3810
|
mention_count: 1
|
|
2471
3811
|
}).select().single();
|
|
2472
3812
|
if (error) {
|
|
2473
|
-
|
|
3813
|
+
log15.error({ error: error.message, name }, "Failed to create entity");
|
|
2474
3814
|
return null;
|
|
2475
3815
|
}
|
|
2476
|
-
|
|
3816
|
+
log15.debug({ id: newEntity.id, name, type: entityType }, "Entity created");
|
|
2477
3817
|
embedEntity(newEntity.id, name, opts?.description).catch(
|
|
2478
|
-
(err) =>
|
|
3818
|
+
(err) => log15.debug({ err }, "Entity embedding failed")
|
|
2479
3819
|
);
|
|
2480
3820
|
return newEntity;
|
|
2481
3821
|
} catch (err) {
|
|
2482
|
-
|
|
3822
|
+
log15.error({ err, name }, "Entity findOrCreate failed");
|
|
2483
3823
|
return null;
|
|
2484
3824
|
}
|
|
2485
3825
|
}
|
|
@@ -2501,7 +3841,7 @@ async function createEntityMention(entityId, memoryId, context, salience = 0.5)
|
|
|
2501
3841
|
salience: Math.max(0, Math.min(1, salience))
|
|
2502
3842
|
}, { onConflict: "entity_id,memory_id" });
|
|
2503
3843
|
if (error) {
|
|
2504
|
-
|
|
3844
|
+
log15.debug({ error: error.message, entityId, memoryId }, "Entity mention failed");
|
|
2505
3845
|
}
|
|
2506
3846
|
}
|
|
2507
3847
|
async function getMemoriesByEntity(entityId, opts) {
|
|
@@ -2513,12 +3853,12 @@ async function getMemoriesByEntity(entityId, opts) {
|
|
|
2513
3853
|
`).eq("entity_id", entityId).order("salience", { ascending: false }).limit(opts?.limit || 20);
|
|
2514
3854
|
const { data, error } = await query;
|
|
2515
3855
|
if (error) {
|
|
2516
|
-
|
|
3856
|
+
log15.error({ error: error.message, entityId }, "Failed to get memories by entity");
|
|
2517
3857
|
return [];
|
|
2518
3858
|
}
|
|
2519
3859
|
const { getOwnerWallet: getOwnerWallet4 } = (init_memory(), __toCommonJS(memory_exports));
|
|
2520
3860
|
const ownerWallet = getOwnerWallet4();
|
|
2521
|
-
const memories = (data || []).map((d) => d.memories).filter((m) => m !== null).filter((m) => !opts?.memoryTypes || opts.memoryTypes.includes(m.memory_type)).filter((m) => !ownerWallet || m.owner_wallet === ownerWallet);
|
|
3861
|
+
const memories = (data || []).map((d) => d.memories).filter((m) => m !== null).filter((m) => m.provider_delegated !== false).filter((m) => !opts?.memoryTypes || opts.memoryTypes.includes(m.memory_type)).filter((m) => !ownerWallet || m.owner_wallet === ownerWallet);
|
|
2522
3862
|
return memories;
|
|
2523
3863
|
}
|
|
2524
3864
|
async function getEntityCooccurrences(entityId, opts) {
|
|
@@ -2530,33 +3870,27 @@ async function getEntityCooccurrences(entityId, opts) {
|
|
|
2530
3870
|
max_results: opts?.maxResults ?? 10
|
|
2531
3871
|
});
|
|
2532
3872
|
if (error || !data) {
|
|
2533
|
-
|
|
3873
|
+
log15.debug({ error: error?.message, entityId }, "Entity co-occurrence lookup failed");
|
|
2534
3874
|
return [];
|
|
2535
3875
|
}
|
|
2536
3876
|
return data;
|
|
2537
3877
|
} catch (err) {
|
|
2538
|
-
|
|
3878
|
+
log15.debug({ err, entityId }, "Entity co-occurrence skipped (RPC unavailable)");
|
|
2539
3879
|
return [];
|
|
2540
3880
|
}
|
|
2541
3881
|
}
|
|
2542
3882
|
async function createEntityRelation(sourceEntityId, targetEntityId, relationType, evidenceMemoryId, strength = 0.5) {
|
|
2543
3883
|
if (sourceEntityId === targetEntityId) return;
|
|
2544
3884
|
const db = getDb();
|
|
2545
|
-
const {
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
source_entity_id: sourceEntityId,
|
|
2555
|
-
target_entity_id: targetEntityId,
|
|
2556
|
-
relation_type: relationType,
|
|
2557
|
-
strength,
|
|
2558
|
-
evidence_memory_ids: evidenceMemoryId ? [evidenceMemoryId] : []
|
|
2559
|
-
});
|
|
3885
|
+
const { error } = await db.rpc("upsert_entity_relation", {
|
|
3886
|
+
p_src: sourceEntityId,
|
|
3887
|
+
p_tgt: targetEntityId,
|
|
3888
|
+
p_type: relationType,
|
|
3889
|
+
p_evidence: evidenceMemoryId ?? null,
|
|
3890
|
+
p_strength: strength
|
|
3891
|
+
});
|
|
3892
|
+
if (error) {
|
|
3893
|
+
log15.warn({ err: error.message, sourceEntityId, targetEntityId, relationType }, "upsert_entity_relation failed");
|
|
2560
3894
|
}
|
|
2561
3895
|
}
|
|
2562
3896
|
function extractEntitiesFromText(text) {
|
|
@@ -2636,11 +3970,11 @@ async function extractAndLinkEntities(memoryId, content, summary, relatedUser) {
|
|
|
2636
3970
|
// base co-occurrence strength
|
|
2637
3971
|
);
|
|
2638
3972
|
} catch (err) {
|
|
2639
|
-
|
|
3973
|
+
log15.debug({ err, source: entityIds[i].name, target: entityIds[j].name }, "Failed to create entity relation");
|
|
2640
3974
|
}
|
|
2641
3975
|
}
|
|
2642
3976
|
}
|
|
2643
|
-
|
|
3977
|
+
log15.debug({ memoryId, entityCount: extracted.length, relations: Math.max(0, entityIds.length * (entityIds.length - 1) / 2) }, "Entities extracted, linked, and related");
|
|
2644
3978
|
}
|
|
2645
3979
|
async function findSimilarEntities(query, opts) {
|
|
2646
3980
|
if (!isEmbeddingEnabled()) return [];
|
|
@@ -2654,7 +3988,7 @@ async function findSimilarEntities(query, opts) {
|
|
|
2654
3988
|
filter_types: opts?.entityTypes || null
|
|
2655
3989
|
});
|
|
2656
3990
|
if (error) {
|
|
2657
|
-
|
|
3991
|
+
log15.debug({ error: error.message }, "Entity vector search failed");
|
|
2658
3992
|
return [];
|
|
2659
3993
|
}
|
|
2660
3994
|
const ids = (data || []).map((d) => d.id);
|
|
@@ -2662,14 +3996,14 @@ async function findSimilarEntities(query, opts) {
|
|
|
2662
3996
|
const { data: entities } = await db.from("entities").select("*").in("id", ids);
|
|
2663
3997
|
return entities || [];
|
|
2664
3998
|
}
|
|
2665
|
-
var
|
|
3999
|
+
var log15;
|
|
2666
4000
|
var init_graph = __esm({
|
|
2667
4001
|
"packages/brain/src/memory/graph.ts"() {
|
|
2668
4002
|
"use strict";
|
|
2669
4003
|
init_database();
|
|
2670
4004
|
init_logger();
|
|
2671
4005
|
init_embeddings();
|
|
2672
|
-
|
|
4006
|
+
log15 = createChildLogger("memory-graph");
|
|
2673
4007
|
}
|
|
2674
4008
|
});
|
|
2675
4009
|
|
|
@@ -2697,8 +4031,11 @@ var init_owner_context = __esm({
|
|
|
2697
4031
|
// packages/brain/src/memory/memory.ts
|
|
2698
4032
|
var memory_exports = {};
|
|
2699
4033
|
__export(memory_exports, {
|
|
2700
|
-
SCOPE_BOT_OWN: () =>
|
|
4034
|
+
SCOPE_BOT_OWN: () => SCOPE_BOT_OWN2,
|
|
4035
|
+
_resetOutboxDegradeLog: () => _resetOutboxDegradeLog,
|
|
2701
4036
|
_setOwnerWallet: () => _setOwnerWallet,
|
|
4037
|
+
applyOwnerPostGuard: () => applyOwnerPostGuard,
|
|
4038
|
+
buildFragmentRpcArgs: () => buildFragmentRpcArgs,
|
|
2702
4039
|
calculateImportance: () => calculateImportance,
|
|
2703
4040
|
createMemoryLink: () => createMemoryLink,
|
|
2704
4041
|
createMemoryLinksBatch: () => createMemoryLinksBatch,
|
|
@@ -2709,12 +4046,15 @@ __export(memory_exports, {
|
|
|
2709
4046
|
formatMemoryContext: () => formatMemoryContext,
|
|
2710
4047
|
generateHashId: () => generateHashId,
|
|
2711
4048
|
getMemoryStats: () => getMemoryStats,
|
|
4049
|
+
getOwnerScope: () => getOwnerScope,
|
|
2712
4050
|
getOwnerWallet: () => getOwnerWallet,
|
|
2713
4051
|
getRecentMemories: () => getRecentMemories,
|
|
2714
4052
|
getSelfModel: () => getSelfModel,
|
|
2715
4053
|
hydrateMemories: () => hydrateMemories,
|
|
2716
4054
|
inferConcepts: () => inferConcepts,
|
|
2717
4055
|
invalidateInstalledPacksCache: () => invalidateInstalledPacksCache,
|
|
4056
|
+
isBenchMode: () => isBenchMode,
|
|
4057
|
+
isOwnerScopeFailClosed: () => isOwnerScopeFailClosed,
|
|
2718
4058
|
isValidHashId: () => isValidHashId,
|
|
2719
4059
|
listMemories: () => listMemories,
|
|
2720
4060
|
markJepaQueried: () => markJepaQueried,
|
|
@@ -2722,11 +4062,15 @@ __export(memory_exports, {
|
|
|
2722
4062
|
moodToValence: () => moodToValence,
|
|
2723
4063
|
recallMemories: () => recallMemories,
|
|
2724
4064
|
recallMemorySummaries: () => recallMemorySummaries,
|
|
4065
|
+
runEnrichPipeline: () => runEnrichPipeline,
|
|
2725
4066
|
scopeToOwner: () => scopeToOwner,
|
|
4067
|
+
scoreImportanceOnWrite: () => scoreImportanceOnWrite,
|
|
2726
4068
|
scoreImportanceWithLLM: () => scoreImportanceWithLLM,
|
|
2727
4069
|
scoreMemory: () => scoreMemory,
|
|
4070
|
+
shouldTrackAccess: () => shouldTrackAccess,
|
|
2728
4071
|
storeDreamLog: () => storeDreamLog,
|
|
2729
4072
|
storeMemory: () => storeMemory,
|
|
4073
|
+
storeMemoryWithOutbox: () => storeMemoryWithOutbox,
|
|
2730
4074
|
updateMemory: () => updateMemory
|
|
2731
4075
|
});
|
|
2732
4076
|
function getCachedEmbedding2(query) {
|
|
@@ -2755,18 +4099,51 @@ function getOwnerWallet() {
|
|
|
2755
4099
|
if (contextWallet !== void 0) return contextWallet;
|
|
2756
4100
|
return _ownerWallet;
|
|
2757
4101
|
}
|
|
2758
|
-
function
|
|
4102
|
+
function isOwnerScopeFailClosed() {
|
|
4103
|
+
return process.env.OWNER_SCOPE_FAILCLOSED === "true";
|
|
4104
|
+
}
|
|
4105
|
+
function getOwnerScope() {
|
|
2759
4106
|
const wallet = getOwnerWallet();
|
|
2760
|
-
if (wallet
|
|
4107
|
+
if (wallet) return wallet;
|
|
4108
|
+
return isOwnerScopeFailClosed() ? SCOPE_BOT_OWN2 : null;
|
|
4109
|
+
}
|
|
4110
|
+
function scopeToOwner(query) {
|
|
4111
|
+
const scope = getOwnerScope();
|
|
4112
|
+
if (scope === SCOPE_BOT_OWN2) {
|
|
2761
4113
|
return query.is("owner_wallet", null);
|
|
2762
4114
|
}
|
|
2763
|
-
if (
|
|
2764
|
-
return query.eq("owner_wallet",
|
|
4115
|
+
if (scope) {
|
|
4116
|
+
return query.eq("owner_wallet", scope);
|
|
2765
4117
|
}
|
|
2766
4118
|
return query;
|
|
2767
4119
|
}
|
|
4120
|
+
function applyOwnerPostGuard(results, scope) {
|
|
4121
|
+
if (!scope) return { kept: results, stripped: 0 };
|
|
4122
|
+
const kept = scope === SCOPE_BOT_OWN2 ? results.filter((m) => m.owner_wallet == null) : results.filter((m) => m.owner_wallet === scope);
|
|
4123
|
+
return { kept, stripped: results.length - kept.length };
|
|
4124
|
+
}
|
|
4125
|
+
function isBenchMode() {
|
|
4126
|
+
return process.env.BENCH_MODE === "true";
|
|
4127
|
+
}
|
|
4128
|
+
function shouldTrackAccess(opts) {
|
|
4129
|
+
if (isBenchMode()) return false;
|
|
4130
|
+
return opts.trackAccess !== false;
|
|
4131
|
+
}
|
|
4132
|
+
function buildFragmentRpcArgs(opts) {
|
|
4133
|
+
const args = {
|
|
4134
|
+
query_embedding: opts.embeddingJson,
|
|
4135
|
+
match_threshold: opts.matchThreshold,
|
|
4136
|
+
match_count: opts.matchCount,
|
|
4137
|
+
filter_owner: getOwnerScope()
|
|
4138
|
+
};
|
|
4139
|
+
if (process.env.MEMORY_FRAGMENT_FILTERS === "true") {
|
|
4140
|
+
args.min_decay = opts.minDecay;
|
|
4141
|
+
args.filter_types = opts.memoryTypes && opts.memoryTypes.length > 0 ? opts.memoryTypes : null;
|
|
4142
|
+
}
|
|
4143
|
+
return args;
|
|
4144
|
+
}
|
|
2768
4145
|
function generateHashId() {
|
|
2769
|
-
return `${HASH_ID_PREFIX}-${(0,
|
|
4146
|
+
return `${HASH_ID_PREFIX}-${(0, import_crypto3.randomBytes)(4).toString("hex")}`;
|
|
2770
4147
|
}
|
|
2771
4148
|
function isValidHashId(id) {
|
|
2772
4149
|
return /^clude-[a-f0-9]{8}$/.test(id);
|
|
@@ -2801,6 +4178,23 @@ function inferConcepts(summary, source, tags) {
|
|
|
2801
4178
|
concepts.push("identity_evolution");
|
|
2802
4179
|
return [...new Set(concepts)];
|
|
2803
4180
|
}
|
|
4181
|
+
function scoreImportanceOnWrite(opts) {
|
|
4182
|
+
const base = {
|
|
4183
|
+
self_model: 0.75,
|
|
4184
|
+
semantic: 0.7,
|
|
4185
|
+
procedural: 0.65,
|
|
4186
|
+
introspective: 0.6,
|
|
4187
|
+
episodic: 0.45
|
|
4188
|
+
};
|
|
4189
|
+
let score = base[opts.type] ?? 0.5;
|
|
4190
|
+
const len = (opts.content ?? "").trim().length;
|
|
4191
|
+
score += Math.min(len / 1e3, 0.15);
|
|
4192
|
+
if (opts.tags && opts.tags.length) score += 0.05;
|
|
4193
|
+
const concepts = opts.concepts ?? inferConcepts(opts.summary, opts.source, opts.tags || []);
|
|
4194
|
+
if (concepts.length) score += 0.05;
|
|
4195
|
+
if (INTERNAL_MEMORY_SOURCES.has(opts.source)) score -= 0.05;
|
|
4196
|
+
return Math.max(0, Math.min(1, score));
|
|
4197
|
+
}
|
|
2804
4198
|
function normalizeSummary(summary) {
|
|
2805
4199
|
return summary.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<id>").replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^\s]*/g, "<ts>").toLowerCase().trim();
|
|
2806
4200
|
}
|
|
@@ -2832,7 +4226,7 @@ async function getInstalledPackIdsCached(ownerWallet) {
|
|
|
2832
4226
|
ids = Array.from(/* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...fromDb]));
|
|
2833
4227
|
}
|
|
2834
4228
|
} catch (err) {
|
|
2835
|
-
|
|
4229
|
+
log16.debug({ err }, "Failed to fetch installed packs (using default only)");
|
|
2836
4230
|
}
|
|
2837
4231
|
}
|
|
2838
4232
|
installedPacksCache.set(key, { ids, expiresAt: now + INSTALLED_PACKS_TTL_MS });
|
|
@@ -2869,7 +4263,7 @@ async function ensureTopicEmbeddings(installedPackIds) {
|
|
|
2869
4263
|
if (emb) topicEmbeddingCache.set(s.topicId, emb);
|
|
2870
4264
|
});
|
|
2871
4265
|
} catch (err) {
|
|
2872
|
-
|
|
4266
|
+
log16.warn({ err }, "Failed to populate topic embeddings cache");
|
|
2873
4267
|
} finally {
|
|
2874
4268
|
topicEmbeddingPopulationLock = null;
|
|
2875
4269
|
}
|
|
@@ -2880,12 +4274,18 @@ async function ensureTopicEmbeddings(installedPackIds) {
|
|
|
2880
4274
|
);
|
|
2881
4275
|
}
|
|
2882
4276
|
async function storeMemory(opts) {
|
|
4277
|
+
const trimmedContent = (opts.content ?? "").trim();
|
|
4278
|
+
if (!trimmedContent) {
|
|
4279
|
+
log16.warn({ source: opts.source }, "Rejected empty-content memory");
|
|
4280
|
+
return null;
|
|
4281
|
+
}
|
|
2883
4282
|
if (opts.source.startsWith("shiro_") && isDuplicateWrite(opts.source, opts.summary)) {
|
|
2884
|
-
|
|
4283
|
+
log16.debug({ source: opts.source, summary: opts.summary.slice(0, 60) }, "Skipping duplicate memory write");
|
|
2885
4284
|
return null;
|
|
2886
4285
|
}
|
|
2887
4286
|
const db = getDb();
|
|
2888
4287
|
const ownerWallet = getOwnerWallet();
|
|
4288
|
+
const importance = clamp(opts.importance ?? scoreImportanceOnWrite(opts), 0, 1);
|
|
2889
4289
|
const concepts = opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || []);
|
|
2890
4290
|
const installedPackIds = await getInstalledPackIdsCached(ownerWallet);
|
|
2891
4291
|
const taggedTags = autoCategorizeTags({
|
|
@@ -2896,9 +4296,10 @@ async function storeMemory(opts) {
|
|
|
2896
4296
|
});
|
|
2897
4297
|
const hashId = generateHashId();
|
|
2898
4298
|
try {
|
|
2899
|
-
const plaintextContent =
|
|
2900
|
-
const
|
|
2901
|
-
const
|
|
4299
|
+
const plaintextContent = trimmedContent.slice(0, MEMORY_MAX_CONTENT_LENGTH);
|
|
4300
|
+
const envelope = await encryptForStorage(plaintextContent, ownerWallet || null);
|
|
4301
|
+
const legacyEncrypt = !envelope && isEncryptionEnabled();
|
|
4302
|
+
const storedContent = envelope ? envelope.ciphertext : legacyEncrypt ? encryptContent(plaintextContent) : plaintextContent;
|
|
2902
4303
|
const { data, error } = await db.from("memories").insert({
|
|
2903
4304
|
hash_id: hashId,
|
|
2904
4305
|
memory_type: opts.type,
|
|
@@ -2907,7 +4308,7 @@ async function storeMemory(opts) {
|
|
|
2907
4308
|
tags: taggedTags,
|
|
2908
4309
|
concepts,
|
|
2909
4310
|
emotional_valence: clamp(opts.emotionalValence ?? 0, -1, 1),
|
|
2910
|
-
importance
|
|
4311
|
+
importance,
|
|
2911
4312
|
source: opts.source,
|
|
2912
4313
|
source_id: opts.sourceId || null,
|
|
2913
4314
|
related_user: opts.relatedUser || null,
|
|
@@ -2915,43 +4316,72 @@ async function storeMemory(opts) {
|
|
|
2915
4316
|
metadata: opts.metadata || {},
|
|
2916
4317
|
evidence_ids: opts.evidenceIds || [],
|
|
2917
4318
|
compacted: false,
|
|
2918
|
-
encrypted:
|
|
2919
|
-
encryption_pubkey:
|
|
4319
|
+
encrypted: envelope !== null || legacyEncrypt,
|
|
4320
|
+
encryption_pubkey: envelope ? envelope.ownerPubkey : legacyEncrypt ? getEncryptionPubkey() : null,
|
|
4321
|
+
provider_delegated: delegationStateForWrite(envelope !== null),
|
|
4322
|
+
// true|null at write; false is revoke-only (§7)
|
|
2920
4323
|
owner_wallet: ownerWallet || null
|
|
2921
4324
|
}).select("id, hash_id, content, memory_type, owner_wallet, created_at, tags, source, related_user, related_wallet").single();
|
|
2922
4325
|
if (error) {
|
|
2923
|
-
|
|
4326
|
+
log16.error({ error: error.message }, "Failed to store memory");
|
|
2924
4327
|
return null;
|
|
2925
4328
|
}
|
|
2926
|
-
|
|
4329
|
+
log16.debug({
|
|
2927
4330
|
id: data.id,
|
|
2928
4331
|
hashId: data.hash_id,
|
|
2929
4332
|
type: opts.type,
|
|
2930
4333
|
summary: opts.summary.slice(0, 60),
|
|
2931
|
-
importance
|
|
4334
|
+
importance,
|
|
2932
4335
|
concepts
|
|
2933
4336
|
}, "Memory stored");
|
|
4337
|
+
await setContentTokens(db, data.id, plaintextContent);
|
|
4338
|
+
if (envelope) {
|
|
4339
|
+
const { error: wrapErr } = await db.from("memory_dek_wraps").insert(
|
|
4340
|
+
envelope.wraps.map((w) => ({ memory_id: data.id, ...w }))
|
|
4341
|
+
);
|
|
4342
|
+
if (wrapErr) {
|
|
4343
|
+
log16.error({ id: data.id, error: wrapErr.message }, "DEK wrap write failed \u2014 reverting memory to plaintext");
|
|
4344
|
+
await db.from("memories").update({
|
|
4345
|
+
content: plaintextContent,
|
|
4346
|
+
encrypted: false,
|
|
4347
|
+
encryption_pubkey: null,
|
|
4348
|
+
provider_delegated: null
|
|
4349
|
+
}).eq("id", data.id);
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
2934
4352
|
eventBus.emit("memory:stored", {
|
|
2935
|
-
importance
|
|
4353
|
+
importance,
|
|
2936
4354
|
memoryType: opts.type,
|
|
2937
4355
|
source: opts.source
|
|
2938
4356
|
});
|
|
2939
|
-
commitMemoryToChain(data.id, opts, data).catch(
|
|
2940
|
-
(err) =>
|
|
4357
|
+
commitMemoryToChain(data.id, opts, data, plaintextContent, envelope !== null || legacyEncrypt).catch(
|
|
4358
|
+
(err) => log16.warn({ err }, "On-chain memory commit failed")
|
|
2941
4359
|
);
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
4360
|
+
let enqueued = false;
|
|
4361
|
+
if (config.memory.outboxEnabled) {
|
|
4362
|
+
enqueued = await enqueueEnrichJob(db, data.id);
|
|
4363
|
+
}
|
|
4364
|
+
if (!enqueued) {
|
|
4365
|
+
const embedP = embedMemory(data.id, opts);
|
|
4366
|
+
embedP.catch((err) => log16.warn({ err }, "Embedding generation failed"));
|
|
4367
|
+
autoLinkMemory(data.id, opts).catch((err) => log16.warn({ err }, "Auto-linking failed"));
|
|
4368
|
+
extractAndLinkEntitiesForMemory(data.id, opts).catch((err) => log16.debug({ err }, "Entity extraction failed"));
|
|
4369
|
+
if (config.memory.reconcileEnabled) {
|
|
4370
|
+
const reconcileScope = ownerWallet || SCOPE_BOT_OWN2;
|
|
4371
|
+
embedP.then(() => runReconcileShadow(data.id, reconcileScope)).catch(() => {
|
|
4372
|
+
});
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
2945
4375
|
return data.id;
|
|
2946
4376
|
} catch (err) {
|
|
2947
|
-
|
|
4377
|
+
log16.error({ err }, "Memory store failed");
|
|
2948
4378
|
return null;
|
|
2949
4379
|
}
|
|
2950
4380
|
}
|
|
2951
|
-
async function commitMemoryToChain(memoryId, opts, row) {
|
|
4381
|
+
async function commitMemoryToChain(memoryId, opts, row, plaintextContent, encrypted) {
|
|
2952
4382
|
if (TOKENISATION_SKIP_SOURCES.has(opts.source)) return;
|
|
2953
4383
|
const canonicalHash = memoryContentHash({
|
|
2954
|
-
content:
|
|
4384
|
+
content: plaintextContent,
|
|
2955
4385
|
memory_type: row.memory_type,
|
|
2956
4386
|
owner_wallet: row.owner_wallet,
|
|
2957
4387
|
created_at: row.created_at,
|
|
@@ -2963,7 +4393,6 @@ async function commitMemoryToChain(memoryId, opts, row) {
|
|
|
2963
4393
|
const contentHashBuf = Buffer.from(canonicalHash, "hex");
|
|
2964
4394
|
let signature = null;
|
|
2965
4395
|
if (isRegistryEnabled()) {
|
|
2966
|
-
const encrypted = isEncryptionEnabled();
|
|
2967
4396
|
signature = await registerMemoryOnChain(
|
|
2968
4397
|
contentHashBuf,
|
|
2969
4398
|
opts.type,
|
|
@@ -2992,18 +4421,101 @@ async function commitMemoryToChain(memoryId, opts, row) {
|
|
|
2992
4421
|
tokenization_status: "minted",
|
|
2993
4422
|
tokenized_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2994
4423
|
}).eq("id", memoryId);
|
|
2995
|
-
|
|
4424
|
+
log16.debug({ memoryId, signature: signature.slice(0, 16) }, "Memory committed on-chain (PMP + legacy)");
|
|
4425
|
+
}
|
|
4426
|
+
function logOutboxDegradeOnce(err, memoryId) {
|
|
4427
|
+
if (outboxDegradeLogged) return;
|
|
4428
|
+
outboxDegradeLogged = true;
|
|
4429
|
+
const e = err;
|
|
4430
|
+
log16.warn(
|
|
4431
|
+
{ code: e?.code, message: e?.message, memoryId },
|
|
4432
|
+
"Outbox enqueue degraded \u2014 falling back to fire-and-forget (migration 044 unapplied?)"
|
|
4433
|
+
);
|
|
4434
|
+
}
|
|
4435
|
+
async function enqueueEnrichJob(db, memoryId) {
|
|
4436
|
+
try {
|
|
4437
|
+
const { error } = await db.from("memory_write_jobs").insert({ memory_id: memoryId, job_type: "enrich" });
|
|
4438
|
+
if (error) {
|
|
4439
|
+
logOutboxDegradeOnce(error, memoryId);
|
|
4440
|
+
return false;
|
|
4441
|
+
}
|
|
4442
|
+
return true;
|
|
4443
|
+
} catch (err) {
|
|
4444
|
+
logOutboxDegradeOnce(err, memoryId);
|
|
4445
|
+
return false;
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
function _resetOutboxDegradeLog() {
|
|
4449
|
+
outboxDegradeLogged = false;
|
|
4450
|
+
}
|
|
4451
|
+
async function runEnrichPipeline(memoryId, opts) {
|
|
4452
|
+
await embedMemory(memoryId, opts, { replaceFragments: true });
|
|
4453
|
+
await autoLinkMemory(memoryId, opts);
|
|
4454
|
+
await extractAndLinkEntitiesForMemory(memoryId, opts);
|
|
4455
|
+
if (config.memory.reconcileEnabled) {
|
|
4456
|
+
await runReconcileShadow(memoryId, getOwnerWallet() || SCOPE_BOT_OWN2);
|
|
4457
|
+
}
|
|
2996
4458
|
}
|
|
2997
|
-
async function
|
|
4459
|
+
async function storeMemoryWithOutbox(opts) {
|
|
4460
|
+
const id = await storeMemory(opts);
|
|
4461
|
+
if (id === null) return null;
|
|
4462
|
+
const db = getDb();
|
|
4463
|
+
const { data: row } = await db.from("memories").select("hash_id").eq("id", id).maybeSingle();
|
|
4464
|
+
const { count } = await db.from("memory_write_jobs").select("id", { count: "exact", head: true }).eq("memory_id", id);
|
|
4465
|
+
return { id, hash_id: row?.hash_id ?? "", jobs_queued: count ?? 0 };
|
|
4466
|
+
}
|
|
4467
|
+
async function embedMemory(memoryId, opts, embedOpts = {}) {
|
|
2998
4468
|
if (!isEmbeddingEnabled()) return;
|
|
2999
4469
|
const db = getDb();
|
|
3000
|
-
const
|
|
4470
|
+
const fragments = [];
|
|
4471
|
+
fragments.push({ type: "summary", text: opts.summary });
|
|
4472
|
+
const content = opts.content.slice(0, MEMORY_MAX_CONTENT_LENGTH);
|
|
4473
|
+
if (content.length > EMBEDDING_FRAGMENT_MAX_LENGTH) {
|
|
4474
|
+
const sentences = content.match(/[^.!?\n]+[.!?\n]+/g) || [content];
|
|
4475
|
+
let chunk = "";
|
|
4476
|
+
for (const sentence of sentences) {
|
|
4477
|
+
if (chunk.length + sentence.length > EMBEDDING_FRAGMENT_MAX_LENGTH && chunk.length > 0) {
|
|
4478
|
+
fragments.push({ type: "content_chunk", text: chunk.trim() });
|
|
4479
|
+
chunk = "";
|
|
4480
|
+
}
|
|
4481
|
+
chunk += sentence;
|
|
4482
|
+
}
|
|
4483
|
+
if (chunk.trim()) fragments.push({ type: "content_chunk", text: chunk.trim() });
|
|
4484
|
+
} else {
|
|
4485
|
+
fragments.push({ type: "content_chunk", text: content });
|
|
4486
|
+
}
|
|
4487
|
+
const allLabels = [...opts.tags || [], ...opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || [])];
|
|
4488
|
+
if (allLabels.length > 0) {
|
|
4489
|
+
fragments.push({ type: "tag_context", text: `Context: ${allLabels.join(", ")}. ${opts.summary}` });
|
|
4490
|
+
}
|
|
4491
|
+
const embeddings = await generateEmbeddings(fragments.map((f) => f.text));
|
|
3001
4492
|
const summaryEmbedding = embeddings[0];
|
|
3002
4493
|
if (!summaryEmbedding) {
|
|
3003
|
-
|
|
4494
|
+
log16.debug({ memoryId }, "No embedding generated; skipping semantic tagging");
|
|
3004
4495
|
return;
|
|
3005
4496
|
}
|
|
3006
|
-
|
|
4497
|
+
const vertexEmbeddings = isVertexConfigured() ? await generateVertexEmbeddings(fragments.map((f) => f.text)) : [];
|
|
4498
|
+
const vertexSummary = vertexEmbeddings[0];
|
|
4499
|
+
await db.from("memories").update({
|
|
4500
|
+
embedding: JSON.stringify(summaryEmbedding),
|
|
4501
|
+
...vertexSummary ? { embedding_vertex: JSON.stringify(vertexSummary) } : {}
|
|
4502
|
+
}).eq("id", memoryId);
|
|
4503
|
+
const fragmentRows = fragments.map((f, i) => ({
|
|
4504
|
+
memory_id: memoryId,
|
|
4505
|
+
fragment_type: f.type,
|
|
4506
|
+
content: f.text.slice(0, EMBEDDING_FRAGMENT_MAX_LENGTH),
|
|
4507
|
+
embedding: embeddings[i] ? JSON.stringify(embeddings[i]) : null,
|
|
4508
|
+
...vertexEmbeddings[i] ? { embedding_vertex: JSON.stringify(vertexEmbeddings[i]) } : {}
|
|
4509
|
+
})).filter((r) => r.embedding !== null);
|
|
4510
|
+
if (fragmentRows.length > 0) {
|
|
4511
|
+
if (embedOpts.replaceFragments) {
|
|
4512
|
+
await db.from("memory_fragments").delete().eq("memory_id", memoryId);
|
|
4513
|
+
}
|
|
4514
|
+
const { error } = await db.from("memory_fragments").insert(fragmentRows);
|
|
4515
|
+
if (error) {
|
|
4516
|
+
log16.warn({ err: error.message, memoryId }, "Failed to store memory fragments (summary embedding intact)");
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
3007
4519
|
try {
|
|
3008
4520
|
const ownerWallet = getOwnerWallet();
|
|
3009
4521
|
const installedPacks = await getInstalledPackIdsCached(ownerWallet);
|
|
@@ -3019,7 +4531,7 @@ async function embedMemory(memoryId, opts) {
|
|
|
3019
4531
|
const merged = Array.from(/* @__PURE__ */ new Set([...existing, ...semantic]));
|
|
3020
4532
|
if (merged.length !== existing.length) {
|
|
3021
4533
|
await db.from("memories").update({ tags: merged }).eq("id", memoryId);
|
|
3022
|
-
|
|
4534
|
+
log16.debug({
|
|
3023
4535
|
memoryId,
|
|
3024
4536
|
added: semantic.filter((t) => !existing.includes(t))
|
|
3025
4537
|
}, "Semantic pack tags appended");
|
|
@@ -3027,9 +4539,9 @@ async function embedMemory(memoryId, opts) {
|
|
|
3027
4539
|
}
|
|
3028
4540
|
}
|
|
3029
4541
|
} catch (err) {
|
|
3030
|
-
|
|
4542
|
+
log16.debug({ err, memoryId }, "Semantic tagging skipped");
|
|
3031
4543
|
}
|
|
3032
|
-
|
|
4544
|
+
log16.debug({ memoryId }, "Memory embedded");
|
|
3033
4545
|
}
|
|
3034
4546
|
async function expandQuery(query) {
|
|
3035
4547
|
if (!isOpenRouterEnabled()) return [query];
|
|
@@ -3047,10 +4559,10 @@ async function expandQuery(query) {
|
|
|
3047
4559
|
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3e3))
|
|
3048
4560
|
]);
|
|
3049
4561
|
const expansions = response.split("\n").map((l) => l.trim()).filter((l) => l.length > 5 && l.length < 200).slice(0, 3);
|
|
3050
|
-
|
|
4562
|
+
log16.debug({ original: query, expansions: expansions.length }, "Query expanded");
|
|
3051
4563
|
return [query, ...expansions];
|
|
3052
4564
|
} catch (err) {
|
|
3053
|
-
|
|
4565
|
+
log16.debug({ err }, "Query expansion failed, using original");
|
|
3054
4566
|
return [query];
|
|
3055
4567
|
}
|
|
3056
4568
|
}
|
|
@@ -3063,11 +4575,12 @@ async function recallMemories(opts) {
|
|
|
3063
4575
|
let vectorScores = opts._vectorScores || /* @__PURE__ */ new Map();
|
|
3064
4576
|
let primaryQueryEmbedding = null;
|
|
3065
4577
|
const vectorSearchPromise = queries.length > 0 && isEmbeddingEnabled() && !opts._vectorScores ? (async () => {
|
|
4578
|
+
const space = activeEmbeddingSpace();
|
|
3066
4579
|
const queryEmbeddings = await Promise.all(
|
|
3067
4580
|
queries.map(async (q) => {
|
|
3068
4581
|
const cached = getCachedEmbedding2(q);
|
|
3069
4582
|
if (cached) return cached;
|
|
3070
|
-
const emb = await
|
|
4583
|
+
const emb = await generateQueryEmbeddingForSpace(space, q);
|
|
3071
4584
|
if (emb) setCachedEmbedding2(q, emb);
|
|
3072
4585
|
return emb;
|
|
3073
4586
|
})
|
|
@@ -3075,39 +4588,69 @@ async function recallMemories(opts) {
|
|
|
3075
4588
|
const validEmbeddings = queryEmbeddings.filter((e) => e !== null);
|
|
3076
4589
|
if (validEmbeddings.length > 0) primaryQueryEmbedding = validEmbeddings[0];
|
|
3077
4590
|
if (validEmbeddings.length === 0) {
|
|
3078
|
-
|
|
4591
|
+
log16.debug("All query embeddings returned null, using keyword-only retrieval");
|
|
3079
4592
|
return;
|
|
3080
4593
|
}
|
|
3081
4594
|
try {
|
|
3082
|
-
const allSearches = validEmbeddings.
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
4595
|
+
const allSearches = validEmbeddings.flatMap((emb) => {
|
|
4596
|
+
const searches = [
|
|
4597
|
+
Promise.resolve(db.rpc(vectorRpcName("match_memories"), {
|
|
4598
|
+
query_embedding: JSON.stringify(emb),
|
|
4599
|
+
match_threshold: VECTOR_MATCH_THRESHOLD,
|
|
4600
|
+
match_count: limit * (opts.skipExpansion ? 12 : 4),
|
|
4601
|
+
filter_types: opts.memoryTypes || null,
|
|
4602
|
+
filter_user: opts.relatedUser || null,
|
|
4603
|
+
min_decay: minDecay,
|
|
4604
|
+
filter_owner: getOwnerScope(),
|
|
4605
|
+
filter_tags: opts.tags && opts.tags.length > 0 ? opts.tags : null
|
|
4606
|
+
})).then((r) => r.data || [])
|
|
4607
|
+
];
|
|
4608
|
+
if (!opts.skipExpansion) {
|
|
4609
|
+
searches.push(
|
|
4610
|
+
Promise.resolve(db.rpc(vectorRpcName("match_memory_fragments"), buildFragmentRpcArgs({
|
|
4611
|
+
embeddingJson: JSON.stringify(emb),
|
|
4612
|
+
matchThreshold: VECTOR_MATCH_THRESHOLD,
|
|
4613
|
+
matchCount: limit * 2,
|
|
4614
|
+
minDecay,
|
|
4615
|
+
memoryTypes: opts.memoryTypes
|
|
4616
|
+
}))).then((r) => r.data || [])
|
|
4617
|
+
);
|
|
4618
|
+
}
|
|
4619
|
+
return searches;
|
|
4620
|
+
});
|
|
3094
4621
|
const results2 = await Promise.all(allSearches);
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
4622
|
+
let fragmentHits = 0;
|
|
4623
|
+
if (opts.skipExpansion) {
|
|
4624
|
+
for (const batch of results2) {
|
|
4625
|
+
for (const m of batch) {
|
|
4626
|
+
const current = vectorScores.get(m.id) || 0;
|
|
4627
|
+
vectorScores.set(m.id, Math.max(current, m.similarity));
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
} else {
|
|
4631
|
+
for (let i = 0; i < results2.length; i++) {
|
|
4632
|
+
const isFragmentBatch = i % 2 === 1;
|
|
4633
|
+
for (const r of results2[i]) {
|
|
4634
|
+
const id = isFragmentBatch ? r.memory_id : r.id;
|
|
4635
|
+
const sim = isFragmentBatch ? r.max_similarity : r.similarity;
|
|
4636
|
+
if (!id) continue;
|
|
4637
|
+
const current = vectorScores.get(id) || 0;
|
|
4638
|
+
vectorScores.set(id, Math.max(current, sim));
|
|
4639
|
+
if (isFragmentBatch) fragmentHits++;
|
|
4640
|
+
}
|
|
3099
4641
|
}
|
|
3100
4642
|
}
|
|
3101
|
-
|
|
4643
|
+
log16.debug({
|
|
3102
4644
|
queryVariants: validEmbeddings.length,
|
|
3103
4645
|
uniqueMemories: vectorScores.size,
|
|
4646
|
+
fragmentHits,
|
|
3104
4647
|
fastMode: !!opts.skipExpansion
|
|
3105
4648
|
}, "Vector search completed");
|
|
3106
4649
|
} catch (err) {
|
|
3107
|
-
|
|
4650
|
+
log16.warn({ err }, "Vector search RPC failed, falling back to keyword retrieval");
|
|
3108
4651
|
}
|
|
3109
4652
|
})() : Promise.resolve();
|
|
3110
|
-
let importanceQuery = db.from("memories").select("*").gte("decay_factor", minDecay).not("source", "in", '("demo","demo-maas")').order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit * 3);
|
|
4653
|
+
let importanceQuery = db.from("memories").select("*").gte("decay_factor", minDecay).not("source", "in", '("demo","demo-maas")').not("provider_delegated", "is", false).order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit * 3);
|
|
3111
4654
|
importanceQuery = scopeToOwner(importanceQuery);
|
|
3112
4655
|
if (opts.memoryTypes && opts.memoryTypes.length > 0) {
|
|
3113
4656
|
importanceQuery = importanceQuery.in("memory_type", opts.memoryTypes);
|
|
@@ -3130,7 +4673,12 @@ async function recallMemories(opts) {
|
|
|
3130
4673
|
return await bm25SearchMemories(opts.query, {
|
|
3131
4674
|
limit: limit * 2,
|
|
3132
4675
|
minDecay,
|
|
3133
|
-
|
|
4676
|
+
// Use getOwnerWallet() (AsyncLocalStorage-aware), NOT the bare module-level
|
|
4677
|
+
// _ownerWallet. withOwnerWallet() sets the async context, not the module var,
|
|
4678
|
+
// so the bare var is null under hosted/wrapped calls and BM25 would search the
|
|
4679
|
+
// ENTIRE table unscoped — burying the owner's exact-match facts. This is why
|
|
4680
|
+
// BM25 silently never surfaced bench facts even after the RPC was fixed.
|
|
4681
|
+
filterOwner: getOwnerScope() || void 0,
|
|
3134
4682
|
filterTypes: opts.memoryTypes || void 0,
|
|
3135
4683
|
filterTags: opts.tags || void 0
|
|
3136
4684
|
});
|
|
@@ -3184,7 +4732,7 @@ async function recallMemories(opts) {
|
|
|
3184
4732
|
}
|
|
3185
4733
|
}
|
|
3186
4734
|
}
|
|
3187
|
-
if (seedsAdded > 0)
|
|
4735
|
+
if (seedsAdded > 0) log16.debug({ seedsAdded, seedsTotal: knowledgeSeeds.length }, "Knowledge seeds added to candidates");
|
|
3188
4736
|
}
|
|
3189
4737
|
}
|
|
3190
4738
|
const bm25Scores = /* @__PURE__ */ new Map();
|
|
@@ -3205,18 +4753,18 @@ async function recallMemories(opts) {
|
|
|
3205
4753
|
}
|
|
3206
4754
|
}
|
|
3207
4755
|
}
|
|
3208
|
-
|
|
4756
|
+
log16.debug({ bm25Hits: bm25Results.length, bm25New: bm25MissingIds.length }, "BM25 search added candidates");
|
|
3209
4757
|
}
|
|
3210
4758
|
if (error) {
|
|
3211
|
-
|
|
4759
|
+
log16.error({ error: error.message }, "Memory recall query failed");
|
|
3212
4760
|
return [];
|
|
3213
4761
|
}
|
|
3214
|
-
let candidates =
|
|
4762
|
+
let candidates = await decryptMemories(data || []);
|
|
3215
4763
|
if (vectorScores.size > 0) {
|
|
3216
4764
|
const metadataIds = new Set(candidates.map((m) => m.id));
|
|
3217
4765
|
const missingIds = [...vectorScores.keys()].filter((id) => !metadataIds.has(id));
|
|
3218
4766
|
if (missingIds.length > 0) {
|
|
3219
|
-
let vectorQuery = db.from("memories").select("*").in("id", missingIds);
|
|
4767
|
+
let vectorQuery = db.from("memories").select("*").in("id", missingIds).gte("decay_factor", minDecay).not("embedding", "is", null);
|
|
3220
4768
|
vectorQuery = scopeToOwner(vectorQuery);
|
|
3221
4769
|
if (opts.memoryTypes && opts.memoryTypes.length > 0) {
|
|
3222
4770
|
vectorQuery = vectorQuery.in("memory_type", opts.memoryTypes);
|
|
@@ -3225,7 +4773,7 @@ async function recallMemories(opts) {
|
|
|
3225
4773
|
vectorQuery = vectorQuery.overlaps("tags", opts.tags);
|
|
3226
4774
|
}
|
|
3227
4775
|
const { data: vectorOnly } = await vectorQuery;
|
|
3228
|
-
if (vectorOnly) candidates = [...candidates, ...
|
|
4776
|
+
if (vectorOnly) candidates = [...candidates, ...await decryptMemories(vectorOnly)];
|
|
3229
4777
|
}
|
|
3230
4778
|
}
|
|
3231
4779
|
if (candidates.length === 0) return [];
|
|
@@ -3249,10 +4797,10 @@ async function recallMemories(opts) {
|
|
|
3249
4797
|
if (entities.length > 0) {
|
|
3250
4798
|
const resultIdSet = new Set(results.map((m) => m.id));
|
|
3251
4799
|
for (const entity of entities) {
|
|
3252
|
-
const entityMemories = await getMemoriesByEntity(entity.id, {
|
|
4800
|
+
const entityMemories = await decryptMemories(await getMemoriesByEntity(entity.id, {
|
|
3253
4801
|
limit: Math.ceil(limit / 2),
|
|
3254
4802
|
memoryTypes: opts.memoryTypes
|
|
3255
|
-
});
|
|
4803
|
+
}));
|
|
3256
4804
|
for (const mem of entityMemories) {
|
|
3257
4805
|
addLinkPath(mem.id, "entity");
|
|
3258
4806
|
if (!resultIdSet.has(mem.id)) {
|
|
@@ -3264,17 +4812,17 @@ async function recallMemories(opts) {
|
|
|
3264
4812
|
}
|
|
3265
4813
|
}
|
|
3266
4814
|
}
|
|
3267
|
-
|
|
4815
|
+
log16.debug({ entities: entities.map((e) => e.name) }, "Entity-aware recall applied");
|
|
3268
4816
|
let cooccurrenceAdded = 0;
|
|
3269
4817
|
const cooccurrenceNames = [];
|
|
3270
4818
|
for (const entity of entities) {
|
|
3271
4819
|
const cooccurrences = await getEntityCooccurrences(entity.id, { minCooccurrence: 2, maxResults: 3 });
|
|
3272
4820
|
for (const cooc of cooccurrences) {
|
|
3273
4821
|
if (cooccurrenceAdded >= limit) break;
|
|
3274
|
-
const coMems = await getMemoriesByEntity(cooc.related_entity_id, {
|
|
4822
|
+
const coMems = await decryptMemories(await getMemoriesByEntity(cooc.related_entity_id, {
|
|
3275
4823
|
limit: 3,
|
|
3276
4824
|
memoryTypes: opts.memoryTypes
|
|
3277
|
-
});
|
|
4825
|
+
}));
|
|
3278
4826
|
for (const mem of coMems) {
|
|
3279
4827
|
if (cooccurrenceAdded >= limit) break;
|
|
3280
4828
|
addLinkPath(mem.id, "entity");
|
|
@@ -3294,11 +4842,11 @@ async function recallMemories(opts) {
|
|
|
3294
4842
|
}
|
|
3295
4843
|
}
|
|
3296
4844
|
if (cooccurrenceAdded > 0) {
|
|
3297
|
-
|
|
4845
|
+
log16.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, "Entity co-occurrence recall applied");
|
|
3298
4846
|
}
|
|
3299
4847
|
}
|
|
3300
4848
|
} catch (err) {
|
|
3301
|
-
|
|
4849
|
+
log16.debug({ err }, "Entity-aware recall skipped");
|
|
3302
4850
|
}
|
|
3303
4851
|
}
|
|
3304
4852
|
if (results.length > 0) {
|
|
@@ -3308,17 +4856,17 @@ async function recallMemories(opts) {
|
|
|
3308
4856
|
seed_ids: seedIds,
|
|
3309
4857
|
min_strength: 0.2,
|
|
3310
4858
|
max_results: limit,
|
|
3311
|
-
filter_owner:
|
|
4859
|
+
filter_owner: getOwnerScope()
|
|
3312
4860
|
});
|
|
3313
4861
|
if (linked && linked.length > 0) {
|
|
3314
4862
|
const resultIdSet = new Set(seedIds);
|
|
3315
4863
|
const graphCandidateIds = linked.filter((l) => !resultIdSet.has(l.memory_id)).map((l) => l.memory_id);
|
|
3316
4864
|
if (graphCandidateIds.length > 0) {
|
|
3317
|
-
let graphQuery = db.from("memories").select("*").in("id", graphCandidateIds);
|
|
4865
|
+
let graphQuery = db.from("memories").select("*").in("id", graphCandidateIds).not("provider_delegated", "is", false);
|
|
3318
4866
|
graphQuery = scopeToOwner(graphQuery);
|
|
3319
4867
|
const { data: graphMemories } = await graphQuery;
|
|
3320
4868
|
if (graphMemories && graphMemories.length > 0) {
|
|
3321
|
-
|
|
4869
|
+
await decryptMemories(graphMemories);
|
|
3322
4870
|
const linkBoostMap = /* @__PURE__ */ new Map();
|
|
3323
4871
|
for (const l of linked) {
|
|
3324
4872
|
const bondWeight = BOND_TYPE_WEIGHTS[l.link_type] ?? 0.4;
|
|
@@ -3332,7 +4880,7 @@ async function recallMemories(opts) {
|
|
|
3332
4880
|
_score: scoreMemory(mem, scoredOpts) + RETRIEVAL_WEIGHT_GRAPH * (linkBoostMap.get(mem.id) || 0)
|
|
3333
4881
|
}));
|
|
3334
4882
|
results = [...results, ...graphScored].sort((a, b) => b._score - a._score).slice(0, limit);
|
|
3335
|
-
|
|
4883
|
+
log16.debug({
|
|
3336
4884
|
graphExpanded: graphMemories.length,
|
|
3337
4885
|
linkedTotal: linked.length,
|
|
3338
4886
|
bondTypes: [...new Set(linked.map((l) => l.link_type))]
|
|
@@ -3341,7 +4889,7 @@ async function recallMemories(opts) {
|
|
|
3341
4889
|
}
|
|
3342
4890
|
}
|
|
3343
4891
|
} catch (err) {
|
|
3344
|
-
|
|
4892
|
+
log16.debug({ err }, "Graph expansion skipped (RPC unavailable)");
|
|
3345
4893
|
}
|
|
3346
4894
|
}
|
|
3347
4895
|
if (results.length >= 3) {
|
|
@@ -3357,23 +4905,23 @@ async function recallMemories(opts) {
|
|
|
3357
4905
|
...results.slice(0, results.length - replaceCount),
|
|
3358
4906
|
...diverseCandidates.slice(0, replaceCount)
|
|
3359
4907
|
].sort((a, b) => b._score - a._score).slice(0, limit);
|
|
3360
|
-
|
|
4908
|
+
log16.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, "Type diversity applied");
|
|
3361
4909
|
}
|
|
3362
4910
|
}
|
|
3363
4911
|
}
|
|
3364
|
-
const
|
|
3365
|
-
|
|
3366
|
-
const
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
log10.warn({ stripped: beforeCount - results.length, owner: finalOwner }, "Owner guard stripped foreign memories from recall results");
|
|
4912
|
+
const finalScope = getOwnerScope();
|
|
4913
|
+
{
|
|
4914
|
+
const { kept, stripped } = applyOwnerPostGuard(results, finalScope);
|
|
4915
|
+
if (stripped > 0) {
|
|
4916
|
+
log16.warn({ stripped, botOwn: finalScope === SCOPE_BOT_OWN2 }, "Owner guard stripped foreign memories from recall results");
|
|
3370
4917
|
}
|
|
4918
|
+
results = kept;
|
|
3371
4919
|
}
|
|
3372
|
-
if (opts
|
|
4920
|
+
if (shouldTrackAccess(opts)) {
|
|
3373
4921
|
const ids = results.map((m) => m.id);
|
|
3374
4922
|
const sources = results.map((m) => m.source || "");
|
|
3375
|
-
updateMemoryAccess(ids, sources).catch((err) =>
|
|
3376
|
-
reinforceCoRetrievedLinks(ids).catch((err) =>
|
|
4923
|
+
updateMemoryAccess(ids, sources).catch((err) => log16.warn({ err }, "Memory access tracking failed"));
|
|
4924
|
+
reinforceCoRetrievedLinks(ids).catch((err) => log16.debug({ err }, "Link reinforcement failed"));
|
|
3377
4925
|
}
|
|
3378
4926
|
for (const m of results) {
|
|
3379
4927
|
const paths = linkPathMap.get(m.id);
|
|
@@ -3381,7 +4929,7 @@ async function recallMemories(opts) {
|
|
|
3381
4929
|
m.link_path = [...paths];
|
|
3382
4930
|
}
|
|
3383
4931
|
}
|
|
3384
|
-
|
|
4932
|
+
log16.debug({
|
|
3385
4933
|
recalled: results.length,
|
|
3386
4934
|
topScore: results[0]?._score?.toFixed(3),
|
|
3387
4935
|
query: opts.query?.slice(0, 40),
|
|
@@ -3391,7 +4939,7 @@ async function recallMemories(opts) {
|
|
|
3391
4939
|
}, "Memories recalled");
|
|
3392
4940
|
return results;
|
|
3393
4941
|
} catch (err) {
|
|
3394
|
-
|
|
4942
|
+
log16.error({ err }, "Memory recall failed");
|
|
3395
4943
|
return [];
|
|
3396
4944
|
}
|
|
3397
4945
|
}
|
|
@@ -3405,7 +4953,7 @@ function extractQueryTerms(query) {
|
|
|
3405
4953
|
}
|
|
3406
4954
|
function scoreMemory(mem, opts) {
|
|
3407
4955
|
const now = Date.now();
|
|
3408
|
-
const hoursSinceAccess = (now - new Date(mem.last_accessed).getTime()) / (1e3 * 60 * 60);
|
|
4956
|
+
const hoursSinceAccess = Math.max(0, (now - new Date(mem.last_accessed).getTime()) / (1e3 * 60 * 60));
|
|
3409
4957
|
const recency = Math.pow(RECENCY_DECAY_BASE, hoursSinceAccess);
|
|
3410
4958
|
let textScore = 0.5;
|
|
3411
4959
|
if (opts.query) {
|
|
@@ -3441,7 +4989,7 @@ function scoreMemory(mem, opts) {
|
|
|
3441
4989
|
}
|
|
3442
4990
|
const bm25Rank = opts._bm25Scores?.get(mem.id) || 0;
|
|
3443
4991
|
if (bm25Rank > 0) {
|
|
3444
|
-
rawScore +=
|
|
4992
|
+
rawScore += RETRIEVAL_WEIGHT_BM25 * Math.min(bm25Rank, 1);
|
|
3445
4993
|
}
|
|
3446
4994
|
const typeBoost = KNOWLEDGE_TYPE_BOOST[mem.memory_type] || 0;
|
|
3447
4995
|
rawScore += typeBoost;
|
|
@@ -3462,7 +5010,7 @@ async function recallMemorySummaries(opts) {
|
|
|
3462
5010
|
const limit = opts.limit || 10;
|
|
3463
5011
|
const minDecay = opts.minDecay ?? 0.1;
|
|
3464
5012
|
try {
|
|
3465
|
-
let query = db.from("memories").select("id, memory_type, summary, tags, concepts, importance, decay_factor, created_at, source").gte("decay_factor", minDecay).not("source", "in", '("demo","demo-maas")').order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit);
|
|
5013
|
+
let query = db.from("memories").select("id, memory_type, summary, tags, concepts, importance, decay_factor, created_at, source").gte("decay_factor", minDecay).not("source", "in", '("demo","demo-maas")').not("provider_delegated", "is", false).order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit);
|
|
3466
5014
|
query = scopeToOwner(query);
|
|
3467
5015
|
if (opts.memoryTypes && opts.memoryTypes.length > 0) {
|
|
3468
5016
|
query = query.in("memory_type", opts.memoryTypes);
|
|
@@ -3481,12 +5029,12 @@ async function recallMemorySummaries(opts) {
|
|
|
3481
5029
|
}
|
|
3482
5030
|
const { data, error } = await query;
|
|
3483
5031
|
if (error) {
|
|
3484
|
-
|
|
5032
|
+
log16.error({ error: error.message }, "Memory summary recall failed");
|
|
3485
5033
|
return [];
|
|
3486
5034
|
}
|
|
3487
5035
|
return data || [];
|
|
3488
5036
|
} catch (err) {
|
|
3489
|
-
|
|
5037
|
+
log16.error({ err }, "Memory summary recall failed");
|
|
3490
5038
|
return [];
|
|
3491
5039
|
}
|
|
3492
5040
|
}
|
|
@@ -3498,28 +5046,23 @@ async function hydrateMemories(ids) {
|
|
|
3498
5046
|
query = scopeToOwner(query);
|
|
3499
5047
|
const { data, error } = await query;
|
|
3500
5048
|
if (error) {
|
|
3501
|
-
|
|
5049
|
+
log16.error({ error: error.message }, "Memory hydration failed");
|
|
3502
5050
|
return [];
|
|
3503
5051
|
}
|
|
3504
|
-
return
|
|
5052
|
+
return await decryptMemories(data || []);
|
|
3505
5053
|
} catch (err) {
|
|
3506
|
-
|
|
5054
|
+
log16.error({ err }, "Memory hydration failed");
|
|
3507
5055
|
return [];
|
|
3508
5056
|
}
|
|
3509
5057
|
}
|
|
3510
|
-
async function updateMemoryAccess(ids,
|
|
5058
|
+
async function updateMemoryAccess(ids, _sources = []) {
|
|
3511
5059
|
if (ids.length === 0) return;
|
|
3512
5060
|
const db = getDb();
|
|
3513
|
-
const importanceBoosts = ids.map((_, i) => {
|
|
3514
|
-
const source = sources[i] || "";
|
|
3515
|
-
return INTERNAL_MEMORY_SOURCES.has(source) ? INTERNAL_IMPORTANCE_BOOST : 0.02;
|
|
3516
|
-
});
|
|
3517
5061
|
const { error } = await db.rpc("batch_boost_memory_access", {
|
|
3518
|
-
memory_ids: ids
|
|
3519
|
-
importance_boosts: importanceBoosts
|
|
5062
|
+
memory_ids: ids
|
|
3520
5063
|
});
|
|
3521
5064
|
if (error) {
|
|
3522
|
-
|
|
5065
|
+
log16.warn({ error: error.message, ids }, "Batch memory access update failed");
|
|
3523
5066
|
}
|
|
3524
5067
|
}
|
|
3525
5068
|
async function createMemoryLink(sourceId, targetId, linkType, strength = 0.5) {
|
|
@@ -3532,7 +5075,7 @@ async function createMemoryLink(sourceId, targetId, linkType, strength = 0.5) {
|
|
|
3532
5075
|
strength: clamp(strength, 0, 1)
|
|
3533
5076
|
}, { onConflict: "source_id,target_id,link_type" });
|
|
3534
5077
|
if (error) {
|
|
3535
|
-
|
|
5078
|
+
log16.debug({ error: error.message, sourceId, targetId, linkType }, "Link creation failed");
|
|
3536
5079
|
}
|
|
3537
5080
|
}
|
|
3538
5081
|
async function createMemoryLinksBatch(links) {
|
|
@@ -3554,7 +5097,7 @@ async function createMemoryLinksBatch(links) {
|
|
|
3554
5097
|
const db = getDb();
|
|
3555
5098
|
const { error } = await db.from("memory_links").upsert(rows, { onConflict: "source_id,target_id,link_type" });
|
|
3556
5099
|
if (error) {
|
|
3557
|
-
|
|
5100
|
+
log16.debug({ error: error.message, count: rows.length }, "Batch link creation failed");
|
|
3558
5101
|
}
|
|
3559
5102
|
}
|
|
3560
5103
|
async function autoLinkMemory(memoryId, opts) {
|
|
@@ -3573,7 +5116,7 @@ async function autoLinkMemory(memoryId, opts) {
|
|
|
3573
5116
|
query_embedding: JSON.stringify(embedding),
|
|
3574
5117
|
match_threshold: LINK_SIMILARITY_THRESHOLD,
|
|
3575
5118
|
match_count: MAX_AUTO_LINKS * 2,
|
|
3576
|
-
filter_owner:
|
|
5119
|
+
filter_owner: getOwnerScope()
|
|
3577
5120
|
});
|
|
3578
5121
|
if (similar) {
|
|
3579
5122
|
const similarIds = similar.map((s) => s.id).filter((id) => id !== memoryId);
|
|
@@ -3622,7 +5165,7 @@ async function autoLinkMemory(memoryId, opts) {
|
|
|
3622
5165
|
}
|
|
3623
5166
|
if (pendingLinks.length > 0) {
|
|
3624
5167
|
await createMemoryLinksBatch(pendingLinks);
|
|
3625
|
-
|
|
5168
|
+
log16.debug({ memoryId, linksCreated: pendingLinks.length }, "Auto-linked memory");
|
|
3626
5169
|
}
|
|
3627
5170
|
}
|
|
3628
5171
|
function classifyLinkType(newMem, candidate, newConcepts) {
|
|
@@ -3644,16 +5187,16 @@ async function reinforceCoRetrievedLinks(ids) {
|
|
|
3644
5187
|
boost_amount: LINK_CO_RETRIEVAL_BOOST
|
|
3645
5188
|
});
|
|
3646
5189
|
if (error) {
|
|
3647
|
-
|
|
5190
|
+
log16.debug({ error: error.message }, "Link reinforcement RPC failed");
|
|
3648
5191
|
} else if (data && data > 0) {
|
|
3649
|
-
|
|
5192
|
+
log16.debug({ boosted: data }, "Co-retrieval link reinforcement applied");
|
|
3650
5193
|
}
|
|
3651
5194
|
}
|
|
3652
5195
|
async function extractAndLinkEntitiesForMemory(memoryId, opts) {
|
|
3653
5196
|
try {
|
|
3654
5197
|
await extractAndLinkEntities(memoryId, opts.content, opts.summary, opts.relatedUser);
|
|
3655
5198
|
} catch (err) {
|
|
3656
|
-
|
|
5199
|
+
log16.debug({ err, memoryId }, "Entity extraction failed");
|
|
3657
5200
|
}
|
|
3658
5201
|
}
|
|
3659
5202
|
async function decayMemories() {
|
|
@@ -3669,17 +5212,17 @@ async function decayMemories() {
|
|
|
3669
5212
|
cutoff
|
|
3670
5213
|
});
|
|
3671
5214
|
if (error) {
|
|
3672
|
-
|
|
5215
|
+
log16.warn({ error: error.message, memType }, "Batch decay failed for type");
|
|
3673
5216
|
continue;
|
|
3674
5217
|
}
|
|
3675
5218
|
totalDecayed += data || 0;
|
|
3676
5219
|
}
|
|
3677
5220
|
if (totalDecayed > 0) {
|
|
3678
|
-
|
|
5221
|
+
log16.info({ decayed: totalDecayed }, "Type-specific memory decay applied");
|
|
3679
5222
|
}
|
|
3680
5223
|
return totalDecayed;
|
|
3681
5224
|
} catch (err) {
|
|
3682
|
-
|
|
5225
|
+
log16.error({ err }, "Memory decay failed");
|
|
3683
5226
|
return 0;
|
|
3684
5227
|
}
|
|
3685
5228
|
}
|
|
@@ -3689,7 +5232,7 @@ async function deleteMemory(id) {
|
|
|
3689
5232
|
query = scopeToOwner(query);
|
|
3690
5233
|
const { error } = await query;
|
|
3691
5234
|
if (error) {
|
|
3692
|
-
|
|
5235
|
+
log16.error({ error: error.message, id }, "Failed to delete memory");
|
|
3693
5236
|
return false;
|
|
3694
5237
|
}
|
|
3695
5238
|
return true;
|
|
@@ -3707,7 +5250,7 @@ async function updateMemory(id, patches) {
|
|
|
3707
5250
|
query = scopeToOwner(query);
|
|
3708
5251
|
const { error } = await query;
|
|
3709
5252
|
if (error) {
|
|
3710
|
-
|
|
5253
|
+
log16.error({ error: error.message, id }, "Failed to update memory");
|
|
3711
5254
|
return false;
|
|
3712
5255
|
}
|
|
3713
5256
|
return true;
|
|
@@ -3728,10 +5271,10 @@ async function listMemories(opts) {
|
|
|
3728
5271
|
if (opts.min_importance !== void 0) dataQ = dataQ.gte("importance", opts.min_importance);
|
|
3729
5272
|
const { data, error } = await dataQ;
|
|
3730
5273
|
if (error) {
|
|
3731
|
-
|
|
5274
|
+
log16.error({ error: error.message }, "Failed to list memories");
|
|
3732
5275
|
return { memories: [], total: 0 };
|
|
3733
5276
|
}
|
|
3734
|
-
return { memories:
|
|
5277
|
+
return { memories: await decryptMemories(data || []), total: count ?? 0 };
|
|
3735
5278
|
}
|
|
3736
5279
|
async function getMemoryStats() {
|
|
3737
5280
|
const db = getDb();
|
|
@@ -3768,7 +5311,7 @@ async function getMemoryStats() {
|
|
|
3768
5311
|
const MAX_PAGES = 20;
|
|
3769
5312
|
let allMemories = [];
|
|
3770
5313
|
for (let page = 0; page < MAX_PAGES; page++) {
|
|
3771
|
-
let pageQuery = db.from("memories").select("importance, decay_factor, created_at, related_user, tags, concepts").order("id", { ascending: true }).range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1);
|
|
5314
|
+
let pageQuery = db.from("memories").select("importance, decay_factor, created_at, related_user, owner_wallet, tags, concepts").order("id", { ascending: true }).range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1);
|
|
3772
5315
|
pageQuery = scopeToOwner(pageQuery);
|
|
3773
5316
|
const { data: pageData } = await pageQuery;
|
|
3774
5317
|
if (!pageData || pageData.length === 0) break;
|
|
@@ -3785,6 +5328,7 @@ async function getMemoryStats() {
|
|
|
3785
5328
|
impSum += m.importance;
|
|
3786
5329
|
decaySum += m.decay_factor;
|
|
3787
5330
|
if (m.related_user) users.add(m.related_user);
|
|
5331
|
+
if (m.owner_wallet) users.add(m.owner_wallet);
|
|
3788
5332
|
if (m.tags) {
|
|
3789
5333
|
for (const tag of m.tags) {
|
|
3790
5334
|
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
|
@@ -3805,13 +5349,15 @@ async function getMemoryStats() {
|
|
|
3805
5349
|
stats.oldestMemory = sorted[0] || null;
|
|
3806
5350
|
stats.newestMemory = sorted[sorted.length - 1] || null;
|
|
3807
5351
|
}
|
|
3808
|
-
|
|
5352
|
+
let dreamQuery = db.from("dream_logs").select("id", { count: "exact", head: true });
|
|
5353
|
+
dreamQuery = scopeToOwner(dreamQuery);
|
|
5354
|
+
const { count, error: dreamError } = await dreamQuery;
|
|
3809
5355
|
if (dreamError) {
|
|
3810
|
-
|
|
5356
|
+
log16.warn({ error: dreamError.message }, "Failed to count dream logs");
|
|
3811
5357
|
}
|
|
3812
5358
|
stats.totalDreamSessions = count || 0;
|
|
3813
5359
|
} catch (err) {
|
|
3814
|
-
|
|
5360
|
+
log16.error({ err }, "Failed to get memory stats");
|
|
3815
5361
|
}
|
|
3816
5362
|
return stats;
|
|
3817
5363
|
}
|
|
@@ -3825,10 +5371,10 @@ async function getRecentMemories(hours, types, limit) {
|
|
|
3825
5371
|
}
|
|
3826
5372
|
const { data, error } = await query;
|
|
3827
5373
|
if (error) {
|
|
3828
|
-
|
|
5374
|
+
log16.error({ error: error.message }, "Failed to get recent memories");
|
|
3829
5375
|
return [];
|
|
3830
5376
|
}
|
|
3831
|
-
return
|
|
5377
|
+
return await decryptMemories(data || []);
|
|
3832
5378
|
}
|
|
3833
5379
|
async function getSelfModel() {
|
|
3834
5380
|
const db = getDb();
|
|
@@ -3836,10 +5382,10 @@ async function getSelfModel() {
|
|
|
3836
5382
|
query = scopeToOwner(query);
|
|
3837
5383
|
const { data, error } = await query;
|
|
3838
5384
|
if (error) {
|
|
3839
|
-
|
|
5385
|
+
log16.error({ error: error.message }, "Failed to get self model");
|
|
3840
5386
|
return [];
|
|
3841
5387
|
}
|
|
3842
|
-
return
|
|
5388
|
+
return await decryptMemories(data || []);
|
|
3843
5389
|
}
|
|
3844
5390
|
async function storeDreamLog(sessionType, inputMemoryIds, output, newMemoryIds) {
|
|
3845
5391
|
const db = getDb();
|
|
@@ -3847,54 +5393,48 @@ async function storeDreamLog(sessionType, inputMemoryIds, output, newMemoryIds)
|
|
|
3847
5393
|
session_type: sessionType,
|
|
3848
5394
|
input_memory_ids: inputMemoryIds,
|
|
3849
5395
|
output: output.slice(0, MEMORY_MAX_CONTENT_LENGTH),
|
|
3850
|
-
new_memories_created: newMemoryIds
|
|
5396
|
+
new_memories_created: newMemoryIds,
|
|
5397
|
+
// Stamp owner so getMemoryStats can scope the dream count (migration 021).
|
|
5398
|
+
owner_wallet: getOwnerWallet() === SCOPE_BOT_OWN2 ? null : getOwnerWallet()
|
|
3851
5399
|
});
|
|
3852
5400
|
if (error) {
|
|
3853
|
-
|
|
5401
|
+
log16.error({ error: error.message }, "Failed to store dream log");
|
|
3854
5402
|
}
|
|
3855
5403
|
}
|
|
3856
5404
|
function formatMemoryContext(memories) {
|
|
3857
5405
|
if (memories.length === 0) return "";
|
|
3858
5406
|
const lines = ["## Memory Recall"];
|
|
3859
|
-
const episodic = memories.filter((m) => m.memory_type === "episodic");
|
|
3860
|
-
const semantic = memories.filter((m) => m.memory_type === "semantic");
|
|
3861
|
-
const procedural = memories.filter((m) => m.memory_type === "procedural");
|
|
3862
|
-
const selfModel = memories.filter((m) => m.memory_type === "self_model");
|
|
3863
|
-
const introspective = memories.filter((m) => m.memory_type === "introspective");
|
|
5407
|
+
const episodic = memories.filter((m) => m.memory_type === "episodic").sort(byMemoryDateAsc);
|
|
5408
|
+
const semantic = memories.filter((m) => m.memory_type === "semantic").sort(byMemoryDateAsc);
|
|
5409
|
+
const procedural = memories.filter((m) => m.memory_type === "procedural").sort(byMemoryDateAsc);
|
|
5410
|
+
const selfModel = memories.filter((m) => m.memory_type === "self_model").sort(byMemoryDateAsc);
|
|
5411
|
+
const introspective = memories.filter((m) => m.memory_type === "introspective").sort(byMemoryDateAsc);
|
|
3864
5412
|
if (episodic.length > 0) {
|
|
3865
5413
|
lines.push("### Past Interactions");
|
|
3866
|
-
for (const m of episodic)
|
|
3867
|
-
lines.push(`- [${timeAgo(m.created_at)}] ${m.summary}`);
|
|
3868
|
-
}
|
|
5414
|
+
for (const m of episodic) lines.push(renderGroundedLine(m));
|
|
3869
5415
|
}
|
|
3870
5416
|
if (semantic.length > 0) {
|
|
3871
5417
|
lines.push("### Things You Know");
|
|
3872
|
-
for (const m of semantic)
|
|
3873
|
-
lines.push(`- ${m.summary}`);
|
|
3874
|
-
}
|
|
5418
|
+
for (const m of semantic) lines.push(renderGroundedLine(m));
|
|
3875
5419
|
}
|
|
3876
5420
|
if (procedural.length > 0) {
|
|
3877
5421
|
lines.push("### Learned Strategies (from past outcomes)");
|
|
3878
5422
|
for (const m of procedural) {
|
|
3879
5423
|
const meta = m.metadata;
|
|
3880
5424
|
const confidence = meta?.positiveRate != null ? ` [${Math.round(meta.positiveRate * 100)}% success rate, based on ${meta.basedOn || "?"} interactions]` : "";
|
|
3881
|
-
lines.push(
|
|
5425
|
+
lines.push(renderGroundedLine(m, confidence));
|
|
3882
5426
|
}
|
|
3883
5427
|
}
|
|
3884
5428
|
if (introspective.length > 0) {
|
|
3885
5429
|
lines.push("### Your Own Reflections");
|
|
3886
|
-
for (const m of introspective)
|
|
3887
|
-
lines.push(`- [${timeAgo(m.created_at)}] ${m.summary}`);
|
|
3888
|
-
}
|
|
5430
|
+
for (const m of introspective) lines.push(renderGroundedLine(m));
|
|
3889
5431
|
}
|
|
3890
5432
|
if (selfModel.length > 0) {
|
|
3891
5433
|
lines.push("### Self-Observations");
|
|
3892
|
-
for (const m of selfModel)
|
|
3893
|
-
lines.push(`- ${m.summary}`);
|
|
3894
|
-
}
|
|
5434
|
+
for (const m of selfModel) lines.push(renderGroundedLine(m));
|
|
3895
5435
|
}
|
|
3896
5436
|
lines.push("");
|
|
3897
|
-
lines.push(
|
|
5437
|
+
lines.push(CONTEXT_GROUNDING_RULES);
|
|
3898
5438
|
if (procedural.length > 0) {
|
|
3899
5439
|
lines.push("");
|
|
3900
5440
|
lines.push("IMPORTANT: You MUST follow the Learned Strategies above. They are behavioral rules you derived from analyzing your own past successes and failures. Apply them to this response.");
|
|
@@ -3919,10 +5459,10 @@ async function scoreImportanceWithLLM(description, fallbackOpts) {
|
|
|
3919
5459
|
if (!isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
3920
5460
|
return parsed / 10;
|
|
3921
5461
|
}
|
|
3922
|
-
|
|
5462
|
+
log16.warn({ response }, "LLM importance score unparseable, using fallback");
|
|
3923
5463
|
return calculateImportance(fallbackOpts || {});
|
|
3924
5464
|
} catch (err) {
|
|
3925
|
-
|
|
5465
|
+
log16.warn({ err }, "LLM importance scoring failed, using fallback");
|
|
3926
5466
|
return calculateImportance(fallbackOpts || {});
|
|
3927
5467
|
}
|
|
3928
5468
|
}
|
|
@@ -3946,7 +5486,8 @@ async function matchByEmbedding(opts) {
|
|
|
3946
5486
|
query_embedding: JSON.stringify(opts.embedding),
|
|
3947
5487
|
match_threshold: opts.threshold,
|
|
3948
5488
|
match_count: opts.limit,
|
|
3949
|
-
|
|
5489
|
+
// Explicit ownerWallet wins; otherwise the ambient scope (sentinel-aware, C0).
|
|
5490
|
+
filter_owner: opts.ownerWallet ?? getOwnerScope()
|
|
3950
5491
|
});
|
|
3951
5492
|
return (data ?? []).map((r) => ({
|
|
3952
5493
|
id: r.id,
|
|
@@ -3969,32 +5510,39 @@ function moodToValence(mood) {
|
|
|
3969
5510
|
return 0;
|
|
3970
5511
|
}
|
|
3971
5512
|
}
|
|
3972
|
-
var
|
|
5513
|
+
var import_crypto3, EMBED_CACHE_MAX, embeddingCache, _ownerWallet, SCOPE_BOT_OWN2, HASH_ID_PREFIX, log16, DEDUP_TTL_MS, dedupCache, installedPacksCache, INSTALLED_PACKS_TTL_MS, topicEmbeddingCache, topicEmbeddingPopulationLock, TOKENISATION_SKIP_SOURCES, outboxDegradeLogged, STOPWORDS, CONTEXT_GROUNDING_RULES;
|
|
3973
5514
|
var init_memory = __esm({
|
|
3974
5515
|
"packages/brain/src/memory/memory.ts"() {
|
|
3975
5516
|
"use strict";
|
|
3976
5517
|
init_database();
|
|
5518
|
+
init_config();
|
|
3977
5519
|
init_logger();
|
|
5520
|
+
init_memory_grounding();
|
|
3978
5521
|
init_utils();
|
|
3979
5522
|
init_claude_client();
|
|
3980
5523
|
init_solana_client();
|
|
3981
5524
|
init_src();
|
|
3982
5525
|
init_embeddings();
|
|
5526
|
+
init_migration_profile();
|
|
3983
5527
|
init_wiki_packs();
|
|
3984
5528
|
init_config2();
|
|
3985
5529
|
init_bm25_search();
|
|
5530
|
+
init_content_tokens();
|
|
5531
|
+
init_reconcile_shadow();
|
|
5532
|
+
init_memory_encryption();
|
|
3986
5533
|
init_openrouter_client();
|
|
3987
5534
|
init_encryption();
|
|
5535
|
+
init_memory_decryption();
|
|
3988
5536
|
init_event_bus();
|
|
3989
|
-
|
|
5537
|
+
import_crypto3 = require("crypto");
|
|
3990
5538
|
init_graph();
|
|
3991
5539
|
init_owner_context();
|
|
3992
5540
|
EMBED_CACHE_MAX = 200;
|
|
3993
5541
|
embeddingCache = /* @__PURE__ */ new Map();
|
|
3994
5542
|
_ownerWallet = null;
|
|
3995
|
-
|
|
5543
|
+
SCOPE_BOT_OWN2 = "__BOT_OWN__";
|
|
3996
5544
|
HASH_ID_PREFIX = "clude";
|
|
3997
|
-
|
|
5545
|
+
log16 = createChildLogger("memory");
|
|
3998
5546
|
DEDUP_TTL_MS = 10 * 60 * 1e3;
|
|
3999
5547
|
dedupCache = /* @__PURE__ */ new Map();
|
|
4000
5548
|
installedPacksCache = /* @__PURE__ */ new Map();
|
|
@@ -4007,6 +5555,7 @@ var init_memory = __esm({
|
|
|
4007
5555
|
"locomo-benchmark",
|
|
4008
5556
|
"longmemeval-benchmark"
|
|
4009
5557
|
]);
|
|
5558
|
+
outboxDegradeLogged = false;
|
|
4010
5559
|
STOPWORDS = /* @__PURE__ */ new Set([
|
|
4011
5560
|
"the",
|
|
4012
5561
|
"a",
|
|
@@ -4091,18 +5640,92 @@ var init_memory = __esm({
|
|
|
4091
5640
|
"our",
|
|
4092
5641
|
"their"
|
|
4093
5642
|
]);
|
|
5643
|
+
CONTEXT_GROUNDING_RULES = `Ground specific claims \u2014 facts, preferences, history, dates, numbers, names \u2014 in the memories above; if a detail isn't written here, say you don't remember it rather than guessing. Each line is dated: when two memories disagree, read them as a timeline \u2014 use the most recent value for a question about now, and the value that was in effect at the time for a question about a specific past date ("as of \u2026"); never blend conflicting values. Don't invent memories or details that aren't above. General knowledge is unaffected \u2014 answer those normally.`;
|
|
5644
|
+
}
|
|
5645
|
+
});
|
|
5646
|
+
|
|
5647
|
+
// packages/brain/src/memory/memory-revoke.ts
|
|
5648
|
+
async function revokeMemory(db, memoryId) {
|
|
5649
|
+
const { data: mem } = await db.from("memories").select("summary, embedding, encrypted, provider_delegated").eq("id", memoryId).maybeSingle();
|
|
5650
|
+
if (!mem || mem.encrypted !== true || mem.provider_delegated === false) {
|
|
5651
|
+
return { revoked: false, reason: "not_delegated" };
|
|
5652
|
+
}
|
|
5653
|
+
const { data: wrap } = await db.from("memory_dek_wraps").select("wrapped_dek, wrap_pubkey").eq("memory_id", memoryId).eq("recipient", "provider").maybeSingle();
|
|
5654
|
+
if (!wrap) return { revoked: false, reason: "no_provider_wrap" };
|
|
5655
|
+
let providerSecret;
|
|
5656
|
+
try {
|
|
5657
|
+
providerSecret = loadProviderKeypair().secretKey;
|
|
5658
|
+
} catch {
|
|
5659
|
+
return { revoked: false, reason: "no_provider_key" };
|
|
5660
|
+
}
|
|
5661
|
+
const dek = unwrapDek(wrap.wrapped_dek, wrap.wrap_pubkey, providerSecret);
|
|
5662
|
+
if (!dek) return { revoked: false, reason: "unwrap_failed" };
|
|
5663
|
+
const summaryCt = encryptField(String(mem.summary ?? ""), dek);
|
|
5664
|
+
const embeddingCt = mem.embedding != null ? encryptField(String(mem.embedding), dek) : "";
|
|
5665
|
+
const { error } = await db.rpc("revoke_memory", {
|
|
5666
|
+
p_memory_id: memoryId,
|
|
5667
|
+
p_summary_ct: summaryCt,
|
|
5668
|
+
p_embedding_ct: embeddingCt
|
|
5669
|
+
});
|
|
5670
|
+
if (error) {
|
|
5671
|
+
log17.error({ memoryId, error: error.message }, "revoke_memory RPC failed");
|
|
5672
|
+
return { revoked: false, reason: "rpc_failed" };
|
|
5673
|
+
}
|
|
5674
|
+
log17.info({ memoryId }, "Memory revoked \u2014 provider access destroyed");
|
|
5675
|
+
return { revoked: true };
|
|
5676
|
+
}
|
|
5677
|
+
async function redelegateMemory(db, memoryId, providerWrap) {
|
|
5678
|
+
const { data: mem } = await db.from("memories").select("content, summary_ciphertext, embedding_ciphertext, encrypted").eq("id", memoryId).maybeSingle();
|
|
5679
|
+
if (!mem || mem.encrypted !== true) return { redelegated: false, reason: "not_encrypted" };
|
|
5680
|
+
let providerSecret;
|
|
5681
|
+
try {
|
|
5682
|
+
providerSecret = loadProviderKeypair().secretKey;
|
|
5683
|
+
} catch {
|
|
5684
|
+
return { redelegated: false, reason: "no_provider_key" };
|
|
5685
|
+
}
|
|
5686
|
+
const dek = unwrapDek(providerWrap.wrapped_dek, providerWrap.wrap_pubkey, providerSecret);
|
|
5687
|
+
if (!dek) return { redelegated: false, reason: "invalid_wrap" };
|
|
5688
|
+
const content = decryptField(String(mem.content ?? ""), dek);
|
|
5689
|
+
if (content === null) return { redelegated: false, reason: "invalid_wrap" };
|
|
5690
|
+
const summary = mem.summary_ciphertext ? decryptField(mem.summary_ciphertext, dek) : "";
|
|
5691
|
+
const embedding = mem.embedding_ciphertext ? decryptField(mem.embedding_ciphertext, dek) : "";
|
|
5692
|
+
const { error } = await db.rpc("redelegate_memory", {
|
|
5693
|
+
p_memory_id: memoryId,
|
|
5694
|
+
p_summary: summary ?? "",
|
|
5695
|
+
p_embedding: embedding ?? "",
|
|
5696
|
+
p_content: content,
|
|
5697
|
+
p_wrapped_dek: providerWrap.wrapped_dek,
|
|
5698
|
+
p_wrap_pubkey: providerWrap.wrap_pubkey
|
|
5699
|
+
});
|
|
5700
|
+
if (error) {
|
|
5701
|
+
log17.error({ memoryId, error: error.message }, "redelegate_memory RPC failed");
|
|
5702
|
+
return { redelegated: false, reason: "rpc_failed" };
|
|
5703
|
+
}
|
|
5704
|
+
log17.info({ memoryId }, "Memory re-delegated \u2014 provider access restored");
|
|
5705
|
+
return { redelegated: true };
|
|
5706
|
+
}
|
|
5707
|
+
var log17;
|
|
5708
|
+
var init_memory_revoke = __esm({
|
|
5709
|
+
"packages/brain/src/memory/memory-revoke.ts"() {
|
|
5710
|
+
"use strict";
|
|
5711
|
+
init_memory_envelope();
|
|
5712
|
+
init_encryption_keys();
|
|
5713
|
+
init_logger();
|
|
5714
|
+
log17 = createChildLogger("memory-revoke");
|
|
4094
5715
|
}
|
|
4095
5716
|
});
|
|
4096
5717
|
|
|
4097
5718
|
// packages/brain/src/memory/index.ts
|
|
4098
5719
|
var memory_exports2 = {};
|
|
4099
5720
|
__export(memory_exports2, {
|
|
4100
|
-
SCOPE_BOT_OWN: () =>
|
|
5721
|
+
SCOPE_BOT_OWN: () => SCOPE_BOT_OWN2,
|
|
4101
5722
|
_setOwnerWallet: () => _setOwnerWallet,
|
|
4102
5723
|
calculateImportance: () => calculateImportance,
|
|
4103
5724
|
createMemoryLink: () => createMemoryLink,
|
|
4104
5725
|
createMemoryLinksBatch: () => createMemoryLinksBatch,
|
|
4105
5726
|
decayMemories: () => decayMemories,
|
|
5727
|
+
decryptMemories: () => decryptMemories,
|
|
5728
|
+
decryptOneContent: () => decryptOneContent,
|
|
4106
5729
|
deleteMemory: () => deleteMemory,
|
|
4107
5730
|
formatMemoryContext: () => formatMemoryContext,
|
|
4108
5731
|
generateHashId: () => generateHashId,
|
|
@@ -4118,6 +5741,8 @@ __export(memory_exports2, {
|
|
|
4118
5741
|
moodToValence: () => moodToValence,
|
|
4119
5742
|
recallMemories: () => recallMemories,
|
|
4120
5743
|
recallMemorySummaries: () => recallMemorySummaries,
|
|
5744
|
+
redelegateMemory: () => redelegateMemory,
|
|
5745
|
+
revokeMemory: () => revokeMemory,
|
|
4121
5746
|
scopeToOwner: () => scopeToOwner,
|
|
4122
5747
|
scoreImportanceWithLLM: () => scoreImportanceWithLLM,
|
|
4123
5748
|
scoreMemory: () => scoreMemory,
|
|
@@ -4129,6 +5754,8 @@ var init_memory2 = __esm({
|
|
|
4129
5754
|
"packages/brain/src/memory/index.ts"() {
|
|
4130
5755
|
"use strict";
|
|
4131
5756
|
init_memory();
|
|
5757
|
+
init_memory_decryption();
|
|
5758
|
+
init_memory_revoke();
|
|
4132
5759
|
}
|
|
4133
5760
|
});
|
|
4134
5761
|
|
|
@@ -4258,7 +5885,7 @@ function shuffle(arr) {
|
|
|
4258
5885
|
async function runDeepConnectionPhase(ownerWallet) {
|
|
4259
5886
|
const SKIPPED = { linksCreated: 0, skipped: true, topLinks: [] };
|
|
4260
5887
|
if (process.env.JEPA_ENABLED !== "true") {
|
|
4261
|
-
|
|
5888
|
+
log18.debug("JEPA disabled \u2014 skipping deep connection phase");
|
|
4262
5889
|
return SKIPPED;
|
|
4263
5890
|
}
|
|
4264
5891
|
const jepaClient = getJepaClient();
|
|
@@ -4281,16 +5908,16 @@ async function runDeepConnectionPhase(ownerWallet) {
|
|
|
4281
5908
|
}
|
|
4282
5909
|
}
|
|
4283
5910
|
if (candidates.length === 0) {
|
|
4284
|
-
|
|
5911
|
+
log18.debug("No candidate memories \u2014 skipping deep connection phase");
|
|
4285
5912
|
return SKIPPED;
|
|
4286
5913
|
}
|
|
4287
5914
|
const queriedRecently = await fetchJepaQueriedSince(Date.now() - TWENTY_FOUR_HOURS_MS);
|
|
4288
5915
|
const toProcess = candidates.filter((m) => !queriedRecently.has(m.id));
|
|
4289
5916
|
if (toProcess.length === 0) {
|
|
4290
|
-
|
|
5917
|
+
log18.debug("All candidates JEPA-queried in last 24 h \u2014 skipping");
|
|
4291
5918
|
return SKIPPED;
|
|
4292
5919
|
}
|
|
4293
|
-
|
|
5920
|
+
log18.info({ total: candidates.length, toProcess: toProcess.length }, "Deep connection phase: processing memories");
|
|
4294
5921
|
const allLinks = [];
|
|
4295
5922
|
const topLinks = [];
|
|
4296
5923
|
for (const mem of toProcess) {
|
|
@@ -4376,16 +6003,16 @@ async function runDeepConnectionPhase(ownerWallet) {
|
|
|
4376
6003
|
new_memories_created: []
|
|
4377
6004
|
});
|
|
4378
6005
|
} catch (err) {
|
|
4379
|
-
|
|
6006
|
+
log18.warn({ err }, "Failed to write deep_connection dream log");
|
|
4380
6007
|
}
|
|
4381
|
-
|
|
6008
|
+
log18.info({ linksCreated: allLinks.length, topLinks: topLinks.length }, "Deep connection phase complete");
|
|
4382
6009
|
return {
|
|
4383
6010
|
linksCreated: allLinks.length,
|
|
4384
6011
|
skipped: false,
|
|
4385
6012
|
topLinks
|
|
4386
6013
|
};
|
|
4387
6014
|
}
|
|
4388
|
-
var
|
|
6015
|
+
var log18, TWENTY_FOUR_HOURS_MS, RECENT_SAMPLE, HIGH_IMPORTANCE_SAMPLE, HIGH_IMPORTANCE_THRESHOLD, EMBEDDING_SIMILARITY_THRESHOLD, MATCH_LIMIT, TOP_LINKS_PER_MEMORY, _jepaClient;
|
|
4389
6016
|
var init_deep_connection = __esm({
|
|
4390
6017
|
"packages/brain/src/memory/dream/deep-connection.ts"() {
|
|
4391
6018
|
"use strict";
|
|
@@ -4393,7 +6020,7 @@ var init_deep_connection = __esm({
|
|
|
4393
6020
|
init_logger();
|
|
4394
6021
|
init_memory();
|
|
4395
6022
|
init_jepa();
|
|
4396
|
-
|
|
6023
|
+
log18 = createChildLogger("dream-deep-connection");
|
|
4397
6024
|
TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
|
|
4398
6025
|
RECENT_SAMPLE = 15;
|
|
4399
6026
|
HIGH_IMPORTANCE_SAMPLE = 5;
|
|
@@ -4428,7 +6055,7 @@ async function throttlePost() {
|
|
|
4428
6055
|
const elapsed = now - lastPostTime;
|
|
4429
6056
|
if (elapsed < MIN_POST_GAP_MS) {
|
|
4430
6057
|
const wait = MIN_POST_GAP_MS - elapsed;
|
|
4431
|
-
|
|
6058
|
+
log19.debug({ waitMs: wait }, "Throttling post");
|
|
4432
6059
|
await new Promise((r) => setTimeout(r, wait));
|
|
4433
6060
|
}
|
|
4434
6061
|
lastPostTime = Date.now();
|
|
@@ -4440,7 +6067,7 @@ async function withRetry(fn) {
|
|
|
4440
6067
|
if (err?.code === 429 || err?.data?.status === 429) {
|
|
4441
6068
|
const retryAfter = Number(err?.rateLimit?.reset || 0) * 1e3 - Date.now();
|
|
4442
6069
|
const wait = Math.max(retryAfter, 6e4);
|
|
4443
|
-
|
|
6070
|
+
log19.warn({ waitMs: wait }, "X API 429 \u2014 backing off");
|
|
4444
6071
|
await new Promise((r) => setTimeout(r, wait));
|
|
4445
6072
|
return fn();
|
|
4446
6073
|
}
|
|
@@ -4461,20 +6088,20 @@ function smartTruncate(text, maxLength = MAX_TWEET_LENGTH) {
|
|
|
4461
6088
|
}
|
|
4462
6089
|
async function postReply(tweetId, text) {
|
|
4463
6090
|
const truncated = smartTruncate(stripEmDashes(text));
|
|
4464
|
-
|
|
6091
|
+
log19.info({ tweetId, originalLength: text.length, length: truncated.length }, "Posting reply");
|
|
4465
6092
|
await throttlePost();
|
|
4466
6093
|
const result = await withRetry(() => rwClient.v2.reply(truncated, tweetId));
|
|
4467
6094
|
return result.data.id;
|
|
4468
6095
|
}
|
|
4469
6096
|
async function postTweet(text) {
|
|
4470
6097
|
const truncated = smartTruncate(stripEmDashes(text));
|
|
4471
|
-
|
|
6098
|
+
log19.info({ originalLength: text.length, length: truncated.length }, "Posting tweet");
|
|
4472
6099
|
await throttlePost();
|
|
4473
6100
|
const result = await withRetry(() => rwClient.v2.tweet(truncated));
|
|
4474
6101
|
return result.data.id;
|
|
4475
6102
|
}
|
|
4476
6103
|
async function postThread(texts) {
|
|
4477
|
-
|
|
6104
|
+
log19.info({ count: texts.length }, "Posting thread");
|
|
4478
6105
|
const tweetIds = [];
|
|
4479
6106
|
let previousId;
|
|
4480
6107
|
for (const text of texts) {
|
|
@@ -4528,7 +6155,7 @@ function getUsernameOrId(userId) {
|
|
|
4528
6155
|
return _usernameCache.get(userId) || userId;
|
|
4529
6156
|
}
|
|
4530
6157
|
async function getMentions(sinceId) {
|
|
4531
|
-
|
|
6158
|
+
log19.debug({ sinceId }, "Fetching mentions");
|
|
4532
6159
|
const params = {
|
|
4533
6160
|
"tweet.fields": "created_at,author_id,conversation_id,text",
|
|
4534
6161
|
"expansions": "author_id",
|
|
@@ -4567,12 +6194,12 @@ async function getTweetWithContext(tweetId, depth = 3) {
|
|
|
4567
6194
|
}
|
|
4568
6195
|
return { tweet, parents };
|
|
4569
6196
|
} catch (err) {
|
|
4570
|
-
|
|
6197
|
+
log19.warn({ tweetId, err }, "Failed to fetch tweet context");
|
|
4571
6198
|
return { tweet: { id: tweetId, text: "" }, parents: [] };
|
|
4572
6199
|
}
|
|
4573
6200
|
}
|
|
4574
6201
|
async function searchHashtagTweets(sinceId, maxResults = 100) {
|
|
4575
|
-
|
|
6202
|
+
log19.info({ sinceId, maxResults }, "Searching campaign tweets");
|
|
4576
6203
|
try {
|
|
4577
6204
|
const query = `(@${BOT_X_HANDLE} OR #CludeHackathon OR ($CLUDE (hackathon OR "10 days" OR "growing a brain" OR "blockchain brain"))) -is:retweet`;
|
|
4578
6205
|
const params = {
|
|
@@ -4605,15 +6232,15 @@ async function searchHashtagTweets(sinceId, maxResults = 100) {
|
|
|
4605
6232
|
} : void 0
|
|
4606
6233
|
});
|
|
4607
6234
|
}
|
|
4608
|
-
|
|
6235
|
+
log19.info({ found: tweets.length }, "Campaign tweet search complete");
|
|
4609
6236
|
return tweets;
|
|
4610
6237
|
} catch (err) {
|
|
4611
|
-
|
|
6238
|
+
log19.error({ err }, "Failed to search campaign tweets");
|
|
4612
6239
|
return [];
|
|
4613
6240
|
}
|
|
4614
6241
|
}
|
|
4615
6242
|
async function searchTokenMentions(sinceId, maxResults = 50) {
|
|
4616
|
-
|
|
6243
|
+
log19.info({ sinceId, maxResults }, "Searching $CLUDE mentions");
|
|
4617
6244
|
try {
|
|
4618
6245
|
const query = `($CLUDE OR @${BOT_X_HANDLE}) -is:retweet`;
|
|
4619
6246
|
const params = {
|
|
@@ -4646,15 +6273,15 @@ async function searchTokenMentions(sinceId, maxResults = 50) {
|
|
|
4646
6273
|
} : void 0
|
|
4647
6274
|
});
|
|
4648
6275
|
}
|
|
4649
|
-
|
|
6276
|
+
log19.info({ found: tweets.length }, "Token mention search complete");
|
|
4650
6277
|
return tweets;
|
|
4651
6278
|
} catch (err) {
|
|
4652
|
-
|
|
6279
|
+
log19.error({ err }, "Failed to search token mentions");
|
|
4653
6280
|
return [];
|
|
4654
6281
|
}
|
|
4655
6282
|
}
|
|
4656
6283
|
async function getUserTweets(userId, sinceId, maxResults = 10) {
|
|
4657
|
-
|
|
6284
|
+
log19.debug({ userId, sinceId }, "Fetching user tweets");
|
|
4658
6285
|
try {
|
|
4659
6286
|
const params = {
|
|
4660
6287
|
"tweet.fields": "created_at,author_id,public_metrics",
|
|
@@ -4680,7 +6307,7 @@ async function getUserTweets(userId, sinceId, maxResults = 10) {
|
|
|
4680
6307
|
}
|
|
4681
6308
|
return tweets;
|
|
4682
6309
|
} catch (err) {
|
|
4683
|
-
|
|
6310
|
+
log19.warn({ userId, err }, "Failed to fetch user tweets");
|
|
4684
6311
|
return [];
|
|
4685
6312
|
}
|
|
4686
6313
|
}
|
|
@@ -4707,7 +6334,7 @@ async function refreshTweetMetrics(tweetIds) {
|
|
|
4707
6334
|
}
|
|
4708
6335
|
}
|
|
4709
6336
|
} catch (err) {
|
|
4710
|
-
|
|
6337
|
+
log19.warn({ err, batchSize: batch.length }, "Failed to refresh tweet metrics batch");
|
|
4711
6338
|
}
|
|
4712
6339
|
}
|
|
4713
6340
|
return metricsMap;
|
|
@@ -4723,11 +6350,11 @@ async function getUserById(userId) {
|
|
|
4723
6350
|
});
|
|
4724
6351
|
return result.data;
|
|
4725
6352
|
} catch (err) {
|
|
4726
|
-
|
|
6353
|
+
log19.warn({ userId, err }, "Failed to fetch user");
|
|
4727
6354
|
return null;
|
|
4728
6355
|
}
|
|
4729
6356
|
}
|
|
4730
|
-
var import_twitter_api_v2,
|
|
6357
|
+
var import_twitter_api_v2, log19, client, rwClient, MIN_POST_GAP_MS, lastPostTime, MAX_TWEET_LENGTH, _usernameCache;
|
|
4731
6358
|
var init_x_client = __esm({
|
|
4732
6359
|
"packages/shared/src/core/x-client.ts"() {
|
|
4733
6360
|
"use strict";
|
|
@@ -4735,7 +6362,7 @@ var init_x_client = __esm({
|
|
|
4735
6362
|
init_config();
|
|
4736
6363
|
init_logger();
|
|
4737
6364
|
init_constants();
|
|
4738
|
-
|
|
6365
|
+
log19 = createChildLogger("x-client");
|
|
4739
6366
|
client = new import_twitter_api_v2.TwitterApi({
|
|
4740
6367
|
appKey: config.x.apiKey,
|
|
4741
6368
|
appSecret: config.x.apiSecret,
|
|
@@ -4781,10 +6408,10 @@ Trigger: "${record.trigger.slice(0, 200)}"` : "");
|
|
|
4781
6408
|
...record.metadata
|
|
4782
6409
|
}
|
|
4783
6410
|
});
|
|
4784
|
-
|
|
6411
|
+
log20.info({ actionId: record.actionId, feature: record.feature, memoryId }, "Action logged");
|
|
4785
6412
|
return memoryId;
|
|
4786
6413
|
} catch (err) {
|
|
4787
|
-
|
|
6414
|
+
log20.error({ err, actionId: record.actionId }, "Failed to log action");
|
|
4788
6415
|
return null;
|
|
4789
6416
|
}
|
|
4790
6417
|
}
|
|
@@ -4814,10 +6441,10 @@ Original action: ${record.actionId}`;
|
|
|
4814
6441
|
}
|
|
4815
6442
|
});
|
|
4816
6443
|
await updateActionImportance(record.actionId, record.sentiment);
|
|
4817
|
-
|
|
6444
|
+
log20.info({ actionId: record.actionId, sentiment: record.sentiment, memoryId }, "Outcome logged");
|
|
4818
6445
|
return memoryId;
|
|
4819
6446
|
} catch (err) {
|
|
4820
|
-
|
|
6447
|
+
log20.error({ err, actionId: record.actionId }, "Failed to log outcome");
|
|
4821
6448
|
return null;
|
|
4822
6449
|
}
|
|
4823
6450
|
}
|
|
@@ -4835,9 +6462,9 @@ async function updateActionImportance(actionId, sentiment) {
|
|
|
4835
6462
|
tags.push(`outcome:${sentiment}`);
|
|
4836
6463
|
const newImportance = sentiment === "positive" ? Math.min(1, currentImportance + 0.15) : sentiment === "negative" ? Math.min(1, currentImportance + 0.2) : currentImportance;
|
|
4837
6464
|
await db.from("memories").update({ importance: newImportance, tags }).eq("id", mem.id);
|
|
4838
|
-
|
|
6465
|
+
log20.debug({ actionId, oldImportance: currentImportance, newImportance }, "Action importance updated");
|
|
4839
6466
|
} catch (err) {
|
|
4840
|
-
|
|
6467
|
+
log20.error({ err, actionId }, "Failed to update action importance");
|
|
4841
6468
|
}
|
|
4842
6469
|
}
|
|
4843
6470
|
async function refineStrategies() {
|
|
@@ -4850,7 +6477,7 @@ async function refineStrategies() {
|
|
|
4850
6477
|
if (ownerWallet) recentQuery = recentQuery.eq("owner_wallet", ownerWallet);
|
|
4851
6478
|
const { data: recentActions } = await recentQuery;
|
|
4852
6479
|
if (!recentActions || recentActions.length === 0) {
|
|
4853
|
-
|
|
6480
|
+
log20.debug("No recent actions to analyze");
|
|
4854
6481
|
return lessons;
|
|
4855
6482
|
}
|
|
4856
6483
|
const withOutcome = recentActions.filter(
|
|
@@ -4863,7 +6490,7 @@ async function refineStrategies() {
|
|
|
4863
6490
|
(m) => (m.tags || []).includes("outcome:negative")
|
|
4864
6491
|
);
|
|
4865
6492
|
if (withOutcome.length < 3) {
|
|
4866
|
-
|
|
6493
|
+
log20.debug({ total: recentActions.length, withOutcome: withOutcome.length }, "Not enough outcome data for strategy refinement");
|
|
4867
6494
|
return lessons;
|
|
4868
6495
|
}
|
|
4869
6496
|
const byFeature = /* @__PURE__ */ new Map();
|
|
@@ -4908,7 +6535,7 @@ async function refineStrategies() {
|
|
|
4908
6535
|
}
|
|
4909
6536
|
});
|
|
4910
6537
|
lessons.push(lesson);
|
|
4911
|
-
|
|
6538
|
+
log20.info({ feature, lesson: lesson.slice(0, 100) }, "New strategy learned");
|
|
4912
6539
|
}
|
|
4913
6540
|
}
|
|
4914
6541
|
if (positive.length > 0) {
|
|
@@ -4916,7 +6543,7 @@ async function refineStrategies() {
|
|
|
4916
6543
|
}
|
|
4917
6544
|
return lessons;
|
|
4918
6545
|
} catch (err) {
|
|
4919
|
-
|
|
6546
|
+
log20.error({ err }, "Strategy refinement failed");
|
|
4920
6547
|
return lessons;
|
|
4921
6548
|
}
|
|
4922
6549
|
}
|
|
@@ -4940,7 +6567,7 @@ Write a concise procedural rule (1-2 sentences) that captures this successful st
|
|
|
4940
6567
|
});
|
|
4941
6568
|
return response.trim();
|
|
4942
6569
|
} catch (err) {
|
|
4943
|
-
|
|
6570
|
+
log20.error({ err, feature }, "Failed to generate lesson");
|
|
4944
6571
|
return null;
|
|
4945
6572
|
}
|
|
4946
6573
|
}
|
|
@@ -4962,11 +6589,11 @@ async function reinforceSuccessfulStrategies(positiveActions) {
|
|
|
4962
6589
|
if (relevantPositive.length > 0) {
|
|
4963
6590
|
const newImportance = Math.min(1, (strategy.importance || 0.5) + 0.05 * relevantPositive.length);
|
|
4964
6591
|
await db.from("memories").update({ importance: newImportance, access_count: (strategy.access_count || 0) + 1 }).eq("id", strategy.id);
|
|
4965
|
-
|
|
6592
|
+
log20.debug({ strategyId: strategy.id, feature, boost: relevantPositive.length }, "Strategy reinforced");
|
|
4966
6593
|
}
|
|
4967
6594
|
}
|
|
4968
6595
|
} catch (err) {
|
|
4969
|
-
|
|
6596
|
+
log20.error({ err }, "Failed to reinforce strategies");
|
|
4970
6597
|
}
|
|
4971
6598
|
}
|
|
4972
6599
|
async function trackSocialOutcomes() {
|
|
@@ -5020,16 +6647,16 @@ async function trackSocialOutcomes() {
|
|
|
5020
6647
|
tracked++;
|
|
5021
6648
|
}
|
|
5022
6649
|
} catch {
|
|
5023
|
-
|
|
6650
|
+
log20.debug({ replyId }, "Could not fetch tweet metrics");
|
|
5024
6651
|
}
|
|
5025
6652
|
}
|
|
5026
|
-
|
|
6653
|
+
log20.info({ tracked }, "Social outcomes tracked");
|
|
5027
6654
|
} catch (err) {
|
|
5028
|
-
|
|
6655
|
+
log20.error({ err }, "Social outcome tracking failed");
|
|
5029
6656
|
}
|
|
5030
6657
|
return tracked;
|
|
5031
6658
|
}
|
|
5032
|
-
var
|
|
6659
|
+
var log20;
|
|
5033
6660
|
var init_action_learning = __esm({
|
|
5034
6661
|
"packages/brain/src/memory/action-learning.ts"() {
|
|
5035
6662
|
"use strict";
|
|
@@ -5037,7 +6664,7 @@ var init_action_learning = __esm({
|
|
|
5037
6664
|
init_logger();
|
|
5038
6665
|
init_database();
|
|
5039
6666
|
init_claude_client();
|
|
5040
|
-
|
|
6667
|
+
log20 = createChildLogger("action-learning");
|
|
5041
6668
|
}
|
|
5042
6669
|
});
|
|
5043
6670
|
|
|
@@ -5064,7 +6691,7 @@ function accumulateImportance(importance) {
|
|
|
5064
6691
|
const timeSinceLastReflection = Date.now() - lastReflectionTime;
|
|
5065
6692
|
const pastMinInterval = timeSinceLastReflection >= REFLECTION_MIN_INTERVAL_MS;
|
|
5066
6693
|
if (importanceAccumulator >= REFLECTION_IMPORTANCE_THRESHOLD && pastMinInterval && !reflectionInProgress) {
|
|
5067
|
-
|
|
6694
|
+
log21.info({
|
|
5068
6695
|
accumulator: importanceAccumulator.toFixed(2),
|
|
5069
6696
|
threshold: REFLECTION_IMPORTANCE_THRESHOLD,
|
|
5070
6697
|
minutesSinceLast: Math.round(timeSinceLastReflection / 6e4)
|
|
@@ -5074,7 +6701,7 @@ function accumulateImportance(importance) {
|
|
|
5074
6701
|
try {
|
|
5075
6702
|
await triggerReflection();
|
|
5076
6703
|
} catch (err) {
|
|
5077
|
-
|
|
6704
|
+
log21.error({ err }, "Event-driven reflection failed");
|
|
5078
6705
|
}
|
|
5079
6706
|
});
|
|
5080
6707
|
}
|
|
@@ -5082,7 +6709,7 @@ function accumulateImportance(importance) {
|
|
|
5082
6709
|
async function triggerReflection() {
|
|
5083
6710
|
reflectionInProgress = true;
|
|
5084
6711
|
try {
|
|
5085
|
-
|
|
6712
|
+
log21.info({ accumulator: importanceAccumulator.toFixed(2) }, "=== DREAM CYCLE TRIGGERED ===");
|
|
5086
6713
|
await Promise.race([
|
|
5087
6714
|
(async () => {
|
|
5088
6715
|
await runConsolidation();
|
|
@@ -5097,9 +6724,9 @@ async function triggerReflection() {
|
|
|
5097
6724
|
await sleep(3e3);
|
|
5098
6725
|
try {
|
|
5099
6726
|
const deepResult = await runDeepConnectionPhase();
|
|
5100
|
-
|
|
6727
|
+
log21.info({ linksCreated: deepResult.linksCreated }, "JEPA deep connection complete");
|
|
5101
6728
|
} catch (err) {
|
|
5102
|
-
|
|
6729
|
+
log21.warn({ err }, "JEPA deep connection phase failed, continuing");
|
|
5103
6730
|
}
|
|
5104
6731
|
await sleep(3e3);
|
|
5105
6732
|
await runEmergence();
|
|
@@ -5111,9 +6738,9 @@ async function triggerReflection() {
|
|
|
5111
6738
|
importanceAccumulator = 0;
|
|
5112
6739
|
lastReflectionTime = Date.now();
|
|
5113
6740
|
await saveAccumulator();
|
|
5114
|
-
|
|
6741
|
+
log21.info("=== DREAM CYCLE COMPLETE ===");
|
|
5115
6742
|
} catch (err) {
|
|
5116
|
-
|
|
6743
|
+
log21.error({ err }, "Dream cycle failed or timed out");
|
|
5117
6744
|
} finally {
|
|
5118
6745
|
reflectionInProgress = false;
|
|
5119
6746
|
}
|
|
@@ -5125,7 +6752,7 @@ async function loadAccumulator() {
|
|
|
5125
6752
|
if (data) {
|
|
5126
6753
|
importanceAccumulator = (data.count || 0) / 100;
|
|
5127
6754
|
lastReflectionTime = new Date(data.window_start).getTime();
|
|
5128
|
-
|
|
6755
|
+
log21.debug({
|
|
5129
6756
|
accumulator: importanceAccumulator.toFixed(2),
|
|
5130
6757
|
lastReflection: new Date(lastReflectionTime).toISOString()
|
|
5131
6758
|
}, "Loaded reflection accumulator");
|
|
@@ -5142,7 +6769,7 @@ async function saveAccumulator() {
|
|
|
5142
6769
|
window_start: new Date(lastReflectionTime).toISOString()
|
|
5143
6770
|
});
|
|
5144
6771
|
} catch (err) {
|
|
5145
|
-
|
|
6772
|
+
log21.warn({ err }, "Failed to save reflection accumulator");
|
|
5146
6773
|
}
|
|
5147
6774
|
}
|
|
5148
6775
|
function parseEvidenceCitations(text, sourceMemories) {
|
|
@@ -5186,14 +6813,14 @@ ${memoryDump}`,
|
|
|
5186
6813
|
return response.split("\n").map((l) => l.trim()).filter((l) => l.length > 10 && l.includes("?")).slice(0, 3);
|
|
5187
6814
|
}
|
|
5188
6815
|
async function runConsolidation() {
|
|
5189
|
-
|
|
6816
|
+
log21.info("Dream phase 1: CONSOLIDATION starting");
|
|
5190
6817
|
const recentSummaries = await recallMemorySummaries({
|
|
5191
6818
|
memoryTypes: ["episodic"],
|
|
5192
6819
|
limit: 20,
|
|
5193
6820
|
trackAccess: false
|
|
5194
6821
|
});
|
|
5195
6822
|
if (recentSummaries.length < 3) {
|
|
5196
|
-
|
|
6823
|
+
log21.info({ count: recentSummaries.length }, "Too few recent memories for consolidation");
|
|
5197
6824
|
return;
|
|
5198
6825
|
}
|
|
5199
6826
|
const recentEpisodic = await getRecentMemories(6, ["episodic"], 20);
|
|
@@ -5201,14 +6828,14 @@ async function runConsolidation() {
|
|
|
5201
6828
|
try {
|
|
5202
6829
|
focalPoints = await generateFocalPoints(recentEpisodic);
|
|
5203
6830
|
} catch (err) {
|
|
5204
|
-
|
|
6831
|
+
log21.warn({ err }, "Focal point generation failed, using direct consolidation");
|
|
5205
6832
|
focalPoints = [];
|
|
5206
6833
|
}
|
|
5207
6834
|
if (focalPoints.length === 0) {
|
|
5208
6835
|
await runDirectConsolidation(recentEpisodic);
|
|
5209
6836
|
return;
|
|
5210
6837
|
}
|
|
5211
|
-
|
|
6838
|
+
log21.info({ focalPoints }, "Focal points generated");
|
|
5212
6839
|
const allNewIds = [];
|
|
5213
6840
|
const allInputIds = new Set(recentEpisodic.map((m) => m.id));
|
|
5214
6841
|
for (const question of focalPoints) {
|
|
@@ -5233,7 +6860,7 @@ ${numberedMemories}`,
|
|
|
5233
6860
|
});
|
|
5234
6861
|
const { text, evidenceIds } = parseEvidenceCitations(response, relevant);
|
|
5235
6862
|
if (isCopoutResponse(text)) {
|
|
5236
|
-
|
|
6863
|
+
log21.warn({ question }, "Consolidation produced cop-out response \u2014 discarding");
|
|
5237
6864
|
continue;
|
|
5238
6865
|
}
|
|
5239
6866
|
const id = await storeMemory({
|
|
@@ -5264,7 +6891,7 @@ Semantic insights: ${allNewIds.length - proceduralIds.length}
|
|
|
5264
6891
|
Procedural patterns: ${proceduralIds.length}`,
|
|
5265
6892
|
allNewIds
|
|
5266
6893
|
);
|
|
5267
|
-
|
|
6894
|
+
log21.info({ focalPoints: focalPoints.length, insights: allNewIds.length, procedural: proceduralIds.length }, "Focal-point consolidation complete");
|
|
5268
6895
|
}
|
|
5269
6896
|
async function runDirectConsolidation(recentEpisodic) {
|
|
5270
6897
|
const memoryDump = recentEpisodic.map(
|
|
@@ -5285,7 +6912,7 @@ Total interactions: ${recentEpisodic.length}`,
|
|
|
5285
6912
|
for (const obs of observations.slice(0, 3)) {
|
|
5286
6913
|
const { text, evidenceIds } = parseEvidenceCitations(obs, recentEpisodic);
|
|
5287
6914
|
if (isCopoutResponse(text)) {
|
|
5288
|
-
|
|
6915
|
+
log21.warn("Direct consolidation produced cop-out response \u2014 discarding");
|
|
5289
6916
|
continue;
|
|
5290
6917
|
}
|
|
5291
6918
|
const id = await storeMemory({
|
|
@@ -5308,24 +6935,24 @@ Total interactions: ${recentEpisodic.length}`,
|
|
|
5308
6935
|
response,
|
|
5309
6936
|
newIds
|
|
5310
6937
|
);
|
|
5311
|
-
|
|
6938
|
+
log21.info({ observations: newIds.length, procedural: proceduralIds.length }, "Direct consolidation complete");
|
|
5312
6939
|
}
|
|
5313
6940
|
async function runCompaction() {
|
|
5314
|
-
|
|
6941
|
+
log21.info("Dream phase 1.5: COMPACTION starting");
|
|
5315
6942
|
const db = getDb();
|
|
5316
6943
|
let compactionQuery = db.from("memories").select("id, hash_id, memory_type, summary, tags, concepts, importance, decay_factor, created_at").eq("memory_type", "episodic").eq("compacted", false).lt("decay_factor", 0.3).lt("importance", 0.5).lt("created_at", new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString()).order("created_at", { ascending: true }).limit(30);
|
|
5317
6944
|
const ownerWallet = getOwnerWallet();
|
|
5318
6945
|
if (ownerWallet) compactionQuery = compactionQuery.eq("owner_wallet", ownerWallet);
|
|
5319
6946
|
const { data: candidates, error } = await compactionQuery;
|
|
5320
6947
|
if (error) {
|
|
5321
|
-
|
|
6948
|
+
log21.error({ error: error.message }, "Failed to fetch compaction candidates");
|
|
5322
6949
|
return;
|
|
5323
6950
|
}
|
|
5324
6951
|
if (!candidates || candidates.length < 5) {
|
|
5325
|
-
|
|
6952
|
+
log21.debug({ count: candidates?.length || 0 }, "Too few candidates for compaction");
|
|
5326
6953
|
return;
|
|
5327
6954
|
}
|
|
5328
|
-
|
|
6955
|
+
log21.info({ count: candidates.length }, "Found compaction candidates");
|
|
5329
6956
|
const groups = /* @__PURE__ */ new Map();
|
|
5330
6957
|
for (const mem of candidates) {
|
|
5331
6958
|
const concept = mem.concepts?.[0] || "general";
|
|
@@ -5369,10 +6996,10 @@ ${memoryDump}`,
|
|
|
5369
6996
|
});
|
|
5370
6997
|
}
|
|
5371
6998
|
compactedCount += memories.length;
|
|
5372
|
-
|
|
6999
|
+
log21.debug({ concept, count: memories.length }, "Memory group compacted");
|
|
5373
7000
|
}
|
|
5374
7001
|
} catch (err) {
|
|
5375
|
-
|
|
7002
|
+
log21.warn({ err, concept }, "Failed to compact memory group");
|
|
5376
7003
|
}
|
|
5377
7004
|
}
|
|
5378
7005
|
if (compactedCount > 0) {
|
|
@@ -5383,10 +7010,10 @@ ${memoryDump}`,
|
|
|
5383
7010
|
summaryIds
|
|
5384
7011
|
);
|
|
5385
7012
|
}
|
|
5386
|
-
|
|
7013
|
+
log21.info({ compacted: compactedCount, summaries: summaryIds.length }, "Compaction complete");
|
|
5387
7014
|
}
|
|
5388
7015
|
async function runReflection() {
|
|
5389
|
-
|
|
7016
|
+
log21.info("Dream phase 2: REFLECTION starting");
|
|
5390
7017
|
const selfModel = await getSelfModel();
|
|
5391
7018
|
const recentSemantic = await getRecentMemories(48, ["semantic"], 10);
|
|
5392
7019
|
const stats = await getMemoryStats();
|
|
@@ -5409,7 +7036,7 @@ async function runReflection() {
|
|
|
5409
7036
|
});
|
|
5410
7037
|
const { text, evidenceIds } = parseEvidenceCitations(response, allInputMemories);
|
|
5411
7038
|
if (isCopoutResponse(text)) {
|
|
5412
|
-
|
|
7039
|
+
log21.warn("Reflection produced cop-out response \u2014 discarding");
|
|
5413
7040
|
await storeDreamLog("reflection", allInputMemories.map((m) => m.id), "(discarded \u2014 cop-out)", []);
|
|
5414
7041
|
return;
|
|
5415
7042
|
}
|
|
@@ -5441,10 +7068,10 @@ async function runReflection() {
|
|
|
5441
7068
|
response,
|
|
5442
7069
|
id ? [id] : []
|
|
5443
7070
|
);
|
|
5444
|
-
|
|
7071
|
+
log21.info({ evidenceCount: evidenceIds.length }, "Reflection complete (progressive disclosure enabled)");
|
|
5445
7072
|
}
|
|
5446
7073
|
async function runContradictionResolution() {
|
|
5447
|
-
|
|
7074
|
+
log21.info("Dream phase 2.5: CONTRADICTION RESOLUTION starting");
|
|
5448
7075
|
const db = getDb();
|
|
5449
7076
|
let pairs;
|
|
5450
7077
|
try {
|
|
@@ -5453,22 +7080,22 @@ async function runContradictionResolution() {
|
|
|
5453
7080
|
filter_owner: getOwnerWallet() || null
|
|
5454
7081
|
});
|
|
5455
7082
|
if (error || !data || data.length === 0) {
|
|
5456
|
-
|
|
7083
|
+
log21.debug({ error: error?.message }, "No unresolved contradictions found \u2014 skipping phase");
|
|
5457
7084
|
return;
|
|
5458
7085
|
}
|
|
5459
7086
|
pairs = data;
|
|
5460
7087
|
} catch (err) {
|
|
5461
|
-
|
|
7088
|
+
log21.debug({ err }, "Contradiction resolution skipped (RPC unavailable)");
|
|
5462
7089
|
return;
|
|
5463
7090
|
}
|
|
5464
|
-
|
|
7091
|
+
log21.info({ pairCount: pairs.length }, "Found unresolved contradictions");
|
|
5465
7092
|
const allIds = [...new Set(pairs.flatMap((p) => [p.source_id, p.target_id]))];
|
|
5466
7093
|
let memFetchQuery = db.from("memories").select("*").in("id", allIds);
|
|
5467
7094
|
const ownerWallet = getOwnerWallet();
|
|
5468
7095
|
if (ownerWallet) memFetchQuery = memFetchQuery.eq("owner_wallet", ownerWallet);
|
|
5469
7096
|
const { data: memories } = await memFetchQuery;
|
|
5470
7097
|
if (!memories || memories.length === 0) {
|
|
5471
|
-
|
|
7098
|
+
log21.warn("Could not fetch contradiction memories \u2014 skipping phase");
|
|
5472
7099
|
return;
|
|
5473
7100
|
}
|
|
5474
7101
|
const memoryMap = new Map(memories.map((m) => [m.id, m]));
|
|
@@ -5496,7 +7123,7 @@ ${numberedContext}`,
|
|
|
5496
7123
|
});
|
|
5497
7124
|
const { text, evidenceIds } = parseEvidenceCitations(response, sourceMemories);
|
|
5498
7125
|
if (isCopoutResponse(text)) {
|
|
5499
|
-
|
|
7126
|
+
log21.warn({ memA: memA.id, memB: memB.id }, "Contradiction resolution produced cop-out \u2014 skipping pair");
|
|
5500
7127
|
continue;
|
|
5501
7128
|
}
|
|
5502
7129
|
outputs.push(text);
|
|
@@ -5520,9 +7147,9 @@ ${numberedContext}`,
|
|
|
5520
7147
|
const newDecay = Math.max(0.05, weaker.decay_factor * 0.8);
|
|
5521
7148
|
const { error: decayErr } = await db.from("memories").update({ decay_factor: newDecay }).eq("id", weaker.id);
|
|
5522
7149
|
if (!decayErr) {
|
|
5523
|
-
|
|
7150
|
+
log21.debug({ memoryId: weaker.id, newDecay: newDecay.toFixed(3) }, "Accelerated decay on weaker contradicting memory");
|
|
5524
7151
|
} else {
|
|
5525
|
-
|
|
7152
|
+
log21.warn({ error: decayErr.message, memoryId: weaker.id }, "Failed to accelerate decay on contradicting memory");
|
|
5526
7153
|
}
|
|
5527
7154
|
}
|
|
5528
7155
|
}
|
|
@@ -5534,29 +7161,29 @@ ${numberedContext}`,
|
|
|
5534
7161
|
newMemoryIds
|
|
5535
7162
|
);
|
|
5536
7163
|
}
|
|
5537
|
-
|
|
7164
|
+
log21.info({ resolved: outputs.length, skipped: pairs.length - outputs.length }, "Contradiction resolution complete");
|
|
5538
7165
|
}
|
|
5539
7166
|
async function runLearning() {
|
|
5540
|
-
|
|
7167
|
+
log21.info("--- Learning phase: tracking outcomes & refining strategies ---");
|
|
5541
7168
|
try {
|
|
5542
7169
|
const { trackSocialOutcomes: trackSocialOutcomes2, refineStrategies: refineStrategies2 } = await Promise.resolve().then(() => (init_action_learning(), action_learning_exports));
|
|
5543
7170
|
const tracked = await trackSocialOutcomes2();
|
|
5544
7171
|
if (tracked > 0) {
|
|
5545
|
-
|
|
7172
|
+
log21.info({ tracked }, "Social outcomes tracked");
|
|
5546
7173
|
}
|
|
5547
7174
|
const lessons = await refineStrategies2();
|
|
5548
7175
|
if (lessons.length > 0) {
|
|
5549
|
-
|
|
7176
|
+
log21.info({ lessons: lessons.length }, "New strategies learned");
|
|
5550
7177
|
for (const lesson of lessons) {
|
|
5551
|
-
|
|
7178
|
+
log21.info({ lesson: lesson.slice(0, 150) }, "Learned strategy");
|
|
5552
7179
|
}
|
|
5553
7180
|
}
|
|
5554
7181
|
} catch (err) {
|
|
5555
|
-
|
|
7182
|
+
log21.error({ err }, "Learning phase failed");
|
|
5556
7183
|
}
|
|
5557
7184
|
}
|
|
5558
7185
|
async function runEmergence() {
|
|
5559
|
-
|
|
7186
|
+
log21.info("Dream phase 3: EMERGENCE starting");
|
|
5560
7187
|
const selfModel = await getSelfModel();
|
|
5561
7188
|
const stats = await getMemoryStats();
|
|
5562
7189
|
const randomMemories = await getRecentMemories(168, ["episodic"], 30);
|
|
@@ -5604,9 +7231,9 @@ async function runEmergence() {
|
|
|
5604
7231
|
if (_emergenceHandler) {
|
|
5605
7232
|
try {
|
|
5606
7233
|
await _emergenceHandler(response);
|
|
5607
|
-
|
|
7234
|
+
log21.info("Emergence thought sent to SDK handler");
|
|
5608
7235
|
} catch (err) {
|
|
5609
|
-
|
|
7236
|
+
log21.error({ err }, "SDK emergence handler failed");
|
|
5610
7237
|
}
|
|
5611
7238
|
} else {
|
|
5612
7239
|
const canPost = await checkRateLimit("global:emergence-tweet", 1, 720);
|
|
@@ -5614,15 +7241,15 @@ async function runEmergence() {
|
|
|
5614
7241
|
try {
|
|
5615
7242
|
const { postTweet: postTweet2 } = (init_x_client(), __toCommonJS(x_client_exports));
|
|
5616
7243
|
await postTweet2(response);
|
|
5617
|
-
|
|
7244
|
+
log21.info("Emergence thought posted to X");
|
|
5618
7245
|
} catch (err) {
|
|
5619
|
-
|
|
7246
|
+
log21.error({ err }, "Failed to post emergence thought");
|
|
5620
7247
|
}
|
|
5621
7248
|
} else if (!hasDepth) {
|
|
5622
|
-
|
|
7249
|
+
log21.debug({ selfModelCount: selfModel.length }, "Emergence not deep enough to post \u2014 skipping tweet");
|
|
5623
7250
|
}
|
|
5624
7251
|
}
|
|
5625
|
-
|
|
7252
|
+
log21.info("Emergence complete");
|
|
5626
7253
|
}
|
|
5627
7254
|
async function extractProceduralInsights(recentEpisodic) {
|
|
5628
7255
|
if (recentEpisodic.length < 3) return [];
|
|
@@ -5643,7 +7270,7 @@ ${numberedMemories}`,
|
|
|
5643
7270
|
for (const pattern of patterns.slice(0, 2)) {
|
|
5644
7271
|
const { text, evidenceIds } = parseEvidenceCitations(pattern, recentEpisodic);
|
|
5645
7272
|
if (isCopoutResponse(text)) {
|
|
5646
|
-
|
|
7273
|
+
log21.warn("Procedural extraction produced cop-out response \u2014 discarding");
|
|
5647
7274
|
continue;
|
|
5648
7275
|
}
|
|
5649
7276
|
const id = await storeMemory({
|
|
@@ -5658,10 +7285,10 @@ ${numberedMemories}`,
|
|
|
5658
7285
|
});
|
|
5659
7286
|
if (id) newIds.push(id);
|
|
5660
7287
|
}
|
|
5661
|
-
|
|
7288
|
+
log21.info({ count: newIds.length }, "Procedural insights extracted");
|
|
5662
7289
|
return newIds;
|
|
5663
7290
|
} catch (err) {
|
|
5664
|
-
|
|
7291
|
+
log21.warn({ err }, "Procedural extraction failed");
|
|
5665
7292
|
return [];
|
|
5666
7293
|
}
|
|
5667
7294
|
}
|
|
@@ -5688,33 +7315,33 @@ function buildReflectionStats(stats) {
|
|
|
5688
7315
|
return lines.join("\n");
|
|
5689
7316
|
}
|
|
5690
7317
|
async function startDreamCycle() {
|
|
5691
|
-
|
|
7318
|
+
log21.info("Starting dream cycle scheduler");
|
|
5692
7319
|
await loadAccumulator();
|
|
5693
7320
|
const cron = require("node-cron");
|
|
5694
7321
|
dreamCron = cron.schedule("0 */6 * * *", async () => {
|
|
5695
7322
|
if (reflectionInProgress) {
|
|
5696
|
-
|
|
7323
|
+
log21.info("Scheduled dream cycle skipped \u2014 reflection already in progress");
|
|
5697
7324
|
return;
|
|
5698
7325
|
}
|
|
5699
|
-
|
|
7326
|
+
log21.info({
|
|
5700
7327
|
accumulator: importanceAccumulator.toFixed(2)
|
|
5701
7328
|
}, "=== SCHEDULED DREAM CYCLE (6h fallback) ===");
|
|
5702
7329
|
await triggerReflection();
|
|
5703
7330
|
});
|
|
5704
7331
|
decayCron = cron.schedule("0 3 * * *", async () => {
|
|
5705
|
-
|
|
7332
|
+
log21.info("Running memory decay");
|
|
5706
7333
|
try {
|
|
5707
7334
|
await decayMemories();
|
|
5708
7335
|
} catch (err) {
|
|
5709
|
-
|
|
7336
|
+
log21.error({ err }, "Memory decay failed");
|
|
5710
7337
|
}
|
|
5711
7338
|
});
|
|
5712
7339
|
setTimeout(async () => {
|
|
5713
|
-
|
|
7340
|
+
log21.info("Running initial dream cycle");
|
|
5714
7341
|
try {
|
|
5715
7342
|
await triggerReflection();
|
|
5716
7343
|
} catch (err) {
|
|
5717
|
-
|
|
7344
|
+
log21.error({ err }, "Initial dream cycle failed");
|
|
5718
7345
|
}
|
|
5719
7346
|
}, 12e4);
|
|
5720
7347
|
}
|
|
@@ -5727,12 +7354,12 @@ function stopDreamCycle() {
|
|
|
5727
7354
|
decayCron.stop();
|
|
5728
7355
|
decayCron = null;
|
|
5729
7356
|
}
|
|
5730
|
-
|
|
7357
|
+
log21.info("Dream cycle stopped");
|
|
5731
7358
|
}
|
|
5732
7359
|
function sleep(ms) {
|
|
5733
7360
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5734
7361
|
}
|
|
5735
|
-
var
|
|
7362
|
+
var log21, COPOUT_PATTERNS, importanceAccumulator, lastReflectionTime, reflectionInProgress, REFLECTION_TIMEOUT_MS, _emergenceHandler, dreamCron, decayCron;
|
|
5736
7363
|
var init_cycle = __esm({
|
|
5737
7364
|
"packages/brain/src/memory/dream/cycle.ts"() {
|
|
5738
7365
|
"use strict";
|
|
@@ -5744,7 +7371,7 @@ var init_cycle = __esm({
|
|
|
5744
7371
|
init_constants();
|
|
5745
7372
|
init_format();
|
|
5746
7373
|
init_deep_connection();
|
|
5747
|
-
|
|
7374
|
+
log21 = createChildLogger("dream-cycle");
|
|
5748
7375
|
COPOUT_PATTERNS = [
|
|
5749
7376
|
/^good question/i,
|
|
5750
7377
|
/^let me think about that/i,
|
|
@@ -5769,7 +7396,7 @@ async function findClinamen(opts) {
|
|
|
5769
7396
|
const limit = opts.limit || DEFAULT_LIMIT;
|
|
5770
7397
|
const minImportance = opts.minImportance ?? MIN_IMPORTANCE;
|
|
5771
7398
|
const maxRelevance = opts.maxRelevance ?? MAX_RELEVANCE_SIM;
|
|
5772
|
-
|
|
7399
|
+
log22.debug({ context: opts.context.slice(0, 60), limit }, "Searching for clinamen");
|
|
5773
7400
|
let contextEmbedding = null;
|
|
5774
7401
|
if (isEmbeddingEnabled()) {
|
|
5775
7402
|
const cached = getCachedEmbedding(opts.context);
|
|
@@ -5781,7 +7408,7 @@ async function findClinamen(opts) {
|
|
|
5781
7408
|
}
|
|
5782
7409
|
}
|
|
5783
7410
|
if (!contextEmbedding) {
|
|
5784
|
-
|
|
7411
|
+
log22.debug("No embedding available \u2014 falling back to importance-only clinamen");
|
|
5785
7412
|
return importanceOnlyClinamen(db, limit, minImportance, opts.memoryTypes);
|
|
5786
7413
|
}
|
|
5787
7414
|
const cutoff = new Date(Date.now() - MIN_AGE_HOURS * 60 * 60 * 1e3).toISOString();
|
|
@@ -5792,7 +7419,7 @@ async function findClinamen(opts) {
|
|
|
5792
7419
|
}
|
|
5793
7420
|
const { data: candidates, error } = await query;
|
|
5794
7421
|
if (error || !candidates || candidates.length === 0) {
|
|
5795
|
-
|
|
7422
|
+
log22.debug({ error: error?.message }, "No clinamen candidates found");
|
|
5796
7423
|
return [];
|
|
5797
7424
|
}
|
|
5798
7425
|
const scored = [];
|
|
@@ -5818,7 +7445,7 @@ async function findClinamen(opts) {
|
|
|
5818
7445
|
}
|
|
5819
7446
|
scored.sort((a, b) => b._divergence - a._divergence);
|
|
5820
7447
|
const results = scored.slice(0, limit);
|
|
5821
|
-
|
|
7448
|
+
log22.info({
|
|
5822
7449
|
candidates: candidates.length,
|
|
5823
7450
|
afterFilter: scored.length,
|
|
5824
7451
|
returned: results.length,
|
|
@@ -5844,7 +7471,7 @@ async function importanceOnlyClinamen(db, limit, minImportance, memoryTypes) {
|
|
|
5844
7471
|
_relevanceSim: 0
|
|
5845
7472
|
}));
|
|
5846
7473
|
}
|
|
5847
|
-
var
|
|
7474
|
+
var log22, MIN_IMPORTANCE, MAX_RELEVANCE_SIM, MIN_AGE_HOURS, CANDIDATE_POOL_SIZE, DEFAULT_LIMIT;
|
|
5848
7475
|
var init_clinamen = __esm({
|
|
5849
7476
|
"packages/brain/src/memory/clinamen.ts"() {
|
|
5850
7477
|
"use strict";
|
|
@@ -5852,7 +7479,7 @@ var init_clinamen = __esm({
|
|
|
5852
7479
|
init_memory();
|
|
5853
7480
|
init_embeddings();
|
|
5854
7481
|
init_logger();
|
|
5855
|
-
|
|
7482
|
+
log22 = createChildLogger("clinamen");
|
|
5856
7483
|
MIN_IMPORTANCE = 0.6;
|
|
5857
7484
|
MAX_RELEVANCE_SIM = 0.35;
|
|
5858
7485
|
MIN_AGE_HOURS = 24;
|
|
@@ -5888,13 +7515,13 @@ async function selectSeeds() {
|
|
|
5888
7515
|
memoryTypes: ["episodic", "semantic", "procedural"]
|
|
5889
7516
|
});
|
|
5890
7517
|
if (clinamenMemories.length > 0) {
|
|
5891
|
-
|
|
7518
|
+
log23.info({
|
|
5892
7519
|
count: clinamenMemories.length,
|
|
5893
7520
|
summaries: clinamenMemories.map((m) => m.summary.slice(0, 40))
|
|
5894
7521
|
}, "Clinamen seeds selected (anomaly retrieval)");
|
|
5895
7522
|
}
|
|
5896
7523
|
} catch (err) {
|
|
5897
|
-
|
|
7524
|
+
log23.debug({ err }, "Clinamen retrieval failed \u2014 falling back to random");
|
|
5898
7525
|
const older = await getRecentMemories(720, ["episodic", "semantic", "procedural"], 50);
|
|
5899
7526
|
if (older.length > 0) {
|
|
5900
7527
|
clinamenMemories = [older[Math.floor(Math.random() * older.length)]];
|
|
@@ -5927,18 +7554,18 @@ ${summaries}`,
|
|
|
5927
7554
|
}
|
|
5928
7555
|
async function runActiveReflection() {
|
|
5929
7556
|
if (reflectionInProgress2) {
|
|
5930
|
-
|
|
7557
|
+
log23.info("Active reflection already in progress \u2014 skipping");
|
|
5931
7558
|
return null;
|
|
5932
7559
|
}
|
|
5933
7560
|
reflectionInProgress2 = true;
|
|
5934
7561
|
try {
|
|
5935
|
-
|
|
7562
|
+
log23.info("=== ACTIVE REFLECTION STARTING ===");
|
|
5936
7563
|
const { seeds, theme } = await selectSeeds();
|
|
5937
7564
|
if (seeds.length < MIN_MEMORIES_FOR_REFLECTION) {
|
|
5938
|
-
|
|
7565
|
+
log23.info({ count: seeds.length }, "Too few memories for reflection \u2014 skipping");
|
|
5939
7566
|
return null;
|
|
5940
7567
|
}
|
|
5941
|
-
|
|
7568
|
+
log23.info({ seedCount: seeds.length, theme }, "Reflection seeds selected");
|
|
5942
7569
|
const selfModel = await getSelfModel();
|
|
5943
7570
|
const stats = await getMemoryStats();
|
|
5944
7571
|
const previousReflections = await getRecentMemories(72, ["introspective"], 3);
|
|
@@ -5969,10 +7596,10 @@ async function runActiveReflection() {
|
|
|
5969
7596
|
cognitiveFunction: "reflect"
|
|
5970
7597
|
});
|
|
5971
7598
|
if (!journal || journal.trim().length < 50) {
|
|
5972
|
-
|
|
7599
|
+
log23.warn("Reflection produced empty or trivial output \u2014 discarding");
|
|
5973
7600
|
return null;
|
|
5974
7601
|
}
|
|
5975
|
-
|
|
7602
|
+
log23.info({ length: journal.length, theme }, "Journal entry generated");
|
|
5976
7603
|
const title = await generateTitle(journal);
|
|
5977
7604
|
const memoryId = await storeMemory({
|
|
5978
7605
|
type: "introspective",
|
|
@@ -6006,17 +7633,17 @@ ${journal}`,
|
|
|
6006
7633
|
if (_reflectionHandler) {
|
|
6007
7634
|
try {
|
|
6008
7635
|
await _reflectionHandler(result);
|
|
6009
|
-
|
|
7636
|
+
log23.info("Reflection sent to SDK handler");
|
|
6010
7637
|
} catch (err) {
|
|
6011
|
-
|
|
7638
|
+
log23.error({ err }, "SDK reflection handler failed");
|
|
6012
7639
|
}
|
|
6013
7640
|
} else {
|
|
6014
7641
|
await maybePostThread(result);
|
|
6015
7642
|
}
|
|
6016
|
-
|
|
7643
|
+
log23.info({ memoryId, title }, "=== ACTIVE REFLECTION COMPLETE ===");
|
|
6017
7644
|
return result;
|
|
6018
7645
|
} catch (err) {
|
|
6019
|
-
|
|
7646
|
+
log23.error({ err }, "Active reflection failed");
|
|
6020
7647
|
return null;
|
|
6021
7648
|
} finally {
|
|
6022
7649
|
reflectionInProgress2 = false;
|
|
@@ -6040,25 +7667,25 @@ async function maybePostThread(journal) {
|
|
|
6040
7667
|
const hour = (/* @__PURE__ */ new Date()).getUTCHours();
|
|
6041
7668
|
const isQuietHours = hour >= 23 || hour < 8;
|
|
6042
7669
|
if (isQuietHours) {
|
|
6043
|
-
|
|
7670
|
+
log23.debug("Quiet hours \u2014 skipping thread post");
|
|
6044
7671
|
return;
|
|
6045
7672
|
}
|
|
6046
7673
|
if (journal.text.length < 300) {
|
|
6047
|
-
|
|
7674
|
+
log23.debug("Journal too short for thread \u2014 skipping post");
|
|
6048
7675
|
return;
|
|
6049
7676
|
}
|
|
6050
7677
|
const canPost = await checkRateLimit(THREAD_RATE_KEY, THREAD_RATE_LIMIT, THREAD_RATE_WINDOW);
|
|
6051
7678
|
if (!canPost) {
|
|
6052
|
-
|
|
7679
|
+
log23.debug("Thread rate limit hit \u2014 skipping post");
|
|
6053
7680
|
return;
|
|
6054
7681
|
}
|
|
6055
7682
|
try {
|
|
6056
7683
|
const tweets = formatAsThread(journal);
|
|
6057
7684
|
const { postThread: postThread2 } = (init_x_client(), __toCommonJS(x_client_exports));
|
|
6058
7685
|
const ids = await postThread2(tweets);
|
|
6059
|
-
|
|
7686
|
+
log23.info({ tweetCount: ids.length, title: journal.title }, "Reflection thread posted to X");
|
|
6060
7687
|
} catch (err) {
|
|
6061
|
-
|
|
7688
|
+
log23.error({ err }, "Failed to post reflection thread");
|
|
6062
7689
|
}
|
|
6063
7690
|
}
|
|
6064
7691
|
function formatAsThread(journal) {
|
|
@@ -6081,29 +7708,29 @@ A reflection \u{1F9F5}`;
|
|
|
6081
7708
|
return tweets.slice(0, MAX_THREAD_TWEETS);
|
|
6082
7709
|
}
|
|
6083
7710
|
async function startActiveReflection() {
|
|
6084
|
-
|
|
7711
|
+
log23.info("Starting active reflection scheduler");
|
|
6085
7712
|
const cron = require("node-cron");
|
|
6086
7713
|
reflectionCron = cron.schedule("30 1,4,7,10,13,16,19,22 * * *", async () => {
|
|
6087
7714
|
if (reflectionInProgress2) {
|
|
6088
|
-
|
|
7715
|
+
log23.info("Scheduled reflection skipped \u2014 already in progress");
|
|
6089
7716
|
return;
|
|
6090
7717
|
}
|
|
6091
|
-
|
|
7718
|
+
log23.info("=== SCHEDULED ACTIVE REFLECTION ===");
|
|
6092
7719
|
await Promise.race([
|
|
6093
7720
|
runActiveReflection(),
|
|
6094
7721
|
new Promise(
|
|
6095
7722
|
(_, reject) => setTimeout(() => reject(new Error("Active reflection timed out")), REFLECTION_TIMEOUT_MS2)
|
|
6096
7723
|
)
|
|
6097
7724
|
]).catch((err) => {
|
|
6098
|
-
|
|
7725
|
+
log23.error({ err }, "Scheduled active reflection failed or timed out");
|
|
6099
7726
|
});
|
|
6100
7727
|
});
|
|
6101
7728
|
setTimeout(async () => {
|
|
6102
|
-
|
|
7729
|
+
log23.info("Running initial active reflection");
|
|
6103
7730
|
try {
|
|
6104
7731
|
await runActiveReflection();
|
|
6105
7732
|
} catch (err) {
|
|
6106
|
-
|
|
7733
|
+
log23.error({ err }, "Initial active reflection failed");
|
|
6107
7734
|
}
|
|
6108
7735
|
}, 30 * 60 * 1e3);
|
|
6109
7736
|
}
|
|
@@ -6112,9 +7739,9 @@ function stopActiveReflection() {
|
|
|
6112
7739
|
reflectionCron.stop();
|
|
6113
7740
|
reflectionCron = null;
|
|
6114
7741
|
}
|
|
6115
|
-
|
|
7742
|
+
log23.info("Active reflection stopped");
|
|
6116
7743
|
}
|
|
6117
|
-
var
|
|
7744
|
+
var log23, REFLECTION_INTERVAL_MS, MIN_MEMORIES_FOR_REFLECTION, MAX_JOURNAL_TOKENS, MAX_THREAD_TWEETS, THREAD_RATE_KEY, THREAD_RATE_LIMIT, THREAD_RATE_WINDOW, reflectionInProgress2, reflectionCron, REFLECTION_TIMEOUT_MS2, _reflectionHandler;
|
|
6118
7745
|
var init_active_reflection = __esm({
|
|
6119
7746
|
"packages/brain/src/memory/active-reflection.ts"() {
|
|
6120
7747
|
"use strict";
|
|
@@ -6124,7 +7751,7 @@ var init_active_reflection = __esm({
|
|
|
6124
7751
|
init_logger();
|
|
6125
7752
|
init_constants();
|
|
6126
7753
|
init_clinamen();
|
|
6127
|
-
|
|
7754
|
+
log23 = createChildLogger("active-reflection");
|
|
6128
7755
|
REFLECTION_INTERVAL_MS = 3 * 60 * 60 * 1e3;
|
|
6129
7756
|
MIN_MEMORIES_FOR_REFLECTION = 5;
|
|
6130
7757
|
MAX_JOURNAL_TOKENS = 1500;
|
|
@@ -6554,7 +8181,7 @@ var Cortex = class {
|
|
|
6554
8181
|
};
|
|
6555
8182
|
|
|
6556
8183
|
// packages/brain/src/sdk/cortex-v2.ts
|
|
6557
|
-
var
|
|
8184
|
+
var import_crypto4 = __toESM(require("crypto"));
|
|
6558
8185
|
var DEFAULT_ROUTES = {
|
|
6559
8186
|
routes: {
|
|
6560
8187
|
embed: { provider: "voyage", model: "voyage-4-large" },
|
|
@@ -6615,7 +8242,7 @@ var CortexV2 = class extends Cortex {
|
|
|
6615
8242
|
});
|
|
6616
8243
|
}
|
|
6617
8244
|
const pack = {
|
|
6618
|
-
id:
|
|
8245
|
+
id: import_crypto4.default.randomUUID(),
|
|
6619
8246
|
name: opts.name,
|
|
6620
8247
|
description: opts.description,
|
|
6621
8248
|
memories: memories.map((m) => ({
|