@askexenow/exe-os 0.9.34 → 0.9.35

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.
Files changed (70) hide show
  1. package/dist/bin/backfill-conversations.js +210 -10
  2. package/dist/bin/backfill-responses.js +210 -10
  3. package/dist/bin/backfill-vectors.js +13 -2
  4. package/dist/bin/cleanup-stale-review-tasks.js +217 -10
  5. package/dist/bin/cli.js +222 -11
  6. package/dist/bin/exe-assign.js +210 -10
  7. package/dist/bin/exe-boot.js +24 -3
  8. package/dist/bin/exe-dispatch.js +222 -11
  9. package/dist/bin/exe-doctor.js +13 -2
  10. package/dist/bin/exe-export-behaviors.js +217 -10
  11. package/dist/bin/exe-forget.js +217 -10
  12. package/dist/bin/exe-gateway.js +222 -11
  13. package/dist/bin/exe-heartbeat.js +217 -10
  14. package/dist/bin/exe-kill.js +217 -10
  15. package/dist/bin/exe-launch-agent.js +92 -2
  16. package/dist/bin/exe-link.js +8 -0
  17. package/dist/bin/exe-pending-messages.js +217 -10
  18. package/dist/bin/exe-pending-notifications.js +217 -10
  19. package/dist/bin/exe-pending-reviews.js +217 -10
  20. package/dist/bin/exe-rename.js +8 -0
  21. package/dist/bin/exe-review.js +217 -10
  22. package/dist/bin/exe-search.js +217 -10
  23. package/dist/bin/exe-session-cleanup.js +222 -11
  24. package/dist/bin/exe-start-codex.js +92 -2
  25. package/dist/bin/exe-start-opencode.js +92 -2
  26. package/dist/bin/exe-status.js +217 -10
  27. package/dist/bin/exe-team.js +217 -10
  28. package/dist/bin/git-sweep.js +222 -11
  29. package/dist/bin/graph-backfill.js +92 -2
  30. package/dist/bin/graph-export.js +217 -10
  31. package/dist/bin/intercom-check.js +222 -11
  32. package/dist/bin/scan-tasks.js +222 -11
  33. package/dist/bin/setup.js +8 -0
  34. package/dist/bin/shard-migrate.js +92 -2
  35. package/dist/gateway/index.js +222 -11
  36. package/dist/hooks/bug-report-worker.js +222 -11
  37. package/dist/hooks/codex-stop-task-finalizer.js +217 -10
  38. package/dist/hooks/commit-complete.js +222 -11
  39. package/dist/hooks/error-recall.js +217 -10
  40. package/dist/hooks/ingest.js +217 -10
  41. package/dist/hooks/instructions-loaded.js +217 -10
  42. package/dist/hooks/notification.js +217 -10
  43. package/dist/hooks/post-compact.js +217 -10
  44. package/dist/hooks/post-tool-combined.js +217 -10
  45. package/dist/hooks/pre-compact.js +222 -11
  46. package/dist/hooks/pre-tool-use.js +217 -10
  47. package/dist/hooks/prompt-submit.js +222 -11
  48. package/dist/hooks/session-end.js +222 -11
  49. package/dist/hooks/session-start.js +217 -10
  50. package/dist/hooks/stop.js +217 -10
  51. package/dist/hooks/subagent-stop.js +217 -10
  52. package/dist/hooks/summary-worker.js +14 -1
  53. package/dist/index.js +222 -11
  54. package/dist/lib/cloud-sync.js +8 -0
  55. package/dist/lib/consolidation.js +3 -1
  56. package/dist/lib/database.js +8 -0
  57. package/dist/lib/db.js +8 -0
  58. package/dist/lib/device-registry.js +8 -0
  59. package/dist/lib/exe-daemon.js +1667 -1413
  60. package/dist/lib/hybrid-search.js +217 -10
  61. package/dist/lib/schedules.js +13 -2
  62. package/dist/lib/store.js +210 -10
  63. package/dist/lib/tasks.js +5 -1
  64. package/dist/lib/tmux-routing.js +5 -1
  65. package/dist/mcp/server.js +222 -11
  66. package/dist/mcp/tools/create-task.js +5 -1
  67. package/dist/mcp/tools/update-task.js +5 -1
  68. package/dist/runtime/index.js +222 -11
  69. package/dist/tui/App.js +222 -11
  70. package/package.json +1 -1
@@ -2276,6 +2276,14 @@ async function ensureSchema() {
2276
2276
  );
2277
2277
  } catch {
2278
2278
  }
2279
+ try {
2280
+ await client.execute(
2281
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2282
+ ON memories(content_hash, agent_id, project_name, memory_type)
2283
+ WHERE content_hash IS NOT NULL`
2284
+ );
2285
+ } catch {
2286
+ }
2279
2287
  await client.executeMultiple(`
2280
2288
  CREATE TABLE IF NOT EXISTS entities (
2281
2289
  id TEXT PRIMARY KEY,
@@ -3021,6 +3029,196 @@ var init_state_bus = __esm({
3021
3029
  }
3022
3030
  });
3023
3031
 
3032
+ // src/lib/memory-write-governor.ts
3033
+ import { createHash } from "crypto";
3034
+ function normalizeMemoryText(text) {
3035
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3036
+ }
3037
+ function classifyMemoryType(input) {
3038
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3039
+ const tool = input.tool_name.toLowerCase();
3040
+ const text = input.raw_text.toLowerCase();
3041
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3042
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3043
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3044
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3045
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3046
+ if (tool === "store_memory" || tool === "manual") return "observation";
3047
+ return "raw";
3048
+ }
3049
+ function shouldDropMemory(text) {
3050
+ const normalized = normalizeMemoryText(text);
3051
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3052
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3053
+ return { drop: true, reason: "known_boilerplate_noise" };
3054
+ }
3055
+ return { drop: false };
3056
+ }
3057
+ function shouldSkipEmbedding(input) {
3058
+ const type = classifyMemoryType(input);
3059
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3060
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3061
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3062
+ return false;
3063
+ }
3064
+ function hashMemoryContent(text) {
3065
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3066
+ }
3067
+ function scopedDedupArgs(input) {
3068
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3069
+ }
3070
+ function governMemoryRecord(record) {
3071
+ const normalized = normalizeMemoryText(record.raw_text);
3072
+ const memoryType = classifyMemoryType({
3073
+ raw_text: normalized,
3074
+ agent_id: record.agent_id,
3075
+ project_name: record.project_name,
3076
+ tool_name: record.tool_name,
3077
+ memory_type: record.memory_type
3078
+ });
3079
+ const drop = shouldDropMemory(normalized);
3080
+ const skipEmbedding = shouldSkipEmbedding({
3081
+ raw_text: normalized,
3082
+ agent_id: record.agent_id,
3083
+ project_name: record.project_name,
3084
+ tool_name: record.tool_name,
3085
+ memory_type: memoryType
3086
+ });
3087
+ return {
3088
+ record: {
3089
+ ...record,
3090
+ raw_text: normalized,
3091
+ memory_type: memoryType,
3092
+ vector: skipEmbedding ? null : record.vector
3093
+ },
3094
+ contentHash: hashMemoryContent(normalized),
3095
+ shouldDrop: drop.drop,
3096
+ dropReason: drop.reason,
3097
+ skipEmbedding,
3098
+ hygiene: {
3099
+ dedup: true,
3100
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3101
+ }
3102
+ };
3103
+ }
3104
+ async function findScopedDuplicate(input) {
3105
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3106
+ const client = getClient2();
3107
+ const args = scopedDedupArgs(input);
3108
+ let sql = `SELECT id FROM memories
3109
+ WHERE content_hash = ?
3110
+ AND agent_id = ?
3111
+ AND project_name = ?
3112
+ AND COALESCE(memory_type, 'raw') = ?
3113
+ AND COALESCE(status, 'active') != 'deleted'`;
3114
+ if (input.excludeId) {
3115
+ sql += " AND id != ?";
3116
+ args.push(input.excludeId);
3117
+ }
3118
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3119
+ const result = await client.execute({ sql, args });
3120
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3121
+ }
3122
+ async function runPostWriteMemoryHygiene(memoryId) {
3123
+ try {
3124
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3125
+ const client = getClient2();
3126
+ const current = await client.execute({
3127
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3128
+ importance, timestamp
3129
+ FROM memories
3130
+ WHERE id = ?
3131
+ LIMIT 1`,
3132
+ args: [memoryId]
3133
+ });
3134
+ const row = current.rows[0];
3135
+ if (!row) return;
3136
+ const memoryType = String(row.memory_type ?? "raw");
3137
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3138
+ const agentId = String(row.agent_id);
3139
+ const projectName = String(row.project_name);
3140
+ if (contentHash) {
3141
+ await client.execute({
3142
+ sql: `UPDATE memories
3143
+ SET status = 'deleted',
3144
+ outcome = COALESCE(outcome, 'superseded')
3145
+ WHERE id != ?
3146
+ AND content_hash = ?
3147
+ AND agent_id = ?
3148
+ AND project_name = ?
3149
+ AND COALESCE(memory_type, 'raw') = ?
3150
+ AND COALESCE(status, 'active') = 'active'`,
3151
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3152
+ });
3153
+ }
3154
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3155
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3156
+ const old = await client.execute({
3157
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3158
+ args: [supersedesId]
3159
+ });
3160
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3161
+ const newImportance = Number(row.importance ?? 0);
3162
+ await client.batch([
3163
+ {
3164
+ sql: `UPDATE memories
3165
+ SET status = 'archived',
3166
+ outcome = COALESCE(outcome, 'superseded')
3167
+ WHERE id = ?`,
3168
+ args: [supersedesId]
3169
+ },
3170
+ {
3171
+ sql: `UPDATE memories
3172
+ SET importance = MAX(COALESCE(importance, 5), ?),
3173
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3174
+ WHERE id = ?`,
3175
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3176
+ }
3177
+ ], "write");
3178
+ }
3179
+ } catch (err) {
3180
+ process.stderr.write(
3181
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3182
+ `
3183
+ );
3184
+ }
3185
+ }
3186
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3187
+ if (memoryIds.length === 0) return;
3188
+ const run = () => {
3189
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3190
+ };
3191
+ if (typeof setImmediate === "function") setImmediate(run);
3192
+ else setTimeout(run, 0);
3193
+ }
3194
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3195
+ var init_memory_write_governor = __esm({
3196
+ "src/lib/memory-write-governor.ts"() {
3197
+ "use strict";
3198
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3199
+ "decision",
3200
+ "adr",
3201
+ "behavior",
3202
+ "procedure"
3203
+ ]);
3204
+ NOISE_DROP_PATTERNS = [
3205
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3206
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3207
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3208
+ /^\s*Intercom is a speedup, not delivery/im,
3209
+ /^\s*Context bar reads as USAGE not remaining/im
3210
+ ];
3211
+ SKIP_EMBED_PATTERNS = [
3212
+ /tmux capture-pane\b/i,
3213
+ /docker ps\b/i,
3214
+ /docker images\b/i,
3215
+ /git status\b/i,
3216
+ /grep .*node_modules/i,
3217
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3218
+ ];
3219
+ }
3220
+ });
3221
+
3024
3222
  // src/lib/shard-manager.ts
3025
3223
  var shard_manager_exports = {};
3026
3224
  __export(shard_manager_exports, {
@@ -3185,7 +3383,8 @@ async function ensureShardSchema(client) {
3185
3383
  }
3186
3384
  for (const idx of [
3187
3385
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3188
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3386
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3387
+ "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
3189
3388
  ]) {
3190
3389
  try {
3191
3390
  await client.execute(idx);
@@ -3610,7 +3809,6 @@ __export(store_exports, {
3610
3809
  vectorToBlob: () => vectorToBlob,
3611
3810
  writeMemory: () => writeMemory
3612
3811
  });
3613
- import { createHash } from "crypto";
3614
3812
  function isBusyError2(err) {
3615
3813
  if (err instanceof Error) {
3616
3814
  const msg = err.message.toLowerCase();
@@ -3724,17 +3922,24 @@ async function writeMemory(record) {
3724
3922
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3725
3923
  );
3726
3924
  }
3727
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3728
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3925
+ const governed = governMemoryRecord(record);
3926
+ if (governed.shouldDrop) return;
3927
+ record = governed.record;
3928
+ const contentHash = governed.contentHash;
3929
+ const memoryType = record.memory_type ?? "raw";
3930
+ if (_pendingRecords.some(
3931
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3932
+ )) {
3729
3933
  return;
3730
3934
  }
3731
3935
  try {
3732
- const client = getClient();
3733
- const existing = await client.execute({
3734
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3735
- args: [contentHash, record.agent_id]
3936
+ const existing = await findScopedDuplicate({
3937
+ contentHash,
3938
+ agentId: record.agent_id,
3939
+ projectName: record.project_name,
3940
+ memoryType
3736
3941
  });
3737
- if (existing.rows.length > 0) return;
3942
+ if (existing) return;
3738
3943
  } catch {
3739
3944
  }
3740
3945
  const dbRow = {
@@ -3765,7 +3970,7 @@ async function writeMemory(record) {
3765
3970
  tier: record.tier ?? classifyTier(record),
3766
3971
  supersedes_id: record.supersedes_id ?? null,
3767
3972
  draft: record.draft ? 1 : 0,
3768
- memory_type: record.memory_type ?? "raw",
3973
+ memory_type: memoryType,
3769
3974
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3770
3975
  content_hash: contentHash,
3771
3976
  intent: record.intent ?? null,
@@ -3924,6 +4129,7 @@ async function flushBatch() {
3924
4129
  const globalClient = getClient();
3925
4130
  const globalStmts = batch.map(buildStmt);
3926
4131
  await globalClient.batch(globalStmts, "write");
4132
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3927
4133
  _pendingRecords.splice(0, batch.length);
3928
4134
  try {
3929
4135
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4182,6 +4388,7 @@ var init_store = __esm({
4182
4388
  init_keychain();
4183
4389
  init_config();
4184
4390
  init_state_bus();
4391
+ init_memory_write_governor();
4185
4392
  INIT_MAX_RETRIES = 3;
4186
4393
  INIT_RETRY_DELAY_MS = 1e3;
4187
4394
  _pendingRecords = [];
@@ -2072,6 +2072,14 @@ async function ensureSchema() {
2072
2072
  );
2073
2073
  } catch {
2074
2074
  }
2075
+ try {
2076
+ await client.execute(
2077
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2078
+ ON memories(content_hash, agent_id, project_name, memory_type)
2079
+ WHERE content_hash IS NOT NULL`
2080
+ );
2081
+ } catch {
2082
+ }
2075
2083
  await client.executeMultiple(`
2076
2084
  CREATE TABLE IF NOT EXISTS entities (
2077
2085
  id TEXT PRIMARY KEY,
@@ -2741,7 +2749,8 @@ async function ensureShardSchema(client) {
2741
2749
  }
2742
2750
  for (const idx of [
2743
2751
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2744
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
2752
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2753
+ "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
2745
2754
  ]) {
2746
2755
  try {
2747
2756
  await client.execute(idx);
@@ -3156,7 +3165,6 @@ import { execSync as execSync3 } from "child_process";
3156
3165
  // src/lib/store.ts
3157
3166
  init_memory();
3158
3167
  init_database();
3159
- import { createHash } from "crypto";
3160
3168
 
3161
3169
  // src/lib/keychain.ts
3162
3170
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -3389,6 +3397,9 @@ var StateBus = class {
3389
3397
  };
3390
3398
  var orgBus = new StateBus();
3391
3399
 
3400
+ // src/lib/memory-write-governor.ts
3401
+ import { createHash } from "crypto";
3402
+
3392
3403
  // src/lib/store.ts
3393
3404
  var INIT_MAX_RETRIES = 3;
3394
3405
  var INIT_RETRY_DELAY_MS = 1e3;
package/dist/lib/store.js CHANGED
@@ -2072,6 +2072,14 @@ async function ensureSchema() {
2072
2072
  );
2073
2073
  } catch {
2074
2074
  }
2075
+ try {
2076
+ await client.execute(
2077
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2078
+ ON memories(content_hash, agent_id, project_name, memory_type)
2079
+ WHERE content_hash IS NOT NULL`
2080
+ );
2081
+ } catch {
2082
+ }
2075
2083
  await client.executeMultiple(`
2076
2084
  CREATE TABLE IF NOT EXISTS entities (
2077
2085
  id TEXT PRIMARY KEY,
@@ -2741,7 +2749,8 @@ async function ensureShardSchema(client) {
2741
2749
  }
2742
2750
  for (const idx of [
2743
2751
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2744
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
2752
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2753
+ "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
2745
2754
  ]) {
2746
2755
  try {
2747
2756
  await client.execute(idx);
@@ -3151,7 +3160,6 @@ ${p.content}`).join("\n\n");
3151
3160
  // src/lib/store.ts
3152
3161
  init_memory();
3153
3162
  init_database();
3154
- import { createHash } from "crypto";
3155
3163
 
3156
3164
  // src/lib/keychain.ts
3157
3165
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -3384,6 +3392,190 @@ var StateBus = class {
3384
3392
  };
3385
3393
  var orgBus = new StateBus();
3386
3394
 
3395
+ // src/lib/memory-write-governor.ts
3396
+ import { createHash } from "crypto";
3397
+ var HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3398
+ "decision",
3399
+ "adr",
3400
+ "behavior",
3401
+ "procedure"
3402
+ ]);
3403
+ var NOISE_DROP_PATTERNS = [
3404
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3405
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3406
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3407
+ /^\s*Intercom is a speedup, not delivery/im,
3408
+ /^\s*Context bar reads as USAGE not remaining/im
3409
+ ];
3410
+ var SKIP_EMBED_PATTERNS = [
3411
+ /tmux capture-pane\b/i,
3412
+ /docker ps\b/i,
3413
+ /docker images\b/i,
3414
+ /git status\b/i,
3415
+ /grep .*node_modules/i,
3416
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3417
+ ];
3418
+ function normalizeMemoryText(text) {
3419
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3420
+ }
3421
+ function classifyMemoryType(input) {
3422
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3423
+ const tool = input.tool_name.toLowerCase();
3424
+ const text = input.raw_text.toLowerCase();
3425
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3426
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3427
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3428
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3429
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3430
+ if (tool === "store_memory" || tool === "manual") return "observation";
3431
+ return "raw";
3432
+ }
3433
+ function shouldDropMemory(text) {
3434
+ const normalized = normalizeMemoryText(text);
3435
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3436
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3437
+ return { drop: true, reason: "known_boilerplate_noise" };
3438
+ }
3439
+ return { drop: false };
3440
+ }
3441
+ function shouldSkipEmbedding(input) {
3442
+ const type = classifyMemoryType(input);
3443
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3444
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3445
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3446
+ return false;
3447
+ }
3448
+ function hashMemoryContent(text) {
3449
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3450
+ }
3451
+ function scopedDedupArgs(input) {
3452
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3453
+ }
3454
+ function governMemoryRecord(record) {
3455
+ const normalized = normalizeMemoryText(record.raw_text);
3456
+ const memoryType = classifyMemoryType({
3457
+ raw_text: normalized,
3458
+ agent_id: record.agent_id,
3459
+ project_name: record.project_name,
3460
+ tool_name: record.tool_name,
3461
+ memory_type: record.memory_type
3462
+ });
3463
+ const drop = shouldDropMemory(normalized);
3464
+ const skipEmbedding = shouldSkipEmbedding({
3465
+ raw_text: normalized,
3466
+ agent_id: record.agent_id,
3467
+ project_name: record.project_name,
3468
+ tool_name: record.tool_name,
3469
+ memory_type: memoryType
3470
+ });
3471
+ return {
3472
+ record: {
3473
+ ...record,
3474
+ raw_text: normalized,
3475
+ memory_type: memoryType,
3476
+ vector: skipEmbedding ? null : record.vector
3477
+ },
3478
+ contentHash: hashMemoryContent(normalized),
3479
+ shouldDrop: drop.drop,
3480
+ dropReason: drop.reason,
3481
+ skipEmbedding,
3482
+ hygiene: {
3483
+ dedup: true,
3484
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3485
+ }
3486
+ };
3487
+ }
3488
+ async function findScopedDuplicate(input) {
3489
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3490
+ const client = getClient2();
3491
+ const args = scopedDedupArgs(input);
3492
+ let sql = `SELECT id FROM memories
3493
+ WHERE content_hash = ?
3494
+ AND agent_id = ?
3495
+ AND project_name = ?
3496
+ AND COALESCE(memory_type, 'raw') = ?
3497
+ AND COALESCE(status, 'active') != 'deleted'`;
3498
+ if (input.excludeId) {
3499
+ sql += " AND id != ?";
3500
+ args.push(input.excludeId);
3501
+ }
3502
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3503
+ const result = await client.execute({ sql, args });
3504
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3505
+ }
3506
+ async function runPostWriteMemoryHygiene(memoryId) {
3507
+ try {
3508
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3509
+ const client = getClient2();
3510
+ const current = await client.execute({
3511
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3512
+ importance, timestamp
3513
+ FROM memories
3514
+ WHERE id = ?
3515
+ LIMIT 1`,
3516
+ args: [memoryId]
3517
+ });
3518
+ const row = current.rows[0];
3519
+ if (!row) return;
3520
+ const memoryType = String(row.memory_type ?? "raw");
3521
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3522
+ const agentId = String(row.agent_id);
3523
+ const projectName = String(row.project_name);
3524
+ if (contentHash) {
3525
+ await client.execute({
3526
+ sql: `UPDATE memories
3527
+ SET status = 'deleted',
3528
+ outcome = COALESCE(outcome, 'superseded')
3529
+ WHERE id != ?
3530
+ AND content_hash = ?
3531
+ AND agent_id = ?
3532
+ AND project_name = ?
3533
+ AND COALESCE(memory_type, 'raw') = ?
3534
+ AND COALESCE(status, 'active') = 'active'`,
3535
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3536
+ });
3537
+ }
3538
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3539
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3540
+ const old = await client.execute({
3541
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3542
+ args: [supersedesId]
3543
+ });
3544
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3545
+ const newImportance = Number(row.importance ?? 0);
3546
+ await client.batch([
3547
+ {
3548
+ sql: `UPDATE memories
3549
+ SET status = 'archived',
3550
+ outcome = COALESCE(outcome, 'superseded')
3551
+ WHERE id = ?`,
3552
+ args: [supersedesId]
3553
+ },
3554
+ {
3555
+ sql: `UPDATE memories
3556
+ SET importance = MAX(COALESCE(importance, 5), ?),
3557
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3558
+ WHERE id = ?`,
3559
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3560
+ }
3561
+ ], "write");
3562
+ }
3563
+ } catch (err) {
3564
+ process.stderr.write(
3565
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3566
+ `
3567
+ );
3568
+ }
3569
+ }
3570
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3571
+ if (memoryIds.length === 0) return;
3572
+ const run = () => {
3573
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3574
+ };
3575
+ if (typeof setImmediate === "function") setImmediate(run);
3576
+ else setTimeout(run, 0);
3577
+ }
3578
+
3387
3579
  // src/lib/store.ts
3388
3580
  var INIT_MAX_RETRIES = 3;
3389
3581
  var INIT_RETRY_DELAY_MS = 1e3;
@@ -3506,17 +3698,24 @@ async function writeMemory(record) {
3506
3698
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3507
3699
  );
3508
3700
  }
3509
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3510
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3701
+ const governed = governMemoryRecord(record);
3702
+ if (governed.shouldDrop) return;
3703
+ record = governed.record;
3704
+ const contentHash = governed.contentHash;
3705
+ const memoryType = record.memory_type ?? "raw";
3706
+ if (_pendingRecords.some(
3707
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3708
+ )) {
3511
3709
  return;
3512
3710
  }
3513
3711
  try {
3514
- const client = getClient();
3515
- const existing = await client.execute({
3516
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3517
- args: [contentHash, record.agent_id]
3712
+ const existing = await findScopedDuplicate({
3713
+ contentHash,
3714
+ agentId: record.agent_id,
3715
+ projectName: record.project_name,
3716
+ memoryType
3518
3717
  });
3519
- if (existing.rows.length > 0) return;
3718
+ if (existing) return;
3520
3719
  } catch {
3521
3720
  }
3522
3721
  const dbRow = {
@@ -3547,7 +3746,7 @@ async function writeMemory(record) {
3547
3746
  tier: record.tier ?? classifyTier(record),
3548
3747
  supersedes_id: record.supersedes_id ?? null,
3549
3748
  draft: record.draft ? 1 : 0,
3550
- memory_type: record.memory_type ?? "raw",
3749
+ memory_type: memoryType,
3551
3750
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3552
3751
  content_hash: contentHash,
3553
3752
  intent: record.intent ?? null,
@@ -3706,6 +3905,7 @@ async function flushBatch() {
3706
3905
  const globalClient = getClient();
3707
3906
  const globalStmts = batch.map(buildStmt);
3708
3907
  await globalClient.batch(globalStmts, "write");
3908
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3709
3909
  _pendingRecords.splice(0, batch.length);
3710
3910
  try {
3711
3911
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
package/dist/lib/tasks.js CHANGED
@@ -2389,7 +2389,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2389
2389
  }
2390
2390
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
2391
2391
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
2392
- envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
2392
+ let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
2393
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
2394
+ ccModel += "[1m]";
2395
+ }
2396
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
2393
2397
  }
2394
2398
  }
2395
2399
  let spawnCommand;
@@ -4415,7 +4415,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4415
4415
  }
4416
4416
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4417
4417
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
4418
- envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4418
+ let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
4419
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
4420
+ ccModel += "[1m]";
4421
+ }
4422
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
4419
4423
  }
4420
4424
  }
4421
4425
  let spawnCommand;