@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
@@ -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,197 @@ 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 (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3188
+ if (memoryIds.length === 0) return;
3189
+ const run = () => {
3190
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3191
+ };
3192
+ if (typeof setImmediate === "function") setImmediate(run);
3193
+ else setTimeout(run, 0);
3194
+ }
3195
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3196
+ var init_memory_write_governor = __esm({
3197
+ "src/lib/memory-write-governor.ts"() {
3198
+ "use strict";
3199
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3200
+ "decision",
3201
+ "adr",
3202
+ "behavior",
3203
+ "procedure"
3204
+ ]);
3205
+ NOISE_DROP_PATTERNS = [
3206
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3207
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3208
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3209
+ /^\s*Intercom is a speedup, not delivery/im,
3210
+ /^\s*Context bar reads as USAGE not remaining/im
3211
+ ];
3212
+ SKIP_EMBED_PATTERNS = [
3213
+ /tmux capture-pane\b/i,
3214
+ /docker ps\b/i,
3215
+ /docker images\b/i,
3216
+ /git status\b/i,
3217
+ /grep .*node_modules/i,
3218
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3219
+ ];
3220
+ }
3221
+ });
3222
+
3024
3223
  // src/lib/shard-manager.ts
3025
3224
  var shard_manager_exports = {};
3026
3225
  __export(shard_manager_exports, {
@@ -3185,7 +3384,8 @@ async function ensureShardSchema(client) {
3185
3384
  }
3186
3385
  for (const idx of [
3187
3386
  "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"
3387
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3388
+ "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
3389
  ]) {
3190
3390
  try {
3191
3391
  await client.execute(idx);
@@ -3379,7 +3579,7 @@ var init_platform_procedures = __esm({
3379
3579
  title: "Chain of command \u2014 who talks to whom",
3380
3580
  domain: "workflow",
3381
3581
  priority: "p0",
3382
- 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."
3582
+ 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."
3383
3583
  },
3384
3584
  {
3385
3585
  title: "Single dispatch path \u2014 create_task only",
@@ -3610,7 +3810,6 @@ __export(store_exports, {
3610
3810
  vectorToBlob: () => vectorToBlob,
3611
3811
  writeMemory: () => writeMemory
3612
3812
  });
3613
- import { createHash } from "crypto";
3614
3813
  function isBusyError2(err) {
3615
3814
  if (err instanceof Error) {
3616
3815
  const msg = err.message.toLowerCase();
@@ -3724,17 +3923,24 @@ async function writeMemory(record) {
3724
3923
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3725
3924
  );
3726
3925
  }
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)) {
3926
+ const governed = governMemoryRecord(record);
3927
+ if (governed.shouldDrop) return;
3928
+ record = governed.record;
3929
+ const contentHash = governed.contentHash;
3930
+ const memoryType = record.memory_type ?? "raw";
3931
+ if (_pendingRecords.some(
3932
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3933
+ )) {
3729
3934
  return;
3730
3935
  }
3731
3936
  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]
3937
+ const existing = await findScopedDuplicate({
3938
+ contentHash,
3939
+ agentId: record.agent_id,
3940
+ projectName: record.project_name,
3941
+ memoryType
3736
3942
  });
3737
- if (existing.rows.length > 0) return;
3943
+ if (existing) return;
3738
3944
  } catch {
3739
3945
  }
3740
3946
  const dbRow = {
@@ -3765,7 +3971,7 @@ async function writeMemory(record) {
3765
3971
  tier: record.tier ?? classifyTier(record),
3766
3972
  supersedes_id: record.supersedes_id ?? null,
3767
3973
  draft: record.draft ? 1 : 0,
3768
- memory_type: record.memory_type ?? "raw",
3974
+ memory_type: memoryType,
3769
3975
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3770
3976
  content_hash: contentHash,
3771
3977
  intent: record.intent ?? null,
@@ -3924,6 +4130,7 @@ async function flushBatch() {
3924
4130
  const globalClient = getClient();
3925
4131
  const globalStmts = batch.map(buildStmt);
3926
4132
  await globalClient.batch(globalStmts, "write");
4133
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3927
4134
  _pendingRecords.splice(0, batch.length);
3928
4135
  try {
3929
4136
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4044,7 +4251,11 @@ async function searchMemories(queryVector, agentId, options) {
4044
4251
  sql += ` AND timestamp >= ?`;
4045
4252
  args.push(options.since);
4046
4253
  }
4047
- if (options?.memoryType) {
4254
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4255
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4256
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4257
+ args.push(...uniqueTypes);
4258
+ } else if (options?.memoryType) {
4048
4259
  sql += ` AND memory_type = ?`;
4049
4260
  args.push(options.memoryType);
4050
4261
  }
@@ -4182,6 +4393,7 @@ var init_store = __esm({
4182
4393
  init_keychain();
4183
4394
  init_config();
4184
4395
  init_state_bus();
4396
+ init_memory_write_governor();
4185
4397
  INIT_MAX_RETRIES = 3;
4186
4398
  INIT_RETRY_DELAY_MS = 1e3;
4187
4399
  _pendingRecords = [];
@@ -5335,6 +5547,17 @@ var init_entity_boost = __esm({
5335
5547
  init_store();
5336
5548
  init_database();
5337
5549
  var RRF_K = 60;
5550
+ function appendMemoryTypeFilter(sql, args, column, options) {
5551
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
5552
+ const uniqueTypes = [...new Set(options.memoryTypes)];
5553
+ sql += ` AND ${column} IN (${uniqueTypes.map(() => "?").join(",")})`;
5554
+ args.push(...uniqueTypes);
5555
+ } else if (options?.memoryType) {
5556
+ sql += ` AND ${column} = ?`;
5557
+ args.push(options.memoryType);
5558
+ }
5559
+ return sql;
5560
+ }
5338
5561
  async function hybridSearch(queryText, agentId, options) {
5339
5562
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
5340
5563
  const config = await loadConfig2();
@@ -5563,6 +5786,7 @@ async function estimateCardinality(agentId, options) {
5563
5786
  sql += ` AND timestamp >= ?`;
5564
5787
  args.push(options.since);
5565
5788
  }
5789
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
5566
5790
  try {
5567
5791
  const result = await client.execute({ sql, args });
5568
5792
  return Number(result.rows[0]?.cnt) || 0;
@@ -5684,10 +5908,7 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
5684
5908
  sql += ` AND m.timestamp >= ?`;
5685
5909
  args.push(options.since);
5686
5910
  }
5687
- if (options?.memoryType) {
5688
- sql += ` AND m.memory_type = ?`;
5689
- args.push(options.memoryType);
5690
- }
5911
+ sql = appendMemoryTypeFilter(sql, args, "m.memory_type", options);
5691
5912
  sql += ` ORDER BY rank LIMIT ?`;
5692
5913
  args.push(limit);
5693
5914
  const result = await client.execute({ sql, args });
@@ -5744,9 +5965,16 @@ async function recentRecords(agentId, options, limit, textFilter) {
5744
5965
  AND timestamp >= ? AND timestamp <= ?
5745
5966
  AND COALESCE(status, 'active') = 'active'
5746
5967
  AND ${options?.includeRaw === false ? "COALESCE(memory_type, 'raw') != 'raw'" : "1 = 1"}
5968
+ ${options?.memoryTypes?.length ? `AND memory_type IN (${options.memoryTypes.map(() => "?").join(",")})` : options?.memoryType ? "AND memory_type = ?" : ""}
5747
5969
  AND COALESCE(confidence, 0.7) >= 0.3
5748
5970
  ORDER BY timestamp DESC LIMIT ?`,
5749
- args: [agentId, windowStart, killedAt, boundarySlots]
5971
+ args: [
5972
+ agentId,
5973
+ windowStart,
5974
+ killedAt,
5975
+ ...options?.memoryTypes?.length ? [...new Set(options.memoryTypes)] : options?.memoryType ? [options.memoryType] : [],
5976
+ boundarySlots
5977
+ ]
5750
5978
  });
5751
5979
  for (const row of boundaryResult.rows) {
5752
5980
  sessionBoundaryMemories.push(rowToMemoryRecord(row));
@@ -5792,10 +6020,7 @@ async function recentRecords(agentId, options, limit, textFilter) {
5792
6020
  sql += ` AND timestamp >= ?`;
5793
6021
  args.push(options.since);
5794
6022
  }
5795
- if (options?.memoryType) {
5796
- sql += ` AND memory_type = ?`;
5797
- args.push(options.memoryType);
5798
- }
6023
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
5799
6024
  if (textFilter) {
5800
6025
  sql += ` AND raw_text LIKE '%' || ? || '%'`;
5801
6026
  args.push(textFilter);
@@ -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);
@@ -2935,7 +2944,7 @@ var init_platform_procedures = __esm({
2935
2944
  title: "Chain of command \u2014 who talks to whom",
2936
2945
  domain: "workflow",
2937
2946
  priority: "p0",
2938
- 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."
2947
+ 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."
2939
2948
  },
2940
2949
  {
2941
2950
  title: "Single dispatch path \u2014 create_task only",
@@ -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;