@askexenow/exe-os 0.9.34 → 0.9.36

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 (76) hide show
  1. package/dist/bin/backfill-conversations.js +212 -11
  2. package/dist/bin/backfill-responses.js +212 -11
  3. package/dist/bin/backfill-vectors.js +14 -3
  4. package/dist/bin/cleanup-stale-review-tasks.js +224 -12
  5. package/dist/bin/cli.js +265 -23
  6. package/dist/bin/exe-agent.js +1 -1
  7. package/dist/bin/exe-assign.js +217 -12
  8. package/dist/bin/exe-boot.js +25 -4
  9. package/dist/bin/exe-call.js +7 -5
  10. package/dist/bin/exe-dispatch.js +229 -13
  11. package/dist/bin/exe-doctor.js +14 -3
  12. package/dist/bin/exe-export-behaviors.js +301 -14
  13. package/dist/bin/exe-forget.js +245 -21
  14. package/dist/bin/exe-gateway.js +229 -13
  15. package/dist/bin/exe-heartbeat.js +224 -12
  16. package/dist/bin/exe-kill.js +224 -12
  17. package/dist/bin/exe-launch-agent.js +177 -9
  18. package/dist/bin/exe-link.js +8 -0
  19. package/dist/bin/exe-new-employee.js +26 -2
  20. package/dist/bin/exe-pending-messages.js +224 -12
  21. package/dist/bin/exe-pending-notifications.js +224 -12
  22. package/dist/bin/exe-pending-reviews.js +224 -12
  23. package/dist/bin/exe-rename.js +9 -1
  24. package/dist/bin/exe-review.js +224 -12
  25. package/dist/bin/exe-search.js +246 -21
  26. package/dist/bin/exe-session-cleanup.js +229 -13
  27. package/dist/bin/exe-start-codex.js +161 -5
  28. package/dist/bin/exe-start-opencode.js +172 -5
  29. package/dist/bin/exe-status.js +224 -12
  30. package/dist/bin/exe-team.js +224 -12
  31. package/dist/bin/git-sweep.js +229 -13
  32. package/dist/bin/graph-backfill.js +94 -3
  33. package/dist/bin/graph-export.js +224 -12
  34. package/dist/bin/install.js +25 -1
  35. package/dist/bin/intercom-check.js +229 -13
  36. package/dist/bin/scan-tasks.js +229 -13
  37. package/dist/bin/setup.js +15 -5
  38. package/dist/bin/shard-migrate.js +94 -3
  39. package/dist/gateway/index.js +229 -13
  40. package/dist/hooks/bug-report-worker.js +229 -13
  41. package/dist/hooks/codex-stop-task-finalizer.js +224 -12
  42. package/dist/hooks/commit-complete.js +229 -13
  43. package/dist/hooks/error-recall.js +246 -21
  44. package/dist/hooks/ingest.js +224 -12
  45. package/dist/hooks/instructions-loaded.js +224 -12
  46. package/dist/hooks/notification.js +224 -12
  47. package/dist/hooks/post-compact.js +224 -12
  48. package/dist/hooks/post-tool-combined.js +246 -21
  49. package/dist/hooks/pre-compact.js +229 -13
  50. package/dist/hooks/pre-tool-use.js +234 -18
  51. package/dist/hooks/prompt-submit.js +346 -23
  52. package/dist/hooks/session-end.js +229 -13
  53. package/dist/hooks/session-start.js +418 -42
  54. package/dist/hooks/stop.js +224 -12
  55. package/dist/hooks/subagent-stop.js +224 -12
  56. package/dist/hooks/summary-worker.js +15 -2
  57. package/dist/index.js +229 -13
  58. package/dist/lib/cloud-sync.js +8 -0
  59. package/dist/lib/consolidation.js +3 -1
  60. package/dist/lib/database.js +8 -0
  61. package/dist/lib/db.js +8 -0
  62. package/dist/lib/device-registry.js +8 -0
  63. package/dist/lib/employee-templates.js +7 -5
  64. package/dist/lib/exe-daemon.js +1776 -1433
  65. package/dist/lib/hybrid-search.js +246 -21
  66. package/dist/lib/schedules.js +14 -3
  67. package/dist/lib/store.js +217 -12
  68. package/dist/lib/tasks.js +5 -1
  69. package/dist/lib/tmux-routing.js +5 -1
  70. package/dist/mcp/server.js +331 -37
  71. package/dist/mcp/tools/create-task.js +5 -1
  72. package/dist/mcp/tools/update-task.js +5 -1
  73. package/dist/runtime/index.js +229 -13
  74. package/dist/tui/App.js +229 -13
  75. package/package.json +1 -1
  76. package/src/commands/exe/save.md +48 -0
@@ -2321,6 +2321,14 @@ async function ensureSchema() {
2321
2321
  );
2322
2322
  } catch {
2323
2323
  }
2324
+ try {
2325
+ await client.execute(
2326
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2327
+ ON memories(content_hash, agent_id, project_name, memory_type)
2328
+ WHERE content_hash IS NOT NULL`
2329
+ );
2330
+ } catch {
2331
+ }
2324
2332
  await client.executeMultiple(`
2325
2333
  CREATE TABLE IF NOT EXISTS entities (
2326
2334
  id TEXT PRIMARY KEY,
@@ -3066,6 +3074,197 @@ var init_state_bus = __esm({
3066
3074
  }
3067
3075
  });
3068
3076
 
3077
+ // src/lib/memory-write-governor.ts
3078
+ import { createHash } from "crypto";
3079
+ function normalizeMemoryText(text) {
3080
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3081
+ }
3082
+ function classifyMemoryType(input) {
3083
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3084
+ const tool = input.tool_name.toLowerCase();
3085
+ const text = input.raw_text.toLowerCase();
3086
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3087
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3088
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3089
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3090
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3091
+ if (tool === "store_memory" || tool === "manual") return "observation";
3092
+ return "raw";
3093
+ }
3094
+ function shouldDropMemory(text) {
3095
+ const normalized = normalizeMemoryText(text);
3096
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3097
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3098
+ return { drop: true, reason: "known_boilerplate_noise" };
3099
+ }
3100
+ return { drop: false };
3101
+ }
3102
+ function shouldSkipEmbedding(input) {
3103
+ const type = classifyMemoryType(input);
3104
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3105
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3106
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3107
+ return false;
3108
+ }
3109
+ function hashMemoryContent(text) {
3110
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3111
+ }
3112
+ function scopedDedupArgs(input) {
3113
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3114
+ }
3115
+ function governMemoryRecord(record) {
3116
+ const normalized = normalizeMemoryText(record.raw_text);
3117
+ const memoryType = classifyMemoryType({
3118
+ raw_text: normalized,
3119
+ agent_id: record.agent_id,
3120
+ project_name: record.project_name,
3121
+ tool_name: record.tool_name,
3122
+ memory_type: record.memory_type
3123
+ });
3124
+ const drop = shouldDropMemory(normalized);
3125
+ const skipEmbedding = shouldSkipEmbedding({
3126
+ raw_text: normalized,
3127
+ agent_id: record.agent_id,
3128
+ project_name: record.project_name,
3129
+ tool_name: record.tool_name,
3130
+ memory_type: memoryType
3131
+ });
3132
+ return {
3133
+ record: {
3134
+ ...record,
3135
+ raw_text: normalized,
3136
+ memory_type: memoryType,
3137
+ vector: skipEmbedding ? null : record.vector
3138
+ },
3139
+ contentHash: hashMemoryContent(normalized),
3140
+ shouldDrop: drop.drop,
3141
+ dropReason: drop.reason,
3142
+ skipEmbedding,
3143
+ hygiene: {
3144
+ dedup: true,
3145
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3146
+ }
3147
+ };
3148
+ }
3149
+ async function findScopedDuplicate(input) {
3150
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3151
+ const client = getClient2();
3152
+ const args = scopedDedupArgs(input);
3153
+ let sql = `SELECT id FROM memories
3154
+ WHERE content_hash = ?
3155
+ AND agent_id = ?
3156
+ AND project_name = ?
3157
+ AND COALESCE(memory_type, 'raw') = ?
3158
+ AND COALESCE(status, 'active') != 'deleted'`;
3159
+ if (input.excludeId) {
3160
+ sql += " AND id != ?";
3161
+ args.push(input.excludeId);
3162
+ }
3163
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3164
+ const result = await client.execute({ sql, args });
3165
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3166
+ }
3167
+ async function runPostWriteMemoryHygiene(memoryId) {
3168
+ try {
3169
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3170
+ const client = getClient2();
3171
+ const current = await client.execute({
3172
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3173
+ importance, timestamp
3174
+ FROM memories
3175
+ WHERE id = ?
3176
+ LIMIT 1`,
3177
+ args: [memoryId]
3178
+ });
3179
+ const row = current.rows[0];
3180
+ if (!row) return;
3181
+ const memoryType = String(row.memory_type ?? "raw");
3182
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3183
+ const agentId = String(row.agent_id);
3184
+ const projectName = String(row.project_name);
3185
+ if (contentHash) {
3186
+ await client.execute({
3187
+ sql: `UPDATE memories
3188
+ SET status = 'deleted',
3189
+ outcome = COALESCE(outcome, 'superseded')
3190
+ WHERE id != ?
3191
+ AND content_hash = ?
3192
+ AND agent_id = ?
3193
+ AND project_name = ?
3194
+ AND COALESCE(memory_type, 'raw') = ?
3195
+ AND COALESCE(status, 'active') = 'active'`,
3196
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3197
+ });
3198
+ }
3199
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3200
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3201
+ const old = await client.execute({
3202
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3203
+ args: [supersedesId]
3204
+ });
3205
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3206
+ const newImportance = Number(row.importance ?? 0);
3207
+ await client.batch([
3208
+ {
3209
+ sql: `UPDATE memories
3210
+ SET status = 'archived',
3211
+ outcome = COALESCE(outcome, 'superseded')
3212
+ WHERE id = ?`,
3213
+ args: [supersedesId]
3214
+ },
3215
+ {
3216
+ sql: `UPDATE memories
3217
+ SET importance = MAX(COALESCE(importance, 5), ?),
3218
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3219
+ WHERE id = ?`,
3220
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3221
+ }
3222
+ ], "write");
3223
+ }
3224
+ } catch (err) {
3225
+ process.stderr.write(
3226
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3227
+ `
3228
+ );
3229
+ }
3230
+ }
3231
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3232
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3233
+ if (memoryIds.length === 0) return;
3234
+ const run = () => {
3235
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3236
+ };
3237
+ if (typeof setImmediate === "function") setImmediate(run);
3238
+ else setTimeout(run, 0);
3239
+ }
3240
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3241
+ var init_memory_write_governor = __esm({
3242
+ "src/lib/memory-write-governor.ts"() {
3243
+ "use strict";
3244
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3245
+ "decision",
3246
+ "adr",
3247
+ "behavior",
3248
+ "procedure"
3249
+ ]);
3250
+ NOISE_DROP_PATTERNS = [
3251
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3252
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3253
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3254
+ /^\s*Intercom is a speedup, not delivery/im,
3255
+ /^\s*Context bar reads as USAGE not remaining/im
3256
+ ];
3257
+ SKIP_EMBED_PATTERNS = [
3258
+ /tmux capture-pane\b/i,
3259
+ /docker ps\b/i,
3260
+ /docker images\b/i,
3261
+ /git status\b/i,
3262
+ /grep .*node_modules/i,
3263
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3264
+ ];
3265
+ }
3266
+ });
3267
+
3069
3268
  // src/lib/shard-manager.ts
3070
3269
  var shard_manager_exports = {};
3071
3270
  __export(shard_manager_exports, {
@@ -3230,7 +3429,8 @@ async function ensureShardSchema(client) {
3230
3429
  }
3231
3430
  for (const idx of [
3232
3431
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3233
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3432
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3433
+ "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"
3234
3434
  ]) {
3235
3435
  try {
3236
3436
  await client.execute(idx);
@@ -3424,7 +3624,7 @@ var init_platform_procedures = __esm({
3424
3624
  title: "Chain of command \u2014 who talks to whom",
3425
3625
  domain: "workflow",
3426
3626
  priority: "p0",
3427
- content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3627
+ content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3428
3628
  },
3429
3629
  {
3430
3630
  title: "Single dispatch path \u2014 create_task only",
@@ -3655,7 +3855,6 @@ __export(store_exports, {
3655
3855
  vectorToBlob: () => vectorToBlob,
3656
3856
  writeMemory: () => writeMemory
3657
3857
  });
3658
- import { createHash } from "crypto";
3659
3858
  function isBusyError2(err) {
3660
3859
  if (err instanceof Error) {
3661
3860
  const msg = err.message.toLowerCase();
@@ -3769,17 +3968,24 @@ async function writeMemory(record) {
3769
3968
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3770
3969
  );
3771
3970
  }
3772
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3773
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3971
+ const governed = governMemoryRecord(record);
3972
+ if (governed.shouldDrop) return;
3973
+ record = governed.record;
3974
+ const contentHash = governed.contentHash;
3975
+ const memoryType = record.memory_type ?? "raw";
3976
+ if (_pendingRecords.some(
3977
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3978
+ )) {
3774
3979
  return;
3775
3980
  }
3776
3981
  try {
3777
- const client = getClient();
3778
- const existing = await client.execute({
3779
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3780
- args: [contentHash, record.agent_id]
3982
+ const existing = await findScopedDuplicate({
3983
+ contentHash,
3984
+ agentId: record.agent_id,
3985
+ projectName: record.project_name,
3986
+ memoryType
3781
3987
  });
3782
- if (existing.rows.length > 0) return;
3988
+ if (existing) return;
3783
3989
  } catch {
3784
3990
  }
3785
3991
  const dbRow = {
@@ -3810,7 +4016,7 @@ async function writeMemory(record) {
3810
4016
  tier: record.tier ?? classifyTier(record),
3811
4017
  supersedes_id: record.supersedes_id ?? null,
3812
4018
  draft: record.draft ? 1 : 0,
3813
- memory_type: record.memory_type ?? "raw",
4019
+ memory_type: memoryType,
3814
4020
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3815
4021
  content_hash: contentHash,
3816
4022
  intent: record.intent ?? null,
@@ -3969,6 +4175,7 @@ async function flushBatch() {
3969
4175
  const globalClient = getClient();
3970
4176
  const globalStmts = batch.map(buildStmt);
3971
4177
  await globalClient.batch(globalStmts, "write");
4178
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3972
4179
  _pendingRecords.splice(0, batch.length);
3973
4180
  try {
3974
4181
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4089,7 +4296,11 @@ async function searchMemories(queryVector, agentId, options) {
4089
4296
  sql += ` AND timestamp >= ?`;
4090
4297
  args.push(options.since);
4091
4298
  }
4092
- if (options?.memoryType) {
4299
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4300
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4301
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4302
+ args.push(...uniqueTypes);
4303
+ } else if (options?.memoryType) {
4093
4304
  sql += ` AND memory_type = ?`;
4094
4305
  args.push(options.memoryType);
4095
4306
  }
@@ -4227,6 +4438,7 @@ var init_store = __esm({
4227
4438
  init_keychain();
4228
4439
  init_config();
4229
4440
  init_state_bus();
4441
+ init_memory_write_governor();
4230
4442
  INIT_MAX_RETRIES = 3;
4231
4443
  INIT_RETRY_DELAY_MS = 1e3;
4232
4444
  _pendingRecords = [];
@@ -7673,7 +7885,11 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
7673
7885
  }
7674
7886
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
7675
7887
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
7676
- envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
7888
+ let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
7889
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
7890
+ ccModel += "[1m]";
7891
+ }
7892
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
7677
7893
  }
7678
7894
  }
7679
7895
  let spawnCommand;
@@ -129,6 +129,21 @@ var init_secure_files = __esm({
129
129
  });
130
130
 
131
131
  // src/lib/config.ts
132
+ var config_exports = {};
133
+ __export(config_exports, {
134
+ CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
135
+ CONFIG_PATH: () => CONFIG_PATH,
136
+ CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
137
+ DB_PATH: () => DB_PATH,
138
+ EXE_AI_DIR: () => EXE_AI_DIR,
139
+ LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
140
+ MODELS_DIR: () => MODELS_DIR,
141
+ loadConfig: () => loadConfig,
142
+ loadConfigFrom: () => loadConfigFrom,
143
+ loadConfigSync: () => loadConfigSync,
144
+ migrateConfig: () => migrateConfig,
145
+ saveConfig: () => saveConfig
146
+ });
132
147
  import { readFile, writeFile } from "fs/promises";
133
148
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
134
149
  import path from "path";
@@ -232,6 +247,46 @@ async function loadConfig() {
232
247
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
233
248
  }
234
249
  }
250
+ function loadConfigSync() {
251
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
252
+ const configPath = path.join(dir, "config.json");
253
+ if (!existsSync2(configPath)) {
254
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
255
+ }
256
+ try {
257
+ const raw = readFileSync(configPath, "utf-8");
258
+ let parsed = JSON.parse(raw);
259
+ parsed = migrateLegacyConfig(parsed);
260
+ const { config: migratedCfg } = migrateConfig(parsed);
261
+ normalizeScalingRoadmap(migratedCfg);
262
+ normalizeSessionLifecycle(migratedCfg);
263
+ normalizeAutoUpdate(migratedCfg);
264
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
265
+ } catch {
266
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
267
+ }
268
+ }
269
+ async function saveConfig(config) {
270
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
271
+ await ensurePrivateDir(dir);
272
+ const configPath = path.join(dir, "config.json");
273
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
274
+ await enforcePrivateFile(configPath);
275
+ }
276
+ async function loadConfigFrom(configPath) {
277
+ const raw = await readFile(configPath, "utf-8");
278
+ try {
279
+ let parsed = JSON.parse(raw);
280
+ parsed = migrateLegacyConfig(parsed);
281
+ const { config: migratedCfg } = migrateConfig(parsed);
282
+ normalizeScalingRoadmap(migratedCfg);
283
+ normalizeSessionLifecycle(migratedCfg);
284
+ normalizeAutoUpdate(migratedCfg);
285
+ return { ...DEFAULT_CONFIG, ...migratedCfg };
286
+ } catch {
287
+ return { ...DEFAULT_CONFIG };
288
+ }
289
+ }
235
290
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
236
291
  var init_config = __esm({
237
292
  "src/lib/config.ts"() {
@@ -2095,6 +2150,14 @@ async function ensureSchema() {
2095
2150
  );
2096
2151
  } catch {
2097
2152
  }
2153
+ try {
2154
+ await client.execute(
2155
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2156
+ ON memories(content_hash, agent_id, project_name, memory_type)
2157
+ WHERE content_hash IS NOT NULL`
2158
+ );
2159
+ } catch {
2160
+ }
2098
2161
  await client.executeMultiple(`
2099
2162
  CREATE TABLE IF NOT EXISTS entities (
2100
2163
  id TEXT PRIMARY KEY,
@@ -2764,7 +2827,8 @@ async function ensureShardSchema(client) {
2764
2827
  }
2765
2828
  for (const idx of [
2766
2829
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2767
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
2830
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2831
+ "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"
2768
2832
  ]) {
2769
2833
  try {
2770
2834
  await client.execute(idx);
@@ -2958,7 +3022,7 @@ var init_platform_procedures = __esm({
2958
3022
  title: "Chain of command \u2014 who talks to whom",
2959
3023
  domain: "workflow",
2960
3024
  priority: "p0",
2961
- content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3025
+ content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
2962
3026
  },
2963
3027
  {
2964
3028
  title: "Single dispatch path \u2014 create_task only",
@@ -3512,6 +3576,7 @@ var init_preferences = __esm({
3512
3576
  // src/adapters/claude/installer.ts
3513
3577
  import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, readdir } from "fs/promises";
3514
3578
  import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync6, copyFileSync, mkdirSync as mkdirSync6 } from "fs";
3579
+ import { createHash as createHash2 } from "crypto";
3515
3580
  import path12 from "path";
3516
3581
  import os9 from "os";
3517
3582
  import { execSync as execSync5 } from "child_process";
@@ -3973,7 +4038,6 @@ import { spawnSync } from "child_process";
3973
4038
  // src/lib/store.ts
3974
4039
  init_memory();
3975
4040
  init_database();
3976
- import { createHash } from "crypto";
3977
4041
 
3978
4042
  // src/lib/keychain.ts
3979
4043
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -4206,6 +4270,88 @@ var StateBus = class {
4206
4270
  };
4207
4271
  var orgBus = new StateBus();
4208
4272
 
4273
+ // src/lib/memory-write-governor.ts
4274
+ import { createHash } from "crypto";
4275
+ var HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
4276
+ "decision",
4277
+ "adr",
4278
+ "behavior",
4279
+ "procedure"
4280
+ ]);
4281
+ async function runPostWriteMemoryHygiene(memoryId) {
4282
+ try {
4283
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4284
+ const client = getClient2();
4285
+ const current = await client.execute({
4286
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
4287
+ importance, timestamp
4288
+ FROM memories
4289
+ WHERE id = ?
4290
+ LIMIT 1`,
4291
+ args: [memoryId]
4292
+ });
4293
+ const row = current.rows[0];
4294
+ if (!row) return;
4295
+ const memoryType = String(row.memory_type ?? "raw");
4296
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
4297
+ const agentId = String(row.agent_id);
4298
+ const projectName = String(row.project_name);
4299
+ if (contentHash) {
4300
+ await client.execute({
4301
+ sql: `UPDATE memories
4302
+ SET status = 'deleted',
4303
+ outcome = COALESCE(outcome, 'superseded')
4304
+ WHERE id != ?
4305
+ AND content_hash = ?
4306
+ AND agent_id = ?
4307
+ AND project_name = ?
4308
+ AND COALESCE(memory_type, 'raw') = ?
4309
+ AND COALESCE(status, 'active') = 'active'`,
4310
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
4311
+ });
4312
+ }
4313
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
4314
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
4315
+ const old = await client.execute({
4316
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
4317
+ args: [supersedesId]
4318
+ });
4319
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
4320
+ const newImportance = Number(row.importance ?? 0);
4321
+ await client.batch([
4322
+ {
4323
+ sql: `UPDATE memories
4324
+ SET status = 'archived',
4325
+ outcome = COALESCE(outcome, 'superseded')
4326
+ WHERE id = ?`,
4327
+ args: [supersedesId]
4328
+ },
4329
+ {
4330
+ sql: `UPDATE memories
4331
+ SET importance = MAX(COALESCE(importance, 5), ?),
4332
+ parent_memory_id = COALESCE(parent_memory_id, ?)
4333
+ WHERE id = ?`,
4334
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
4335
+ }
4336
+ ], "write");
4337
+ }
4338
+ } catch (err) {
4339
+ process.stderr.write(
4340
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
4341
+ `
4342
+ );
4343
+ }
4344
+ }
4345
+ function schedulePostWriteMemoryHygiene(memoryIds) {
4346
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
4347
+ if (memoryIds.length === 0) return;
4348
+ const run = () => {
4349
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
4350
+ };
4351
+ if (typeof setImmediate === "function") setImmediate(run);
4352
+ else setTimeout(run, 0);
4353
+ }
4354
+
4209
4355
  // src/lib/store.ts
4210
4356
  var INIT_MAX_RETRIES = 3;
4211
4357
  var INIT_RETRY_DELAY_MS = 1e3;
@@ -4405,6 +4551,7 @@ async function flushBatch() {
4405
4551
  const globalClient = getClient();
4406
4552
  const globalStmts = batch.map(buildStmt);
4407
4553
  await globalClient.batch(globalStmts, "write");
4554
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4408
4555
  _pendingRecords.splice(0, batch.length);
4409
4556
  try {
4410
4557
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4516,7 +4663,16 @@ var BEHAVIORS_EXPORT_DIR = path8.join(
4516
4663
  "behaviors-export"
4517
4664
  );
4518
4665
  var STALE_EXPORT_AGE_MS = 60 * 60 * 1e3;
4519
- var EXPORT_BEHAVIOR_LIMIT = 30;
4666
+ var DEFAULT_BEHAVIOR_LIMIT = 30;
4667
+ function getBehaviorLimit() {
4668
+ try {
4669
+ const { loadConfigSync: loadConfigSync2 } = (init_config(), __toCommonJS(config_exports));
4670
+ const cfg = loadConfigSync2();
4671
+ return cfg.behaviorExportLimit ?? DEFAULT_BEHAVIOR_LIMIT;
4672
+ } catch {
4673
+ return DEFAULT_BEHAVIOR_LIMIT;
4674
+ }
4675
+ }
4520
4676
  function sweepStaleBehaviorExports(now = Date.now()) {
4521
4677
  if (!existsSync8(BEHAVIORS_EXPORT_DIR)) return;
4522
4678
  let entries;
@@ -4570,7 +4726,7 @@ function exportFilePath(agentId, projectName, sessionKey) {
4570
4726
  async function exportBehaviorsForAgent(agentId, projectName, sessionKey) {
4571
4727
  mkdirSync3(BEHAVIORS_EXPORT_DIR, { recursive: true });
4572
4728
  sweepStaleBehaviorExports();
4573
- const behaviors = await listBehaviors(agentId, projectName, EXPORT_BEHAVIOR_LIMIT);
4729
+ const behaviors = await listBehaviors(agentId, projectName, getBehaviorLimit());
4574
4730
  if (behaviors.length === 0) return null;
4575
4731
  const body = renderBehaviorExport(behaviors);
4576
4732
  const target = exportFilePath(agentId, projectName, sessionKey);