@clude/sdk 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
- supabase = (0, import_supabase_js.createClient)(config.supabase.url, config.supabase.serviceKey);
246
- log.info("Supabase client initialized");
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 initDatabase() {
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 (filter_owner IS NULL OR m.owner_wallet = filter_owner)
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 (filter_owner IS NULL OR m.owner_wallet = filter_owner)
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 (filter_owner IS NULL OR m.owner_wallet = filter_owner)
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
- -- BM25-ranked full-text search RPC (Exp 8)
700
- -- Note: ts_summary column requires manual migration (GENERATED ALWAYS AS is not ALTER-able)
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, ts_rank_cd(m.ts_summary, tsquery_val, 32)::float AS rank
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 (filter_owner IS NULL OR m.owner_wallet = filter_owner)
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
- log.info("Database initialized");
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
- temperature: opts.temperature ?? 0.7
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, INTERNAL_IMPORTANCE_BOOST, REFLECTION_IMPORTANCE_THRESHOLD, REFLECTION_MIN_INTERVAL_MS, WHALE_SELL_COOLDOWN_MS, MEMO_MAX_LENGTH;
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
- INTERNAL_IMPORTANCE_BOOST = 5e-3;
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: getModel(),
1973
+ model: directModel,
1296
1974
  max_tokens: options.maxTokens || 1024,
1297
- temperature: 0.9,
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: getModel(),
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
- var log5, PROVIDERS, _enabled, _overrideConfig, EMBEDDING_CACHE_MAX, EMBEDDING_CACHE_TTL_MS, _embeddingCache;
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 hkdfAsync("sha256", seed, HKDF_SALT, HKDF_INFO, 32);
3624
+ const derived = await hkdfAsync2("sha256", seed, HKDF_SALT2, HKDF_INFO2, 32);
2343
3625
  encryptionKey = new Uint8Array(derived);
2344
- const keypair = import_tweetnacl2.default.sign.keyPair.fromSecretKey(solanaSecretKey);
3626
+ const keypair = import_tweetnacl5.default.sign.keyPair.fromSecretKey(solanaSecretKey);
2345
3627
  encryptionPubkey = Buffer.from(keypair.publicKey).toString("base64");
2346
- log8.info({ pubkey: encryptionPubkey.slice(0, 12) + "..." }, "Encryption configured");
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 = import_tweetnacl2.default.randomBytes(NONCE_LENGTH);
3640
+ const nonce = import_tweetnacl5.default.randomBytes(NONCE_LENGTH2);
2359
3641
  const messageBytes = new TextEncoder().encode(plaintext);
2360
- const ciphertext = import_tweetnacl2.default.secretbox(messageBytes, nonce, encryptionKey);
2361
- const combined = new Uint8Array(NONCE_LENGTH + ciphertext.length);
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, NONCE_LENGTH);
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 < NONCE_LENGTH + 1) {
3654
+ if (combined.length < NONCE_LENGTH2 + 1) {
2373
3655
  return null;
2374
3656
  }
2375
- const nonce = combined.subarray(0, NONCE_LENGTH);
2376
- const ciphertext = combined.subarray(NONCE_LENGTH);
2377
- const plaintext = import_tweetnacl2.default.secretbox.open(ciphertext, nonce, encryptionKey);
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
- log8.debug({ memPubkey: mem.encryption_pubkey?.slice(0, 12) }, "Skipping memory encrypted by different key");
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
- log8.warn("Failed to decrypt memory content \u2014 wrong key or corrupted data");
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 import_tweetnacl2, import_crypto, import_util, log8, hkdfAsync, HKDF_SALT, HKDF_INFO, NONCE_LENGTH, encryptionKey, encryptionPubkey;
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
- import_tweetnacl2 = __toESM(require("tweetnacl"));
2408
- import_crypto = require("crypto");
2409
- import_util = require("util");
3689
+ import_tweetnacl5 = __toESM(require("tweetnacl"));
3690
+ import_crypto2 = require("crypto");
3691
+ import_util2 = require("util");
2410
3692
  init_logger();
2411
- log8 = createChildLogger("encryption");
2412
- hkdfAsync = (0, import_util.promisify)(import_crypto.hkdf);
2413
- HKDF_SALT = "clude-cortex-v1";
2414
- HKDF_INFO = "memory-encryption";
2415
- NONCE_LENGTH = import_tweetnacl2.default.secretbox.nonceLength;
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
- log9.error({ error: error.message, name }, "Failed to create entity");
3813
+ log15.error({ error: error.message, name }, "Failed to create entity");
2474
3814
  return null;
2475
3815
  }
2476
- log9.debug({ id: newEntity.id, name, type: entityType }, "Entity created");
3816
+ log15.debug({ id: newEntity.id, name, type: entityType }, "Entity created");
2477
3817
  embedEntity(newEntity.id, name, opts?.description).catch(
2478
- (err) => log9.debug({ err }, "Entity embedding failed")
3818
+ (err) => log15.debug({ err }, "Entity embedding failed")
2479
3819
  );
2480
3820
  return newEntity;
2481
3821
  } catch (err) {
2482
- log9.error({ err, name }, "Entity findOrCreate failed");
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
- log9.debug({ error: error.message, entityId, memoryId }, "Entity mention failed");
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
- log9.error({ error: error.message, entityId }, "Failed to get memories by entity");
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
- log9.debug({ error: error?.message, entityId }, "Entity co-occurrence lookup failed");
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
- log9.debug({ err, entityId }, "Entity co-occurrence skipped (RPC unavailable)");
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 { data: existing } = await db.from("entity_relations").select("id, strength, evidence_memory_ids").eq("source_entity_id", sourceEntityId).eq("target_entity_id", targetEntityId).eq("relation_type", relationType).single();
2546
- if (existing) {
2547
- const newEvidence = evidenceMemoryId && !existing.evidence_memory_ids.includes(evidenceMemoryId) ? [...existing.evidence_memory_ids, evidenceMemoryId] : existing.evidence_memory_ids;
2548
- await db.from("entity_relations").update({
2549
- strength: Math.min(1, existing.strength + 0.1),
2550
- evidence_memory_ids: newEvidence
2551
- }).eq("id", existing.id);
2552
- } else {
2553
- await db.from("entity_relations").insert({
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
- log9.debug({ err, source: entityIds[i].name, target: entityIds[j].name }, "Failed to create entity relation");
3973
+ log15.debug({ err, source: entityIds[i].name, target: entityIds[j].name }, "Failed to create entity relation");
2640
3974
  }
2641
3975
  }
2642
3976
  }
2643
- log9.debug({ memoryId, entityCount: extracted.length, relations: Math.max(0, entityIds.length * (entityIds.length - 1) / 2) }, "Entities extracted, linked, and related");
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
- log9.debug({ error: error.message }, "Entity vector search failed");
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 log9;
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
- log9 = createChildLogger("memory-graph");
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: () => 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 scopeToOwner(query) {
4102
+ function isOwnerScopeFailClosed() {
4103
+ return process.env.OWNER_SCOPE_FAILCLOSED === "true";
4104
+ }
4105
+ function getOwnerScope() {
2759
4106
  const wallet = getOwnerWallet();
2760
- if (wallet === SCOPE_BOT_OWN) {
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 (wallet) {
2764
- return query.eq("owner_wallet", 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, import_crypto2.randomBytes)(4).toString("hex")}`;
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
- log10.debug({ err }, "Failed to fetch installed packs (using default only)");
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
- log10.warn({ err }, "Failed to populate topic embeddings cache");
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
- log10.debug({ source: opts.source, summary: opts.summary.slice(0, 60) }, "Skipping duplicate memory write");
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 = opts.content.slice(0, MEMORY_MAX_CONTENT_LENGTH);
2900
- const shouldEncrypt = isEncryptionEnabled();
2901
- const storedContent = shouldEncrypt ? encryptContent(plaintextContent) : plaintextContent;
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: clamp(opts.importance ?? 0.5, 0, 1),
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: shouldEncrypt,
2919
- encryption_pubkey: shouldEncrypt ? getEncryptionPubkey() : null,
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
- log10.error({ error: error.message }, "Failed to store memory");
4326
+ log16.error({ error: error.message }, "Failed to store memory");
2924
4327
  return null;
2925
4328
  }
2926
- log10.debug({
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: opts.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: clamp(opts.importance ?? 0.5, 0, 1),
4353
+ importance,
2936
4354
  memoryType: opts.type,
2937
4355
  source: opts.source
2938
4356
  });
2939
- commitMemoryToChain(data.id, opts, data).catch(
2940
- (err) => log10.warn({ err }, "On-chain memory commit failed")
4357
+ commitMemoryToChain(data.id, opts, data, plaintextContent, envelope !== null || legacyEncrypt).catch(
4358
+ (err) => log16.warn({ err }, "On-chain memory commit failed")
2941
4359
  );
2942
- embedMemory(data.id, opts).catch((err) => log10.warn({ err }, "Embedding generation failed"));
2943
- autoLinkMemory(data.id, opts).catch((err) => log10.warn({ err }, "Auto-linking failed"));
2944
- extractAndLinkEntitiesForMemory(data.id, opts).catch((err) => log10.debug({ err }, "Entity extraction failed"));
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
- log10.error({ err }, "Memory store failed");
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: row.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
- log10.debug({ memoryId, signature: signature.slice(0, 16) }, "Memory committed on-chain (PMP + legacy)");
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 embedMemory(memoryId, opts) {
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 embeddings = await generateEmbeddings([opts.summary]);
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
- log10.debug({ memoryId }, "No embedding generated; skipping semantic tagging");
4494
+ log16.debug({ memoryId }, "No embedding generated; skipping semantic tagging");
3004
4495
  return;
3005
4496
  }
3006
- await db.from("memories").update({ embedding: JSON.stringify(summaryEmbedding) }).eq("id", memoryId);
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
- log10.debug({
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
- log10.debug({ err, memoryId }, "Semantic tagging skipped");
4542
+ log16.debug({ err, memoryId }, "Semantic tagging skipped");
3031
4543
  }
3032
- log10.debug({ memoryId }, "Memory embedded");
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
- log10.debug({ original: query, expansions: expansions.length }, "Query expanded");
4562
+ log16.debug({ original: query, expansions: expansions.length }, "Query expanded");
3051
4563
  return [query, ...expansions];
3052
4564
  } catch (err) {
3053
- log10.debug({ err }, "Query expansion failed, using original");
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 generateQueryEmbedding(q);
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
- log10.debug("All query embeddings returned null, using keyword-only retrieval");
4591
+ log16.debug("All query embeddings returned null, using keyword-only retrieval");
3079
4592
  return;
3080
4593
  }
3081
4594
  try {
3082
- const allSearches = validEmbeddings.map(
3083
- (emb) => Promise.resolve(db.rpc("match_memories", {
3084
- query_embedding: JSON.stringify(emb),
3085
- match_threshold: VECTOR_MATCH_THRESHOLD,
3086
- match_count: limit * (opts.skipExpansion ? 12 : 4),
3087
- filter_types: opts.memoryTypes || null,
3088
- filter_user: opts.relatedUser || null,
3089
- min_decay: minDecay,
3090
- filter_owner: getOwnerWallet() || null,
3091
- filter_tags: opts.tags && opts.tags.length > 0 ? opts.tags : null
3092
- })).then((r) => r.data || [])
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
- for (const batch of results2) {
3096
- for (const m of batch) {
3097
- const current = vectorScores.get(m.id) || 0;
3098
- vectorScores.set(m.id, Math.max(current, m.similarity));
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
- log10.debug({
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
- log10.warn({ err }, "Vector search RPC failed, falling back to keyword retrieval");
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
- filterOwner: _ownerWallet || void 0,
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) log10.debug({ seedsAdded, seedsTotal: knowledgeSeeds.length }, "Knowledge seeds added to candidates");
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
- log10.debug({ bm25Hits: bm25Results.length, bm25New: bm25MissingIds.length }, "BM25 search added candidates");
4756
+ log16.debug({ bm25Hits: bm25Results.length, bm25New: bm25MissingIds.length }, "BM25 search added candidates");
3209
4757
  }
3210
4758
  if (error) {
3211
- log10.error({ error: error.message }, "Memory recall query failed");
4759
+ log16.error({ error: error.message }, "Memory recall query failed");
3212
4760
  return [];
3213
4761
  }
3214
- let candidates = decryptMemoryBatch(data || []);
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, ...decryptMemoryBatch(vectorOnly)];
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
- log10.debug({ entities: entities.map((e) => e.name) }, "Entity-aware recall applied");
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
- log10.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, "Entity co-occurrence recall applied");
4845
+ log16.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, "Entity co-occurrence recall applied");
3298
4846
  }
3299
4847
  }
3300
4848
  } catch (err) {
3301
- log10.debug({ err }, "Entity-aware recall skipped");
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: getOwnerWallet() || null
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
- decryptMemoryBatch(graphMemories);
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
- log10.debug({
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
- log10.debug({ err }, "Graph expansion skipped (RPC unavailable)");
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
- log10.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, "Type diversity applied");
4908
+ log16.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, "Type diversity applied");
3361
4909
  }
3362
4910
  }
3363
4911
  }
3364
- const finalOwner = getOwnerWallet();
3365
- if (finalOwner) {
3366
- const beforeCount = results.length;
3367
- results = results.filter((m) => m.owner_wallet === finalOwner);
3368
- if (results.length < beforeCount) {
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.trackAccess !== false) {
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) => log10.warn({ err }, "Memory access tracking failed"));
3376
- reinforceCoRetrievedLinks(ids).catch((err) => log10.debug({ err }, "Link reinforcement failed"));
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
- log10.debug({
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
- log10.error({ err }, "Memory recall failed");
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 += 0.15 * Math.min(bm25Rank, 1);
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
- log10.error({ error: error.message }, "Memory summary recall failed");
5032
+ log16.error({ error: error.message }, "Memory summary recall failed");
3485
5033
  return [];
3486
5034
  }
3487
5035
  return data || [];
3488
5036
  } catch (err) {
3489
- log10.error({ err }, "Memory summary recall failed");
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
- log10.error({ error: error.message }, "Memory hydration failed");
5049
+ log16.error({ error: error.message }, "Memory hydration failed");
3502
5050
  return [];
3503
5051
  }
3504
- return decryptMemoryBatch(data || []);
5052
+ return await decryptMemories(data || []);
3505
5053
  } catch (err) {
3506
- log10.error({ err }, "Memory hydration failed");
5054
+ log16.error({ err }, "Memory hydration failed");
3507
5055
  return [];
3508
5056
  }
3509
5057
  }
3510
- async function updateMemoryAccess(ids, sources = []) {
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
- log10.warn({ error: error.message, ids }, "Batch memory access update failed");
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
- log10.debug({ error: error.message, sourceId, targetId, linkType }, "Link creation failed");
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
- log10.debug({ error: error.message, count: rows.length }, "Batch link creation failed");
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: getOwnerWallet() || null
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
- log10.debug({ memoryId, linksCreated: pendingLinks.length }, "Auto-linked memory");
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
- log10.debug({ error: error.message }, "Link reinforcement RPC failed");
5190
+ log16.debug({ error: error.message }, "Link reinforcement RPC failed");
3648
5191
  } else if (data && data > 0) {
3649
- log10.debug({ boosted: data }, "Co-retrieval link reinforcement applied");
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
- log10.debug({ err, memoryId }, "Entity extraction failed");
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
- log10.warn({ error: error.message, memType }, "Batch decay failed for type");
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
- log10.info({ decayed: totalDecayed }, "Type-specific memory decay applied");
5221
+ log16.info({ decayed: totalDecayed }, "Type-specific memory decay applied");
3679
5222
  }
3680
5223
  return totalDecayed;
3681
5224
  } catch (err) {
3682
- log10.error({ err }, "Memory decay failed");
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
- log10.error({ error: error.message, id }, "Failed to delete memory");
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
- log10.error({ error: error.message, id }, "Failed to update memory");
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
- log10.error({ error: error.message }, "Failed to list memories");
5274
+ log16.error({ error: error.message }, "Failed to list memories");
3732
5275
  return { memories: [], total: 0 };
3733
5276
  }
3734
- return { memories: decryptMemoryBatch(data || []), total: count ?? 0 };
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
- const { count, error: dreamError } = await db.from("dream_logs").select("id", { count: "exact", head: true });
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
- log10.warn({ error: dreamError.message }, "Failed to count dream logs");
5356
+ log16.warn({ error: dreamError.message }, "Failed to count dream logs");
3811
5357
  }
3812
5358
  stats.totalDreamSessions = count || 0;
3813
5359
  } catch (err) {
3814
- log10.error({ err }, "Failed to get memory stats");
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
- log10.error({ error: error.message }, "Failed to get recent memories");
5374
+ log16.error({ error: error.message }, "Failed to get recent memories");
3829
5375
  return [];
3830
5376
  }
3831
- return decryptMemoryBatch(data || []);
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
- log10.error({ error: error.message }, "Failed to get self model");
5385
+ log16.error({ error: error.message }, "Failed to get self model");
3840
5386
  return [];
3841
5387
  }
3842
- return decryptMemoryBatch(data || []);
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
- log10.error({ error: error.message }, "Failed to store dream log");
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(`- ${m.summary}${confidence}`);
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("You REMEMBER these interactions and facts. Reference them naturally if relevant.");
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
- log10.warn({ response }, "LLM importance score unparseable, using fallback");
5462
+ log16.warn({ response }, "LLM importance score unparseable, using fallback");
3923
5463
  return calculateImportance(fallbackOpts || {});
3924
5464
  } catch (err) {
3925
- log10.warn({ err }, "LLM importance scoring failed, using fallback");
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
- filter_owner: opts.ownerWallet ?? null
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 import_crypto2, EMBED_CACHE_MAX, embeddingCache, _ownerWallet, SCOPE_BOT_OWN, HASH_ID_PREFIX, log10, DEDUP_TTL_MS, dedupCache, installedPacksCache, INSTALLED_PACKS_TTL_MS, topicEmbeddingCache, topicEmbeddingPopulationLock, TOKENISATION_SKIP_SOURCES, STOPWORDS;
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
- import_crypto2 = require("crypto");
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
- SCOPE_BOT_OWN = "__BOT_OWN__";
5543
+ SCOPE_BOT_OWN2 = "__BOT_OWN__";
3996
5544
  HASH_ID_PREFIX = "clude";
3997
- log10 = createChildLogger("memory");
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: () => 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
- log11.debug("JEPA disabled \u2014 skipping deep connection phase");
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
- log11.debug("No candidate memories \u2014 skipping deep connection phase");
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
- log11.debug("All candidates JEPA-queried in last 24 h \u2014 skipping");
5917
+ log18.debug("All candidates JEPA-queried in last 24 h \u2014 skipping");
4291
5918
  return SKIPPED;
4292
5919
  }
4293
- log11.info({ total: candidates.length, toProcess: toProcess.length }, "Deep connection phase: processing memories");
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
- log11.warn({ err }, "Failed to write deep_connection dream log");
6006
+ log18.warn({ err }, "Failed to write deep_connection dream log");
4380
6007
  }
4381
- log11.info({ linksCreated: allLinks.length, topLinks: topLinks.length }, "Deep connection phase complete");
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 log11, TWENTY_FOUR_HOURS_MS, RECENT_SAMPLE, HIGH_IMPORTANCE_SAMPLE, HIGH_IMPORTANCE_THRESHOLD, EMBEDDING_SIMILARITY_THRESHOLD, MATCH_LIMIT, TOP_LINKS_PER_MEMORY, _jepaClient;
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
- log11 = createChildLogger("dream-deep-connection");
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
- log12.debug({ waitMs: wait }, "Throttling post");
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
- log12.warn({ waitMs: wait }, "X API 429 \u2014 backing off");
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
- log12.info({ tweetId, originalLength: text.length, length: truncated.length }, "Posting reply");
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
- log12.info({ originalLength: text.length, length: truncated.length }, "Posting tweet");
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
- log12.info({ count: texts.length }, "Posting thread");
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
- log12.debug({ sinceId }, "Fetching mentions");
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
- log12.warn({ tweetId, err }, "Failed to fetch tweet context");
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
- log12.info({ sinceId, maxResults }, "Searching campaign tweets");
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
- log12.info({ found: tweets.length }, "Campaign tweet search complete");
6235
+ log19.info({ found: tweets.length }, "Campaign tweet search complete");
4609
6236
  return tweets;
4610
6237
  } catch (err) {
4611
- log12.error({ err }, "Failed to search campaign tweets");
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
- log12.info({ sinceId, maxResults }, "Searching $CLUDE mentions");
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
- log12.info({ found: tweets.length }, "Token mention search complete");
6276
+ log19.info({ found: tweets.length }, "Token mention search complete");
4650
6277
  return tweets;
4651
6278
  } catch (err) {
4652
- log12.error({ err }, "Failed to search token mentions");
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
- log12.debug({ userId, sinceId }, "Fetching user tweets");
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
- log12.warn({ userId, err }, "Failed to fetch user tweets");
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
- log12.warn({ err, batchSize: batch.length }, "Failed to refresh tweet metrics batch");
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
- log12.warn({ userId, err }, "Failed to fetch user");
6353
+ log19.warn({ userId, err }, "Failed to fetch user");
4727
6354
  return null;
4728
6355
  }
4729
6356
  }
4730
- var import_twitter_api_v2, log12, client, rwClient, MIN_POST_GAP_MS, lastPostTime, MAX_TWEET_LENGTH, _usernameCache;
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
- log12 = createChildLogger("x-client");
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
- log13.info({ actionId: record.actionId, feature: record.feature, memoryId }, "Action logged");
6411
+ log20.info({ actionId: record.actionId, feature: record.feature, memoryId }, "Action logged");
4785
6412
  return memoryId;
4786
6413
  } catch (err) {
4787
- log13.error({ err, actionId: record.actionId }, "Failed to log action");
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
- log13.info({ actionId: record.actionId, sentiment: record.sentiment, memoryId }, "Outcome logged");
6444
+ log20.info({ actionId: record.actionId, sentiment: record.sentiment, memoryId }, "Outcome logged");
4818
6445
  return memoryId;
4819
6446
  } catch (err) {
4820
- log13.error({ err, actionId: record.actionId }, "Failed to log outcome");
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
- log13.debug({ actionId, oldImportance: currentImportance, newImportance }, "Action importance updated");
6465
+ log20.debug({ actionId, oldImportance: currentImportance, newImportance }, "Action importance updated");
4839
6466
  } catch (err) {
4840
- log13.error({ err, actionId }, "Failed to update action importance");
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
- log13.debug("No recent actions to analyze");
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
- log13.debug({ total: recentActions.length, withOutcome: withOutcome.length }, "Not enough outcome data for strategy refinement");
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
- log13.info({ feature, lesson: lesson.slice(0, 100) }, "New strategy learned");
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
- log13.error({ err }, "Strategy refinement failed");
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
- log13.error({ err, feature }, "Failed to generate lesson");
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
- log13.debug({ strategyId: strategy.id, feature, boost: relevantPositive.length }, "Strategy reinforced");
6592
+ log20.debug({ strategyId: strategy.id, feature, boost: relevantPositive.length }, "Strategy reinforced");
4966
6593
  }
4967
6594
  }
4968
6595
  } catch (err) {
4969
- log13.error({ err }, "Failed to reinforce strategies");
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
- log13.debug({ replyId }, "Could not fetch tweet metrics");
6650
+ log20.debug({ replyId }, "Could not fetch tweet metrics");
5024
6651
  }
5025
6652
  }
5026
- log13.info({ tracked }, "Social outcomes tracked");
6653
+ log20.info({ tracked }, "Social outcomes tracked");
5027
6654
  } catch (err) {
5028
- log13.error({ err }, "Social outcome tracking failed");
6655
+ log20.error({ err }, "Social outcome tracking failed");
5029
6656
  }
5030
6657
  return tracked;
5031
6658
  }
5032
- var log13;
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
- log13 = createChildLogger("action-learning");
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
- log14.info({
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
- log14.error({ err }, "Event-driven reflection failed");
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
- log14.info({ accumulator: importanceAccumulator.toFixed(2) }, "=== DREAM CYCLE TRIGGERED ===");
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
- log14.info({ linksCreated: deepResult.linksCreated }, "JEPA deep connection complete");
6727
+ log21.info({ linksCreated: deepResult.linksCreated }, "JEPA deep connection complete");
5101
6728
  } catch (err) {
5102
- log14.warn({ err }, "JEPA deep connection phase failed, continuing");
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
- log14.info("=== DREAM CYCLE COMPLETE ===");
6741
+ log21.info("=== DREAM CYCLE COMPLETE ===");
5115
6742
  } catch (err) {
5116
- log14.error({ err }, "Dream cycle failed or timed out");
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
- log14.debug({
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
- log14.warn({ err }, "Failed to save reflection accumulator");
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
- log14.info("Dream phase 1: CONSOLIDATION starting");
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
- log14.info({ count: recentSummaries.length }, "Too few recent memories for consolidation");
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
- log14.warn({ err }, "Focal point generation failed, using direct consolidation");
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
- log14.info({ focalPoints }, "Focal points generated");
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
- log14.warn({ question }, "Consolidation produced cop-out response \u2014 discarding");
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
- log14.info({ focalPoints: focalPoints.length, insights: allNewIds.length, procedural: proceduralIds.length }, "Focal-point consolidation complete");
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
- log14.warn("Direct consolidation produced cop-out response \u2014 discarding");
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
- log14.info({ observations: newIds.length, procedural: proceduralIds.length }, "Direct consolidation complete");
6938
+ log21.info({ observations: newIds.length, procedural: proceduralIds.length }, "Direct consolidation complete");
5312
6939
  }
5313
6940
  async function runCompaction() {
5314
- log14.info("Dream phase 1.5: COMPACTION starting");
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
- log14.error({ error: error.message }, "Failed to fetch compaction candidates");
6948
+ log21.error({ error: error.message }, "Failed to fetch compaction candidates");
5322
6949
  return;
5323
6950
  }
5324
6951
  if (!candidates || candidates.length < 5) {
5325
- log14.debug({ count: candidates?.length || 0 }, "Too few candidates for compaction");
6952
+ log21.debug({ count: candidates?.length || 0 }, "Too few candidates for compaction");
5326
6953
  return;
5327
6954
  }
5328
- log14.info({ count: candidates.length }, "Found compaction candidates");
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
- log14.debug({ concept, count: memories.length }, "Memory group compacted");
6999
+ log21.debug({ concept, count: memories.length }, "Memory group compacted");
5373
7000
  }
5374
7001
  } catch (err) {
5375
- log14.warn({ err, concept }, "Failed to compact memory group");
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
- log14.info({ compacted: compactedCount, summaries: summaryIds.length }, "Compaction complete");
7013
+ log21.info({ compacted: compactedCount, summaries: summaryIds.length }, "Compaction complete");
5387
7014
  }
5388
7015
  async function runReflection() {
5389
- log14.info("Dream phase 2: REFLECTION starting");
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
- log14.warn("Reflection produced cop-out response \u2014 discarding");
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
- log14.info({ evidenceCount: evidenceIds.length }, "Reflection complete (progressive disclosure enabled)");
7071
+ log21.info({ evidenceCount: evidenceIds.length }, "Reflection complete (progressive disclosure enabled)");
5445
7072
  }
5446
7073
  async function runContradictionResolution() {
5447
- log14.info("Dream phase 2.5: CONTRADICTION RESOLUTION starting");
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
- log14.debug({ error: error?.message }, "No unresolved contradictions found \u2014 skipping phase");
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
- log14.debug({ err }, "Contradiction resolution skipped (RPC unavailable)");
7088
+ log21.debug({ err }, "Contradiction resolution skipped (RPC unavailable)");
5462
7089
  return;
5463
7090
  }
5464
- log14.info({ pairCount: pairs.length }, "Found unresolved contradictions");
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
- log14.warn("Could not fetch contradiction memories \u2014 skipping phase");
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
- log14.warn({ memA: memA.id, memB: memB.id }, "Contradiction resolution produced cop-out \u2014 skipping pair");
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
- log14.debug({ memoryId: weaker.id, newDecay: newDecay.toFixed(3) }, "Accelerated decay on weaker contradicting memory");
7150
+ log21.debug({ memoryId: weaker.id, newDecay: newDecay.toFixed(3) }, "Accelerated decay on weaker contradicting memory");
5524
7151
  } else {
5525
- log14.warn({ error: decayErr.message, memoryId: weaker.id }, "Failed to accelerate decay on contradicting memory");
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
- log14.info({ resolved: outputs.length, skipped: pairs.length - outputs.length }, "Contradiction resolution complete");
7164
+ log21.info({ resolved: outputs.length, skipped: pairs.length - outputs.length }, "Contradiction resolution complete");
5538
7165
  }
5539
7166
  async function runLearning() {
5540
- log14.info("--- Learning phase: tracking outcomes & refining strategies ---");
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
- log14.info({ tracked }, "Social outcomes tracked");
7172
+ log21.info({ tracked }, "Social outcomes tracked");
5546
7173
  }
5547
7174
  const lessons = await refineStrategies2();
5548
7175
  if (lessons.length > 0) {
5549
- log14.info({ lessons: lessons.length }, "New strategies learned");
7176
+ log21.info({ lessons: lessons.length }, "New strategies learned");
5550
7177
  for (const lesson of lessons) {
5551
- log14.info({ lesson: lesson.slice(0, 150) }, "Learned strategy");
7178
+ log21.info({ lesson: lesson.slice(0, 150) }, "Learned strategy");
5552
7179
  }
5553
7180
  }
5554
7181
  } catch (err) {
5555
- log14.error({ err }, "Learning phase failed");
7182
+ log21.error({ err }, "Learning phase failed");
5556
7183
  }
5557
7184
  }
5558
7185
  async function runEmergence() {
5559
- log14.info("Dream phase 3: EMERGENCE starting");
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
- log14.info("Emergence thought sent to SDK handler");
7234
+ log21.info("Emergence thought sent to SDK handler");
5608
7235
  } catch (err) {
5609
- log14.error({ err }, "SDK emergence handler failed");
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
- log14.info("Emergence thought posted to X");
7244
+ log21.info("Emergence thought posted to X");
5618
7245
  } catch (err) {
5619
- log14.error({ err }, "Failed to post emergence thought");
7246
+ log21.error({ err }, "Failed to post emergence thought");
5620
7247
  }
5621
7248
  } else if (!hasDepth) {
5622
- log14.debug({ selfModelCount: selfModel.length }, "Emergence not deep enough to post \u2014 skipping tweet");
7249
+ log21.debug({ selfModelCount: selfModel.length }, "Emergence not deep enough to post \u2014 skipping tweet");
5623
7250
  }
5624
7251
  }
5625
- log14.info("Emergence complete");
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
- log14.warn("Procedural extraction produced cop-out response \u2014 discarding");
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
- log14.info({ count: newIds.length }, "Procedural insights extracted");
7288
+ log21.info({ count: newIds.length }, "Procedural insights extracted");
5662
7289
  return newIds;
5663
7290
  } catch (err) {
5664
- log14.warn({ err }, "Procedural extraction failed");
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
- log14.info("Starting dream cycle scheduler");
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
- log14.info("Scheduled dream cycle skipped \u2014 reflection already in progress");
7323
+ log21.info("Scheduled dream cycle skipped \u2014 reflection already in progress");
5697
7324
  return;
5698
7325
  }
5699
- log14.info({
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
- log14.info("Running memory decay");
7332
+ log21.info("Running memory decay");
5706
7333
  try {
5707
7334
  await decayMemories();
5708
7335
  } catch (err) {
5709
- log14.error({ err }, "Memory decay failed");
7336
+ log21.error({ err }, "Memory decay failed");
5710
7337
  }
5711
7338
  });
5712
7339
  setTimeout(async () => {
5713
- log14.info("Running initial dream cycle");
7340
+ log21.info("Running initial dream cycle");
5714
7341
  try {
5715
7342
  await triggerReflection();
5716
7343
  } catch (err) {
5717
- log14.error({ err }, "Initial dream cycle failed");
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
- log14.info("Dream cycle stopped");
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 log14, COPOUT_PATTERNS, importanceAccumulator, lastReflectionTime, reflectionInProgress, REFLECTION_TIMEOUT_MS, _emergenceHandler, dreamCron, decayCron;
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
- log14 = createChildLogger("dream-cycle");
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
- log15.debug({ context: opts.context.slice(0, 60), limit }, "Searching for clinamen");
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
- log15.debug("No embedding available \u2014 falling back to importance-only clinamen");
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
- log15.debug({ error: error?.message }, "No clinamen candidates found");
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
- log15.info({
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 log15, MIN_IMPORTANCE, MAX_RELEVANCE_SIM, MIN_AGE_HOURS, CANDIDATE_POOL_SIZE, DEFAULT_LIMIT;
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
- log15 = createChildLogger("clinamen");
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
- log16.info({
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
- log16.debug({ err }, "Clinamen retrieval failed \u2014 falling back to random");
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
- log16.info("Active reflection already in progress \u2014 skipping");
7557
+ log23.info("Active reflection already in progress \u2014 skipping");
5931
7558
  return null;
5932
7559
  }
5933
7560
  reflectionInProgress2 = true;
5934
7561
  try {
5935
- log16.info("=== ACTIVE REFLECTION STARTING ===");
7562
+ log23.info("=== ACTIVE REFLECTION STARTING ===");
5936
7563
  const { seeds, theme } = await selectSeeds();
5937
7564
  if (seeds.length < MIN_MEMORIES_FOR_REFLECTION) {
5938
- log16.info({ count: seeds.length }, "Too few memories for reflection \u2014 skipping");
7565
+ log23.info({ count: seeds.length }, "Too few memories for reflection \u2014 skipping");
5939
7566
  return null;
5940
7567
  }
5941
- log16.info({ seedCount: seeds.length, theme }, "Reflection seeds selected");
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
- log16.warn("Reflection produced empty or trivial output \u2014 discarding");
7599
+ log23.warn("Reflection produced empty or trivial output \u2014 discarding");
5973
7600
  return null;
5974
7601
  }
5975
- log16.info({ length: journal.length, theme }, "Journal entry generated");
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
- log16.info("Reflection sent to SDK handler");
7636
+ log23.info("Reflection sent to SDK handler");
6010
7637
  } catch (err) {
6011
- log16.error({ err }, "SDK reflection handler failed");
7638
+ log23.error({ err }, "SDK reflection handler failed");
6012
7639
  }
6013
7640
  } else {
6014
7641
  await maybePostThread(result);
6015
7642
  }
6016
- log16.info({ memoryId, title }, "=== ACTIVE REFLECTION COMPLETE ===");
7643
+ log23.info({ memoryId, title }, "=== ACTIVE REFLECTION COMPLETE ===");
6017
7644
  return result;
6018
7645
  } catch (err) {
6019
- log16.error({ err }, "Active reflection failed");
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
- log16.debug("Quiet hours \u2014 skipping thread post");
7670
+ log23.debug("Quiet hours \u2014 skipping thread post");
6044
7671
  return;
6045
7672
  }
6046
7673
  if (journal.text.length < 300) {
6047
- log16.debug("Journal too short for thread \u2014 skipping post");
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
- log16.debug("Thread rate limit hit \u2014 skipping post");
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
- log16.info({ tweetCount: ids.length, title: journal.title }, "Reflection thread posted to X");
7686
+ log23.info({ tweetCount: ids.length, title: journal.title }, "Reflection thread posted to X");
6060
7687
  } catch (err) {
6061
- log16.error({ err }, "Failed to post reflection thread");
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
- log16.info("Starting active reflection scheduler");
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
- log16.info("Scheduled reflection skipped \u2014 already in progress");
7715
+ log23.info("Scheduled reflection skipped \u2014 already in progress");
6089
7716
  return;
6090
7717
  }
6091
- log16.info("=== SCHEDULED ACTIVE REFLECTION ===");
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
- log16.error({ err }, "Scheduled active reflection failed or timed out");
7725
+ log23.error({ err }, "Scheduled active reflection failed or timed out");
6099
7726
  });
6100
7727
  });
6101
7728
  setTimeout(async () => {
6102
- log16.info("Running initial active reflection");
7729
+ log23.info("Running initial active reflection");
6103
7730
  try {
6104
7731
  await runActiveReflection();
6105
7732
  } catch (err) {
6106
- log16.error({ err }, "Initial active reflection failed");
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
- log16.info("Active reflection stopped");
7742
+ log23.info("Active reflection stopped");
6116
7743
  }
6117
- var log16, 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;
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
- log16 = createChildLogger("active-reflection");
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 import_crypto3 = __toESM(require("crypto"));
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: import_crypto3.default.randomUUID(),
8245
+ id: import_crypto4.default.randomUUID(),
6619
8246
  name: opts.name,
6620
8247
  description: opts.description,
6621
8248
  memories: memories.map((m) => ({