memorix 1.2.1 → 1.2.2

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 (199) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +14 -2
  3. package/README.zh-CN.md +14 -2
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +15407 -13779
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1321 -529
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +8458 -8087
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +16 -0
  13. package/dist/sdk.d.ts +7 -2
  14. package/dist/sdk.js +1349 -535
  15. package/dist/sdk.js.map +1 -1
  16. package/dist/types.d.ts +49 -1
  17. package/dist/types.js.map +1 -1
  18. package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
  19. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
  20. package/docs/API_REFERENCE.md +24 -4
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/README.md +1 -1
  23. package/docs/dev-log/progress.txt +91 -11
  24. package/package.json +1 -1
  25. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  26. package/src/audit/index.ts +156 -156
  27. package/src/cli/command-guide.ts +192 -0
  28. package/src/cli/commands/audit-list.ts +89 -89
  29. package/src/cli/commands/audit.ts +9 -4
  30. package/src/cli/commands/background.ts +659 -659
  31. package/src/cli/commands/cleanup.ts +5 -1
  32. package/src/cli/commands/codegraph.ts +15 -5
  33. package/src/cli/commands/context.ts +3 -2
  34. package/src/cli/commands/doctor.ts +4 -2
  35. package/src/cli/commands/explain.ts +9 -3
  36. package/src/cli/commands/formation.ts +48 -48
  37. package/src/cli/commands/git-hook-install.ts +111 -111
  38. package/src/cli/commands/handoff.ts +75 -61
  39. package/src/cli/commands/hooks-status.ts +63 -63
  40. package/src/cli/commands/identity.ts +116 -0
  41. package/src/cli/commands/ingest-commit.ts +153 -153
  42. package/src/cli/commands/ingest-image.ts +71 -69
  43. package/src/cli/commands/ingest-log.ts +180 -180
  44. package/src/cli/commands/ingest.ts +44 -44
  45. package/src/cli/commands/integrate-shared.ts +15 -15
  46. package/src/cli/commands/lock.ts +93 -92
  47. package/src/cli/commands/memory.ts +58 -21
  48. package/src/cli/commands/message.ts +123 -118
  49. package/src/cli/commands/operator-shared.ts +98 -3
  50. package/src/cli/commands/poll.ts +74 -64
  51. package/src/cli/commands/purge-all-memory.ts +85 -85
  52. package/src/cli/commands/purge-project-memory.ts +83 -83
  53. package/src/cli/commands/reasoning.ts +135 -121
  54. package/src/cli/commands/retention.ts +9 -4
  55. package/src/cli/commands/serve-http.ts +8 -2
  56. package/src/cli/commands/serve-shared.ts +118 -118
  57. package/src/cli/commands/session.ts +29 -3
  58. package/src/cli/commands/skills.ts +124 -119
  59. package/src/cli/commands/status.ts +4 -3
  60. package/src/cli/commands/task.ts +193 -184
  61. package/src/cli/commands/team.ts +14 -10
  62. package/src/cli/commands/transfer.ts +108 -55
  63. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  64. package/src/cli/identity.ts +89 -0
  65. package/src/cli/index.ts +96 -19
  66. package/src/cli/invocation.ts +115 -0
  67. package/src/cli/tui/ChatView.tsx +234 -234
  68. package/src/cli/tui/CommandBar.tsx +312 -312
  69. package/src/cli/tui/ContextRail.tsx +118 -118
  70. package/src/cli/tui/HeaderBar.tsx +72 -72
  71. package/src/cli/tui/LogoBanner.tsx +51 -51
  72. package/src/cli/tui/Sidebar.tsx +179 -179
  73. package/src/cli/tui/chat-service.ts +41 -18
  74. package/src/cli/tui/data.ts +23 -44
  75. package/src/cli/tui/index.ts +41 -41
  76. package/src/cli/tui/markdown-render.tsx +371 -371
  77. package/src/cli/tui/operator-context.ts +60 -0
  78. package/src/cli/tui/use-mouse.ts +157 -157
  79. package/src/cli/tui/useNavigation.ts +56 -56
  80. package/src/cli/tui/views/MemoryView.tsx +10 -8
  81. package/src/cli/update-checker.ts +211 -211
  82. package/src/cli/version.ts +7 -7
  83. package/src/cli/workbench.ts +1 -1
  84. package/src/codegraph/auto-context.ts +31 -2
  85. package/src/codegraph/context-pack.ts +1 -0
  86. package/src/codegraph/project-context.ts +2 -0
  87. package/src/compact/engine.ts +26 -10
  88. package/src/compact/index-format.ts +25 -2
  89. package/src/compact/token-budget.ts +74 -74
  90. package/src/dashboard/project-classification.ts +64 -64
  91. package/src/dashboard/server.ts +46 -9
  92. package/src/embedding/fastembed-provider.ts +142 -142
  93. package/src/embedding/transformers-provider.ts +111 -111
  94. package/src/git/extractor.ts +209 -209
  95. package/src/git/hooks-path.ts +85 -85
  96. package/src/hooks/admission.ts +117 -0
  97. package/src/hooks/handler.ts +98 -91
  98. package/src/hooks/pattern-detector.ts +173 -173
  99. package/src/hooks/significance-filter.ts +250 -250
  100. package/src/knowledge/context-assembly.ts +97 -0
  101. package/src/knowledge/workset.ts +179 -10
  102. package/src/llm/memory-manager.ts +328 -328
  103. package/src/llm/provider.ts +885 -885
  104. package/src/llm/quality.ts +248 -248
  105. package/src/memory/admission.ts +57 -0
  106. package/src/memory/attribution-guard.ts +249 -249
  107. package/src/memory/consolidation.ts +13 -2
  108. package/src/memory/disclosure-policy.ts +140 -135
  109. package/src/memory/entity-extractor.ts +197 -197
  110. package/src/memory/export-import.ts +11 -3
  111. package/src/memory/formation/evaluate.ts +217 -217
  112. package/src/memory/formation/extract.ts +361 -361
  113. package/src/memory/formation/index.ts +417 -417
  114. package/src/memory/formation/resolve.ts +344 -344
  115. package/src/memory/formation/types.ts +315 -315
  116. package/src/memory/freshness.ts +122 -122
  117. package/src/memory/graph-context.ts +8 -2
  118. package/src/memory/graph.ts +197 -197
  119. package/src/memory/observations.ts +162 -4
  120. package/src/memory/quality-audit.ts +2 -0
  121. package/src/memory/refs.ts +94 -94
  122. package/src/memory/retention.ts +22 -2
  123. package/src/memory/secret-filter.ts +79 -79
  124. package/src/memory/session.ts +5 -2
  125. package/src/memory/visibility.ts +80 -0
  126. package/src/multimodal/image-loader.ts +143 -143
  127. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  128. package/src/orchestrate/adapters/claude.ts +111 -111
  129. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  130. package/src/orchestrate/adapters/codex.ts +41 -41
  131. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  132. package/src/orchestrate/adapters/gemini.ts +42 -42
  133. package/src/orchestrate/adapters/index.ts +73 -73
  134. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  135. package/src/orchestrate/adapters/opencode.ts +47 -47
  136. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  137. package/src/orchestrate/adapters/types.ts +77 -77
  138. package/src/orchestrate/capability-router.ts +284 -284
  139. package/src/orchestrate/context-compact.ts +188 -188
  140. package/src/orchestrate/cost-tracker.ts +219 -219
  141. package/src/orchestrate/error-recovery.ts +191 -191
  142. package/src/orchestrate/evidence.ts +140 -140
  143. package/src/orchestrate/ledger.ts +110 -110
  144. package/src/orchestrate/memorix-bridge.ts +378 -340
  145. package/src/orchestrate/output-budget.ts +80 -80
  146. package/src/orchestrate/permission.ts +152 -152
  147. package/src/orchestrate/pipeline-trace.ts +131 -131
  148. package/src/orchestrate/prompt-builder.ts +155 -155
  149. package/src/orchestrate/ring-buffer.ts +37 -37
  150. package/src/orchestrate/task-graph.ts +389 -389
  151. package/src/orchestrate/worktree.ts +232 -232
  152. package/src/project/aliases.ts +374 -374
  153. package/src/project/detector.ts +268 -268
  154. package/src/rules/adapters/claude-code.ts +99 -99
  155. package/src/rules/adapters/codex.ts +97 -97
  156. package/src/rules/adapters/copilot.ts +124 -124
  157. package/src/rules/adapters/cursor.ts +114 -114
  158. package/src/rules/adapters/kiro.ts +126 -126
  159. package/src/rules/adapters/trae.ts +56 -56
  160. package/src/rules/adapters/windsurf.ts +83 -83
  161. package/src/rules/syncer.ts +235 -235
  162. package/src/runtime/control-plane-maintenance.ts +1 -0
  163. package/src/runtime/isolated-maintenance.ts +1 -0
  164. package/src/runtime/lifecycle.ts +18 -0
  165. package/src/runtime/maintenance-jobs.ts +1 -0
  166. package/src/runtime/maintenance-runner.ts +2 -0
  167. package/src/runtime/project-maintenance.ts +89 -0
  168. package/src/sdk.ts +334 -304
  169. package/src/search/intent-detector.ts +289 -289
  170. package/src/search/query-expansion.ts +52 -52
  171. package/src/server/formation-timeout.ts +27 -27
  172. package/src/server.ts +260 -81
  173. package/src/skills/mini-skills.ts +386 -386
  174. package/src/store/chat-store.ts +119 -119
  175. package/src/store/graph-store.ts +249 -249
  176. package/src/store/mini-skill-store.ts +349 -349
  177. package/src/store/orama-store.ts +61 -6
  178. package/src/store/persistence-json.ts +212 -212
  179. package/src/store/persistence.ts +291 -291
  180. package/src/store/project-affinity.ts +195 -195
  181. package/src/store/sqlite-db.ts +23 -1
  182. package/src/store/sqlite-store.ts +12 -2
  183. package/src/team/event-bus.ts +76 -76
  184. package/src/team/file-locks.ts +173 -173
  185. package/src/team/handoff.ts +168 -161
  186. package/src/team/messages.ts +203 -203
  187. package/src/team/poll.ts +132 -132
  188. package/src/team/tasks.ts +211 -211
  189. package/src/types.ts +51 -0
  190. package/src/wiki/generator.ts +2 -0
  191. package/src/workspace/mcp-adapters/codex.ts +191 -191
  192. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  193. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  194. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  195. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  196. package/src/workspace/mcp-adapters/trae.ts +134 -134
  197. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  198. package/src/workspace/sanitizer.ts +60 -60
  199. package/src/workspace/workflow-sync.ts +131 -131
package/dist/index.js CHANGED
@@ -209,7 +209,11 @@ CREATE TABLE IF NOT EXISTS observations (
209
209
  relatedCommits TEXT,
210
210
  relatedEntities TEXT,
211
211
  sourceDetail TEXT,
212
- valueCategory TEXT
212
+ valueCategory TEXT,
213
+ admissionState TEXT,
214
+ admissionReason TEXT,
215
+ visibility TEXT,
216
+ sharedWithAgentIds TEXT
213
217
  );
214
218
  `;
215
219
  CREATE_MINI_SKILLS_TABLE = `
@@ -632,6 +636,8 @@ CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId)
632
636
  CREATE INDEX IF NOT EXISTS idx_observations_topicKey ON observations(projectId, topicKey);
633
637
  CREATE INDEX IF NOT EXISTS idx_observations_status ON observations(status);
634
638
  CREATE INDEX IF NOT EXISTS idx_observations_project_status_id ON observations(projectId, status, id);
639
+ CREATE INDEX IF NOT EXISTS idx_observations_project_admission ON observations(projectId, status, admissionState, id);
640
+ CREATE INDEX IF NOT EXISTS idx_observations_project_visibility ON observations(projectId, status, visibility, id);
635
641
  CREATE INDEX IF NOT EXISTS idx_mini_skills_projectId ON mini_skills(projectId);
636
642
  CREATE INDEX IF NOT EXISTS idx_sessions_projectId ON sessions(projectId);
637
643
  CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(projectId, status);
@@ -676,6 +682,22 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
676
682
  WHERE status IN ('pending', 'running', 'retry');
677
683
  `;
678
684
  SCHEMA_MIGRATIONS = [
685
+ {
686
+ id: "1.2.2-observation-admission",
687
+ apply: (db2) => {
688
+ addColumnIfMissing(db2, "observations", "admissionState", "admissionState TEXT");
689
+ addColumnIfMissing(db2, "observations", "admissionReason", "admissionReason TEXT");
690
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_observations_project_admission ON observations(projectId, status, admissionState, id)");
691
+ }
692
+ },
693
+ {
694
+ id: "1.2.2-observation-visibility",
695
+ apply: (db2) => {
696
+ addColumnIfMissing(db2, "observations", "visibility", "visibility TEXT");
697
+ addColumnIfMissing(db2, "observations", "sharedWithAgentIds", "sharedWithAgentIds TEXT");
698
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_observations_project_visibility ON observations(projectId, status, visibility, id)");
699
+ }
700
+ },
679
701
  {
680
702
  id: "1.2-code-state-snapshots",
681
703
  apply: (db2) => {
@@ -2118,6 +2140,58 @@ var init_mini_skills = __esm({
2118
2140
  }
2119
2141
  });
2120
2142
 
2143
+ // src/memory/visibility.ts
2144
+ function resolveObservationVisibility(record) {
2145
+ switch (record.visibility) {
2146
+ case "personal":
2147
+ case "team":
2148
+ case "project":
2149
+ return record.visibility;
2150
+ default:
2151
+ return "project";
2152
+ }
2153
+ }
2154
+ function sharedAgentIds(record) {
2155
+ if (Array.isArray(record.sharedWithAgentIds)) return record.sharedWithAgentIds;
2156
+ if (typeof record.sharedWithAgentIds !== "string" || !record.sharedWithAgentIds) return [];
2157
+ try {
2158
+ const parsed = JSON.parse(record.sharedWithAgentIds);
2159
+ return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
2160
+ } catch {
2161
+ return [];
2162
+ }
2163
+ }
2164
+ function canReadObservation(record, reader) {
2165
+ if (!reader) return true;
2166
+ const sameProject = reader.projectId === record.projectId;
2167
+ const visibility = resolveObservationVisibility(record);
2168
+ if (visibility === "project") return !reader.projectId || sameProject;
2169
+ if (!sameProject || !reader.agentId) return false;
2170
+ if (visibility === "team") return reader.isTeamMember === true;
2171
+ return record.createdByAgentId === reader.agentId || sharedAgentIds(record).includes(reader.agentId);
2172
+ }
2173
+ function canManageObservation(record, reader) {
2174
+ if (!reader) return true;
2175
+ if (!reader.projectId || reader.projectId !== record.projectId) return false;
2176
+ switch (resolveObservationVisibility(record)) {
2177
+ case "project":
2178
+ return true;
2179
+ case "team":
2180
+ return reader.isTeamMember === true;
2181
+ case "personal":
2182
+ return Boolean(reader.agentId && record.createdByAgentId === reader.agentId);
2183
+ }
2184
+ }
2185
+ function filterReadableObservations(observations2, reader) {
2186
+ return reader ? observations2.filter((observation) => canReadObservation(observation, reader)) : [...observations2];
2187
+ }
2188
+ var init_visibility = __esm({
2189
+ "src/memory/visibility.ts"() {
2190
+ "use strict";
2191
+ init_esm_shims();
2192
+ }
2193
+ });
2194
+
2121
2195
  // src/config/config-paths.ts
2122
2196
  import { join } from "path";
2123
2197
  function getGlobalConfigTomlPath(homeDir) {
@@ -5345,7 +5419,8 @@ __export(orama_store_exports, {
5345
5419
  makeOramaObservationId: () => makeOramaObservationId,
5346
5420
  removeObservation: () => removeObservation,
5347
5421
  resetDb: () => resetDb,
5348
- searchObservations: () => searchObservations
5422
+ searchObservations: () => searchObservations,
5423
+ updateObservationMetadata: () => updateObservationMetadata
5349
5424
  });
5350
5425
  import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
5351
5426
  function getLastSearchMode(projectId) {
@@ -5423,6 +5498,11 @@ async function initializeDb(options, generation) {
5423
5498
  source: "string",
5424
5499
  sourceDetail: "string",
5425
5500
  valueCategory: "string",
5501
+ admissionState: "string",
5502
+ admissionReason: "string",
5503
+ visibility: "string",
5504
+ createdByAgentId: "string",
5505
+ sharedWithAgentIds: "string",
5426
5506
  documentType: "string",
5427
5507
  knowledgeLayer: "string"
5428
5508
  };
@@ -5561,6 +5641,13 @@ async function hydrateIndex(observations2, options = {}) {
5561
5641
  lastAccessedAt: obs.lastAccessedAt || "",
5562
5642
  status: obs.status ?? "active",
5563
5643
  source: obs.source || "agent",
5644
+ sourceDetail: obs.sourceDetail ?? "",
5645
+ valueCategory: obs.valueCategory ?? "",
5646
+ admissionState: obs.admissionState ?? "",
5647
+ admissionReason: obs.admissionReason ?? "",
5648
+ visibility: obs.visibility ?? "project",
5649
+ createdByAgentId: obs.createdByAgentId ?? "",
5650
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
5564
5651
  documentType: "observation",
5565
5652
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
5566
5653
  ...compatibleVector ? { embedding: compatibleVector } : {}
@@ -5594,6 +5681,16 @@ async function insertObservation(doc) {
5594
5681
  await insert2(database, doc);
5595
5682
  rememberObservationDoc(doc);
5596
5683
  }
5684
+ async function updateObservationMetadata(projectId, observationId2, patch) {
5685
+ const database = await getDb();
5686
+ const id = makeOramaObservationId(projectId, observationId2);
5687
+ const existing = getByID(database, id);
5688
+ if (!existing) return false;
5689
+ const next = { ...existing, ...patch };
5690
+ await update(database, id, next);
5691
+ rememberObservationDoc(next);
5692
+ return true;
5693
+ }
5597
5694
  async function removeObservation(oramaId) {
5598
5695
  const database = await getDb();
5599
5696
  await remove2(database, oramaId);
@@ -5747,7 +5844,7 @@ async function searchObservations(options) {
5747
5844
  if (!projectIds) return true;
5748
5845
  const doc = hit.document;
5749
5846
  return projectIds.includes(doc.projectId);
5750
- }).filter((hit) => {
5847
+ }).filter((hit) => canReadObservation(hit.document, options.reader)).filter((hit) => {
5751
5848
  if (statusFilter === "all") return true;
5752
5849
  const doc = hit.document;
5753
5850
  return (doc.status || "active") === statusFilter;
@@ -5778,6 +5875,8 @@ async function searchObservations(options) {
5778
5875
  source: doc.source || "agent",
5779
5876
  sourceDetail: doc.sourceDetail || void 0,
5780
5877
  valueCategory: doc.valueCategory || void 0,
5878
+ admissionState: doc.admissionState || void 0,
5879
+ visibility: doc.visibility || void 0,
5781
5880
  entityName: doc.entityName || void 0,
5782
5881
  documentType: doc.documentType || "observation",
5783
5882
  knowledgeLayer: doc.knowledgeLayer || "project-truth",
@@ -5821,6 +5920,12 @@ async function searchObservations(options) {
5821
5920
  score: isCommandStyleEntry(entry.title) ? entry.score * 0.3 : entry.score
5822
5921
  }));
5823
5922
  }
5923
+ if (hasQuery) {
5924
+ const qualifiedEntries = intermediate.filter(
5925
+ (entry) => entry.admissionState !== "candidate" && entry.admissionState !== "ephemeral"
5926
+ );
5927
+ if (qualifiedEntries.length > 0) intermediate = qualifiedEntries;
5928
+ }
5824
5929
  if (intentResult?.preferChronological) {
5825
5930
  intermediate.sort((a, b) => new Date(b.rawTime).getTime() - new Date(a.rawTime).getTime());
5826
5931
  } else {
@@ -5964,7 +6069,8 @@ async function searchObservations(options) {
5964
6069
  }
5965
6070
  let entries = intermediate.map(({ rawTime: _, _isCommandLog: _c, ...rest }) => rest);
5966
6071
  for (const hit of results.hits) {
5967
- rememberObservationDoc(hit.document);
6072
+ const doc = hit.document;
6073
+ if (canReadObservation(doc, options.reader)) rememberObservationDoc(doc);
5968
6074
  }
5969
6075
  if (hasQuery && originalQuery) {
5970
6076
  const queryLower = originalQuery.toLowerCase();
@@ -6008,7 +6114,8 @@ async function searchObservations(options) {
6008
6114
  entries = applyTokenBudget(entries, options.maxTokens);
6009
6115
  }
6010
6116
  if (options.trackAccess !== false) {
6011
- const hitDocs = results.hits.map((h) => ({ id: h.id, doc: h.document }));
6117
+ const returnedKeys = new Set(entries.map((entry) => makeEntryKey(entry.projectId, entry.id)));
6118
+ const hitDocs = results.hits.map((hit) => ({ id: hit.id, doc: hit.document })).filter(({ doc }) => returnedKeys.has(makeEntryKey(doc.projectId, doc.observationId)));
6012
6119
  recordAccessBatch(hitDocs).catch(() => {
6013
6120
  });
6014
6121
  }
@@ -6040,11 +6147,11 @@ async function getObservationsByIds(ids, projectId) {
6040
6147
  }
6041
6148
  return results;
6042
6149
  }
6043
- async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3) {
6150
+ async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3, reader) {
6044
6151
  const { withFreshIndex: withFreshIndex2 } = await Promise.resolve().then(() => (init_freshness(), freshness_exports));
6045
6152
  const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
6046
6153
  const rawObs = await withFreshIndex2(() => getAllObservations2());
6047
- const allObs = projectId ? rawObs.filter((o) => o.projectId === projectId) : rawObs;
6154
+ const allObs = (projectId ? rawObs.filter((o) => o.projectId === projectId) : rawObs).filter((observation) => canReadObservation(observation, reader));
6048
6155
  const sorted = allObs.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
6049
6156
  const anchorIndex = sorted.findIndex((o) => o.id === anchorId);
6050
6157
  if (anchorIndex === -1) {
@@ -6061,7 +6168,9 @@ async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3)
6061
6168
  tokens: obs.tokens,
6062
6169
  source: obs.source || void 0,
6063
6170
  sourceDetail: obs.sourceDetail || void 0,
6064
- valueCategory: obs.valueCategory || void 0
6171
+ valueCategory: obs.valueCategory || void 0,
6172
+ admissionState: obs.admissionState || void 0,
6173
+ visibility: obs.visibility || void 0
6065
6174
  };
6066
6175
  };
6067
6176
  const before = sorted.slice(Math.max(0, anchorIndex - depthBefore), anchorIndex).map(toIndexEntry);
@@ -6129,6 +6238,7 @@ var init_orama_store = __esm({
6129
6238
  init_esm_shims();
6130
6239
  init_types();
6131
6240
  init_mini_skills();
6241
+ init_visibility();
6132
6242
  init_provider();
6133
6243
  init_project_affinity();
6134
6244
  init_intent_detector();
@@ -6185,6 +6295,10 @@ function obsToRow(obs) {
6185
6295
  relatedEntities: obs.relatedEntities ? JSON.stringify(obs.relatedEntities) : null,
6186
6296
  sourceDetail: obs.sourceDetail ?? null,
6187
6297
  valueCategory: obs.valueCategory ?? null,
6298
+ admissionState: obs.admissionState ?? null,
6299
+ admissionReason: obs.admissionReason ?? null,
6300
+ visibility: obs.visibility ?? null,
6301
+ sharedWithAgentIds: obs.sharedWithAgentIds ? JSON.stringify(obs.sharedWithAgentIds) : null,
6188
6302
  createdByAgentId: obs.createdByAgentId ?? null,
6189
6303
  writeGeneration: obs.writeGeneration ?? 0
6190
6304
  };
@@ -6215,6 +6329,10 @@ function rowToObs(row) {
6215
6329
  ...row.relatedEntities ? { relatedEntities: safeJsonParse3(row.relatedEntities, []) } : {},
6216
6330
  ...row.sourceDetail ? { sourceDetail: row.sourceDetail } : {},
6217
6331
  ...row.valueCategory ? { valueCategory: row.valueCategory } : {},
6332
+ ...row.admissionState ? { admissionState: row.admissionState } : {},
6333
+ ...row.admissionReason ? { admissionReason: row.admissionReason } : {},
6334
+ ...row.visibility ? { visibility: row.visibility } : {},
6335
+ ...row.sharedWithAgentIds ? { sharedWithAgentIds: safeJsonParse3(row.sharedWithAgentIds, []) } : {},
6218
6336
  ...row.createdByAgentId ? { createdByAgentId: row.createdByAgentId } : {},
6219
6337
  ...row.writeGeneration ? { writeGeneration: row.writeGeneration } : {}
6220
6338
  };
@@ -6266,12 +6384,14 @@ var init_sqlite_store = __esm({
6266
6384
  (id, entityName, type, title, narrative, facts, filesModified, concepts, tokens,
6267
6385
  createdAt, updatedAt, projectId, hasCausalLanguage, topicKey, revisionCount,
6268
6386
  sessionId, status, progress, source, commitHash, relatedCommits, relatedEntities,
6269
- sourceDetail, valueCategory, createdByAgentId, writeGeneration)
6387
+ sourceDetail, valueCategory, admissionState, admissionReason, visibility, sharedWithAgentIds,
6388
+ createdByAgentId, writeGeneration)
6270
6389
  VALUES
6271
6390
  (@id, @entityName, @type, @title, @narrative, @facts, @filesModified, @concepts, @tokens,
6272
6391
  @createdAt, @updatedAt, @projectId, @hasCausalLanguage, @topicKey, @revisionCount,
6273
6392
  @sessionId, @status, @progress, @source, @commitHash, @relatedCommits, @relatedEntities,
6274
- @sourceDetail, @valueCategory, @createdByAgentId, @writeGeneration)
6393
+ @sourceDetail, @valueCategory, @admissionState, @admissionReason, @visibility, @sharedWithAgentIds,
6394
+ @createdByAgentId, @writeGeneration)
6275
6395
  `);
6276
6396
  this.stmtUpdate = this.stmtInsert;
6277
6397
  this.stmtSetStatus = this.db.prepare(`UPDATE observations SET status = ? WHERE id = ?`);
@@ -6823,6 +6943,7 @@ var init_maintenance_jobs = __esm({
6823
6943
  "retention-archive",
6824
6944
  "consolidation",
6825
6945
  "codegraph-refresh",
6946
+ "observation-qualify",
6826
6947
  "claim-derive",
6827
6948
  "claim-requalification",
6828
6949
  "knowledge-compile",
@@ -7181,7 +7302,8 @@ __export(lifecycle_exports, {
7181
7302
  enqueueClaimDerivation: () => enqueueClaimDerivation,
7182
7303
  enqueueClaimRequalification: () => enqueueClaimRequalification,
7183
7304
  enqueueCodegraphRefresh: () => enqueueCodegraphRefresh,
7184
- enqueueKnowledgeFollowups: () => enqueueKnowledgeFollowups
7305
+ enqueueKnowledgeFollowups: () => enqueueKnowledgeFollowups,
7306
+ enqueueObservationQualification: () => enqueueObservationQualification
7185
7307
  });
7186
7308
  function queueFor(input) {
7187
7309
  return input.queue ?? new MaintenanceJobStore(input.dataDir);
@@ -7216,6 +7338,14 @@ function enqueueClaimDerivation(input) {
7216
7338
  payload: { observationId: input.observationId }
7217
7339
  });
7218
7340
  }
7341
+ function enqueueObservationQualification(input) {
7342
+ queueFor(input).enqueue({
7343
+ projectId: input.projectId,
7344
+ kind: "observation-qualify",
7345
+ dedupeKey: "observation-qualify",
7346
+ payload: { source: input.source, limit: 100 }
7347
+ });
7348
+ }
7219
7349
  function enqueueKnowledgeFollowups(input) {
7220
7350
  const queue = queueFor(input);
7221
7351
  const payload = {
@@ -8080,6 +8210,7 @@ __export(observations_exports, {
8080
8210
  resolveObservations: () => resolveObservations,
8081
8211
  storeObservation: () => storeObservation,
8082
8212
  suggestTopicKey: () => suggestTopicKey,
8213
+ updateObservationAdmission: () => updateObservationAdmission,
8083
8214
  withFreshObservations: () => withFreshObservations
8084
8215
  });
8085
8216
  function logEmbeddingFailureOnce(key, message) {
@@ -8146,6 +8277,18 @@ function queueClaimDerivation(observation) {
8146
8277
  } catch {
8147
8278
  }
8148
8279
  }
8280
+ function queueObservationQualification(observation) {
8281
+ const dataDir = projectDir;
8282
+ if (!dataDir || observation.admissionState !== "candidate") return;
8283
+ try {
8284
+ enqueueObservationQualification({
8285
+ dataDir,
8286
+ projectId: observation.projectId,
8287
+ source: "automatic-capture:" + observation.id
8288
+ });
8289
+ } catch {
8290
+ }
8291
+ }
8149
8292
  function isVectorCompatibleWithCurrentIndex(embedding) {
8150
8293
  if (!embedding) return false;
8151
8294
  const vectorDimensions = getVectorDimensions();
@@ -8201,6 +8344,9 @@ async function storeObservation(input) {
8201
8344
  (o) => o.topicKey === input.topicKey && o.projectId === input.projectId
8202
8345
  );
8203
8346
  if (existing) {
8347
+ if (input.visibilityReader && !canManageObservation(existing, input.visibilityReader)) {
8348
+ throw new Error("Cannot update a memory outside this session's write scope.");
8349
+ }
8204
8350
  return { observation: await upsertObservation(existing, input, now3), upserted: true };
8205
8351
  }
8206
8352
  }
@@ -8237,6 +8383,9 @@ async function storeObservation(input) {
8237
8383
  if (input.topicKey) {
8238
8384
  const diskExisting = await tx.findByTopicKey(input.projectId, input.topicKey);
8239
8385
  if (diskExisting) {
8386
+ if (input.visibilityReader && !canManageObservation(diskExisting, input.visibilityReader)) {
8387
+ throw new Error("Cannot update a memory outside this session's write scope.");
8388
+ }
8240
8389
  upsertedInsideLock = true;
8241
8390
  observation = diskExisting;
8242
8391
  return;
@@ -8267,6 +8416,10 @@ async function storeObservation(input) {
8267
8416
  relatedEntities: input.relatedEntities,
8268
8417
  sourceDetail: input.sourceDetail,
8269
8418
  valueCategory: input.valueCategory,
8419
+ admissionState: input.admissionState,
8420
+ admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : void 0,
8421
+ visibility: input.visibility ?? "project",
8422
+ sharedWithAgentIds: input.sharedWithAgentIds,
8270
8423
  createdByAgentId: input.createdByAgentId,
8271
8424
  // Predict the generation that atomic() will commit after this callback.
8272
8425
  // bumpGeneration() runs after fn(tx) returns, incrementing by 1.
@@ -8313,6 +8466,10 @@ async function storeObservation(input) {
8313
8466
  relatedEntities: input.relatedEntities,
8314
8467
  sourceDetail: input.sourceDetail,
8315
8468
  valueCategory: input.valueCategory,
8469
+ admissionState: input.admissionState,
8470
+ admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : void 0,
8471
+ visibility: input.visibility ?? "project",
8472
+ sharedWithAgentIds: input.sharedWithAgentIds,
8316
8473
  createdByAgentId: input.createdByAgentId,
8317
8474
  writeGeneration: 0
8318
8475
  };
@@ -8336,7 +8493,12 @@ async function storeObservation(input) {
8336
8493
  status: "active",
8337
8494
  source: input.source ?? "agent",
8338
8495
  sourceDetail: input.sourceDetail ?? "",
8339
- valueCategory: input.valueCategory ?? ""
8496
+ valueCategory: input.valueCategory ?? "",
8497
+ admissionState: input.admissionState ?? "",
8498
+ admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : "",
8499
+ visibility: input.visibility ?? "project",
8500
+ createdByAgentId: input.createdByAgentId ?? "",
8501
+ sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? [])
8340
8502
  };
8341
8503
  await insertObservation(doc);
8342
8504
  };
@@ -8345,7 +8507,10 @@ async function storeObservation(input) {
8345
8507
  return { observation: await upsertObservation(observation, input, now3), upserted: true };
8346
8508
  }
8347
8509
  await bindObservationCodeRefsBestEffort(observation);
8348
- queueClaimDerivation(observation);
8510
+ queueObservationQualification(observation);
8511
+ if (resolveObservationVisibility(observation) === "project") {
8512
+ queueClaimDerivation(observation);
8513
+ }
8349
8514
  const obsId = observation.id;
8350
8515
  vectorMissingIds.add(obsId);
8351
8516
  const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
@@ -8418,6 +8583,10 @@ async function upsertObservation(existing, input, now3) {
8418
8583
  if (input.progress) existing.progress = input.progress;
8419
8584
  if (input.sourceDetail !== void 0) existing.sourceDetail = input.sourceDetail;
8420
8585
  if (input.valueCategory !== void 0) existing.valueCategory = input.valueCategory;
8586
+ if (input.admissionState !== void 0) existing.admissionState = input.admissionState;
8587
+ if (input.admissionReason !== void 0) existing.admissionReason = sanitizeCredentials(input.admissionReason);
8588
+ if (input.visibility !== void 0) existing.visibility = input.visibility;
8589
+ if (input.sharedWithAgentIds !== void 0) existing.sharedWithAgentIds = input.sharedWithAgentIds;
8421
8590
  const doc = {
8422
8591
  id: makeOramaObservationId(existing.projectId, existing.id),
8423
8592
  observationId: existing.id,
@@ -8436,7 +8605,12 @@ async function upsertObservation(existing, input, now3) {
8436
8605
  status: "active",
8437
8606
  source: existing.source ?? "agent",
8438
8607
  sourceDetail: existing.sourceDetail ?? "",
8439
- valueCategory: existing.valueCategory ?? ""
8608
+ valueCategory: existing.valueCategory ?? "",
8609
+ admissionState: existing.admissionState ?? "",
8610
+ admissionReason: existing.admissionReason ?? "",
8611
+ visibility: existing.visibility ?? "project",
8612
+ createdByAgentId: existing.createdByAgentId ?? "",
8613
+ sharedWithAgentIds: JSON.stringify(existing.sharedWithAgentIds ?? [])
8440
8614
  };
8441
8615
  const oramaId = makeOramaObservationId(existing.projectId, existing.id);
8442
8616
  try {
@@ -8459,7 +8633,10 @@ async function upsertObservation(existing, input, now3) {
8459
8633
  await store.update(existing);
8460
8634
  }
8461
8635
  await bindObservationCodeRefsBestEffort(existing);
8462
- queueClaimDerivation(existing);
8636
+ queueObservationQualification(existing);
8637
+ if (resolveObservationVisibility(existing) === "project") {
8638
+ queueClaimDerivation(existing);
8639
+ }
8463
8640
  const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
8464
8641
  const obsId = existing.id;
8465
8642
  vectorMissingIds.add(obsId);
@@ -8498,6 +8675,56 @@ async function upsertObservation(existing, input, now3) {
8498
8675
  function getObservation(id, projectId) {
8499
8676
  return observations.find((o) => o.id === id && (projectId ? o.projectId === projectId : true));
8500
8677
  }
8678
+ async function updateObservationAdmission(input) {
8679
+ await ensureFreshObservations();
8680
+ const cached = observations.find(
8681
+ (observation) => observation.id === input.observationId && (!input.projectId || observation.projectId === input.projectId)
8682
+ );
8683
+ if (!cached) return void 0;
8684
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
8685
+ const admissionReason = sanitizeCredentials(input.admissionReason).slice(0, 240);
8686
+ const apply = (current) => {
8687
+ if (input.projectId && current.projectId !== input.projectId) return void 0;
8688
+ if (input.expectedState && current.admissionState !== input.expectedState) return void 0;
8689
+ return {
8690
+ ...current,
8691
+ admissionState: input.admissionState,
8692
+ admissionReason,
8693
+ ...input.visibility !== void 0 ? { visibility: input.visibility } : {},
8694
+ updatedAt: now3
8695
+ };
8696
+ };
8697
+ let updated;
8698
+ if (projectDir) {
8699
+ const store = getObservationStore();
8700
+ await store.atomic(async (tx) => {
8701
+ const current = await tx.getById(input.observationId);
8702
+ if (!current) return;
8703
+ const next = apply(current);
8704
+ if (!next) return;
8705
+ await tx.update(next);
8706
+ updated = next;
8707
+ });
8708
+ } else {
8709
+ updated = apply(cached);
8710
+ }
8711
+ if (!updated) return void 0;
8712
+ observations = observations.map(
8713
+ (observation) => observation.id === updated.id && observation.projectId === updated.projectId ? updated : observation
8714
+ );
8715
+ try {
8716
+ await updateObservationMetadata(updated.projectId, updated.id, {
8717
+ admissionState: updated.admissionState,
8718
+ admissionReason: updated.admissionReason,
8719
+ ...updated.visibility !== void 0 ? { visibility: updated.visibility } : {}
8720
+ });
8721
+ } catch {
8722
+ }
8723
+ if (updated.admissionState === "qualified" && resolveObservationVisibility(updated) === "project") {
8724
+ queueClaimDerivation(updated);
8725
+ }
8726
+ return updated;
8727
+ }
8501
8728
  async function resolveObservations(ids, status = "resolved") {
8502
8729
  const resolved = [];
8503
8730
  const notFound = [];
@@ -8537,7 +8764,12 @@ async function resolveObservations(ids, status = "resolved") {
8537
8764
  status,
8538
8765
  source: obs.source ?? "agent",
8539
8766
  sourceDetail: obs.sourceDetail ?? "",
8540
- valueCategory: obs.valueCategory ?? ""
8767
+ valueCategory: obs.valueCategory ?? "",
8768
+ admissionState: obs.admissionState ?? "",
8769
+ admissionReason: obs.admissionReason ?? "",
8770
+ visibility: obs.visibility ?? "project",
8771
+ createdByAgentId: obs.createdByAgentId ?? "",
8772
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
8541
8773
  };
8542
8774
  await insertObservation(doc);
8543
8775
  const obsId = obs.id;
@@ -8667,6 +8899,11 @@ async function reindexObservations() {
8667
8899
  source: obs.source ?? "agent",
8668
8900
  sourceDetail: obs.sourceDetail ?? "",
8669
8901
  valueCategory: obs.valueCategory ?? "",
8902
+ admissionState: obs.admissionState ?? "",
8903
+ admissionReason: obs.admissionReason ?? "",
8904
+ visibility: obs.visibility ?? "project",
8905
+ createdByAgentId: obs.createdByAgentId ?? "",
8906
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
8670
8907
  ...compatibleEmbedding ? { embedding: compatibleEmbedding } : {}
8671
8908
  };
8672
8909
  await insertObservation(doc);
@@ -8739,6 +8976,7 @@ async function probeSearchIndex(projectId) {
8739
8976
  await searchObservations({
8740
8977
  query: "semantic memory retrieval status",
8741
8978
  projectId,
8979
+ reader: { projectId },
8742
8980
  limit: 1,
8743
8981
  status: "all",
8744
8982
  trackAccess: false
@@ -8804,6 +9042,11 @@ async function backfillVectorEmbeddings(options = {}) {
8804
9042
  source: obs.source ?? "agent",
8805
9043
  sourceDetail: obs.sourceDetail ?? "",
8806
9044
  valueCategory: obs.valueCategory ?? "",
9045
+ admissionState: obs.admissionState ?? "",
9046
+ admissionReason: obs.admissionReason ?? "",
9047
+ visibility: obs.visibility ?? "project",
9048
+ createdByAgentId: obs.createdByAgentId ?? "",
9049
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
8807
9050
  embedding
8808
9051
  };
8809
9052
  await insertObservation(doc);
@@ -8845,6 +9088,7 @@ var init_observations = __esm({
8845
9088
  init_provider();
8846
9089
  init_secret_filter();
8847
9090
  init_lifecycle();
9091
+ init_visibility();
8848
9092
  observations = [];
8849
9093
  nextId = 1;
8850
9094
  projectDir = null;
@@ -9124,8 +9368,9 @@ function resolveSourceDetail(sourceDetail, source) {
9124
9368
  return void 0;
9125
9369
  }
9126
9370
  function classifyLayer(fields) {
9127
- const { valueCategory } = fields;
9371
+ const { valueCategory, admissionState } = fields;
9128
9372
  const sd = resolveSourceDetail(fields.sourceDetail, fields.source);
9373
+ if (admissionState === "candidate" || admissionState === "ephemeral") return "L1";
9129
9374
  if (valueCategory === "core") return "L2";
9130
9375
  if (sd === "hook") return "L1";
9131
9376
  if (sd === "git-ingest") return "L3";
@@ -9183,6 +9428,7 @@ function getEffectiveRetentionDays(doc) {
9183
9428
  }
9184
9429
  function isImmune(doc) {
9185
9430
  if (doc.type === "probe") return false;
9431
+ if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return false;
9186
9432
  if (doc.valueCategory === "core") return true;
9187
9433
  const importance = getImportanceLevel(doc);
9188
9434
  if (importance === "critical") return true;
@@ -9192,6 +9438,7 @@ function isImmune(doc) {
9192
9438
  }
9193
9439
  function getImmunityReason(doc) {
9194
9440
  if (doc.type === "probe") return null;
9441
+ if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return null;
9195
9442
  if (doc.valueCategory === "core") return "core valueCategory (formation-classified)";
9196
9443
  const importance = getImportanceLevel(doc);
9197
9444
  if (importance === "critical") return "critical importance";
@@ -9324,7 +9571,9 @@ function toRetentionDocument(obs, accessMap) {
9324
9571
  status: obs.status ?? "active",
9325
9572
  source: obs.source ?? "agent",
9326
9573
  sourceDetail: obs.sourceDetail ?? "",
9327
- valueCategory: obs.valueCategory ?? ""
9574
+ valueCategory: obs.valueCategory ?? "",
9575
+ admissionState: obs.admissionState ?? "",
9576
+ admissionReason: obs.admissionReason ?? ""
9328
9577
  };
9329
9578
  }
9330
9579
  async function archiveExpiredBatch(_projectDir, options) {
@@ -9338,7 +9587,7 @@ async function archiveExpiredBatch(_projectDir, options) {
9338
9587
  });
9339
9588
  const hasMore = page.length > limit;
9340
9589
  const scanned = hasMore ? page.slice(0, limit) : page;
9341
- const candidateIds = scanned.filter((observation) => getRetentionZone(
9590
+ const candidateIds = scanned.filter((observation) => !options.reader || canManageObservation(observation, options.reader)).filter((observation) => getRetentionZone(
9342
9591
  toRetentionDocument(observation, options.accessMap),
9343
9592
  options.referenceTime
9344
9593
  ) === "archive-candidate").map((observation) => observation.id);
@@ -9351,7 +9600,7 @@ async function archiveExpiredBatch(_projectDir, options) {
9351
9600
  const nextCursor = hasMore && scanned.length > 0 ? scanned[scanned.length - 1].id : void 0;
9352
9601
  return nextCursor === void 0 ? { archived, scanned: scanned.length } : { archived, scanned: scanned.length, nextCursor };
9353
9602
  }
9354
- async function archiveExpired(projectDir2, referenceTime, accessMap, projectId) {
9603
+ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId, reader) {
9355
9604
  const store = getObservationStore();
9356
9605
  if (projectId) {
9357
9606
  let afterId;
@@ -9361,12 +9610,14 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId)
9361
9610
  projectId,
9362
9611
  afterId,
9363
9612
  referenceTime,
9364
- accessMap
9613
+ accessMap,
9614
+ reader
9365
9615
  });
9366
9616
  archived += batch.archived;
9367
9617
  afterId = batch.nextCursor;
9368
9618
  } while (afterId !== void 0);
9369
- const remaining = await store.countByProject(projectId, { status: "active" });
9619
+ const remainingObservations = await store.loadByProject(projectId, { status: "active" });
9620
+ const remaining = reader ? remainingObservations.filter((observation) => canManageObservation(observation, reader)).length : remainingObservations.length;
9370
9621
  return { archived, remaining };
9371
9622
  }
9372
9623
  return await store.atomic(async (tx) => {
@@ -9396,6 +9647,7 @@ var init_retention = __esm({
9396
9647
  "use strict";
9397
9648
  init_esm_shims();
9398
9649
  init_obs_store();
9650
+ init_visibility();
9399
9651
  RETENTION_DAYS = {
9400
9652
  critical: 365,
9401
9653
  high: 180,
@@ -9431,6 +9683,49 @@ var init_retention = __esm({
9431
9683
  }
9432
9684
  });
9433
9685
 
9686
+ // src/memory/admission.ts
9687
+ var admission_exports = {};
9688
+ __export(admission_exports, {
9689
+ isCandidateObservation: () => isCandidateObservation,
9690
+ isEligibleForAutomaticDelivery: () => isEligibleForAutomaticDelivery,
9691
+ isEligibleForKnowledgePromotion: () => isEligibleForKnowledgePromotion,
9692
+ qualifyCandidateFromCurrentCode: () => qualifyCandidateFromCurrentCode
9693
+ });
9694
+ function isEligibleForAutomaticDelivery(observation) {
9695
+ return observation.admissionState !== "ephemeral" && observation.admissionState !== "candidate";
9696
+ }
9697
+ function isCandidateObservation(observation) {
9698
+ return observation.admissionState === "candidate";
9699
+ }
9700
+ function isEligibleForKnowledgePromotion(observation) {
9701
+ return isEligibleForAutomaticDelivery(observation) && observation.valueCategory !== "ephemeral" && resolveObservationVisibility(observation) === "project";
9702
+ }
9703
+ function qualifyCandidateFromCurrentCode(input) {
9704
+ if (!isCandidateObservation(input.observation)) return void 0;
9705
+ if (input.observation.valueCategory === "ephemeral") return void 0;
9706
+ if (input.currentCodeReferenceCount <= 0) return void 0;
9707
+ const typeLabel = DURABLE_AUTOMATIC_TYPES.has(input.observation.type) ? "high-value automatic record" : "automatic record";
9708
+ return {
9709
+ admissionState: "qualified",
9710
+ admissionReason: `${typeLabel} qualified against ${input.currentCodeReferenceCount} current Code Memory reference(s)`
9711
+ };
9712
+ }
9713
+ var DURABLE_AUTOMATIC_TYPES;
9714
+ var init_admission = __esm({
9715
+ "src/memory/admission.ts"() {
9716
+ "use strict";
9717
+ init_esm_shims();
9718
+ init_visibility();
9719
+ DURABLE_AUTOMATIC_TYPES = /* @__PURE__ */ new Set([
9720
+ "decision",
9721
+ "gotcha",
9722
+ "problem-solution",
9723
+ "trade-off",
9724
+ "why-it-exists"
9725
+ ]);
9726
+ }
9727
+ });
9728
+
9434
9729
  // src/workspace/workflow-sync.ts
9435
9730
  import matter9 from "gray-matter";
9436
9731
  var WorkflowSyncer;
@@ -10831,6 +11126,7 @@ var init_isolated_maintenance = __esm({
10831
11126
  "retention-archive",
10832
11127
  "consolidation",
10833
11128
  "codegraph-refresh",
11129
+ "observation-qualify",
10834
11130
  "claim-derive",
10835
11131
  "claim-requalification",
10836
11132
  "knowledge-compile",
@@ -11266,9 +11562,10 @@ async function loadConsolidationPage(projectId, options) {
11266
11562
  return hasMore && observations2.length > 0 ? { observations: observations2, nextCursor: observations2[observations2.length - 1].id } : { observations: observations2 };
11267
11563
  }
11268
11564
  function findClusters(observations2, threshold) {
11269
- if (observations2.length < MIN_CLUSTER_SIZE) return [];
11565
+ const eligible = observations2.filter(isEligibleForAutomaticDelivery).filter((observation) => resolveObservationVisibility(observation) === "project");
11566
+ if (eligible.length < MIN_CLUSTER_SIZE) return [];
11270
11567
  const groups = /* @__PURE__ */ new Map();
11271
- for (const obs of observations2) {
11568
+ for (const obs of eligible) {
11272
11569
  const key = `${obs.entityName}::${obs.type}`;
11273
11570
  const group = groups.get(key) ?? [];
11274
11571
  group.push(obs);
@@ -11405,6 +11702,8 @@ var init_consolidation = __esm({
11405
11702
  "use strict";
11406
11703
  init_esm_shims();
11407
11704
  init_obs_store();
11705
+ init_admission();
11706
+ init_visibility();
11408
11707
  DEFAULT_SIMILARITY_THRESHOLD = 0.45;
11409
11708
  HIGH_VALUE_SIMILARITY_THRESHOLD = 0.85;
11410
11709
  HIGH_VALUE_TYPES = /* @__PURE__ */ new Set(["gotcha", "decision", "trade-off", "reasoning", "problem-solution"]);
@@ -14423,6 +14722,15 @@ function claimDerivationCursor(payload) {
14423
14722
  const value = payload.cursor;
14424
14723
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
14425
14724
  }
14725
+ function observationQualificationBatchSize(payload) {
14726
+ const value = payload.limit;
14727
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE;
14728
+ return Math.min(500, Math.max(1, Math.floor(value)));
14729
+ }
14730
+ function observationQualificationCursor(payload) {
14731
+ const value = payload.cursor;
14732
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
14733
+ }
14426
14734
  function observationId(payload) {
14427
14735
  const value = payload.observationId;
14428
14736
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
@@ -14601,6 +14909,71 @@ function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot, op
14601
14909
  ...snapshot?.id ? { snapshotId: snapshot.id } : {},
14602
14910
  ...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
14603
14911
  });
14912
+ enqueueObservationQualification({
14913
+ projectId,
14914
+ dataDir: projectDir2,
14915
+ source: "codegraph-refresh",
14916
+ ...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
14917
+ });
14918
+ return { action: "complete" };
14919
+ }
14920
+ if (job.kind === "observation-qualify") {
14921
+ const [
14922
+ { CodeGraphStore: CodeGraphStore2 },
14923
+ { bindObservationToCode: bindObservationToCode2 },
14924
+ { getObservationStore: getObservationStore2 },
14925
+ { qualifyCandidateFromCurrentCode: qualifyCandidateFromCurrentCode2 },
14926
+ { updateObservationAdmission: updateObservationAdmission2 }
14927
+ ] = await Promise.all([
14928
+ Promise.resolve().then(() => (init_store(), store_exports)),
14929
+ Promise.resolve().then(() => (init_binder(), binder_exports)),
14930
+ Promise.resolve().then(() => (init_obs_store(), obs_store_exports)),
14931
+ Promise.resolve().then(() => (init_admission(), admission_exports)),
14932
+ Promise.resolve().then(() => (init_observations(), observations_exports))
14933
+ ]);
14934
+ const codeStore = new CodeGraphStore2();
14935
+ await codeStore.init(projectDir2);
14936
+ const limit = observationQualificationBatchSize(job.payload);
14937
+ const page = await getObservationStore2().loadByProject(projectId, {
14938
+ status: "active",
14939
+ afterId: observationQualificationCursor(job.payload),
14940
+ limit: limit + 1
14941
+ });
14942
+ const hasMore = page.length > limit;
14943
+ const observations2 = hasMore ? page.slice(0, limit) : page;
14944
+ const indexedAtMs = Date.parse(codeStore.status(projectId).indexedAt ?? "");
14945
+ for (const observation of observations2) {
14946
+ if (observation.admissionState !== "candidate") continue;
14947
+ if (!Number.isFinite(indexedAtMs) || indexedAtMs < Date.parse(observation.createdAt)) continue;
14948
+ await bindObservationToCode2(codeStore, observation);
14949
+ const currentCodeReferenceCount = codeStore.listObservationRefs(projectId, observation.id).filter((reference) => reference.status === "current").length;
14950
+ const qualification = qualifyCandidateFromCurrentCode2({
14951
+ observation,
14952
+ currentCodeReferenceCount
14953
+ });
14954
+ if (!qualification) continue;
14955
+ await updateObservationAdmission2({
14956
+ observationId: observation.id,
14957
+ projectId,
14958
+ expectedState: "candidate",
14959
+ // Automatic capture earns project visibility only after a current
14960
+ // Code Memory link exists. Explicit/private records retain scope.
14961
+ ...observation.sourceDetail === "hook" ? { visibility: "project" } : {},
14962
+ ...qualification
14963
+ });
14964
+ }
14965
+ if (hasMore && observations2.length > 0) {
14966
+ return {
14967
+ action: "reschedule",
14968
+ delayMs: 0,
14969
+ resetAttempts: true,
14970
+ payload: {
14971
+ ...job.payload,
14972
+ cursor: observations2[observations2.length - 1].id,
14973
+ limit
14974
+ }
14975
+ };
14976
+ }
14604
14977
  return { action: "complete" };
14605
14978
  }
14606
14979
  if (job.kind === "claim-derive") {
@@ -14766,7 +15139,7 @@ function createProjectMaintenanceDispatcher(projectId, projectDir2, projectRoot,
14766
15139
  return isolatedRunner({ job, projectRoot, dataDir: projectDir2 });
14767
15140
  };
14768
15141
  }
14769
- var DEFAULT_VECTOR_BATCH_SIZE, DEFAULT_RETENTION_BATCH_SIZE, DEFAULT_CONSOLIDATION_BATCH_SIZE, DEFAULT_CODEGRAPH_MAX_FILES, DEFAULT_CLAIM_DERIVATION_BATCH_SIZE, VECTOR_RETRY_DELAY_MS, DEDUP_PER_PAIR_TIMEOUT_MS;
15142
+ var DEFAULT_VECTOR_BATCH_SIZE, DEFAULT_RETENTION_BATCH_SIZE, DEFAULT_CONSOLIDATION_BATCH_SIZE, DEFAULT_CODEGRAPH_MAX_FILES, DEFAULT_CLAIM_DERIVATION_BATCH_SIZE, DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE, VECTOR_RETRY_DELAY_MS, DEDUP_PER_PAIR_TIMEOUT_MS;
14770
15143
  var init_project_maintenance = __esm({
14771
15144
  "src/runtime/project-maintenance.ts"() {
14772
15145
  "use strict";
@@ -14778,6 +15151,7 @@ var init_project_maintenance = __esm({
14778
15151
  DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
14779
15152
  DEFAULT_CODEGRAPH_MAX_FILES = 5e3;
14780
15153
  DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
15154
+ DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE = 100;
14781
15155
  VECTOR_RETRY_DELAY_MS = 5e3;
14782
15156
  DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
14783
15157
  }
@@ -15019,9 +15393,9 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
15019
15393
  return true;
15020
15394
  });
15021
15395
  })() : l2Scored).slice(0, 5).map(({ obs }) => obs);
15022
- const l1HookObs = projectObs.filter((obs) => classifyLayer(obs) === "L1").sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 3);
15396
+ const l1HookObs = projectObs.filter((obs) => isEligibleForAutomaticDelivery(obs) && classifyLayer(obs) === "L1").sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 3);
15023
15397
  const l3GitCount = projectObs.filter((obs) => classifyLayer(obs) === "L3").length;
15024
- const totalHookCount = projectObs.filter((obs) => classifyLayer(obs) === "L1").length;
15398
+ const totalHookCount = projectObs.filter((obs) => isEligibleForAutomaticDelivery(obs) && classifyLayer(obs) === "L1").length;
15025
15399
  const activeEntities = [
15026
15400
  ...new Set(l2Obs.map((o) => o.entityName).filter((n) => !!n && n.trim().length > 0))
15027
15401
  ].slice(0, 5);
@@ -15147,6 +15521,7 @@ var init_session = __esm({
15147
15521
  "src/memory/session.ts"() {
15148
15522
  "use strict";
15149
15523
  init_esm_shims();
15524
+ init_admission();
15150
15525
  init_disclosure_policy();
15151
15526
  init_aliases();
15152
15527
  init_obs_store();
@@ -15299,27 +15674,80 @@ function workflowOutput(selection) {
15299
15674
  cautions: selection.cautions.map((caution) => short(caution, 22))
15300
15675
  };
15301
15676
  }
15302
- function appendLine(lines, candidate, maxTokens, omitted, omittedKind) {
15677
+ function appendLine(lines, candidate, maxTokens, omitted, omittedKind, selected, receiptSelection) {
15303
15678
  const next = lines.length ? lines.join("\n") + "\n" + candidate : candidate;
15304
15679
  if (countTextTokens(next) <= maxTokens) {
15305
15680
  lines.push(candidate);
15681
+ if (receiptSelection) selected?.push(receiptSelection);
15306
15682
  return true;
15307
15683
  }
15308
15684
  omitted.push(omittedKind);
15309
15685
  return false;
15310
15686
  }
15687
+ function freshnessForMemory(status) {
15688
+ if (status === "current" || status === "suspect" || status === "stale") return status;
15689
+ return "unknown";
15690
+ }
15691
+ function receiptOmissionKind(raw) {
15692
+ if (raw.includes("task")) return "task";
15693
+ if (raw.includes("fact")) return "current-fact";
15694
+ if (raw.includes("state")) return "code-state";
15695
+ if (raw.includes("semantic")) return "semantic-code";
15696
+ if (raw.includes("start")) return "start-here";
15697
+ if (raw.includes("memory")) return "memory";
15698
+ if (raw.includes("claim")) return "claim";
15699
+ if (raw.includes("knowledge-page")) return "knowledge-page";
15700
+ if (raw.includes("workflow")) return "workflow";
15701
+ if (raw.includes("verification")) return "verification";
15702
+ if (raw.includes("caution")) return "caution";
15703
+ return void 0;
15704
+ }
15705
+ function receiptOmissions(omitted, hiddenCautionMemoryCount) {
15706
+ const counts = /* @__PURE__ */ new Map();
15707
+ for (const raw of omitted) {
15708
+ const kind = receiptOmissionKind(raw);
15709
+ if (!kind || raw.endsWith("-heading")) continue;
15710
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
15711
+ }
15712
+ const receipt = [...counts.entries()].map(([kind, count2]) => ({
15713
+ kind,
15714
+ reason: "token-budget",
15715
+ count: count2
15716
+ }));
15717
+ if (hiddenCautionMemoryCount > 0) {
15718
+ receipt.push({
15719
+ kind: "caution",
15720
+ reason: "hidden-by-task-lens",
15721
+ count: hiddenCautionMemoryCount
15722
+ });
15723
+ }
15724
+ return receipt;
15725
+ }
15726
+ function scheduledActions(cautions) {
15727
+ return cautions.filter((caution) => caution.kind === "codegraph-refresh-queued").map((caution) => short(caution.message, 28));
15728
+ }
15311
15729
  function renderTaskWorksetPrompt(input) {
15312
15730
  const maxTokens = input.budget?.maxTokens ?? 180;
15313
15731
  const omitted = [];
15732
+ const selected = [];
15314
15733
  const lines = ["Memorix Autopilot Brief"];
15315
15734
  const task = short(input.task || "Continue the current task.", 34);
15316
- appendLine(lines, "Task: " + task, maxTokens, omitted, "task-detail");
15735
+ appendLine(lines, "Task: " + task, maxTokens, omitted, "task-detail", selected, {
15736
+ kind: "task",
15737
+ reason: "current task supplied by the caller",
15738
+ trust: "source-backed"
15739
+ });
15317
15740
  appendLine(lines, "Task lens: " + input.lens, maxTokens, omitted, "lens");
15318
15741
  if (input.cautions.length > 0 || input.cautionMemory.length > 0) {
15319
15742
  appendLine(lines, "", maxTokens, omitted, "caution-heading");
15320
15743
  appendLine(lines, "Cautions", maxTokens, omitted, "caution-heading");
15321
15744
  for (const caution of input.cautions.slice(0, 6)) {
15322
- appendLine(lines, "- " + short(caution.message, 22), maxTokens, omitted, "caution");
15745
+ appendLine(lines, "- " + short(caution.message, 22), maxTokens, omitted, "caution", selected, {
15746
+ kind: "caution",
15747
+ id: "caution:" + caution.kind,
15748
+ reason: "current project caution",
15749
+ trust: "source-backed"
15750
+ });
15323
15751
  }
15324
15752
  for (const memory of input.cautionMemory.slice(0, 3)) {
15325
15753
  const location = memory.path ? memory.path + (memory.symbol ? "#" + memory.symbol : "") : "no current code location";
@@ -15329,7 +15757,15 @@ function renderTaskWorksetPrompt(input) {
15329
15757
  "- #" + memory.id + " " + memory.status + ": " + short(memory.title, 18) + " (" + location + reason + ")",
15330
15758
  maxTokens,
15331
15759
  omitted,
15332
- "caution-memory"
15760
+ "caution-memory",
15761
+ selected,
15762
+ {
15763
+ kind: "memory",
15764
+ id: "memory:" + memory.id,
15765
+ reason: "task-relevant memory requiring source verification",
15766
+ freshness: freshnessForMemory(memory.status),
15767
+ trust: "historical"
15768
+ }
15333
15769
  );
15334
15770
  }
15335
15771
  if (input.hiddenCautionMemoryCount > 0) {
@@ -15340,13 +15776,22 @@ function renderTaskWorksetPrompt(input) {
15340
15776
  appendLine(lines, "", maxTokens, omitted, "facts-heading");
15341
15777
  appendLine(lines, "Current project facts", maxTokens, omitted, "facts-heading");
15342
15778
  for (const fact of input.currentFacts.slice(0, 4)) {
15343
- appendLine(lines, "- " + short(fact, 40), maxTokens, omitted, "current-fact");
15779
+ appendLine(lines, "- " + short(fact, 40), maxTokens, omitted, "current-fact", selected, {
15780
+ kind: "current-fact",
15781
+ reason: "current project state",
15782
+ trust: "source-backed"
15783
+ });
15344
15784
  }
15345
15785
  }
15346
15786
  if (input.codeState) {
15347
15787
  appendLine(lines, "", maxTokens, omitted, "state-heading");
15348
15788
  appendLine(lines, "Project state", maxTokens, omitted, "state-heading");
15349
- appendLine(lines, input.codeState, maxTokens, omitted, "code-state");
15789
+ appendLine(lines, input.codeState, maxTokens, omitted, "code-state", selected, {
15790
+ kind: "code-state",
15791
+ ...input.provenance.snapshotId ? { id: "snapshot:" + input.provenance.snapshotId } : {},
15792
+ reason: "latest available Code State snapshot",
15793
+ trust: "source-backed"
15794
+ });
15350
15795
  }
15351
15796
  if (input.semanticCode && (input.semanticCode.entryPoints.length > 0 || input.semanticCode.relations.length > 0)) {
15352
15797
  appendLine(lines, "", maxTokens, omitted, "semantic-code-heading");
@@ -15358,7 +15803,14 @@ function renderTaskWorksetPrompt(input) {
15358
15803
  "- " + location + ": " + short(relation.from.name, 12) + " " + short(relation.kind, 8) + " " + short(relation.to.name, 12),
15359
15804
  maxTokens,
15360
15805
  omitted,
15361
- "semantic-relation"
15806
+ "semantic-relation",
15807
+ selected,
15808
+ {
15809
+ kind: "semantic-code",
15810
+ id: "code:" + location,
15811
+ reason: "validated optional semantic code relation",
15812
+ trust: "derived"
15813
+ }
15362
15814
  );
15363
15815
  }
15364
15816
  if (input.semanticCode.relations.length === 0) {
@@ -15369,7 +15821,14 @@ function renderTaskWorksetPrompt(input) {
15369
15821
  "- " + location + ": " + short(entry.name, 16) + " (" + short(entry.kind, 8) + ")",
15370
15822
  maxTokens,
15371
15823
  omitted,
15372
- "semantic-entry"
15824
+ "semantic-entry",
15825
+ selected,
15826
+ {
15827
+ kind: "semantic-code",
15828
+ id: "code:" + location,
15829
+ reason: "validated optional semantic code entry point",
15830
+ trust: "derived"
15831
+ }
15373
15832
  );
15374
15833
  }
15375
15834
  }
@@ -15378,7 +15837,12 @@ function renderTaskWorksetPrompt(input) {
15378
15837
  appendLine(lines, "", maxTokens, omitted, "start-heading");
15379
15838
  appendLine(lines, "Start here", maxTokens, omitted, "start-heading");
15380
15839
  for (const source of input.startHere.slice(0, 5)) {
15381
- appendLine(lines, "- " + source, maxTokens, omitted, "start-here");
15840
+ appendLine(lines, "- " + source, maxTokens, omitted, "start-here", selected, {
15841
+ kind: "start-here",
15842
+ id: "path:" + source,
15843
+ reason: "task-lensed starting point",
15844
+ trust: "derived"
15845
+ });
15382
15846
  }
15383
15847
  }
15384
15848
  if (input.reliableMemory.length > 0) {
@@ -15391,7 +15855,15 @@ function renderTaskWorksetPrompt(input) {
15391
15855
  "- #" + memory.id + " " + memory.type + ": " + short(memory.title, 18) + " (" + location + ")",
15392
15856
  maxTokens,
15393
15857
  omitted,
15394
- "reliable-memory"
15858
+ "reliable-memory",
15859
+ selected,
15860
+ {
15861
+ kind: "memory",
15862
+ id: "memory:" + memory.id,
15863
+ reason: "current code-bound memory",
15864
+ freshness: freshnessForMemory(memory.status),
15865
+ trust: "historical"
15866
+ }
15395
15867
  );
15396
15868
  }
15397
15869
  }
@@ -15399,10 +15871,25 @@ function renderTaskWorksetPrompt(input) {
15399
15871
  appendLine(lines, "", maxTokens, omitted, "knowledge-heading");
15400
15872
  appendLine(lines, "Project knowledge", maxTokens, omitted, "knowledge-heading");
15401
15873
  for (const claim of input.claims.slice(0, 3)) {
15402
- appendLine(lines, "- " + claim.assertion + " [" + claim.id + "]", maxTokens, omitted, "claim");
15874
+ appendLine(lines, "- " + claim.assertion + " [" + claim.id + "]", maxTokens, omitted, "claim", selected, {
15875
+ kind: "claim",
15876
+ id: "claim:" + claim.id,
15877
+ reason: "source-qualified task match",
15878
+ trust: "source-backed"
15879
+ });
15403
15880
  }
15404
15881
  for (const page of input.pages.slice(0, 2)) {
15405
- appendLine(lines, "- page: " + page.relativePath, maxTokens, omitted, "knowledge-page");
15882
+ const supportsDeliveredClaim = page.claimIds.some((claimId) => selected.some((item) => item.kind === "claim" && item.id === "claim:" + claimId));
15883
+ if (!supportsDeliveredClaim) {
15884
+ omitted.push("knowledge-page-dependency");
15885
+ continue;
15886
+ }
15887
+ appendLine(lines, "- page: " + page.relativePath, maxTokens, omitted, "knowledge-page", selected, {
15888
+ kind: "knowledge-page",
15889
+ id: "page:" + page.id,
15890
+ reason: "approved page linked to a selected claim",
15891
+ trust: "source-backed"
15892
+ });
15406
15893
  }
15407
15894
  }
15408
15895
  if (input.workflows.length > 0) {
@@ -15414,7 +15901,14 @@ function renderTaskWorksetPrompt(input) {
15414
15901
  "- " + workflow.title + ": " + workflow.firstPhase.title + " - " + workflow.firstPhase.instructions,
15415
15902
  maxTokens,
15416
15903
  omitted,
15417
- "workflow"
15904
+ "workflow",
15905
+ selected,
15906
+ {
15907
+ kind: "workflow",
15908
+ id: "workflow:" + workflow.id,
15909
+ reason: "task-matching project workflow",
15910
+ trust: "source-backed"
15911
+ }
15418
15912
  );
15419
15913
  }
15420
15914
  }
@@ -15422,16 +15916,23 @@ function renderTaskWorksetPrompt(input) {
15422
15916
  appendLine(lines, "", maxTokens, omitted, "verification-heading");
15423
15917
  appendLine(lines, "Verify", maxTokens, omitted, "verification-heading");
15424
15918
  for (const check of input.verification.slice(0, 4)) {
15425
- appendLine(lines, "- " + short(check, 20), maxTokens, omitted, "verification");
15919
+ appendLine(lines, "- " + short(check, 20), maxTokens, omitted, "verification", selected, {
15920
+ kind: "verification",
15921
+ reason: "task-lensed verification guidance",
15922
+ trust: "derived"
15923
+ });
15426
15924
  }
15427
15925
  }
15428
15926
  return {
15429
15927
  prompt: lines.join("\n"),
15430
15928
  tokenCount: countTextTokens(lines.join("\n")),
15431
- omitted: unique(omitted)
15929
+ omitted: unique(omitted),
15930
+ omittedItems: omitted,
15931
+ selected
15432
15932
  };
15433
15933
  }
15434
15934
  async function buildTaskWorkset(input) {
15935
+ const startedAt = Date.now();
15435
15936
  const task = input.task?.trim() ?? "";
15436
15937
  const maxTokens = Math.max(96, Math.min(Math.floor(input.maxTokens ?? 180), 320));
15437
15938
  const cautions = [...input.runtimeCautions ?? [], ...snapshotCautions(input)];
@@ -15533,6 +16034,18 @@ async function buildTaskWorkset(input) {
15533
16034
  ...base,
15534
16035
  budget: { maxTokens }
15535
16036
  });
16037
+ const receipt = {
16038
+ version: "1.2.2",
16039
+ target: input.deliveryTarget ?? "project-context",
16040
+ elapsedMs: Math.max(0, Date.now() - startedAt),
16041
+ budget: {
16042
+ maxTokens,
16043
+ tokenCount: rendered.tokenCount
16044
+ },
16045
+ selected: rendered.selected,
16046
+ omitted: receiptOmissions(rendered.omittedItems, base.hiddenCautionMemoryCount),
16047
+ scheduledActions: scheduledActions(normalizedCautions)
16048
+ };
15536
16049
  return {
15537
16050
  ...base,
15538
16051
  budget: {
@@ -15540,6 +16053,7 @@ async function buildTaskWorkset(input) {
15540
16053
  tokenCount: rendered.tokenCount,
15541
16054
  omitted: rendered.omitted
15542
16055
  },
16056
+ receipt,
15543
16057
  prompt: rendered.prompt
15544
16058
  };
15545
16059
  }
@@ -16317,6 +16831,9 @@ import path19 from "path";
16317
16831
  function activeProjectObservations(observations2, projectId) {
16318
16832
  return observations2.filter((obs) => obs.projectId === projectId && (obs.status ?? "active") === "active");
16319
16833
  }
16834
+ function deliveryEligibleProjectObservations(observations2, projectId) {
16835
+ return activeProjectObservations(observations2, projectId).filter((observation) => isEligibleForAutomaticDelivery(observation));
16836
+ }
16320
16837
  function decideRefresh(input) {
16321
16838
  if (input.mode === "never") {
16322
16839
  return { performed: false, reason: "disabled", message: "Automatic project scan disabled." };
@@ -16391,13 +16908,27 @@ async function buildAutoProjectContext(input) {
16391
16908
  activeProjectObservations(input.observations, input.project.id)
16392
16909
  );
16393
16910
  try {
16394
- const { enqueueClaimRequalification: enqueueClaimRequalification2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
16911
+ const { MaintenanceTargetStore: MaintenanceTargetStore2 } = await Promise.resolve().then(() => (init_maintenance_targets(), maintenance_targets_exports));
16912
+ new MaintenanceTargetStore2(input.dataDir).register({
16913
+ projectId: input.project.id,
16914
+ projectRoot: input.project.rootPath,
16915
+ dataDir: input.dataDir
16916
+ });
16917
+ const {
16918
+ enqueueClaimRequalification: enqueueClaimRequalification2,
16919
+ enqueueObservationQualification: enqueueObservationQualification2
16920
+ } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
16395
16921
  enqueueClaimRequalification2({
16396
16922
  dataDir: input.dataDir,
16397
16923
  projectId: input.project.id,
16398
16924
  source: "foreground-refresh",
16399
16925
  snapshotId: store.latestSnapshot(input.project.id)?.id
16400
16926
  });
16927
+ enqueueObservationQualification2({
16928
+ dataDir: input.dataDir,
16929
+ projectId: input.project.id,
16930
+ source: "foreground-refresh"
16931
+ });
16401
16932
  } catch {
16402
16933
  }
16403
16934
  refresh = { ...refresh, backfill };
@@ -16414,7 +16945,9 @@ async function buildAutoProjectContext(input) {
16414
16945
  const explain = buildProjectContextExplain({
16415
16946
  project: input.project,
16416
16947
  store,
16417
- observations: input.observations,
16948
+ // Binding sees all active evidence, but automatic delivery sees only
16949
+ // evidence that has passed the control-plane admission gate.
16950
+ observations: deliveryEligibleProjectObservations(input.observations, input.project.id),
16418
16951
  exclude
16419
16952
  });
16420
16953
  const overview = explain.overview;
@@ -16505,7 +17038,8 @@ async function buildAutoProjectContext(input) {
16505
17038
  suspect: overview.freshness.suspect,
16506
17039
  stale: overview.freshness.stale
16507
17040
  },
16508
- runtimeCautions
17041
+ runtimeCautions,
17042
+ ...input.deliveryTarget ? { deliveryTarget: input.deliveryTarget } : {}
16509
17043
  });
16510
17044
  return {
16511
17045
  project: input.project,
@@ -16739,6 +17273,7 @@ var init_auto_context = __esm({
16739
17273
  init_external_provider();
16740
17274
  init_project_context();
16741
17275
  init_store();
17276
+ init_admission();
16742
17277
  init_task_lens();
16743
17278
  DEFAULT_MAX_AGE_MS = 10 * 60 * 1e3;
16744
17279
  }
@@ -16931,7 +17466,8 @@ async function attachTaskWorkset(input) {
16931
17466
  suspect: input.pack.warnings.filter((warning) => warning.status === "suspect").length,
16932
17467
  stale: input.pack.warnings.filter((warning) => warning.status === "stale").length
16933
17468
  },
16934
- runtimeCautions: input.runtimeCautions
17469
+ runtimeCautions: input.runtimeCautions,
17470
+ deliveryTarget: "context-pack"
16935
17471
  });
16936
17472
  return { ...input.pack, workset };
16937
17473
  }
@@ -18343,11 +18879,14 @@ __export(export_import_exports, {
18343
18879
  exportAsMarkdown: () => exportAsMarkdown,
18344
18880
  importFromJson: () => importFromJson
18345
18881
  });
18346
- async function exportAsJson(projectDir2, projectId) {
18882
+ async function exportAsJson(projectDir2, projectId, reader) {
18347
18883
  const store = getObservationStore();
18348
18884
  const allObs = await store.loadAll();
18349
18885
  const allSessions = await getSessionStore().loadAll();
18350
- const projectObs = allObs.filter((o) => o.projectId === projectId);
18886
+ const projectObs = filterReadableObservations(
18887
+ allObs.filter((o) => o.projectId === projectId),
18888
+ reader
18889
+ );
18351
18890
  const projectSessions = allSessions.filter((s) => s.projectId === projectId);
18352
18891
  const typeBreakdown = {};
18353
18892
  for (const obs of projectObs) {
@@ -18366,8 +18905,8 @@ async function exportAsJson(projectDir2, projectId) {
18366
18905
  }
18367
18906
  };
18368
18907
  }
18369
- async function exportAsMarkdown(projectDir2, projectId) {
18370
- const data = await exportAsJson(projectDir2, projectId);
18908
+ async function exportAsMarkdown(projectDir2, projectId, reader) {
18909
+ const data = await exportAsJson(projectDir2, projectId, reader);
18371
18910
  const lines = [];
18372
18911
  lines.push(`# Memorix Export: ${projectId}`);
18373
18912
  lines.push(`Exported: ${data.exportedAt}`);
@@ -18469,6 +19008,7 @@ var init_export_import = __esm({
18469
19008
  "src/memory/export-import.ts"() {
18470
19009
  "use strict";
18471
19010
  init_esm_shims();
19011
+ init_visibility();
18472
19012
  init_obs_store();
18473
19013
  init_session_store();
18474
19014
  OBSERVATION_ICONS2 = {
@@ -18645,6 +19185,7 @@ function isEligible(o, projectId) {
18645
19185
  if (isCommandLog2(o)) return false;
18646
19186
  if (isInactive(o)) return false;
18647
19187
  if (isOtherProject(o, projectId)) return false;
19188
+ if (!isEligibleForKnowledgePromotion(o)) return false;
18648
19189
  if (o.valueCategory === "ephemeral") return false;
18649
19190
  if (!contextualHasSubstance(o)) return false;
18650
19191
  return true;
@@ -18793,6 +19334,7 @@ var init_generator = __esm({
18793
19334
  "src/wiki/generator.ts"() {
18794
19335
  "use strict";
18795
19336
  init_esm_shims();
19337
+ init_admission();
18796
19338
  COMMAND_LOG_TITLE4 = /^(Ran:|Command:|Executed:)\s/i;
18797
19339
  SECTION_DEFS = [
18798
19340
  {
@@ -19079,6 +19621,9 @@ function sendError(res, message, status = 500) {
19079
19621
  function filterByProject(items, projectId) {
19080
19622
  return items.filter((item) => item.projectId === projectId);
19081
19623
  }
19624
+ function filterDashboardObservations(observations2, projectId) {
19625
+ return filterReadableObservations(observations2, { projectId });
19626
+ }
19082
19627
  function isActiveStatus(status) {
19083
19628
  return (status ?? "active") === "active";
19084
19629
  }
@@ -19126,7 +19671,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19126
19671
  switch (apiPath) {
19127
19672
  case "/projects": {
19128
19673
  try {
19129
- const allObs = await getObservationStore().loadAll();
19674
+ const allObs = filterReadableObservations(
19675
+ await getObservationStore().loadAll(),
19676
+ {}
19677
+ );
19130
19678
  const projectSet = /* @__PURE__ */ new Map();
19131
19679
  for (const obs of allObs) {
19132
19680
  if (!isActiveStatus(obs.status)) continue;
@@ -19175,13 +19723,19 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19175
19723
  await initGraphStore(effectiveDataDir);
19176
19724
  const gStore = getGraphStore();
19177
19725
  const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
19178
- const graphObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19726
+ const graphObs = filterDashboardObservations(
19727
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19728
+ effectiveProjectId
19729
+ );
19179
19730
  const scoped = scopeKnowledgeGraphToProject(graph, graphObs);
19180
19731
  sendJson(res, { entities: scoped.entities, relations: scoped.relations });
19181
19732
  break;
19182
19733
  }
19183
19734
  case "/observations": {
19184
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19735
+ const observations2 = filterDashboardObservations(
19736
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19737
+ effectiveProjectId
19738
+ );
19185
19739
  sendJson(res, observations2);
19186
19740
  break;
19187
19741
  }
@@ -19194,7 +19748,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19194
19748
  case "/stats": {
19195
19749
  await initGraphStore(effectiveDataDir);
19196
19750
  const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
19197
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19751
+ const observations2 = filterDashboardObservations(
19752
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19753
+ effectiveProjectId
19754
+ );
19198
19755
  const nextId2 = await getObservationStore().loadIdCounter();
19199
19756
  const projectGraphCounts = computeProjectGraphCounts(graph.entities, graph.relations, observations2);
19200
19757
  const typeCounts = {};
@@ -19312,7 +19869,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19312
19869
  break;
19313
19870
  }
19314
19871
  case "/retention": {
19315
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19872
+ const observations2 = filterDashboardObservations(
19873
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19874
+ effectiveProjectId
19875
+ );
19316
19876
  const now3 = Date.now();
19317
19877
  const scored = observations2.filter((obs) => obs.type !== "probe").map((obs) => {
19318
19878
  const age = now3 - new Date(obs.createdAt || now3).getTime();
@@ -19350,7 +19910,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19350
19910
  const { generateKnowledgeBase: generateKnowledgeBase2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
19351
19911
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
19352
19912
  await initMiniSkillStore2(effectiveDataDir);
19353
- const allObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19913
+ const allObs = filterDashboardObservations(
19914
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19915
+ effectiveProjectId
19916
+ );
19354
19917
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
19355
19918
  const overview = generateKnowledgeBase2({
19356
19919
  projectId: effectiveProjectId,
@@ -19366,7 +19929,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19366
19929
  const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
19367
19930
  await initMiniSkillStore2(effectiveDataDir);
19368
19931
  await initGraphStore2(effectiveDataDir);
19369
- const allObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19932
+ const allObs = filterDashboardObservations(
19933
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19934
+ effectiveProjectId
19935
+ );
19370
19936
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
19371
19937
  const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
19372
19938
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
@@ -19489,7 +20055,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19489
20055
  break;
19490
20056
  }
19491
20057
  case "/identity": {
19492
- const allObs = await getObservationStore().loadAll();
20058
+ const allObs = filterReadableObservations(await getObservationStore().loadAll(), {});
19493
20059
  const allProjectIds = [...new Set(allObs.map((o) => o.projectId).filter(Boolean))];
19494
20060
  let classifyProjectId2 = () => "real";
19495
20061
  let isDirtyProjectId2 = () => false;
@@ -19574,6 +20140,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19574
20140
  sendError(res, "Observation not found", 404);
19575
20141
  } else if (matchObs.projectId !== effectiveProjectId) {
19576
20142
  sendError(res, `Observation #${obsId} belongs to project "${matchObs.projectId}", not "${effectiveProjectId}"`, 403);
20143
+ } else if (!canManageObservation(matchObs, { projectId: effectiveProjectId })) {
20144
+ sendError(res, `Observation #${obsId} is not manageable from the unbound dashboard.`, 403);
19577
20145
  } else {
19578
20146
  await obsStore.remove(obsId);
19579
20147
  try {
@@ -19595,7 +20163,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19595
20163
  if (apiPath === "/export") {
19596
20164
  await initGraphStore(effectiveDataDir);
19597
20165
  const fullGraph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
19598
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
20166
+ const observations2 = filterDashboardObservations(
20167
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
20168
+ effectiveProjectId
20169
+ );
19599
20170
  const nextId2 = await getObservationStore().loadIdCounter();
19600
20171
  const scoped = scopeKnowledgeGraphToProject(fullGraph, observations2);
19601
20172
  const exportData = {
@@ -19919,6 +20490,7 @@ var init_server = __esm({
19919
20490
  init_yaml_loader();
19920
20491
  init_yaml_loader();
19921
20492
  init_graph_scope();
20493
+ init_visibility();
19922
20494
  MIME_TYPES = {
19923
20495
  ".html": "text/html; charset=utf-8",
19924
20496
  ".css": "text/css; charset=utf-8",
@@ -20696,6 +21268,11 @@ async function createHandoffArtifact(input, storeObservation2, teamStore) {
20696
21268
  sourceDetail: "explicit",
20697
21269
  valueCategory: "core",
20698
21270
  // immune to retention archival
21271
+ // A direct handoff is not a project-wide memo. The sender retains access
21272
+ // and the explicit recipient receives a read grant. Broadcast handoffs
21273
+ // are deliberately visible only to active team members.
21274
+ visibility: input.toAgentId ? "personal" : "team",
21275
+ ...input.toAgentId ? { sharedWithAgentIds: [input.toAgentId] } : {},
20699
21276
  createdByAgentId: input.fromAgentId
20700
21277
  });
20701
21278
  try {
@@ -22695,6 +23272,7 @@ async function createAutoRelations(obs, extracted, graphManager) {
22695
23272
  // src/server.ts
22696
23273
  init_entity_extractor();
22697
23274
  init_graph_scope();
23275
+ init_visibility();
22698
23276
 
22699
23277
  // src/compact/engine.ts
22700
23278
  init_esm_shims();
@@ -22820,7 +23398,15 @@ function formatTimeline(timeline) {
22820
23398
  function formatObservationDetail(doc) {
22821
23399
  const icon = getTypeIcon(doc.type);
22822
23400
  const lines = [];
22823
- const header = buildProvenanceHeader(doc.sourceDetail, doc.valueCategory, doc.source, doc.commitHash, doc.relatedCommits);
23401
+ const header = buildProvenanceHeader(
23402
+ doc.sourceDetail,
23403
+ doc.valueCategory,
23404
+ doc.admissionState,
23405
+ doc.admissionReason,
23406
+ doc.source,
23407
+ doc.commitHash,
23408
+ doc.relatedCommits
23409
+ );
22824
23410
  if (header) {
22825
23411
  lines.push(header);
22826
23412
  lines.push("");
@@ -22855,7 +23441,7 @@ function formatObservationDetail(doc) {
22855
23441
  }
22856
23442
  return lines.join("\n");
22857
23443
  }
22858
- function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash, relatedCommits) {
23444
+ function buildProvenanceHeader(sourceDetail, valueCategory, admissionState, admissionReason, source, commitHash, relatedCommits) {
22859
23445
  const sd = resolveSourceDetail(sourceDetail, source);
22860
23446
  if (!sd) return "";
22861
23447
  const label = sourceKindLabel(sd);
@@ -22866,11 +23452,21 @@ function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash,
22866
23452
  if (verificationLine) {
22867
23453
  lines.push(verificationLine);
22868
23454
  }
22869
- if (valueCategory === "core") {
23455
+ if (valueCategory === "core" && admissionState !== "candidate" && admissionState !== "ephemeral") {
22870
23456
  lines.push("[CORE] Core \u2014 immune to decay");
23457
+ } else if (valueCategory === "core") {
23458
+ lines.push("[CORE] Core candidate \u2014 not durable until qualification completes");
22871
23459
  } else if (valueCategory === "ephemeral") {
22872
23460
  lines.push("[WARN] Ephemeral \u2014 short-lived signal");
22873
23461
  }
23462
+ if (admissionState === "candidate") {
23463
+ lines.push("[PENDING] Candidate \u2014 excluded from automatic context until current Code Memory qualifies it");
23464
+ } else if (admissionState === "ephemeral") {
23465
+ lines.push("[TRACE] Ephemeral capture \u2014 excluded from automatic context");
23466
+ } else if (admissionState === "qualified") {
23467
+ lines.push("[QUALIFIED] Automatic evidence passed current Code Memory qualification");
23468
+ }
23469
+ if (admissionReason) lines.push(`Admission: ${redactCredentials(admissionReason)}`);
22874
23470
  return lines.join("\n");
22875
23471
  }
22876
23472
  function sourceKindLabel(sd) {
@@ -22951,6 +23547,7 @@ init_obs_store();
22951
23547
  init_mini_skill_store();
22952
23548
  init_mini_skills();
22953
23549
  init_secret_filter();
23550
+ init_visibility();
22954
23551
  function normalizeMemoryBrowseQuery(query) {
22955
23552
  const trimmed = query.trim();
22956
23553
  if (!trimmed) return "";
@@ -22993,11 +23590,13 @@ async function compactSearch(options) {
22993
23590
  );
22994
23591
  let formatted = formatIndexTable(entries, searchOptions.query, !options.projectId);
22995
23592
  if (entries.length === 0 && options.projectId) {
22996
- const allObservations = getAllObservations();
23593
+ const allObservations = filterReadableObservations(getAllObservations(), options.reader);
22997
23594
  const projectAliases = new Set(await resolveAliases(options.projectId).catch(() => [options.projectId]));
22998
23595
  const projectHasStoredMemory = allObservations.some((obs) => obs.projectId && projectAliases.has(obs.projectId));
22999
23596
  if (!projectHasStoredMemory) {
23000
- formatted = `This project does not have any Memorix memories yet.
23597
+ formatted = options.reader ? `No Memorix memories are available to this session yet.
23598
+
23599
+ The tool call worked, but this session has no project-visible memory to retrieve.` : `This project does not have any Memorix memories yet.
23001
23600
 
23002
23601
  It looks like a fresh project: the tool call worked, but there is nothing stored to retrieve yet.
23003
23602
 
@@ -23011,8 +23610,8 @@ This project does have stored Memorix memories, but none matched the current que
23011
23610
  const totalTokens = countTextTokens(formatted);
23012
23611
  return { entries, formatted, totalTokens };
23013
23612
  }
23014
- async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3) {
23015
- const result = await getTimeline(anchorId, projectId, depthBefore, depthAfter);
23613
+ async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3, reader) {
23614
+ const result = await getTimeline(anchorId, projectId, depthBefore, depthAfter, reader);
23016
23615
  const timeline = {
23017
23616
  anchorId,
23018
23617
  anchorEntry: result.anchor,
@@ -23023,7 +23622,7 @@ async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter
23023
23622
  const totalTokens = countTextTokens(formatted);
23024
23623
  return { timeline, formatted, totalTokens };
23025
23624
  }
23026
- async function compactDetail(idsOrRefs) {
23625
+ async function compactDetail(idsOrRefs, options = {}) {
23027
23626
  const invalidRefs = [];
23028
23627
  const parsedRefs = [];
23029
23628
  for (let i = 0; i < idsOrRefs.length; i++) {
@@ -23080,7 +23679,7 @@ async function compactDetail(idsOrRefs) {
23080
23679
  const missingRefs = [];
23081
23680
  for (const ref of refs) {
23082
23681
  const obs = getObservation(ref.id, ref.projectId);
23083
- if (obs && (ref.projectId ? obs.projectId === ref.projectId : true)) {
23682
+ if (obs && (ref.projectId ? obs.projectId === ref.projectId : true) && canReadObservation(obs, options.reader)) {
23084
23683
  documentMap.set(toRefKey(ref), {
23085
23684
  id: makeOramaObservationId(obs.projectId, obs.id),
23086
23685
  observationId: obs.id,
@@ -23099,7 +23698,12 @@ async function compactDetail(idsOrRefs) {
23099
23698
  status: obs.status ?? "active",
23100
23699
  source: obs.source ?? "agent",
23101
23700
  sourceDetail: obs.sourceDetail ?? "",
23102
- valueCategory: obs.valueCategory ?? ""
23701
+ valueCategory: obs.valueCategory ?? "",
23702
+ admissionState: obs.admissionState ?? "",
23703
+ admissionReason: obs.admissionReason ?? "",
23704
+ visibility: obs.visibility ?? "project",
23705
+ createdByAgentId: obs.createdByAgentId ?? "",
23706
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
23103
23707
  });
23104
23708
  } else {
23105
23709
  missingRefs.push(ref);
@@ -23109,17 +23713,17 @@ async function compactDetail(idsOrRefs) {
23109
23713
  for (const ref of missingRefs) {
23110
23714
  const fallbackDocs = await getObservationsByIds([ref.id], ref.projectId);
23111
23715
  const doc = fallbackDocs[0];
23112
- if (doc) {
23716
+ if (doc && canReadObservation(doc, options.reader)) {
23113
23717
  documentMap.set(toRefKey(ref), doc);
23114
23718
  continue;
23115
23719
  }
23116
23720
  const stored = await getPersistedObservation(ref);
23117
- if (stored) {
23721
+ if (stored && canReadObservation(stored, options.reader)) {
23118
23722
  documentMap.set(toRefKey(ref), observationToDocument(stored));
23119
23723
  }
23120
23724
  }
23121
23725
  }
23122
- const allObs = getAllObservations();
23726
+ const allObs = filterReadableObservations(getAllObservations(), options.reader);
23123
23727
  const crossRefMap = /* @__PURE__ */ new Map();
23124
23728
  for (const ref of refs) {
23125
23729
  const obs = getObservation(ref.id, ref.projectId);
@@ -23233,7 +23837,12 @@ function observationToDocument(obs) {
23233
23837
  status: obs.status ?? "active",
23234
23838
  source: obs.source ?? "agent",
23235
23839
  sourceDetail: obs.sourceDetail ?? "",
23236
- valueCategory: obs.valueCategory ?? ""
23840
+ valueCategory: obs.valueCategory ?? "",
23841
+ admissionState: obs.admissionState ?? "",
23842
+ admissionReason: obs.admissionReason ?? "",
23843
+ visibility: obs.visibility ?? "project",
23844
+ createdByAgentId: obs.createdByAgentId ?? "",
23845
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
23237
23846
  };
23238
23847
  }
23239
23848
  function formatMiniSkillDetail(skill2, provenanceStatus) {
@@ -23352,7 +23961,9 @@ function auditMemoryQuality(observations2, options) {
23352
23961
  status: obs.status ?? "active",
23353
23962
  source: obs.source ?? "agent",
23354
23963
  sourceDetail: obs.sourceDetail ?? "",
23355
- valueCategory: obs.valueCategory ?? ""
23964
+ valueCategory: obs.valueCategory ?? "",
23965
+ admissionState: obs.admissionState ?? "",
23966
+ admissionReason: obs.admissionReason ?? ""
23356
23967
  }, options.referenceTime) !== "active").map((obs) => makeEntry(obs, "Outside active retention zone"));
23357
23968
  const summary = {
23358
23969
  total: scoped.length,
@@ -23394,6 +24005,7 @@ function auditMemoryQuality(observations2, options) {
23394
24005
 
23395
24006
  // src/memory/graph-context.ts
23396
24007
  init_retention();
24008
+ init_admission();
23397
24009
  function formatGraphContextPrompt(packet) {
23398
24010
  const lines = [
23399
24011
  "## Memory Context Packet",
@@ -23551,7 +24163,9 @@ function scoreForPacket(obs, queryTokens, referenceTime, broadMemoryQuery) {
23551
24163
  status: obs.status ?? "active",
23552
24164
  source: obs.source ?? "agent",
23553
24165
  sourceDetail: obs.sourceDetail ?? "",
23554
- valueCategory: obs.valueCategory ?? ""
24166
+ valueCategory: obs.valueCategory ?? "",
24167
+ admissionState: obs.admissionState ?? "",
24168
+ admissionReason: obs.admissionReason ?? ""
23555
24169
  }, referenceTime) !== "active") {
23556
24170
  score -= 2;
23557
24171
  }
@@ -23645,8 +24259,11 @@ function buildGraphContextPacket(observations2, options) {
23645
24259
  ...audit.issues.orphans.map((entry) => entry.id),
23646
24260
  ...audit.issues.retentionCandidates.map((entry) => entry.id)
23647
24261
  ]);
23648
- const filteredObservations = observations2.filter((obs) => obs.projectId === options.projectId && !riskIds.has(obs.id));
23649
- const baseObservations = filteredObservations.length > 0 ? filteredObservations : observations2;
24262
+ const deliveryEligible = observations2.filter(
24263
+ (obs) => obs.projectId === options.projectId && isEligibleForAutomaticDelivery(obs)
24264
+ );
24265
+ const filteredObservations = deliveryEligible.filter((obs) => !riskIds.has(obs.id));
24266
+ const baseObservations = filteredObservations.length > 0 ? filteredObservations : deliveryEligible;
23650
24267
  const memories = pickMemories(baseObservations, options.projectId, options.query, options.limit ?? 5, referenceTime);
23651
24268
  const entities = buildEntities(memories);
23652
24269
  const entityNames = new Set(entities.map((entity) => entity.name));
@@ -26133,6 +26750,8 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
26133
26750
  let projectResolutionError = null;
26134
26751
  let explicitProjectBound = false;
26135
26752
  let currentAgentId;
26753
+ let teamStore;
26754
+ let initTeamStoreForProject;
26136
26755
  if (detectedProject) {
26137
26756
  rawProject = detectedProject;
26138
26757
  } else {
@@ -26380,7 +26999,7 @@ The path should point to a directory containing a .git folder.`
26380
26999
  };
26381
27000
  const server = existingServer ?? new McpServer({
26382
27001
  name: "memorix",
26383
- version: true ? "1.2.1" : "1.0.1"
27002
+ version: true ? "1.2.2" : "1.0.1"
26384
27003
  });
26385
27004
  const originalRegisterTool = server.registerTool.bind(server);
26386
27005
  server.registerTool = ((name, ...args) => {
@@ -26400,11 +27019,26 @@ The path should point to a directory containing a .git folder.`
26400
27019
  }
26401
27020
  return originalRegisterTool(name, ...args);
26402
27021
  });
27022
+ const getObservationReader = (scope = "project") => {
27023
+ let isTeamMember = false;
27024
+ if (teamFeaturesEnabled && currentAgentId) {
27025
+ try {
27026
+ const agent = teamStore.getAgent(currentAgentId);
27027
+ isTeamMember = agent?.project_id === project.id && agent.status === "active";
27028
+ } catch {
27029
+ }
27030
+ }
27031
+ return {
27032
+ ...scope === "project" ? { projectId: project.id } : {},
27033
+ ...currentAgentId ? { agentId: currentAgentId } : {},
27034
+ isTeamMember
27035
+ };
27036
+ };
26403
27037
  server.registerTool(
26404
27038
  "memorix_store",
26405
27039
  {
26406
27040
  title: "Store Memory",
26407
- description: "Store a new observation/memory. Automatically indexed for search. Use type to classify: gotcha ([GOTCHA] critical pitfall), decision ([DECISION] architecture choice), problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), session-request ([SESSION] original goal). Stored memories persist across sessions and are shared with other IDEs and agents (Cursor, Windsurf, Claude Code, Codex, Copilot, Gemini CLI, OpenCode, OpenClaw, Hermes Agent, Oh-my-Pi, Kiro, Antigravity, Trae) via the same local data directory.",
27041
+ description: "Store a new observation/memory. Automatically indexed for search. Use type to classify: gotcha ([GOTCHA] critical pitfall), decision ([DECISION] architecture choice), problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), session-request ([SESSION] original goal). Project visibility is the default. Personal or team visibility requires an explicitly joined coordination identity.",
26408
27042
  inputSchema: {
26409
27043
  entityName: z2.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
26410
27044
  type: z2.enum(OBSERVATION_TYPES).describe("Observation type for classification"),
@@ -26422,361 +27056,214 @@ The path should point to a directory containing a .git folder.`
26422
27056
  completion: z2.number().optional().describe("Completion percentage 0-100")
26423
27057
  }).optional().describe("Progress tracking for task/feature observations"),
26424
27058
  relatedCommits: z2.array(z2.string()).optional().describe("Git commit hashes this memory relates to (links ground truth \u2194 reasoning)"),
26425
- relatedEntities: z2.array(z2.string()).optional().describe("Other entity names this memory cross-references")
27059
+ relatedEntities: z2.array(z2.string()).optional().describe("Other entity names this memory cross-references"),
27060
+ visibility: z2.enum(["personal", "project", "team"]).optional().describe(
27061
+ "Retrieval scope. Project is the normal shared default; personal/team require memorix_session_start with joinTeam=true."
27062
+ )
26426
27063
  }
26427
27064
  },
26428
- async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities }) => {
27065
+ async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility }) => {
26429
27066
  const unresolved = requireResolvedProject("store memory in the current project");
26430
27067
  if (unresolved) return unresolved;
26431
- return withFreshIndex(async () => {
26432
- let entityName = rawEntityName;
26433
- let type = rawType;
26434
- let title = rawTitle;
26435
- let safeFacts = facts ? coerceStringArray(facts) : void 0;
26436
- const safeFiles = filesModified ? coerceStringArray(filesModified) : void 0;
26437
- const safeConcepts = concepts ? coerceStringArray(concepts) : void 0;
26438
- let formationMode = "active";
26439
- if (process.env.MEMORIX_FORMATION_MODE) {
26440
- formationMode = process.env.MEMORIX_FORMATION_MODE;
26441
- } else {
26442
- try {
26443
- const { getBehaviorConfig: getBehaviorConfig2 } = await Promise.resolve().then(() => (init_behavior(), behavior_exports));
26444
- formationMode = getBehaviorConfig2({ projectRoot: project.rootPath }).formationMode;
26445
- } catch {
27068
+ const requestedVisibility = visibility ?? "project";
27069
+ const reader = getObservationReader();
27070
+ if (requestedVisibility !== "project" && !currentAgentId) {
27071
+ return {
27072
+ content: [{ type: "text", text: "Personal or team memory requires memorix_session_start with joinTeam=true so Memorix can bind an owner." }],
27073
+ isError: true
27074
+ };
27075
+ }
27076
+ if (requestedVisibility === "team" && !reader.isTeamMember) {
27077
+ return {
27078
+ content: [{ type: "text", text: "Team memory requires an active coordination membership in the current project." }],
27079
+ isError: true
27080
+ };
27081
+ }
27082
+ try {
27083
+ return await withFreshIndex(async () => {
27084
+ let entityName = rawEntityName;
27085
+ let type = rawType;
27086
+ let title = rawTitle;
27087
+ let safeFacts = facts ? coerceStringArray(facts) : void 0;
27088
+ const safeFiles = filesModified ? coerceStringArray(filesModified) : void 0;
27089
+ const safeConcepts = concepts ? coerceStringArray(concepts) : void 0;
27090
+ let formationMode = "active";
27091
+ if (process.env.MEMORIX_FORMATION_MODE) {
27092
+ formationMode = process.env.MEMORIX_FORMATION_MODE;
27093
+ } else {
27094
+ try {
27095
+ const { getBehaviorConfig: getBehaviorConfig2 } = await Promise.resolve().then(() => (init_behavior(), behavior_exports));
27096
+ formationMode = getBehaviorConfig2({ projectRoot: project.rootPath }).formationMode;
27097
+ } catch {
27098
+ }
26446
27099
  }
26447
- }
26448
- const useFormation = formationMode === "active";
26449
- let formationResult = null;
26450
- let formationNote = "";
26451
- if (useFormation && !topicKey && !progress) {
26452
- let currentFormationStage = "setup";
26453
- const completedFormationStages = {};
26454
- const formationStartTime = Date.now();
26455
- const onFormationStageEvent = (event) => {
26456
- if (event.status === "start") {
27100
+ const useFormation = formationMode === "active";
27101
+ let formationResult = null;
27102
+ let formationNote = "";
27103
+ if (useFormation && !topicKey && !progress) {
27104
+ let currentFormationStage = "setup";
27105
+ const completedFormationStages = {};
27106
+ const formationStartTime = Date.now();
27107
+ const onFormationStageEvent = (event) => {
27108
+ if (event.status === "start") {
27109
+ currentFormationStage = event.stage;
27110
+ return;
27111
+ }
26457
27112
  currentFormationStage = event.stage;
26458
- return;
26459
- }
26460
- currentFormationStage = event.stage;
26461
- if (event.stageDurationMs !== void 0) {
26462
- completedFormationStages[event.stage] = event.stageDurationMs;
26463
- }
26464
- };
26465
- try {
26466
- const formationConfig = {
26467
- mode: "active",
26468
- useLLM: isLLMEnabled(),
26469
- minValueScore: 0.3,
26470
- searchMemories: async (q, limit, pid) => {
26471
- const result = await compactSearch({ query: q, limit, projectId: pid, status: "active" });
26472
- if (result.entries.length === 0) return [];
26473
- const details = await compactDetail(result.entries.map((e) => e.id));
26474
- return details.documents.map((d, i) => ({
26475
- id: Number(d.id.replace("obs-", "")),
26476
- observationId: d.observationId,
26477
- title: d.title,
26478
- narrative: d.narrative,
26479
- facts: d.facts,
26480
- entityName: d.entityName,
26481
- type: d.type,
26482
- score: result.entries[i]?.score ?? 0
26483
- }));
26484
- },
26485
- getObservation: (id) => {
26486
- const o = getObservation(id);
26487
- if (!o) return null;
26488
- return {
26489
- id: o.id,
26490
- entityName: o.entityName,
26491
- type: o.type,
26492
- title: o.title,
26493
- narrative: o.narrative,
26494
- facts: o.facts,
26495
- topicKey: o.topicKey
26496
- };
26497
- },
26498
- getEntityNames: () => graphManager.getEntityNames(),
26499
- onStageEvent: onFormationStageEvent
27113
+ if (event.stageDurationMs !== void 0) {
27114
+ completedFormationStages[event.stage] = event.stageDurationMs;
27115
+ }
26500
27116
  };
26501
- formationResult = await withTimeout2(
26502
- runFormation({
26503
- entityName,
26504
- type,
26505
- title,
26506
- narrative,
26507
- facts: safeFacts,
26508
- projectId: project.id,
26509
- source: "explicit"
26510
- }, formationConfig),
26511
- FORMATION_TIMEOUT_MS,
26512
- "Formation pipeline"
26513
- );
26514
- const modeIcon = "[FAST]";
26515
- formationNote = `
27117
+ try {
27118
+ const formationConfig = {
27119
+ mode: "active",
27120
+ useLLM: isLLMEnabled(),
27121
+ minValueScore: 0.3,
27122
+ searchMemories: async (q, limit, pid) => {
27123
+ const result = await compactSearch({ query: q, limit, projectId: pid, status: "active", reader });
27124
+ if (result.entries.length === 0) return [];
27125
+ const details = await compactDetail(result.entries.map((e) => e.id), { reader });
27126
+ return details.documents.map((d, i) => ({
27127
+ id: Number(d.id.replace("obs-", "")),
27128
+ observationId: d.observationId,
27129
+ title: d.title,
27130
+ narrative: d.narrative,
27131
+ facts: d.facts,
27132
+ entityName: d.entityName,
27133
+ type: d.type,
27134
+ score: result.entries[i]?.score ?? 0
27135
+ }));
27136
+ },
27137
+ getObservation: (id) => {
27138
+ const o = getObservation(id);
27139
+ if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
27140
+ return {
27141
+ id: o.id,
27142
+ entityName: o.entityName,
27143
+ type: o.type,
27144
+ title: o.title,
27145
+ narrative: o.narrative,
27146
+ facts: o.facts,
27147
+ topicKey: o.topicKey
27148
+ };
27149
+ },
27150
+ getEntityNames: () => graphManager.getEntityNames(),
27151
+ onStageEvent: onFormationStageEvent
27152
+ };
27153
+ formationResult = await withTimeout2(
27154
+ runFormation({
27155
+ entityName,
27156
+ type,
27157
+ title,
27158
+ narrative,
27159
+ facts: safeFacts,
27160
+ projectId: project.id,
27161
+ source: "explicit"
27162
+ }, formationConfig),
27163
+ FORMATION_TIMEOUT_MS,
27164
+ "Formation pipeline"
27165
+ );
27166
+ const modeIcon = "[FAST]";
27167
+ formationNote = `
26516
27168
  ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formationResult.evaluation.score.toFixed(2)}) | ${formationResult.resolution.action} | ${formationResult.pipeline.durationMs}ms`;
26517
- if (formationResult.extraction.extractedFacts.length > 0) {
26518
- formationNote += ` | +${formationResult.extraction.extractedFacts.length} facts`;
26519
- }
26520
- if (formationResult.extraction.titleImproved) formationNote += " | title\u2191";
26521
- if (formationResult.extraction.entityResolved) formationNote += ` | entity\u2192${formationResult.entityName}`;
26522
- if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
26523
- } catch (formationErr) {
26524
- const isTimeout = formationErr instanceof Error && formationErr.message.includes("timed out");
26525
- const elapsedMs = Date.now() - formationStartTime;
26526
- const stageSummary = formatFormationStageDurations(completedFormationStages);
26527
- console.error(
26528
- `[memorix] Formation ${isTimeout ? "timed out" : "failed"} in memorix_store after ${elapsedMs}ms/${FORMATION_TIMEOUT_MS}ms at stage ${currentFormationStage}${stageSummary ? ` | completed: ${stageSummary}` : ""}`
26529
- );
26530
- if (!isTimeout && formationErr instanceof Error) {
26531
- console.error(`[memorix] Formation error: ${formationErr.message}`);
26532
- }
26533
- formationNote = `
27169
+ if (formationResult.extraction.extractedFacts.length > 0) {
27170
+ formationNote += ` | +${formationResult.extraction.extractedFacts.length} facts`;
27171
+ }
27172
+ if (formationResult.extraction.titleImproved) formationNote += " | title\u2191";
27173
+ if (formationResult.extraction.entityResolved) formationNote += ` | entity\u2192${formationResult.entityName}`;
27174
+ if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
27175
+ } catch (formationErr) {
27176
+ const isTimeout = formationErr instanceof Error && formationErr.message.includes("timed out");
27177
+ const elapsedMs = Date.now() - formationStartTime;
27178
+ const stageSummary = formatFormationStageDurations(completedFormationStages);
27179
+ console.error(
27180
+ `[memorix] Formation ${isTimeout ? "timed out" : "failed"} in memorix_store after ${elapsedMs}ms/${FORMATION_TIMEOUT_MS}ms at stage ${currentFormationStage}${stageSummary ? ` | completed: ${stageSummary}` : ""}`
27181
+ );
27182
+ if (!isTimeout && formationErr instanceof Error) {
27183
+ console.error(`[memorix] Formation error: ${formationErr.message}`);
27184
+ }
27185
+ formationNote = `
26534
27186
  [WARN] Formation ${isTimeout ? "timed out" : "failed"} \u2014 storing base observation without enrichment`;
26535
- }
26536
- }
26537
- if (useFormation && formationResult && formationResult.resolution.action !== "new") {
26538
- const { action: action2, targetId, reason } = formationResult.resolution;
26539
- if (action2 === "merge" && targetId) {
26540
- const targetObs = getObservation(targetId);
26541
- if (targetObs) {
26542
- await storeObservation({
26543
- entityName: targetObs.entityName,
26544
- type: targetObs.type,
26545
- title: formationResult.title,
26546
- narrative: formationResult.narrative,
26547
- facts: formationResult.facts,
26548
- filesModified: safeFiles,
26549
- concepts: safeConcepts,
26550
- projectId: project.id,
26551
- topicKey: targetObs.topicKey,
26552
- progress,
26553
- sourceDetail: "explicit",
26554
- createdByAgentId: currentAgentId
26555
- });
26556
- return {
26557
- content: [{
26558
- type: "text",
26559
- text: `[UPDATED] Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
26560
- }]
26561
- };
26562
- }
26563
- } else if (action2 === "evolve" && targetId) {
26564
- const targetObs = getObservation(targetId);
26565
- if (targetObs) {
26566
- await storeObservation({
26567
- entityName: targetObs.entityName,
26568
- type: targetObs.type,
26569
- title: formationResult.title,
26570
- narrative: formationResult.narrative,
26571
- facts: formationResult.facts,
26572
- filesModified: safeFiles,
26573
- concepts: safeConcepts,
26574
- projectId: project.id,
26575
- topicKey: targetObs.topicKey,
26576
- progress,
26577
- sourceDetail: "explicit",
26578
- createdByAgentId: currentAgentId
26579
- });
26580
- return {
26581
- content: [{
26582
- type: "text",
26583
- text: `[UPDATED] Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
26584
- }]
26585
- };
26586
27187
  }
26587
- } else if (action2 === "discard") {
26588
- return {
26589
- content: [{
26590
- type: "text",
26591
- text: `[SKIP] Formation DISCARD: ${reason}${formationNote}`
26592
- }]
26593
- };
26594
27188
  }
26595
- }
26596
- let compactAction = "";
26597
- let compactMerged = false;
26598
- if (!useFormation && !topicKey && !progress) {
26599
- try {
26600
- const searchResult = await compactSearch({
26601
- query: `${title} ${narrative.substring(0, 200)}`,
26602
- limit: 5,
26603
- projectId: project.id,
26604
- status: "active"
26605
- });
26606
- const similarEntries = searchResult.entries.map((e) => e);
26607
- if (similarEntries.length > 0) {
26608
- const similarIds = similarEntries.map((e) => e.id);
26609
- const details = await compactDetail(similarIds);
26610
- const existingMemories = details.documents.map((d, i) => ({
26611
- id: d.observationId,
26612
- title: d.title,
26613
- narrative: d.narrative,
26614
- facts: d.facts,
26615
- score: similarEntries[i]?.score ?? 0
26616
- }));
26617
- const decision = await withTimeout2(
26618
- compactOnWrite(
26619
- { title, narrative, facts: safeFacts ?? [] },
26620
- existingMemories
26621
- ),
26622
- COMPACT_ON_WRITE_TIMEOUT_MS,
26623
- "Compact-on-write"
26624
- );
26625
- if (decision.action === "UPDATE" && decision.targetId) {
26626
- const targetObs = getObservation(decision.targetId);
26627
- if (targetObs) {
26628
- await storeObservation({
26629
- entityName: targetObs.entityName,
26630
- type: targetObs.type,
26631
- title: decision.mergedNarrative ? title : targetObs.title,
26632
- narrative: decision.mergedNarrative ?? narrative,
26633
- facts: decision.mergedFacts ?? safeFacts,
26634
- filesModified: safeFiles,
26635
- concepts: safeConcepts,
26636
- projectId: project.id,
26637
- topicKey: targetObs.topicKey,
26638
- progress,
26639
- sourceDetail: "explicit",
26640
- createdByAgentId: currentAgentId
26641
- });
26642
- compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
26643
- compactMerged = true;
26644
- return {
26645
- content: [{
26646
- type: "text",
26647
- text: `${compactAction}
26648
- Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
26649
- }]
26650
- };
26651
- }
26652
- } else if (decision.action === "NONE") {
27189
+ if (useFormation && formationResult && formationResult.resolution.action !== "new") {
27190
+ const { action: action2, targetId, reason } = formationResult.resolution;
27191
+ if (action2 === "merge" && targetId) {
27192
+ const targetObs = getObservation(targetId);
27193
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27194
+ await storeObservation({
27195
+ entityName: targetObs.entityName,
27196
+ type: targetObs.type,
27197
+ title: formationResult.title,
27198
+ narrative: formationResult.narrative,
27199
+ facts: formationResult.facts,
27200
+ filesModified: safeFiles,
27201
+ concepts: safeConcepts,
27202
+ projectId: project.id,
27203
+ topicKey: targetObs.topicKey,
27204
+ progress,
27205
+ sourceDetail: "explicit",
27206
+ createdByAgentId: currentAgentId,
27207
+ visibility,
27208
+ visibilityReader: reader
27209
+ });
26653
27210
  return {
26654
27211
  content: [{
26655
27212
  type: "text",
26656
- text: `[SKIP] Compact SKIP: ${decision.reason}
26657
- Existing memory #${decision.targetId} already covers this.
26658
- Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27213
+ text: `[UPDATED] Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
26659
27214
  }]
26660
27215
  };
26661
- } else if (decision.action === "DELETE" && decision.targetId) {
26662
- const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
26663
- await resolveObservations2([decision.targetId], "resolved");
26664
- compactAction = ` | Compact: resolved outdated #${decision.targetId}`;
26665
27216
  }
26666
- if (decision.enrichedFacts && decision.enrichedFacts.length > 0) {
26667
- const currentFacts = safeFacts ?? [];
26668
- const newFacts = decision.enrichedFacts.filter((f) => !currentFacts.includes(f));
26669
- if (newFacts.length > 0) {
26670
- compactAction += ` | +${newFacts.length} LLM-extracted facts`;
26671
- }
27217
+ } else if (action2 === "evolve" && targetId) {
27218
+ const targetObs = getObservation(targetId);
27219
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27220
+ await storeObservation({
27221
+ entityName: targetObs.entityName,
27222
+ type: targetObs.type,
27223
+ title: formationResult.title,
27224
+ narrative: formationResult.narrative,
27225
+ facts: formationResult.facts,
27226
+ filesModified: safeFiles,
27227
+ concepts: safeConcepts,
27228
+ projectId: project.id,
27229
+ topicKey: targetObs.topicKey,
27230
+ progress,
27231
+ sourceDetail: "explicit",
27232
+ createdByAgentId: currentAgentId,
27233
+ visibility,
27234
+ visibilityReader: reader
27235
+ });
27236
+ return {
27237
+ content: [{
27238
+ type: "text",
27239
+ text: `[UPDATED] Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
27240
+ }]
27241
+ };
26672
27242
  }
27243
+ } else if (action2 === "discard") {
27244
+ return {
27245
+ content: [{
27246
+ type: "text",
27247
+ text: `[SKIP] Formation DISCARD: ${reason}${formationNote}`
27248
+ }]
27249
+ };
26673
27250
  }
26674
- } catch {
26675
- }
26676
- }
26677
- if (formationResult && formationResult.resolution.action === "new") {
26678
- const llmFacts = formationResult.extraction.extractedFacts;
26679
- if (llmFacts.length > 0) {
26680
- const currentFacts = safeFacts ?? [];
26681
- const currentLower = new Set(currentFacts.map((f) => f.toLowerCase().trim()));
26682
- const newFacts = llmFacts.filter((f) => !currentLower.has(f.toLowerCase().trim()));
26683
- if (newFacts.length > 0) {
26684
- safeFacts = [...currentFacts, ...newFacts];
26685
- }
26686
- }
26687
- if (formationResult.extraction.titleImproved && formationResult.title) {
26688
- title = formationResult.title;
26689
- }
26690
- if (formationResult.extraction.typeCorrected && formationResult.type) {
26691
- type = formationResult.type;
26692
- }
26693
- if (formationResult.extraction.entityResolved && formationResult.entityName) {
26694
- entityName = formationResult.entityName;
26695
- }
26696
- }
26697
- await graphManager.createEntities([
26698
- { name: entityName, entityType: "auto", observations: [] }
26699
- ]);
26700
- let sessionId;
26701
- try {
26702
- const { getActiveSession: getActiveSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
26703
- const active = await getActiveSession2(projectDir2, project.id);
26704
- if (active) sessionId = active.id;
26705
- } catch {
26706
- }
26707
- let finalNarrative = narrative;
26708
- let compressionNote = "";
26709
- try {
26710
- const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
26711
- const { compressed, saved, usedLLM } = await withTimeout2(
26712
- compressNarrative2(narrative, safeFacts, type),
26713
- COMPRESSION_TIMEOUT_MS,
26714
- "Narrative compression"
26715
- );
26716
- if (usedLLM && saved > 0) {
26717
- finalNarrative = compressed;
26718
- compressionNote = ` | compressed -${saved} tokens`;
26719
27251
  }
26720
- } catch {
26721
- }
26722
- let attributionWarning = "";
26723
- try {
26724
- const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
26725
- if (attrCheck.suspicious) {
26726
- attributionWarning = `
26727
- [WARN] Attribution notice: entity "${entityName}" has 0 observations in "${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" (confidence: ${attrCheck.confidence}). Verify the correct project is bound before storing.`;
26728
- }
26729
- } catch {
26730
- }
26731
- const { observation: obs, upserted } = await storeObservation({
26732
- entityName,
26733
- type,
26734
- title,
26735
- narrative: finalNarrative,
26736
- facts: safeFacts,
26737
- filesModified: safeFiles,
26738
- concepts: safeConcepts,
26739
- projectId: project.id,
26740
- topicKey,
26741
- sessionId,
26742
- progress,
26743
- relatedCommits,
26744
- relatedEntities,
26745
- sourceDetail: "explicit",
26746
- valueCategory: formationResult?.evaluation.category,
26747
- createdByAgentId: currentAgentId
26748
- });
26749
- await graphManager.addObservations([
26750
- { entityName, contents: [`[#${obs.id}] ${title}`] }
26751
- ]);
26752
- const extracted = extractEntities([title, narrative, ...safeFacts ?? []].join(" "));
26753
- const autoRelCount = await createAutoRelations(obs, extracted, graphManager);
26754
- const enrichmentParts = [];
26755
- const autoFiles = obs.filesModified.filter((f) => !(safeFiles ?? []).includes(f));
26756
- const autoConcepts = obs.concepts.filter((c) => !(safeConcepts ?? []).includes(c));
26757
- if (autoFiles.length > 0) enrichmentParts.push(`+${autoFiles.length} files extracted`);
26758
- if (autoConcepts.length > 0) enrichmentParts.push(`+${autoConcepts.length} concepts enriched`);
26759
- if (autoRelCount > 0) enrichmentParts.push(`+${autoRelCount} relations auto-created`);
26760
- if (obs.hasCausalLanguage) enrichmentParts.push("causal language detected");
26761
- if (upserted) enrichmentParts.push(`topic upserted (rev ${obs.revisionCount ?? 1})`);
26762
- const enrichment = enrichmentParts.length > 0 ? `
26763
- Auto-enriched: ${enrichmentParts.join(", ")}` : "";
26764
- const action = upserted ? "[UPDATED] Updated" : "[OK] Stored";
26765
- if (!useFormation && !topicKey && !progress) {
26766
- const shadowFormation = async () => {
26767
- let oldCompactDecision = null;
27252
+ let compactAction = "";
27253
+ let compactMerged = false;
27254
+ if (!useFormation && !topicKey && !progress) {
26768
27255
  try {
26769
- const compactStart = Date.now();
26770
27256
  const searchResult = await compactSearch({
26771
27257
  query: `${title} ${narrative.substring(0, 200)}`,
26772
27258
  limit: 5,
26773
27259
  projectId: project.id,
26774
- status: "active"
27260
+ status: "active",
27261
+ reader
26775
27262
  });
26776
27263
  const similarEntries = searchResult.entries.map((e) => e);
26777
27264
  if (similarEntries.length > 0) {
26778
27265
  const similarIds = similarEntries.map((e) => e.id);
26779
- const details = await compactDetail(similarIds);
27266
+ const details = await compactDetail(similarIds, { reader });
26780
27267
  const existingMemories = details.documents.map((d, i) => ({
26781
27268
  id: d.observationId,
26782
27269
  title: d.title,
@@ -26784,78 +27271,266 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
26784
27271
  facts: d.facts,
26785
27272
  score: similarEntries[i]?.score ?? 0
26786
27273
  }));
26787
- const decision = await compactOnWrite(
26788
- { title, narrative, facts: safeFacts ?? [] },
26789
- existingMemories
27274
+ const decision = await withTimeout2(
27275
+ compactOnWrite(
27276
+ { title, narrative, facts: safeFacts ?? [] },
27277
+ existingMemories
27278
+ ),
27279
+ COMPACT_ON_WRITE_TIMEOUT_MS,
27280
+ "Compact-on-write"
26790
27281
  );
26791
- oldCompactDecision = {
26792
- action: decision.action,
26793
- targetId: decision.targetId,
26794
- reason: decision.reason,
26795
- durationMs: Date.now() - compactStart
26796
- };
27282
+ if (decision.action === "UPDATE" && decision.targetId) {
27283
+ const targetObs = getObservation(decision.targetId);
27284
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27285
+ await storeObservation({
27286
+ entityName: targetObs.entityName,
27287
+ type: targetObs.type,
27288
+ title: decision.mergedNarrative ? title : targetObs.title,
27289
+ narrative: decision.mergedNarrative ?? narrative,
27290
+ facts: decision.mergedFacts ?? safeFacts,
27291
+ filesModified: safeFiles,
27292
+ concepts: safeConcepts,
27293
+ projectId: project.id,
27294
+ topicKey: targetObs.topicKey,
27295
+ progress,
27296
+ sourceDetail: "explicit",
27297
+ createdByAgentId: currentAgentId,
27298
+ visibility,
27299
+ visibilityReader: reader
27300
+ });
27301
+ compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
27302
+ compactMerged = true;
27303
+ return {
27304
+ content: [{
27305
+ type: "text",
27306
+ text: `${compactAction}
27307
+ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27308
+ }]
27309
+ };
27310
+ }
27311
+ } else if (decision.action === "NONE") {
27312
+ return {
27313
+ content: [{
27314
+ type: "text",
27315
+ text: `[SKIP] Compact SKIP: ${decision.reason}
27316
+ Existing memory #${decision.targetId} already covers this.
27317
+ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27318
+ }]
27319
+ };
27320
+ } else if (decision.action === "DELETE" && decision.targetId) {
27321
+ const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27322
+ await resolveObservations2([decision.targetId], "resolved");
27323
+ compactAction = ` | Compact: resolved outdated #${decision.targetId}`;
27324
+ }
27325
+ if (decision.enrichedFacts && decision.enrichedFacts.length > 0) {
27326
+ const currentFacts = safeFacts ?? [];
27327
+ const newFacts = decision.enrichedFacts.filter((f) => !currentFacts.includes(f));
27328
+ if (newFacts.length > 0) {
27329
+ compactAction += ` | +${newFacts.length} LLM-extracted facts`;
27330
+ }
27331
+ }
26797
27332
  }
26798
27333
  } catch {
26799
27334
  }
26800
- const formationConfig = {
26801
- mode: formationMode,
26802
- useLLM: isLLMEnabled(),
26803
- minValueScore: 0.3,
26804
- searchMemories: async (q, limit, pid) => {
26805
- const result = await compactSearch({ query: q, limit, projectId: pid, status: "active" });
26806
- if (result.entries.length === 0) return [];
26807
- const details = await compactDetail(result.entries.map((e) => e.id));
26808
- return details.documents.map((d, i) => ({
26809
- id: Number(d.id.replace("obs-", "")),
26810
- observationId: d.observationId,
26811
- title: d.title,
26812
- narrative: d.narrative,
26813
- facts: d.facts,
26814
- entityName: d.entityName,
26815
- type: d.type,
26816
- score: result.entries[i]?.score ?? 0
26817
- }));
26818
- },
26819
- getObservation: (id) => {
26820
- const o = getObservation(id);
26821
- if (!o) return null;
26822
- return { id: o.id, entityName: o.entityName, type: o.type, title: o.title, narrative: o.narrative, facts: o.facts, topicKey: o.topicKey };
26823
- },
26824
- getEntityNames: () => graphManager.getEntityNames()
26825
- };
26826
- const formed = await withTimeout2(
26827
- runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
26828
- FORMATION_TIMEOUT_MS,
26829
- "Shadow formation"
27335
+ }
27336
+ if (formationResult && formationResult.resolution.action === "new") {
27337
+ const llmFacts = formationResult.extraction.extractedFacts;
27338
+ if (llmFacts.length > 0) {
27339
+ const currentFacts = safeFacts ?? [];
27340
+ const currentLower = new Set(currentFacts.map((f) => f.toLowerCase().trim()));
27341
+ const newFacts = llmFacts.filter((f) => !currentLower.has(f.toLowerCase().trim()));
27342
+ if (newFacts.length > 0) {
27343
+ safeFacts = [...currentFacts, ...newFacts];
27344
+ }
27345
+ }
27346
+ if (formationResult.extraction.titleImproved && formationResult.title) {
27347
+ title = formationResult.title;
27348
+ }
27349
+ if (formationResult.extraction.typeCorrected && formationResult.type) {
27350
+ type = formationResult.type;
27351
+ }
27352
+ if (formationResult.extraction.entityResolved && formationResult.entityName) {
27353
+ entityName = formationResult.entityName;
27354
+ }
27355
+ }
27356
+ await graphManager.createEntities([
27357
+ { name: entityName, entityType: "auto", observations: [] }
27358
+ ]);
27359
+ let sessionId;
27360
+ try {
27361
+ const { getActiveSession: getActiveSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
27362
+ const active = await getActiveSession2(projectDir2, project.id);
27363
+ if (active) sessionId = active.id;
27364
+ } catch {
27365
+ }
27366
+ let finalNarrative = narrative;
27367
+ let compressionNote = "";
27368
+ try {
27369
+ const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
27370
+ const { compressed, saved, usedLLM } = await withTimeout2(
27371
+ compressNarrative2(narrative, safeFacts, type),
27372
+ COMPRESSION_TIMEOUT_MS,
27373
+ "Narrative compression"
26830
27374
  );
26831
- const { recordBeforeAfterMetrics: recordBeforeAfterMetrics2 } = await Promise.resolve().then(() => (init_formation(), formation_exports));
26832
- if (oldCompactDecision) {
26833
- recordBeforeAfterMetrics2({
26834
- formationAction: formed.resolution.action,
26835
- formationTargetId: formed.resolution.targetId,
26836
- oldCompactAction: oldCompactDecision.action,
26837
- oldCompactTargetId: oldCompactDecision.targetId,
26838
- oldCompactReason: oldCompactDecision.reason,
26839
- formationValueScore: formed.evaluation.score,
26840
- formationValueCategory: formed.evaluation.category,
26841
- formationDurationMs: formed.pipeline.durationMs,
26842
- compactDurationMs: oldCompactDecision.durationMs
26843
- });
27375
+ if (usedLLM && saved > 0) {
27376
+ finalNarrative = compressed;
27377
+ compressionNote = ` | compressed -${saved} tokens`;
26844
27378
  }
26845
- };
26846
- shadowFormation().catch(() => {
27379
+ } catch {
27380
+ }
27381
+ let attributionWarning = "";
27382
+ try {
27383
+ const attrCheck = await checkProjectAttribution(
27384
+ entityName,
27385
+ project.id,
27386
+ filterReadableObservations(getAllObservations(), reader)
27387
+ );
27388
+ if (attrCheck.suspicious) {
27389
+ attributionWarning = `
27390
+ [WARN] Attribution notice: entity "${entityName}" has 0 observations in "${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" (confidence: ${attrCheck.confidence}). Verify the correct project is bound before storing.`;
27391
+ }
27392
+ } catch {
27393
+ }
27394
+ const { observation: obs, upserted } = await storeObservation({
27395
+ entityName,
27396
+ type,
27397
+ title,
27398
+ narrative: finalNarrative,
27399
+ facts: safeFacts,
27400
+ filesModified: safeFiles,
27401
+ concepts: safeConcepts,
27402
+ projectId: project.id,
27403
+ topicKey,
27404
+ sessionId,
27405
+ progress,
27406
+ relatedCommits,
27407
+ relatedEntities,
27408
+ sourceDetail: "explicit",
27409
+ valueCategory: formationResult?.evaluation.category,
27410
+ createdByAgentId: currentAgentId,
27411
+ visibility,
27412
+ visibilityReader: reader
26847
27413
  });
26848
- }
26849
- return {
26850
- content: [
26851
- {
26852
- type: "text",
26853
- text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
27414
+ await graphManager.addObservations([
27415
+ { entityName, contents: [`[#${obs.id}] ${title}`] }
27416
+ ]);
27417
+ const extracted = extractEntities([title, narrative, ...safeFacts ?? []].join(" "));
27418
+ const autoRelCount = await createAutoRelations(obs, extracted, graphManager);
27419
+ const enrichmentParts = [];
27420
+ const autoFiles = obs.filesModified.filter((f) => !(safeFiles ?? []).includes(f));
27421
+ const autoConcepts = obs.concepts.filter((c) => !(safeConcepts ?? []).includes(c));
27422
+ if (autoFiles.length > 0) enrichmentParts.push(`+${autoFiles.length} files extracted`);
27423
+ if (autoConcepts.length > 0) enrichmentParts.push(`+${autoConcepts.length} concepts enriched`);
27424
+ if (autoRelCount > 0) enrichmentParts.push(`+${autoRelCount} relations auto-created`);
27425
+ if (obs.hasCausalLanguage) enrichmentParts.push("causal language detected");
27426
+ if (upserted) enrichmentParts.push(`topic upserted (rev ${obs.revisionCount ?? 1})`);
27427
+ const enrichment = enrichmentParts.length > 0 ? `
27428
+ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
27429
+ const action = upserted ? "[UPDATED] Updated" : "[OK] Stored";
27430
+ if (!useFormation && !topicKey && !progress) {
27431
+ const shadowFormation = async () => {
27432
+ let oldCompactDecision = null;
27433
+ try {
27434
+ const compactStart = Date.now();
27435
+ const searchResult = await compactSearch({
27436
+ query: `${title} ${narrative.substring(0, 200)}`,
27437
+ limit: 5,
27438
+ projectId: project.id,
27439
+ status: "active",
27440
+ reader
27441
+ });
27442
+ const similarEntries = searchResult.entries.map((e) => e);
27443
+ if (similarEntries.length > 0) {
27444
+ const similarIds = similarEntries.map((e) => e.id);
27445
+ const details = await compactDetail(similarIds, { reader });
27446
+ const existingMemories = details.documents.map((d, i) => ({
27447
+ id: d.observationId,
27448
+ title: d.title,
27449
+ narrative: d.narrative,
27450
+ facts: d.facts,
27451
+ score: similarEntries[i]?.score ?? 0
27452
+ }));
27453
+ const decision = await compactOnWrite(
27454
+ { title, narrative, facts: safeFacts ?? [] },
27455
+ existingMemories
27456
+ );
27457
+ oldCompactDecision = {
27458
+ action: decision.action,
27459
+ targetId: decision.targetId,
27460
+ reason: decision.reason,
27461
+ durationMs: Date.now() - compactStart
27462
+ };
27463
+ }
27464
+ } catch {
27465
+ }
27466
+ const formationConfig = {
27467
+ mode: formationMode,
27468
+ useLLM: isLLMEnabled(),
27469
+ minValueScore: 0.3,
27470
+ searchMemories: async (q, limit, pid) => {
27471
+ const result = await compactSearch({ query: q, limit, projectId: pid, status: "active", reader });
27472
+ if (result.entries.length === 0) return [];
27473
+ const details = await compactDetail(result.entries.map((e) => e.id), { reader });
27474
+ return details.documents.map((d, i) => ({
27475
+ id: Number(d.id.replace("obs-", "")),
27476
+ observationId: d.observationId,
27477
+ title: d.title,
27478
+ narrative: d.narrative,
27479
+ facts: d.facts,
27480
+ entityName: d.entityName,
27481
+ type: d.type,
27482
+ score: result.entries[i]?.score ?? 0
27483
+ }));
27484
+ },
27485
+ getObservation: (id) => {
27486
+ const o = getObservation(id);
27487
+ if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
27488
+ return { id: o.id, entityName: o.entityName, type: o.type, title: o.title, narrative: o.narrative, facts: o.facts, topicKey: o.topicKey };
27489
+ },
27490
+ getEntityNames: () => graphManager.getEntityNames()
27491
+ };
27492
+ const formed = await withTimeout2(
27493
+ runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
27494
+ FORMATION_TIMEOUT_MS,
27495
+ "Shadow formation"
27496
+ );
27497
+ const { recordBeforeAfterMetrics: recordBeforeAfterMetrics2 } = await Promise.resolve().then(() => (init_formation(), formation_exports));
27498
+ if (oldCompactDecision) {
27499
+ recordBeforeAfterMetrics2({
27500
+ formationAction: formed.resolution.action,
27501
+ formationTargetId: formed.resolution.targetId,
27502
+ oldCompactAction: oldCompactDecision.action,
27503
+ oldCompactTargetId: oldCompactDecision.targetId,
27504
+ oldCompactReason: oldCompactDecision.reason,
27505
+ formationValueScore: formed.evaluation.score,
27506
+ formationValueCategory: formed.evaluation.category,
27507
+ formationDurationMs: formed.pipeline.durationMs,
27508
+ compactDurationMs: oldCompactDecision.durationMs
27509
+ });
27510
+ }
27511
+ };
27512
+ shadowFormation().catch(() => {
27513
+ });
27514
+ }
27515
+ return {
27516
+ content: [
27517
+ {
27518
+ type: "text",
27519
+ text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
26854
27520
  Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}${formationNote}${attributionWarning}`
26855
- }
26856
- ]
27521
+ }
27522
+ ]
27523
+ };
27524
+ });
27525
+ } catch (error) {
27526
+ return {
27527
+ content: [{
27528
+ type: "text",
27529
+ text: error instanceof Error ? error.message : "Failed to store memory."
27530
+ }],
27531
+ isError: true
26857
27532
  };
26858
- });
27533
+ }
26859
27534
  }
26860
27535
  );
26861
27536
  server.registerTool(
@@ -26927,7 +27602,8 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
26927
27602
  // Use scope: 'global' to explicitly search all projects.
26928
27603
  projectId: scope === "global" ? void 0 : project.id,
26929
27604
  status: status ?? "active",
26930
- source
27605
+ source,
27606
+ reader: getObservationReader(scope === "global" ? "global" : "project")
26931
27607
  });
26932
27608
  const timeoutPromise = new Promise(
26933
27609
  (_, reject) => setTimeout(() => reject(new Error(`Search timeout after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
@@ -26985,7 +27661,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
26985
27661
  const unresolved = requireResolvedProject("build graph context for the current project");
26986
27662
  if (unresolved) return unresolved;
26987
27663
  const { getObservationStore: getObservationStore2 } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
26988
- const observations2 = await getObservationStore2().loadByProject(project.id);
27664
+ const observations2 = filterReadableObservations(
27665
+ await getObservationStore2().loadByProject(project.id),
27666
+ getObservationReader()
27667
+ );
26989
27668
  const packet = buildGraphContextPacket(observations2, {
26990
27669
  projectId: project.id,
26991
27670
  query,
@@ -27037,7 +27716,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27037
27716
  Promise.resolve().then(() => (init_maintenance_jobs(), maintenance_jobs_exports)),
27038
27717
  Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports))
27039
27718
  ]);
27040
- const observations2 = await getObservationStore2().loadByProject(project.id, { status: "active" });
27719
+ const observations2 = filterReadableObservations(
27720
+ await getObservationStore2().loadByProject(project.id, { status: "active" }),
27721
+ getObservationReader()
27722
+ );
27041
27723
  const context = await buildAutoProjectContext2({
27042
27724
  project,
27043
27725
  dataDir: projectDir2,
@@ -27127,7 +27809,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27127
27809
  await store.init(projectDir2);
27128
27810
  const codegraphConfig = getResolvedConfig2({ projectRoot: project.rootPath }).codegraph;
27129
27811
  const exclude = codegraphConfig.excludePatterns;
27130
- const observations2 = await getObservationStore2().loadByProject(project.id, { status: "active" });
27812
+ const observations2 = filterReadableObservations(
27813
+ await getObservationStore2().loadByProject(project.id, { status: "active" }),
27814
+ getObservationReader()
27815
+ );
27131
27816
  observations2.reverse();
27132
27817
  const basePack = assembleContextPackForTask2({
27133
27818
  store,
@@ -27499,9 +28184,15 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27499
28184
  }
27500
28185
  },
27501
28186
  async ({ ids, status }) => {
27502
- const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28187
+ const { resolveObservations: resolveObservations2, getObservation: getObservation2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27503
28188
  const safeIds = (Array.isArray(ids) ? ids : [ids]).map((id) => coerceNumber(id, 0)).filter((id) => id > 0);
27504
- const result = await resolveObservations2(safeIds, status ?? "resolved");
28189
+ const reader = getObservationReader();
28190
+ const authorizedIds = safeIds.filter((id) => {
28191
+ const observation = getObservation2(id, project.id);
28192
+ return observation ? canManageObservation(observation, reader) : false;
28193
+ });
28194
+ const result = await resolveObservations2(authorizedIds, status ?? "resolved");
28195
+ const deniedCount = safeIds.length - authorizedIds.length;
27505
28196
  const parts = [];
27506
28197
  if (result.resolved.length > 0) {
27507
28198
  parts.push(`[OK] Resolved ${result.resolved.length} observation(s): #${result.resolved.join(", #")}`);
@@ -27509,6 +28200,9 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27509
28200
  if (result.notFound.length > 0) {
27510
28201
  parts.push(`[WARN] Not found: #${result.notFound.join(", #")}`);
27511
28202
  }
28203
+ if (deniedCount > 0) {
28204
+ parts.push(`[WARN] Skipped ${deniedCount} observation(s) outside this session's write scope.`);
28205
+ }
27512
28206
  parts.push('\nResolved memories are hidden from default search. Use status="all" to include them.');
27513
28207
  parts.push('[STATS] Run `memorix_retention` with `action: "report"` to check remaining cleanup status.');
27514
28208
  return {
@@ -27538,6 +28232,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27538
28232
  async ({ entityName, decision, alternatives, rationale, constraints, expectedOutcome, risks, concepts, filesModified, relatedCommits, relatedEntities }) => {
27539
28233
  const unresolved = requireResolvedProject("store reasoning in the current project");
27540
28234
  if (unresolved) return unresolved;
28235
+ const reader = getObservationReader();
27541
28236
  return withFreshIndex(async () => {
27542
28237
  const narrativeParts = [rationale];
27543
28238
  if (alternatives && alternatives.length > 0) {
@@ -27560,7 +28255,11 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27560
28255
  ]);
27561
28256
  let reasoningAttributionWarning = "";
27562
28257
  try {
27563
- const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
28258
+ const attrCheck = await checkProjectAttribution(
28259
+ entityName,
28260
+ project.id,
28261
+ filterReadableObservations(getAllObservations(), reader)
28262
+ );
27564
28263
  if (attrCheck.suspicious) {
27565
28264
  reasoningAttributionWarning = `
27566
28265
  [WARN] Attribution notice: entity "${entityName}" has 0 observations in "${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" (confidence: ${attrCheck.confidence}). Verify the correct project is bound before storing.`;
@@ -27580,7 +28279,8 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27580
28279
  relatedCommits,
27581
28280
  relatedEntities,
27582
28281
  sourceDetail: "explicit",
27583
- createdByAgentId: currentAgentId
28282
+ createdByAgentId: currentAgentId,
28283
+ visibility: "project"
27584
28284
  });
27585
28285
  await graphManager.addObservations([
27586
28286
  { entityName, contents: [`[#${obs.id}] [REASONING] ${decision}`] }
@@ -27612,7 +28312,11 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
27612
28312
  const minCount = threshold ?? 2;
27613
28313
  let entries;
27614
28314
  try {
27615
- entries = await auditProjectObservations(project.id, await withFreshIndex(() => getAllObservations()), minCount);
28315
+ entries = await auditProjectObservations(
28316
+ project.id,
28317
+ await withFreshIndex(() => filterReadableObservations(getAllObservations(), getObservationReader())),
28318
+ minCount
28319
+ );
27616
28320
  } catch (err) {
27617
28321
  return {
27618
28322
  content: [{
@@ -27677,7 +28381,8 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
27677
28381
  limit: safeLimit,
27678
28382
  type: "reasoning",
27679
28383
  projectId: scope === "global" ? void 0 : project.id,
27680
- status: "active"
28384
+ status: "active",
28385
+ reader: getObservationReader(scope === "global" ? "global" : "project")
27681
28386
  }));
27682
28387
  if (result.entries.length === 0) {
27683
28388
  return {
@@ -27702,7 +28407,11 @@ ${result.formatted}` }]
27702
28407
  },
27703
28408
  async ({ query, dryRun }) => {
27704
28409
  const { getAllObservations: getAllObservations2, resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27705
- const allObs = await withFreshIndex(() => getAllObservations2().filter((o) => (o.status ?? "active") === "active" && o.projectId === project.id));
28410
+ const reader = getObservationReader();
28411
+ const allObs = await withFreshIndex(() => filterReadableObservations(
28412
+ getAllObservations2().filter((o) => (o.status ?? "active") === "active" && o.projectId === project.id),
28413
+ reader
28414
+ ).filter((observation) => canManageObservation(observation, reader)));
27706
28415
  if (allObs.length < 2) {
27707
28416
  return { content: [{ type: "text", text: "Not enough active memories to deduplicate." }] };
27708
28417
  }
@@ -27716,7 +28425,7 @@ ${result.formatted}` }]
27716
28425
  }
27717
28426
  let candidates;
27718
28427
  if (query) {
27719
- const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: "active" });
28428
+ const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: "active", reader });
27720
28429
  const idSet = new Set(searchResult.entries.map((e) => e.id));
27721
28430
  candidates = allObs.filter((o) => idSet.has(o.id));
27722
28431
  } else {
@@ -27806,7 +28515,8 @@ ${actions.join("\n")}`
27806
28515
  safeAnchor,
27807
28516
  project.id,
27808
28517
  safeBefore,
27809
- safeAfter
28518
+ safeAfter,
28519
+ getObservationReader()
27810
28520
  );
27811
28521
  return {
27812
28522
  content: [
@@ -27840,12 +28550,14 @@ ${actions.join("\n")}`
27840
28550
  const safeTypedRefs = coerceStringArray(typedRefs);
27841
28551
  let result;
27842
28552
  try {
28553
+ const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id) || safeTypedRefs.some((ref) => ref.includes("@") && !ref.endsWith(`@${project.id}`));
28554
+ const reader = getObservationReader(hasCrossProjectRef ? "global" : "project");
27843
28555
  if (safeTypedRefs.length > 0) {
27844
- result = await compactDetail(safeTypedRefs);
28556
+ result = await compactDetail(safeTypedRefs, { reader });
27845
28557
  } else if (safeRefs.length > 0) {
27846
- result = await compactDetail(safeRefs);
28558
+ result = await compactDetail(safeRefs, { reader });
27847
28559
  } else {
27848
- result = await compactDetail(safeIds.map((id) => ({ id, projectId: project.id })));
28560
+ result = await compactDetail(safeIds.map((id) => ({ id, projectId: project.id })), { reader });
27849
28561
  }
27850
28562
  } catch (err) {
27851
28563
  return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
@@ -27871,11 +28583,15 @@ ${actions.join("\n")}`
27871
28583
  },
27872
28584
  async (args) => {
27873
28585
  const action = args.action ?? "report";
27874
- const { getRetentionSummary: getRetentionSummary2, getArchiveCandidates: getArchiveCandidates2, rankByRelevance: rankByRelevance2, archiveExpired: archiveExpired2, getRetentionZone: getRetentionZone2, explainRetention: explainRetention2 } = await Promise.resolve().then(() => (init_retention(), retention_exports));
28586
+ const { getRetentionSummary: getRetentionSummary2, getArchiveCandidates: getArchiveCandidates2, rankByRelevance: rankByRelevance2, getRetentionZone: getRetentionZone2, explainRetention: explainRetention2 } = await Promise.resolve().then(() => (init_retention(), retention_exports));
27875
28587
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
27876
28588
  const { search: search2 } = await import("@orama/orama");
27877
- const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27878
- const allObs = await withFreshIndex(() => getAllObservations2());
28589
+ const { getAllObservations: getAllObservations2, resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28590
+ const reader = getObservationReader();
28591
+ const allObs = await withFreshIndex(() => filterReadableObservations(
28592
+ getAllObservations2().filter((observation) => observation.projectId === project.id),
28593
+ reader
28594
+ ));
27879
28595
  const accessMap = /* @__PURE__ */ new Map();
27880
28596
  try {
27881
28597
  const database = await getDb2();
@@ -27892,20 +28608,6 @@ ${actions.join("\n")}`
27892
28608
  }
27893
28609
  } catch {
27894
28610
  }
27895
- if (action === "archive") {
27896
- const result = await archiveExpired2(projectDir2, void 0, accessMap, project.id);
27897
- if (result.archived === 0) {
27898
- return {
27899
- content: [{ type: "text", text: "[OK] No expired observations to archive. All memories are within their retention period." }]
27900
- };
27901
- }
27902
- return {
27903
- content: [{ type: "text", text: `[ARCHIVED] Archived ${result.archived} expired observations (status set to 'archived' in-place)
27904
- ${result.remaining} active observations remaining.
27905
-
27906
- Archived memories are hidden from default search but can be found with status: "all".` }]
27907
- };
27908
- }
27909
28611
  const docs = allObs.map((obs) => ({
27910
28612
  id: `obs-${obs.id}`,
27911
28613
  observationId: obs.id,
@@ -27924,13 +28626,34 @@ Archived memories are hidden from default search but can be found with status: "
27924
28626
  status: obs.status ?? "active",
27925
28627
  source: obs.source ?? "agent",
27926
28628
  sourceDetail: obs.sourceDetail ?? "",
27927
- valueCategory: obs.valueCategory ?? ""
28629
+ valueCategory: obs.valueCategory ?? "",
28630
+ admissionState: obs.admissionState ?? "",
28631
+ admissionReason: obs.admissionReason ?? "",
28632
+ visibility: obs.visibility ?? "project",
28633
+ createdByAgentId: obs.createdByAgentId ?? "",
28634
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
27928
28635
  }));
27929
28636
  if (docs.length === 0) {
27930
28637
  return {
27931
28638
  content: [{ type: "text", text: "No observations found for this project." }]
27932
28639
  };
27933
28640
  }
28641
+ if (action === "archive") {
28642
+ const managedIds = new Set(
28643
+ allObs.filter((observation) => canManageObservation(observation, reader)).map((observation) => observation.id)
28644
+ );
28645
+ const candidates2 = getArchiveCandidates2(docs).filter((document) => managedIds.has(document.observationId));
28646
+ if (candidates2.length === 0) {
28647
+ return {
28648
+ content: [{ type: "text", text: "[OK] No expired memories in this session's write scope to archive." }]
28649
+ };
28650
+ }
28651
+ const result = await resolveObservations2(candidates2.map((document) => document.observationId), "archived");
28652
+ return {
28653
+ content: [{ type: "text", text: `[ARCHIVED] Archived ${result.resolved.length} expired observation(s) in this session's write scope.
28654
+ ${Math.max(0, managedIds.size - result.resolved.length)} visible writable observations remaining.` }]
28655
+ };
28656
+ }
27934
28657
  if (action === "stale") {
27935
28658
  const staleDocs = docs.filter((d) => getRetentionZone2(d) === "stale");
27936
28659
  if (staleDocs.length === 0) {
@@ -28251,7 +28974,10 @@ Archived memories are hidden from default search but can be found with status: "
28251
28974
  const allObs = await withFreshIndex(() => getAllObservations2());
28252
28975
  const scoped = scopeKnowledgeGraphToProject(
28253
28976
  graph,
28254
- allObs.filter((observation) => observation.projectId === project.id)
28977
+ filterReadableObservations(
28978
+ allObs.filter((observation) => observation.projectId === project.id),
28979
+ getObservationReader()
28980
+ )
28255
28981
  );
28256
28982
  return { entities: scoped.entities, relations: scoped.relations };
28257
28983
  }
@@ -28526,7 +29252,10 @@ ${skill2.content}` }]
28526
29252
  };
28527
29253
  }
28528
29254
  const { getObservationStore: getStore } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
28529
- const allObs = await getStore().loadAll();
29255
+ const allObs = filterReadableObservations(
29256
+ (await getStore().loadAll()).filter((observation) => observation.projectId === project.id),
29257
+ getObservationReader()
29258
+ );
28530
29259
  const obsData = allObs.map((o) => ({
28531
29260
  id: o.id || 0,
28532
29261
  entityName: o.entityName || "unknown",
@@ -28625,7 +29354,11 @@ ${skill2.content}` }]
28625
29354
  }
28626
29355
  const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28627
29356
  const allObs = await withFreshIndex(() => getAllObservations2());
28628
- const matched = allObs.filter((o) => observationIds.includes(o.id));
29357
+ const reader = getObservationReader();
29358
+ const matched = filterReadableObservations(
29359
+ allObs.filter((observation) => observation.projectId === project.id && observationIds.includes(observation.id)),
29360
+ reader
29361
+ );
28629
29362
  if (matched.length === 0) {
28630
29363
  return { content: [{ type: "text", text: `No observations found for IDs: [${observationIds.join(", ")}]. Use \`memorix_search\` to find valid IDs.` }], isError: true };
28631
29364
  }
@@ -28633,6 +29366,13 @@ ${skill2.content}` }]
28633
29366
  if (nonActive.length > 0) {
28634
29367
  return { content: [{ type: "text", text: `Cannot promote: ${nonActive.length} observation(s) are not active: ${nonActive.map((o) => `#${o.id} (${o.status})`).join(", ")}. Only active observations can be promoted to permanent knowledge.` }], isError: true };
28635
29368
  }
29369
+ const nonProjectShared = matched.filter((observation) => resolveObservationVisibility(observation) !== "project");
29370
+ if (nonProjectShared.length > 0) {
29371
+ return {
29372
+ content: [{ type: "text", text: `Cannot promote private or team-scoped observations: ${nonProjectShared.map((observation) => `#${observation.id}`).join(", ")}. Promote only deliberate project-shared knowledge.` }],
29373
+ isError: true
29374
+ };
29375
+ }
28636
29376
  const skill2 = await promoteToMiniSkill2(projectDir2, project.id, matched, { trigger, instruction, tags });
28637
29377
  const lines = [
28638
29378
  `[OK] Created mini-skill #${skill2.id}`,
@@ -28791,8 +29531,11 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
28791
29531
  const lastSeen = registeredAgent.last_seen_obs_generation;
28792
29532
  const store = getObservationStore();
28793
29533
  const currentGen = store.getGeneration();
28794
- const projectObs = await withFreshIndex(() => getAllObservations().filter(
28795
- (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
29534
+ const projectObs = await withFreshIndex(() => filterReadableObservations(
29535
+ getAllObservations().filter(
29536
+ (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
29537
+ ),
29538
+ getObservationReader()
28796
29539
  ));
28797
29540
  const wm = computeWatermark2(lastSeen, currentGen, projectObs.length);
28798
29541
  if (wm.newObservationCount > 0) {
@@ -28961,7 +29704,7 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
28961
29704
  "memorix_transfer",
28962
29705
  {
28963
29706
  title: "Transfer Memories",
28964
- description: 'Export or import project memories. Action "export": export observations and sessions (JSON or Markdown). Action "import": import from a JSON export (re-assigns IDs, skips duplicate topicKeys).',
29707
+ description: 'Export or import project memories. Action "export": export observations visible to the current agent and project sessions (JSON or Markdown). Action "import": import from a JSON export (re-assigns IDs, skips duplicate topicKeys).',
28965
29708
  inputSchema: {
28966
29709
  action: z2.enum(["export", "import"]).describe("Operation: export or import"),
28967
29710
  format: z2.enum(["json", "markdown"]).optional().describe("Export format (for export, default: json)"),
@@ -28972,10 +29715,10 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
28972
29715
  if (action === "export") {
28973
29716
  const { exportAsJson: exportAsJson2, exportAsMarkdown: exportAsMarkdown2 } = await Promise.resolve().then(() => (init_export_import(), export_import_exports));
28974
29717
  if (format === "markdown") {
28975
- const md = await exportAsMarkdown2(projectDir2, project.id);
29718
+ const md = await exportAsMarkdown2(projectDir2, project.id, getObservationReader());
28976
29719
  return { content: [{ type: "text", text: md }] };
28977
29720
  }
28978
- const data = await exportAsJson2(projectDir2, project.id);
29721
+ const data = await exportAsJson2(projectDir2, project.id, getObservationReader());
28979
29722
  const json = JSON.stringify(data, null, 2);
28980
29723
  return {
28981
29724
  content: [{
@@ -29172,8 +29915,6 @@ ${json}
29172
29915
  }
29173
29916
  }
29174
29917
  );
29175
- let teamStore;
29176
- let initTeamStoreForProject;
29177
29918
  if (teamFeaturesEnabled) {
29178
29919
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
29179
29920
  initTeamStoreForProject = initTeamStore2;
@@ -29446,7 +30187,7 @@ ${lines.join("\n")}` }] };
29446
30187
  description: `Send, broadcast, or read messages between agents. Durable: messages survive restarts and reach inactive recipients. Action "send": direct message to one agent. Action "broadcast": message all agents. Action "inbox": read this agent's inbox.`,
29447
30188
  inputSchema: {
29448
30189
  action: z2.enum(["send", "broadcast", "inbox"]).describe("Operation to perform"),
29449
- from: z2.string().optional().describe("Sender agent ID (for send/broadcast)"),
30190
+ from: z2.string().optional().describe("Your sender agent ID. Omit it to use this session identity."),
29450
30191
  to: z2.string().optional().describe("Receiver agent ID (for send)"),
29451
30192
  type: z2.enum(["request", "response", "info", "announcement", "contract", "error", "handoff"]).optional().describe("Message type (for send/broadcast)"),
29452
30193
  content: z2.string().optional().describe("Message content (for send/broadcast)"),
@@ -29457,13 +30198,20 @@ ${lines.join("\n")}` }] };
29457
30198
  }
29458
30199
  },
29459
30200
  async ({ action, from, to, type: msgType, content, agentId, markRead, toRole, handoffStatus }) => {
30201
+ const requireCurrentTeamAgent = () => {
30202
+ if (!currentAgentId) return null;
30203
+ const agent = teamStore.getAgent(currentAgentId);
30204
+ return agent?.project_id === project.id && agent.status === "active" ? agent : null;
30205
+ };
29460
30206
  if (action === "send") {
29461
- if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for send" }], isError: true };
30207
+ const sender = requireCurrentTeamAgent();
30208
+ if (!sender || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] active session identity, type, and content required for send" }], isError: true };
30209
+ if (from && from !== currentAgentId) return { content: [{ type: "text", text: "[ERROR] from must match the current session identity" }], isError: true };
29462
30210
  if (!to && !toRole) return { content: [{ type: "text", text: "[ERROR] either to (agent ID) or toRole is required for send" }], isError: true };
29463
30211
  if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
29464
30212
  const msg = teamStore.sendMessage({
29465
30213
  projectId: project.id,
29466
- senderAgentId: from,
30214
+ senderAgentId: currentAgentId,
29467
30215
  recipientAgentId: to ?? null,
29468
30216
  type: msgType,
29469
30217
  content,
@@ -29475,11 +30223,13 @@ ${lines.join("\n")}` }] };
29475
30223
  return { content: [{ type: "text", text: `Message sent (${msgType}) to ${target} | ID: ${msg.id.slice(0, 8)}\u2026${toRole ? ` [role: ${toRole}]` : ""}` }] };
29476
30224
  }
29477
30225
  if (action === "broadcast") {
29478
- if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for broadcast" }], isError: true };
30226
+ const sender = requireCurrentTeamAgent();
30227
+ if (!sender || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] active session identity, type, and content required for broadcast" }], isError: true };
30228
+ if (from && from !== currentAgentId) return { content: [{ type: "text", text: "[ERROR] from must match the current session identity" }], isError: true };
29479
30229
  if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
29480
30230
  const msg = teamStore.sendMessage({
29481
30231
  projectId: project.id,
29482
- senderAgentId: from,
30232
+ senderAgentId: currentAgentId,
29483
30233
  recipientAgentId: null,
29484
30234
  type: msgType,
29485
30235
  content
@@ -29487,8 +30237,12 @@ ${lines.join("\n")}` }] };
29487
30237
  if ("error" in msg) return { content: [{ type: "text", text: `[ERROR] ${msg.error}` }], isError: true };
29488
30238
  return { content: [{ type: "text", text: `Broadcast (${msgType}) | ID: ${msg.id.slice(0, 8)}\u2026` }] };
29489
30239
  }
29490
- const inboxId = agentId || from || "";
29491
- if (!inboxId) return { content: [{ type: "text", text: "[ERROR] agentId required for inbox" }], isError: true };
30240
+ const inboxAgent = requireCurrentTeamAgent();
30241
+ if (!inboxAgent) return { content: [{ type: "text", text: "[ERROR] active session identity required for inbox" }], isError: true };
30242
+ if (agentId && agentId !== currentAgentId || from && from !== currentAgentId) {
30243
+ return { content: [{ type: "text", text: "[ERROR] inbox access is limited to the current session identity" }], isError: true };
30244
+ }
30245
+ const inboxId = currentAgentId;
29492
30246
  const inbox = teamStore.getInbox(project.id, inboxId);
29493
30247
  const unread = teamStore.getUnreadCount(project.id, inboxId);
29494
30248
  if (inbox.length === 0) return { content: [{ type: "text", text: "Inbox empty" }] };
@@ -29516,24 +30270,34 @@ ${lines.join("\n")}` }] };
29516
30270
  },
29517
30271
  async ({ agentId, markInboxRead }) => {
29518
30272
  const { computeWatermark: computeWatermark2, computePoll: computePoll2 } = await Promise.resolve().then(() => (init_poll(), poll_exports));
30273
+ if (agentId && agentId !== currentAgentId) {
30274
+ return {
30275
+ content: [{ type: "text", text: "[ERROR] agentId must match the current session identity." }],
30276
+ isError: true
30277
+ };
30278
+ }
30279
+ const effectiveAgentId = currentAgentId;
29519
30280
  let watermark = computeWatermark2(0, 0, 0);
29520
- if (agentId) {
29521
- const agent = teamStore.getAgent(agentId);
30281
+ if (effectiveAgentId) {
30282
+ const agent = teamStore.getAgent(effectiveAgentId);
29522
30283
  if (agent) {
29523
30284
  const lastSeen = agent.last_seen_obs_generation;
29524
30285
  const store = getObservationStore();
29525
30286
  const currentGen = store.getGeneration();
29526
- const projectObs = await withFreshIndex(() => getAllObservations().filter(
29527
- (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
30287
+ const projectObs = await withFreshIndex(() => filterReadableObservations(
30288
+ getAllObservations().filter(
30289
+ (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
30290
+ ),
30291
+ getObservationReader()
29528
30292
  ));
29529
30293
  watermark = computeWatermark2(lastSeen, currentGen, projectObs.length);
29530
- teamStore.updateWatermark(agentId, currentGen);
29531
- teamStore.heartbeat(agentId);
30294
+ teamStore.updateWatermark(effectiveAgentId, currentGen);
30295
+ teamStore.heartbeat(effectiveAgentId);
29532
30296
  }
29533
30297
  }
29534
- const poll = computePoll2(teamStore, project.id, agentId ?? null, watermark);
29535
- if (markInboxRead && agentId) {
29536
- teamStore.markAllRead(project.id, agentId);
30298
+ const poll = computePoll2(teamStore, project.id, effectiveAgentId ?? null, watermark);
30299
+ if (markInboxRead && effectiveAgentId) {
30300
+ teamStore.markAllRead(project.id, effectiveAgentId);
29537
30301
  }
29538
30302
  const lines = [];
29539
30303
  if (poll.agent) {
@@ -29593,7 +30357,7 @@ ${lines.join("\n")}` }] };
29593
30357
  "memorix_handoff",
29594
30358
  {
29595
30359
  title: "Team Handoff \u2014 Agent Context Transfer",
29596
- description: "Create a structured handoff artifact when passing work to another agent. The handoff is stored as a durable observation (searchable, immune to archival) and a notification message is sent to the recipient. Use this when completing a task and another agent should continue, or when you want to leave context for whoever works on this next.",
30360
+ description: "Create a structured handoff artifact when passing work to another agent. A targeted handoff is visible only to its sender and recipient; a broadcast handoff is team-visible. The handoff is stored as a durable observation (immune to archival) and a notification message is sent to the recipient. Use this when completing a task and another agent should continue, or when you want to leave context for whoever works on this next.",
29597
30361
  inputSchema: {
29598
30362
  fromAgentId: z2.string().describe("Your agent ID (from team_manage join or session_start with joinTeam=true)"),
29599
30363
  summary: z2.string().describe("Human-readable summary of what you did and what needs to happen next"),
@@ -29606,6 +30370,34 @@ ${lines.join("\n")}` }] };
29606
30370
  },
29607
30371
  async ({ fromAgentId, summary, context, toAgentId, taskId, filesModified, concepts }) => {
29608
30372
  const { createHandoffArtifact: createHandoffArtifact2 } = await Promise.resolve().then(() => (init_handoff(), handoff_exports));
30373
+ if (!currentAgentId) {
30374
+ return {
30375
+ content: [{ type: "text", text: "Create a coordination identity first: call memorix_session_start with joinTeam=true." }],
30376
+ isError: true
30377
+ };
30378
+ }
30379
+ if (fromAgentId !== currentAgentId) {
30380
+ return {
30381
+ content: [{ type: "text", text: "fromAgentId must match the identity returned for this session. Memorix will not create a handoff on behalf of another agent." }],
30382
+ isError: true
30383
+ };
30384
+ }
30385
+ const sender = teamStore.getAgent(currentAgentId);
30386
+ if (!sender || sender.project_id !== project.id || sender.status !== "active") {
30387
+ return {
30388
+ content: [{ type: "text", text: "The current coordination identity is not an active member of this project." }],
30389
+ isError: true
30390
+ };
30391
+ }
30392
+ if (toAgentId) {
30393
+ const recipient = teamStore.getAgent(toAgentId);
30394
+ if (!recipient || recipient.project_id !== project.id) {
30395
+ return {
30396
+ content: [{ type: "text", text: "The handoff recipient must be an agent registered in the current project." }],
30397
+ isError: true
30398
+ };
30399
+ }
30400
+ }
29609
30401
  const result = await createHandoffArtifact2(
29610
30402
  {
29611
30403
  projectId: project.id,