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/sdk.js CHANGED
@@ -32,6 +32,58 @@ var init_esm_shims = __esm({
32
32
  }
33
33
  });
34
34
 
35
+ // src/memory/visibility.ts
36
+ function resolveObservationVisibility(record) {
37
+ switch (record.visibility) {
38
+ case "personal":
39
+ case "team":
40
+ case "project":
41
+ return record.visibility;
42
+ default:
43
+ return "project";
44
+ }
45
+ }
46
+ function sharedAgentIds(record) {
47
+ if (Array.isArray(record.sharedWithAgentIds)) return record.sharedWithAgentIds;
48
+ if (typeof record.sharedWithAgentIds !== "string" || !record.sharedWithAgentIds) return [];
49
+ try {
50
+ const parsed = JSON.parse(record.sharedWithAgentIds);
51
+ return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+ function canReadObservation(record, reader) {
57
+ if (!reader) return true;
58
+ const sameProject = reader.projectId === record.projectId;
59
+ const visibility = resolveObservationVisibility(record);
60
+ if (visibility === "project") return !reader.projectId || sameProject;
61
+ if (!sameProject || !reader.agentId) return false;
62
+ if (visibility === "team") return reader.isTeamMember === true;
63
+ return record.createdByAgentId === reader.agentId || sharedAgentIds(record).includes(reader.agentId);
64
+ }
65
+ function canManageObservation(record, reader) {
66
+ if (!reader) return true;
67
+ if (!reader.projectId || reader.projectId !== record.projectId) return false;
68
+ switch (resolveObservationVisibility(record)) {
69
+ case "project":
70
+ return true;
71
+ case "team":
72
+ return reader.isTeamMember === true;
73
+ case "personal":
74
+ return Boolean(reader.agentId && record.createdByAgentId === reader.agentId);
75
+ }
76
+ }
77
+ function filterReadableObservations(observations2, reader) {
78
+ return reader ? observations2.filter((observation) => canReadObservation(observation, reader)) : [...observations2];
79
+ }
80
+ var init_visibility = __esm({
81
+ "src/memory/visibility.ts"() {
82
+ "use strict";
83
+ init_esm_shims();
84
+ }
85
+ });
86
+
35
87
  // src/store/bun-sqlite-compat.ts
36
88
  import { createRequire } from "module";
37
89
  function loadSqlite() {
@@ -208,7 +260,11 @@ CREATE TABLE IF NOT EXISTS observations (
208
260
  relatedCommits TEXT,
209
261
  relatedEntities TEXT,
210
262
  sourceDetail TEXT,
211
- valueCategory TEXT
263
+ valueCategory TEXT,
264
+ admissionState TEXT,
265
+ admissionReason TEXT,
266
+ visibility TEXT,
267
+ sharedWithAgentIds TEXT
212
268
  );
213
269
  `;
214
270
  CREATE_MINI_SKILLS_TABLE = `
@@ -631,6 +687,8 @@ CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId)
631
687
  CREATE INDEX IF NOT EXISTS idx_observations_topicKey ON observations(projectId, topicKey);
632
688
  CREATE INDEX IF NOT EXISTS idx_observations_status ON observations(status);
633
689
  CREATE INDEX IF NOT EXISTS idx_observations_project_status_id ON observations(projectId, status, id);
690
+ CREATE INDEX IF NOT EXISTS idx_observations_project_admission ON observations(projectId, status, admissionState, id);
691
+ CREATE INDEX IF NOT EXISTS idx_observations_project_visibility ON observations(projectId, status, visibility, id);
634
692
  CREATE INDEX IF NOT EXISTS idx_mini_skills_projectId ON mini_skills(projectId);
635
693
  CREATE INDEX IF NOT EXISTS idx_sessions_projectId ON sessions(projectId);
636
694
  CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(projectId, status);
@@ -675,6 +733,22 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
675
733
  WHERE status IN ('pending', 'running', 'retry');
676
734
  `;
677
735
  SCHEMA_MIGRATIONS = [
736
+ {
737
+ id: "1.2.2-observation-admission",
738
+ apply: (db2) => {
739
+ addColumnIfMissing(db2, "observations", "admissionState", "admissionState TEXT");
740
+ addColumnIfMissing(db2, "observations", "admissionReason", "admissionReason TEXT");
741
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_observations_project_admission ON observations(projectId, status, admissionState, id)");
742
+ }
743
+ },
744
+ {
745
+ id: "1.2.2-observation-visibility",
746
+ apply: (db2) => {
747
+ addColumnIfMissing(db2, "observations", "visibility", "visibility TEXT");
748
+ addColumnIfMissing(db2, "observations", "sharedWithAgentIds", "sharedWithAgentIds TEXT");
749
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_observations_project_visibility ON observations(projectId, status, visibility, id)");
750
+ }
751
+ },
678
752
  {
679
753
  id: "1.2-code-state-snapshots",
680
754
  apply: (db2) => {
@@ -5349,7 +5423,8 @@ __export(orama_store_exports, {
5349
5423
  makeOramaObservationId: () => makeOramaObservationId,
5350
5424
  removeObservation: () => removeObservation,
5351
5425
  resetDb: () => resetDb,
5352
- searchObservations: () => searchObservations
5426
+ searchObservations: () => searchObservations,
5427
+ updateObservationMetadata: () => updateObservationMetadata
5353
5428
  });
5354
5429
  import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
5355
5430
  function getLastSearchMode(projectId) {
@@ -5427,6 +5502,11 @@ async function initializeDb(options, generation) {
5427
5502
  source: "string",
5428
5503
  sourceDetail: "string",
5429
5504
  valueCategory: "string",
5505
+ admissionState: "string",
5506
+ admissionReason: "string",
5507
+ visibility: "string",
5508
+ createdByAgentId: "string",
5509
+ sharedWithAgentIds: "string",
5430
5510
  documentType: "string",
5431
5511
  knowledgeLayer: "string"
5432
5512
  };
@@ -5565,6 +5645,13 @@ async function hydrateIndex(observations2, options = {}) {
5565
5645
  lastAccessedAt: obs.lastAccessedAt || "",
5566
5646
  status: obs.status ?? "active",
5567
5647
  source: obs.source || "agent",
5648
+ sourceDetail: obs.sourceDetail ?? "",
5649
+ valueCategory: obs.valueCategory ?? "",
5650
+ admissionState: obs.admissionState ?? "",
5651
+ admissionReason: obs.admissionReason ?? "",
5652
+ visibility: obs.visibility ?? "project",
5653
+ createdByAgentId: obs.createdByAgentId ?? "",
5654
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
5568
5655
  documentType: "observation",
5569
5656
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
5570
5657
  ...compatibleVector ? { embedding: compatibleVector } : {}
@@ -5598,6 +5685,16 @@ async function insertObservation(doc) {
5598
5685
  await insert2(database, doc);
5599
5686
  rememberObservationDoc(doc);
5600
5687
  }
5688
+ async function updateObservationMetadata(projectId, observationId2, patch) {
5689
+ const database = await getDb();
5690
+ const id = makeOramaObservationId(projectId, observationId2);
5691
+ const existing = getByID(database, id);
5692
+ if (!existing) return false;
5693
+ const next = { ...existing, ...patch };
5694
+ await update(database, id, next);
5695
+ rememberObservationDoc(next);
5696
+ return true;
5697
+ }
5601
5698
  async function removeObservation(oramaId) {
5602
5699
  const database = await getDb();
5603
5700
  await remove2(database, oramaId);
@@ -5751,7 +5848,7 @@ async function searchObservations(options) {
5751
5848
  if (!projectIds) return true;
5752
5849
  const doc = hit.document;
5753
5850
  return projectIds.includes(doc.projectId);
5754
- }).filter((hit) => {
5851
+ }).filter((hit) => canReadObservation(hit.document, options.reader)).filter((hit) => {
5755
5852
  if (statusFilter === "all") return true;
5756
5853
  const doc = hit.document;
5757
5854
  return (doc.status || "active") === statusFilter;
@@ -5782,6 +5879,8 @@ async function searchObservations(options) {
5782
5879
  source: doc.source || "agent",
5783
5880
  sourceDetail: doc.sourceDetail || void 0,
5784
5881
  valueCategory: doc.valueCategory || void 0,
5882
+ admissionState: doc.admissionState || void 0,
5883
+ visibility: doc.visibility || void 0,
5785
5884
  entityName: doc.entityName || void 0,
5786
5885
  documentType: doc.documentType || "observation",
5787
5886
  knowledgeLayer: doc.knowledgeLayer || "project-truth",
@@ -5825,6 +5924,12 @@ async function searchObservations(options) {
5825
5924
  score: isCommandStyleEntry(entry.title) ? entry.score * 0.3 : entry.score
5826
5925
  }));
5827
5926
  }
5927
+ if (hasQuery) {
5928
+ const qualifiedEntries = intermediate.filter(
5929
+ (entry) => entry.admissionState !== "candidate" && entry.admissionState !== "ephemeral"
5930
+ );
5931
+ if (qualifiedEntries.length > 0) intermediate = qualifiedEntries;
5932
+ }
5828
5933
  if (intentResult?.preferChronological) {
5829
5934
  intermediate.sort((a, b) => new Date(b.rawTime).getTime() - new Date(a.rawTime).getTime());
5830
5935
  } else {
@@ -5968,7 +6073,8 @@ async function searchObservations(options) {
5968
6073
  }
5969
6074
  let entries = intermediate.map(({ rawTime: _, _isCommandLog: _c, ...rest }) => rest);
5970
6075
  for (const hit of results.hits) {
5971
- rememberObservationDoc(hit.document);
6076
+ const doc = hit.document;
6077
+ if (canReadObservation(doc, options.reader)) rememberObservationDoc(doc);
5972
6078
  }
5973
6079
  if (hasQuery && originalQuery) {
5974
6080
  const queryLower = originalQuery.toLowerCase();
@@ -6012,7 +6118,8 @@ async function searchObservations(options) {
6012
6118
  entries = applyTokenBudget(entries, options.maxTokens);
6013
6119
  }
6014
6120
  if (options.trackAccess !== false) {
6015
- const hitDocs = results.hits.map((h) => ({ id: h.id, doc: h.document }));
6121
+ const returnedKeys = new Set(entries.map((entry) => makeEntryKey(entry.projectId, entry.id)));
6122
+ const hitDocs = results.hits.map((hit) => ({ id: hit.id, doc: hit.document })).filter(({ doc }) => returnedKeys.has(makeEntryKey(doc.projectId, doc.observationId)));
6016
6123
  recordAccessBatch(hitDocs).catch(() => {
6017
6124
  });
6018
6125
  }
@@ -6044,11 +6151,11 @@ async function getObservationsByIds(ids, projectId) {
6044
6151
  }
6045
6152
  return results;
6046
6153
  }
6047
- async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3) {
6154
+ async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3, reader) {
6048
6155
  const { withFreshIndex: withFreshIndex2 } = await Promise.resolve().then(() => (init_freshness(), freshness_exports));
6049
6156
  const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
6050
6157
  const rawObs = await withFreshIndex2(() => getAllObservations2());
6051
- const allObs = projectId ? rawObs.filter((o) => o.projectId === projectId) : rawObs;
6158
+ const allObs = (projectId ? rawObs.filter((o) => o.projectId === projectId) : rawObs).filter((observation) => canReadObservation(observation, reader));
6052
6159
  const sorted = allObs.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
6053
6160
  const anchorIndex = sorted.findIndex((o) => o.id === anchorId);
6054
6161
  if (anchorIndex === -1) {
@@ -6065,7 +6172,9 @@ async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3)
6065
6172
  tokens: obs.tokens,
6066
6173
  source: obs.source || void 0,
6067
6174
  sourceDetail: obs.sourceDetail || void 0,
6068
- valueCategory: obs.valueCategory || void 0
6175
+ valueCategory: obs.valueCategory || void 0,
6176
+ admissionState: obs.admissionState || void 0,
6177
+ visibility: obs.visibility || void 0
6069
6178
  };
6070
6179
  };
6071
6180
  const before = sorted.slice(Math.max(0, anchorIndex - depthBefore), anchorIndex).map(toIndexEntry);
@@ -6133,6 +6242,7 @@ var init_orama_store = __esm({
6133
6242
  init_esm_shims();
6134
6243
  init_types();
6135
6244
  init_mini_skills();
6245
+ init_visibility();
6136
6246
  init_provider();
6137
6247
  init_project_affinity();
6138
6248
  init_intent_detector();
@@ -6189,6 +6299,10 @@ function obsToRow(obs) {
6189
6299
  relatedEntities: obs.relatedEntities ? JSON.stringify(obs.relatedEntities) : null,
6190
6300
  sourceDetail: obs.sourceDetail ?? null,
6191
6301
  valueCategory: obs.valueCategory ?? null,
6302
+ admissionState: obs.admissionState ?? null,
6303
+ admissionReason: obs.admissionReason ?? null,
6304
+ visibility: obs.visibility ?? null,
6305
+ sharedWithAgentIds: obs.sharedWithAgentIds ? JSON.stringify(obs.sharedWithAgentIds) : null,
6192
6306
  createdByAgentId: obs.createdByAgentId ?? null,
6193
6307
  writeGeneration: obs.writeGeneration ?? 0
6194
6308
  };
@@ -6219,6 +6333,10 @@ function rowToObs(row) {
6219
6333
  ...row.relatedEntities ? { relatedEntities: safeJsonParse3(row.relatedEntities, []) } : {},
6220
6334
  ...row.sourceDetail ? { sourceDetail: row.sourceDetail } : {},
6221
6335
  ...row.valueCategory ? { valueCategory: row.valueCategory } : {},
6336
+ ...row.admissionState ? { admissionState: row.admissionState } : {},
6337
+ ...row.admissionReason ? { admissionReason: row.admissionReason } : {},
6338
+ ...row.visibility ? { visibility: row.visibility } : {},
6339
+ ...row.sharedWithAgentIds ? { sharedWithAgentIds: safeJsonParse3(row.sharedWithAgentIds, []) } : {},
6222
6340
  ...row.createdByAgentId ? { createdByAgentId: row.createdByAgentId } : {},
6223
6341
  ...row.writeGeneration ? { writeGeneration: row.writeGeneration } : {}
6224
6342
  };
@@ -6270,12 +6388,14 @@ var init_sqlite_store = __esm({
6270
6388
  (id, entityName, type, title, narrative, facts, filesModified, concepts, tokens,
6271
6389
  createdAt, updatedAt, projectId, hasCausalLanguage, topicKey, revisionCount,
6272
6390
  sessionId, status, progress, source, commitHash, relatedCommits, relatedEntities,
6273
- sourceDetail, valueCategory, createdByAgentId, writeGeneration)
6391
+ sourceDetail, valueCategory, admissionState, admissionReason, visibility, sharedWithAgentIds,
6392
+ createdByAgentId, writeGeneration)
6274
6393
  VALUES
6275
6394
  (@id, @entityName, @type, @title, @narrative, @facts, @filesModified, @concepts, @tokens,
6276
6395
  @createdAt, @updatedAt, @projectId, @hasCausalLanguage, @topicKey, @revisionCount,
6277
6396
  @sessionId, @status, @progress, @source, @commitHash, @relatedCommits, @relatedEntities,
6278
- @sourceDetail, @valueCategory, @createdByAgentId, @writeGeneration)
6397
+ @sourceDetail, @valueCategory, @admissionState, @admissionReason, @visibility, @sharedWithAgentIds,
6398
+ @createdByAgentId, @writeGeneration)
6279
6399
  `);
6280
6400
  this.stmtUpdate = this.stmtInsert;
6281
6401
  this.stmtSetStatus = this.db.prepare(`UPDATE observations SET status = ? WHERE id = ?`);
@@ -6827,6 +6947,7 @@ var init_maintenance_jobs = __esm({
6827
6947
  "retention-archive",
6828
6948
  "consolidation",
6829
6949
  "codegraph-refresh",
6950
+ "observation-qualify",
6830
6951
  "claim-derive",
6831
6952
  "claim-requalification",
6832
6953
  "knowledge-compile",
@@ -7185,7 +7306,8 @@ __export(lifecycle_exports, {
7185
7306
  enqueueClaimDerivation: () => enqueueClaimDerivation,
7186
7307
  enqueueClaimRequalification: () => enqueueClaimRequalification,
7187
7308
  enqueueCodegraphRefresh: () => enqueueCodegraphRefresh,
7188
- enqueueKnowledgeFollowups: () => enqueueKnowledgeFollowups
7309
+ enqueueKnowledgeFollowups: () => enqueueKnowledgeFollowups,
7310
+ enqueueObservationQualification: () => enqueueObservationQualification
7189
7311
  });
7190
7312
  function queueFor(input) {
7191
7313
  return input.queue ?? new MaintenanceJobStore(input.dataDir);
@@ -7220,6 +7342,14 @@ function enqueueClaimDerivation(input) {
7220
7342
  payload: { observationId: input.observationId }
7221
7343
  });
7222
7344
  }
7345
+ function enqueueObservationQualification(input) {
7346
+ queueFor(input).enqueue({
7347
+ projectId: input.projectId,
7348
+ kind: "observation-qualify",
7349
+ dedupeKey: "observation-qualify",
7350
+ payload: { source: input.source, limit: 100 }
7351
+ });
7352
+ }
7223
7353
  function enqueueKnowledgeFollowups(input) {
7224
7354
  const queue = queueFor(input);
7225
7355
  const payload = {
@@ -8084,6 +8214,7 @@ __export(observations_exports, {
8084
8214
  resolveObservations: () => resolveObservations,
8085
8215
  storeObservation: () => storeObservation,
8086
8216
  suggestTopicKey: () => suggestTopicKey,
8217
+ updateObservationAdmission: () => updateObservationAdmission,
8087
8218
  withFreshObservations: () => withFreshObservations
8088
8219
  });
8089
8220
  function logEmbeddingFailureOnce(key, message) {
@@ -8150,6 +8281,18 @@ function queueClaimDerivation(observation) {
8150
8281
  } catch {
8151
8282
  }
8152
8283
  }
8284
+ function queueObservationQualification(observation) {
8285
+ const dataDir = projectDir;
8286
+ if (!dataDir || observation.admissionState !== "candidate") return;
8287
+ try {
8288
+ enqueueObservationQualification({
8289
+ dataDir,
8290
+ projectId: observation.projectId,
8291
+ source: "automatic-capture:" + observation.id
8292
+ });
8293
+ } catch {
8294
+ }
8295
+ }
8153
8296
  function isVectorCompatibleWithCurrentIndex(embedding) {
8154
8297
  if (!embedding) return false;
8155
8298
  const vectorDimensions = getVectorDimensions();
@@ -8205,6 +8348,9 @@ async function storeObservation(input) {
8205
8348
  (o) => o.topicKey === input.topicKey && o.projectId === input.projectId
8206
8349
  );
8207
8350
  if (existing) {
8351
+ if (input.visibilityReader && !canManageObservation(existing, input.visibilityReader)) {
8352
+ throw new Error("Cannot update a memory outside this session's write scope.");
8353
+ }
8208
8354
  return { observation: await upsertObservation(existing, input, now3), upserted: true };
8209
8355
  }
8210
8356
  }
@@ -8241,6 +8387,9 @@ async function storeObservation(input) {
8241
8387
  if (input.topicKey) {
8242
8388
  const diskExisting = await tx.findByTopicKey(input.projectId, input.topicKey);
8243
8389
  if (diskExisting) {
8390
+ if (input.visibilityReader && !canManageObservation(diskExisting, input.visibilityReader)) {
8391
+ throw new Error("Cannot update a memory outside this session's write scope.");
8392
+ }
8244
8393
  upsertedInsideLock = true;
8245
8394
  observation = diskExisting;
8246
8395
  return;
@@ -8271,6 +8420,10 @@ async function storeObservation(input) {
8271
8420
  relatedEntities: input.relatedEntities,
8272
8421
  sourceDetail: input.sourceDetail,
8273
8422
  valueCategory: input.valueCategory,
8423
+ admissionState: input.admissionState,
8424
+ admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : void 0,
8425
+ visibility: input.visibility ?? "project",
8426
+ sharedWithAgentIds: input.sharedWithAgentIds,
8274
8427
  createdByAgentId: input.createdByAgentId,
8275
8428
  // Predict the generation that atomic() will commit after this callback.
8276
8429
  // bumpGeneration() runs after fn(tx) returns, incrementing by 1.
@@ -8317,6 +8470,10 @@ async function storeObservation(input) {
8317
8470
  relatedEntities: input.relatedEntities,
8318
8471
  sourceDetail: input.sourceDetail,
8319
8472
  valueCategory: input.valueCategory,
8473
+ admissionState: input.admissionState,
8474
+ admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : void 0,
8475
+ visibility: input.visibility ?? "project",
8476
+ sharedWithAgentIds: input.sharedWithAgentIds,
8320
8477
  createdByAgentId: input.createdByAgentId,
8321
8478
  writeGeneration: 0
8322
8479
  };
@@ -8340,7 +8497,12 @@ async function storeObservation(input) {
8340
8497
  status: "active",
8341
8498
  source: input.source ?? "agent",
8342
8499
  sourceDetail: input.sourceDetail ?? "",
8343
- valueCategory: input.valueCategory ?? ""
8500
+ valueCategory: input.valueCategory ?? "",
8501
+ admissionState: input.admissionState ?? "",
8502
+ admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : "",
8503
+ visibility: input.visibility ?? "project",
8504
+ createdByAgentId: input.createdByAgentId ?? "",
8505
+ sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? [])
8344
8506
  };
8345
8507
  await insertObservation(doc);
8346
8508
  };
@@ -8349,7 +8511,10 @@ async function storeObservation(input) {
8349
8511
  return { observation: await upsertObservation(observation, input, now3), upserted: true };
8350
8512
  }
8351
8513
  await bindObservationCodeRefsBestEffort(observation);
8352
- queueClaimDerivation(observation);
8514
+ queueObservationQualification(observation);
8515
+ if (resolveObservationVisibility(observation) === "project") {
8516
+ queueClaimDerivation(observation);
8517
+ }
8353
8518
  const obsId = observation.id;
8354
8519
  vectorMissingIds.add(obsId);
8355
8520
  const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
@@ -8422,6 +8587,10 @@ async function upsertObservation(existing, input, now3) {
8422
8587
  if (input.progress) existing.progress = input.progress;
8423
8588
  if (input.sourceDetail !== void 0) existing.sourceDetail = input.sourceDetail;
8424
8589
  if (input.valueCategory !== void 0) existing.valueCategory = input.valueCategory;
8590
+ if (input.admissionState !== void 0) existing.admissionState = input.admissionState;
8591
+ if (input.admissionReason !== void 0) existing.admissionReason = sanitizeCredentials(input.admissionReason);
8592
+ if (input.visibility !== void 0) existing.visibility = input.visibility;
8593
+ if (input.sharedWithAgentIds !== void 0) existing.sharedWithAgentIds = input.sharedWithAgentIds;
8425
8594
  const doc = {
8426
8595
  id: makeOramaObservationId(existing.projectId, existing.id),
8427
8596
  observationId: existing.id,
@@ -8440,7 +8609,12 @@ async function upsertObservation(existing, input, now3) {
8440
8609
  status: "active",
8441
8610
  source: existing.source ?? "agent",
8442
8611
  sourceDetail: existing.sourceDetail ?? "",
8443
- valueCategory: existing.valueCategory ?? ""
8612
+ valueCategory: existing.valueCategory ?? "",
8613
+ admissionState: existing.admissionState ?? "",
8614
+ admissionReason: existing.admissionReason ?? "",
8615
+ visibility: existing.visibility ?? "project",
8616
+ createdByAgentId: existing.createdByAgentId ?? "",
8617
+ sharedWithAgentIds: JSON.stringify(existing.sharedWithAgentIds ?? [])
8444
8618
  };
8445
8619
  const oramaId = makeOramaObservationId(existing.projectId, existing.id);
8446
8620
  try {
@@ -8463,7 +8637,10 @@ async function upsertObservation(existing, input, now3) {
8463
8637
  await store.update(existing);
8464
8638
  }
8465
8639
  await bindObservationCodeRefsBestEffort(existing);
8466
- queueClaimDerivation(existing);
8640
+ queueObservationQualification(existing);
8641
+ if (resolveObservationVisibility(existing) === "project") {
8642
+ queueClaimDerivation(existing);
8643
+ }
8467
8644
  const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
8468
8645
  const obsId = existing.id;
8469
8646
  vectorMissingIds.add(obsId);
@@ -8502,6 +8679,56 @@ async function upsertObservation(existing, input, now3) {
8502
8679
  function getObservation(id, projectId) {
8503
8680
  return observations.find((o) => o.id === id && (projectId ? o.projectId === projectId : true));
8504
8681
  }
8682
+ async function updateObservationAdmission(input) {
8683
+ await ensureFreshObservations();
8684
+ const cached = observations.find(
8685
+ (observation) => observation.id === input.observationId && (!input.projectId || observation.projectId === input.projectId)
8686
+ );
8687
+ if (!cached) return void 0;
8688
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
8689
+ const admissionReason = sanitizeCredentials(input.admissionReason).slice(0, 240);
8690
+ const apply = (current) => {
8691
+ if (input.projectId && current.projectId !== input.projectId) return void 0;
8692
+ if (input.expectedState && current.admissionState !== input.expectedState) return void 0;
8693
+ return {
8694
+ ...current,
8695
+ admissionState: input.admissionState,
8696
+ admissionReason,
8697
+ ...input.visibility !== void 0 ? { visibility: input.visibility } : {},
8698
+ updatedAt: now3
8699
+ };
8700
+ };
8701
+ let updated;
8702
+ if (projectDir) {
8703
+ const store = getObservationStore();
8704
+ await store.atomic(async (tx) => {
8705
+ const current = await tx.getById(input.observationId);
8706
+ if (!current) return;
8707
+ const next = apply(current);
8708
+ if (!next) return;
8709
+ await tx.update(next);
8710
+ updated = next;
8711
+ });
8712
+ } else {
8713
+ updated = apply(cached);
8714
+ }
8715
+ if (!updated) return void 0;
8716
+ observations = observations.map(
8717
+ (observation) => observation.id === updated.id && observation.projectId === updated.projectId ? updated : observation
8718
+ );
8719
+ try {
8720
+ await updateObservationMetadata(updated.projectId, updated.id, {
8721
+ admissionState: updated.admissionState,
8722
+ admissionReason: updated.admissionReason,
8723
+ ...updated.visibility !== void 0 ? { visibility: updated.visibility } : {}
8724
+ });
8725
+ } catch {
8726
+ }
8727
+ if (updated.admissionState === "qualified" && resolveObservationVisibility(updated) === "project") {
8728
+ queueClaimDerivation(updated);
8729
+ }
8730
+ return updated;
8731
+ }
8505
8732
  async function resolveObservations(ids, status = "resolved") {
8506
8733
  const resolved = [];
8507
8734
  const notFound = [];
@@ -8541,7 +8768,12 @@ async function resolveObservations(ids, status = "resolved") {
8541
8768
  status,
8542
8769
  source: obs.source ?? "agent",
8543
8770
  sourceDetail: obs.sourceDetail ?? "",
8544
- valueCategory: obs.valueCategory ?? ""
8771
+ valueCategory: obs.valueCategory ?? "",
8772
+ admissionState: obs.admissionState ?? "",
8773
+ admissionReason: obs.admissionReason ?? "",
8774
+ visibility: obs.visibility ?? "project",
8775
+ createdByAgentId: obs.createdByAgentId ?? "",
8776
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
8545
8777
  };
8546
8778
  await insertObservation(doc);
8547
8779
  const obsId = obs.id;
@@ -8671,6 +8903,11 @@ async function reindexObservations() {
8671
8903
  source: obs.source ?? "agent",
8672
8904
  sourceDetail: obs.sourceDetail ?? "",
8673
8905
  valueCategory: obs.valueCategory ?? "",
8906
+ admissionState: obs.admissionState ?? "",
8907
+ admissionReason: obs.admissionReason ?? "",
8908
+ visibility: obs.visibility ?? "project",
8909
+ createdByAgentId: obs.createdByAgentId ?? "",
8910
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
8674
8911
  ...compatibleEmbedding ? { embedding: compatibleEmbedding } : {}
8675
8912
  };
8676
8913
  await insertObservation(doc);
@@ -8743,6 +8980,7 @@ async function probeSearchIndex(projectId) {
8743
8980
  await searchObservations({
8744
8981
  query: "semantic memory retrieval status",
8745
8982
  projectId,
8983
+ reader: { projectId },
8746
8984
  limit: 1,
8747
8985
  status: "all",
8748
8986
  trackAccess: false
@@ -8808,6 +9046,11 @@ async function backfillVectorEmbeddings(options = {}) {
8808
9046
  source: obs.source ?? "agent",
8809
9047
  sourceDetail: obs.sourceDetail ?? "",
8810
9048
  valueCategory: obs.valueCategory ?? "",
9049
+ admissionState: obs.admissionState ?? "",
9050
+ admissionReason: obs.admissionReason ?? "",
9051
+ visibility: obs.visibility ?? "project",
9052
+ createdByAgentId: obs.createdByAgentId ?? "",
9053
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
8811
9054
  embedding
8812
9055
  };
8813
9056
  await insertObservation(doc);
@@ -8849,6 +9092,7 @@ var init_observations = __esm({
8849
9092
  init_provider();
8850
9093
  init_secret_filter();
8851
9094
  init_lifecycle();
9095
+ init_visibility();
8852
9096
  observations = [];
8853
9097
  nextId = 1;
8854
9098
  projectDir = null;
@@ -9128,8 +9372,9 @@ function resolveSourceDetail(sourceDetail, source) {
9128
9372
  return void 0;
9129
9373
  }
9130
9374
  function classifyLayer(fields) {
9131
- const { valueCategory } = fields;
9375
+ const { valueCategory, admissionState } = fields;
9132
9376
  const sd = resolveSourceDetail(fields.sourceDetail, fields.source);
9377
+ if (admissionState === "candidate" || admissionState === "ephemeral") return "L1";
9133
9378
  if (valueCategory === "core") return "L2";
9134
9379
  if (sd === "hook") return "L1";
9135
9380
  if (sd === "git-ingest") return "L3";
@@ -9187,6 +9432,7 @@ function getEffectiveRetentionDays(doc) {
9187
9432
  }
9188
9433
  function isImmune(doc) {
9189
9434
  if (doc.type === "probe") return false;
9435
+ if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return false;
9190
9436
  if (doc.valueCategory === "core") return true;
9191
9437
  const importance = getImportanceLevel(doc);
9192
9438
  if (importance === "critical") return true;
@@ -9196,6 +9442,7 @@ function isImmune(doc) {
9196
9442
  }
9197
9443
  function getImmunityReason(doc) {
9198
9444
  if (doc.type === "probe") return null;
9445
+ if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return null;
9199
9446
  if (doc.valueCategory === "core") return "core valueCategory (formation-classified)";
9200
9447
  const importance = getImportanceLevel(doc);
9201
9448
  if (importance === "critical") return "critical importance";
@@ -9328,7 +9575,9 @@ function toRetentionDocument(obs, accessMap) {
9328
9575
  status: obs.status ?? "active",
9329
9576
  source: obs.source ?? "agent",
9330
9577
  sourceDetail: obs.sourceDetail ?? "",
9331
- valueCategory: obs.valueCategory ?? ""
9578
+ valueCategory: obs.valueCategory ?? "",
9579
+ admissionState: obs.admissionState ?? "",
9580
+ admissionReason: obs.admissionReason ?? ""
9332
9581
  };
9333
9582
  }
9334
9583
  async function archiveExpiredBatch(_projectDir, options) {
@@ -9342,7 +9591,7 @@ async function archiveExpiredBatch(_projectDir, options) {
9342
9591
  });
9343
9592
  const hasMore = page.length > limit;
9344
9593
  const scanned = hasMore ? page.slice(0, limit) : page;
9345
- const candidateIds = scanned.filter((observation) => getRetentionZone(
9594
+ const candidateIds = scanned.filter((observation) => !options.reader || canManageObservation(observation, options.reader)).filter((observation) => getRetentionZone(
9346
9595
  toRetentionDocument(observation, options.accessMap),
9347
9596
  options.referenceTime
9348
9597
  ) === "archive-candidate").map((observation) => observation.id);
@@ -9355,7 +9604,7 @@ async function archiveExpiredBatch(_projectDir, options) {
9355
9604
  const nextCursor = hasMore && scanned.length > 0 ? scanned[scanned.length - 1].id : void 0;
9356
9605
  return nextCursor === void 0 ? { archived, scanned: scanned.length } : { archived, scanned: scanned.length, nextCursor };
9357
9606
  }
9358
- async function archiveExpired(projectDir2, referenceTime, accessMap, projectId) {
9607
+ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId, reader) {
9359
9608
  const store = getObservationStore();
9360
9609
  if (projectId) {
9361
9610
  let afterId;
@@ -9365,12 +9614,14 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId)
9365
9614
  projectId,
9366
9615
  afterId,
9367
9616
  referenceTime,
9368
- accessMap
9617
+ accessMap,
9618
+ reader
9369
9619
  });
9370
9620
  archived += batch.archived;
9371
9621
  afterId = batch.nextCursor;
9372
9622
  } while (afterId !== void 0);
9373
- const remaining = await store.countByProject(projectId, { status: "active" });
9623
+ const remainingObservations = await store.loadByProject(projectId, { status: "active" });
9624
+ const remaining = reader ? remainingObservations.filter((observation) => canManageObservation(observation, reader)).length : remainingObservations.length;
9374
9625
  return { archived, remaining };
9375
9626
  }
9376
9627
  return await store.atomic(async (tx) => {
@@ -9400,6 +9651,7 @@ var init_retention = __esm({
9400
9651
  "use strict";
9401
9652
  init_esm_shims();
9402
9653
  init_obs_store();
9654
+ init_visibility();
9403
9655
  RETENTION_DAYS = {
9404
9656
  critical: 365,
9405
9657
  high: 180,
@@ -9435,6 +9687,49 @@ var init_retention = __esm({
9435
9687
  }
9436
9688
  });
9437
9689
 
9690
+ // src/memory/admission.ts
9691
+ var admission_exports = {};
9692
+ __export(admission_exports, {
9693
+ isCandidateObservation: () => isCandidateObservation,
9694
+ isEligibleForAutomaticDelivery: () => isEligibleForAutomaticDelivery,
9695
+ isEligibleForKnowledgePromotion: () => isEligibleForKnowledgePromotion,
9696
+ qualifyCandidateFromCurrentCode: () => qualifyCandidateFromCurrentCode
9697
+ });
9698
+ function isEligibleForAutomaticDelivery(observation) {
9699
+ return observation.admissionState !== "ephemeral" && observation.admissionState !== "candidate";
9700
+ }
9701
+ function isCandidateObservation(observation) {
9702
+ return observation.admissionState === "candidate";
9703
+ }
9704
+ function isEligibleForKnowledgePromotion(observation) {
9705
+ return isEligibleForAutomaticDelivery(observation) && observation.valueCategory !== "ephemeral" && resolveObservationVisibility(observation) === "project";
9706
+ }
9707
+ function qualifyCandidateFromCurrentCode(input) {
9708
+ if (!isCandidateObservation(input.observation)) return void 0;
9709
+ if (input.observation.valueCategory === "ephemeral") return void 0;
9710
+ if (input.currentCodeReferenceCount <= 0) return void 0;
9711
+ const typeLabel = DURABLE_AUTOMATIC_TYPES.has(input.observation.type) ? "high-value automatic record" : "automatic record";
9712
+ return {
9713
+ admissionState: "qualified",
9714
+ admissionReason: `${typeLabel} qualified against ${input.currentCodeReferenceCount} current Code Memory reference(s)`
9715
+ };
9716
+ }
9717
+ var DURABLE_AUTOMATIC_TYPES;
9718
+ var init_admission = __esm({
9719
+ "src/memory/admission.ts"() {
9720
+ "use strict";
9721
+ init_esm_shims();
9722
+ init_visibility();
9723
+ DURABLE_AUTOMATIC_TYPES = /* @__PURE__ */ new Set([
9724
+ "decision",
9725
+ "gotcha",
9726
+ "problem-solution",
9727
+ "trade-off",
9728
+ "why-it-exists"
9729
+ ]);
9730
+ }
9731
+ });
9732
+
9438
9733
  // src/workspace/workflow-sync.ts
9439
9734
  import matter9 from "gray-matter";
9440
9735
  var WorkflowSyncer;
@@ -10835,6 +11130,7 @@ var init_isolated_maintenance = __esm({
10835
11130
  "retention-archive",
10836
11131
  "consolidation",
10837
11132
  "codegraph-refresh",
11133
+ "observation-qualify",
10838
11134
  "claim-derive",
10839
11135
  "claim-requalification",
10840
11136
  "knowledge-compile",
@@ -11270,9 +11566,10 @@ async function loadConsolidationPage(projectId, options) {
11270
11566
  return hasMore && observations2.length > 0 ? { observations: observations2, nextCursor: observations2[observations2.length - 1].id } : { observations: observations2 };
11271
11567
  }
11272
11568
  function findClusters(observations2, threshold) {
11273
- if (observations2.length < MIN_CLUSTER_SIZE) return [];
11569
+ const eligible = observations2.filter(isEligibleForAutomaticDelivery).filter((observation) => resolveObservationVisibility(observation) === "project");
11570
+ if (eligible.length < MIN_CLUSTER_SIZE) return [];
11274
11571
  const groups = /* @__PURE__ */ new Map();
11275
- for (const obs of observations2) {
11572
+ for (const obs of eligible) {
11276
11573
  const key = `${obs.entityName}::${obs.type}`;
11277
11574
  const group = groups.get(key) ?? [];
11278
11575
  group.push(obs);
@@ -11409,6 +11706,8 @@ var init_consolidation = __esm({
11409
11706
  "use strict";
11410
11707
  init_esm_shims();
11411
11708
  init_obs_store();
11709
+ init_admission();
11710
+ init_visibility();
11412
11711
  DEFAULT_SIMILARITY_THRESHOLD = 0.45;
11413
11712
  HIGH_VALUE_SIMILARITY_THRESHOLD = 0.85;
11414
11713
  HIGH_VALUE_TYPES = /* @__PURE__ */ new Set(["gotcha", "decision", "trade-off", "reasoning", "problem-solution"]);
@@ -14427,6 +14726,15 @@ function claimDerivationCursor(payload) {
14427
14726
  const value = payload.cursor;
14428
14727
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
14429
14728
  }
14729
+ function observationQualificationBatchSize(payload) {
14730
+ const value = payload.limit;
14731
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE;
14732
+ return Math.min(500, Math.max(1, Math.floor(value)));
14733
+ }
14734
+ function observationQualificationCursor(payload) {
14735
+ const value = payload.cursor;
14736
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
14737
+ }
14430
14738
  function observationId(payload) {
14431
14739
  const value = payload.observationId;
14432
14740
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
@@ -14605,6 +14913,71 @@ function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot, op
14605
14913
  ...snapshot?.id ? { snapshotId: snapshot.id } : {},
14606
14914
  ...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
14607
14915
  });
14916
+ enqueueObservationQualification({
14917
+ projectId,
14918
+ dataDir: projectDir2,
14919
+ source: "codegraph-refresh",
14920
+ ...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
14921
+ });
14922
+ return { action: "complete" };
14923
+ }
14924
+ if (job.kind === "observation-qualify") {
14925
+ const [
14926
+ { CodeGraphStore: CodeGraphStore2 },
14927
+ { bindObservationToCode: bindObservationToCode2 },
14928
+ { getObservationStore: getObservationStore2 },
14929
+ { qualifyCandidateFromCurrentCode: qualifyCandidateFromCurrentCode2 },
14930
+ { updateObservationAdmission: updateObservationAdmission2 }
14931
+ ] = await Promise.all([
14932
+ Promise.resolve().then(() => (init_store(), store_exports)),
14933
+ Promise.resolve().then(() => (init_binder(), binder_exports)),
14934
+ Promise.resolve().then(() => (init_obs_store(), obs_store_exports)),
14935
+ Promise.resolve().then(() => (init_admission(), admission_exports)),
14936
+ Promise.resolve().then(() => (init_observations(), observations_exports))
14937
+ ]);
14938
+ const codeStore = new CodeGraphStore2();
14939
+ await codeStore.init(projectDir2);
14940
+ const limit = observationQualificationBatchSize(job.payload);
14941
+ const page = await getObservationStore2().loadByProject(projectId, {
14942
+ status: "active",
14943
+ afterId: observationQualificationCursor(job.payload),
14944
+ limit: limit + 1
14945
+ });
14946
+ const hasMore = page.length > limit;
14947
+ const observations2 = hasMore ? page.slice(0, limit) : page;
14948
+ const indexedAtMs = Date.parse(codeStore.status(projectId).indexedAt ?? "");
14949
+ for (const observation of observations2) {
14950
+ if (observation.admissionState !== "candidate") continue;
14951
+ if (!Number.isFinite(indexedAtMs) || indexedAtMs < Date.parse(observation.createdAt)) continue;
14952
+ await bindObservationToCode2(codeStore, observation);
14953
+ const currentCodeReferenceCount = codeStore.listObservationRefs(projectId, observation.id).filter((reference) => reference.status === "current").length;
14954
+ const qualification = qualifyCandidateFromCurrentCode2({
14955
+ observation,
14956
+ currentCodeReferenceCount
14957
+ });
14958
+ if (!qualification) continue;
14959
+ await updateObservationAdmission2({
14960
+ observationId: observation.id,
14961
+ projectId,
14962
+ expectedState: "candidate",
14963
+ // Automatic capture earns project visibility only after a current
14964
+ // Code Memory link exists. Explicit/private records retain scope.
14965
+ ...observation.sourceDetail === "hook" ? { visibility: "project" } : {},
14966
+ ...qualification
14967
+ });
14968
+ }
14969
+ if (hasMore && observations2.length > 0) {
14970
+ return {
14971
+ action: "reschedule",
14972
+ delayMs: 0,
14973
+ resetAttempts: true,
14974
+ payload: {
14975
+ ...job.payload,
14976
+ cursor: observations2[observations2.length - 1].id,
14977
+ limit
14978
+ }
14979
+ };
14980
+ }
14608
14981
  return { action: "complete" };
14609
14982
  }
14610
14983
  if (job.kind === "claim-derive") {
@@ -14770,7 +15143,7 @@ function createProjectMaintenanceDispatcher(projectId, projectDir2, projectRoot,
14770
15143
  return isolatedRunner({ job, projectRoot, dataDir: projectDir2 });
14771
15144
  };
14772
15145
  }
14773
- 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;
15146
+ 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;
14774
15147
  var init_project_maintenance = __esm({
14775
15148
  "src/runtime/project-maintenance.ts"() {
14776
15149
  "use strict";
@@ -14782,6 +15155,7 @@ var init_project_maintenance = __esm({
14782
15155
  DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
14783
15156
  DEFAULT_CODEGRAPH_MAX_FILES = 5e3;
14784
15157
  DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
15158
+ DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE = 100;
14785
15159
  VECTOR_RETRY_DELAY_MS = 5e3;
14786
15160
  DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
14787
15161
  }
@@ -15023,9 +15397,9 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
15023
15397
  return true;
15024
15398
  });
15025
15399
  })() : l2Scored).slice(0, 5).map(({ obs }) => obs);
15026
- const l1HookObs = projectObs.filter((obs) => classifyLayer(obs) === "L1").sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 3);
15400
+ 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);
15027
15401
  const l3GitCount = projectObs.filter((obs) => classifyLayer(obs) === "L3").length;
15028
- const totalHookCount = projectObs.filter((obs) => classifyLayer(obs) === "L1").length;
15402
+ const totalHookCount = projectObs.filter((obs) => isEligibleForAutomaticDelivery(obs) && classifyLayer(obs) === "L1").length;
15029
15403
  const activeEntities = [
15030
15404
  ...new Set(l2Obs.map((o) => o.entityName).filter((n) => !!n && n.trim().length > 0))
15031
15405
  ].slice(0, 5);
@@ -15151,6 +15525,7 @@ var init_session = __esm({
15151
15525
  "src/memory/session.ts"() {
15152
15526
  "use strict";
15153
15527
  init_esm_shims();
15528
+ init_admission();
15154
15529
  init_disclosure_policy();
15155
15530
  init_aliases();
15156
15531
  init_obs_store();
@@ -15303,27 +15678,80 @@ function workflowOutput(selection) {
15303
15678
  cautions: selection.cautions.map((caution) => short(caution, 22))
15304
15679
  };
15305
15680
  }
15306
- function appendLine(lines, candidate, maxTokens, omitted, omittedKind) {
15681
+ function appendLine(lines, candidate, maxTokens, omitted, omittedKind, selected, receiptSelection) {
15307
15682
  const next = lines.length ? lines.join("\n") + "\n" + candidate : candidate;
15308
15683
  if (countTextTokens(next) <= maxTokens) {
15309
15684
  lines.push(candidate);
15685
+ if (receiptSelection) selected?.push(receiptSelection);
15310
15686
  return true;
15311
15687
  }
15312
15688
  omitted.push(omittedKind);
15313
15689
  return false;
15314
15690
  }
15691
+ function freshnessForMemory(status) {
15692
+ if (status === "current" || status === "suspect" || status === "stale") return status;
15693
+ return "unknown";
15694
+ }
15695
+ function receiptOmissionKind(raw) {
15696
+ if (raw.includes("task")) return "task";
15697
+ if (raw.includes("fact")) return "current-fact";
15698
+ if (raw.includes("state")) return "code-state";
15699
+ if (raw.includes("semantic")) return "semantic-code";
15700
+ if (raw.includes("start")) return "start-here";
15701
+ if (raw.includes("memory")) return "memory";
15702
+ if (raw.includes("claim")) return "claim";
15703
+ if (raw.includes("knowledge-page")) return "knowledge-page";
15704
+ if (raw.includes("workflow")) return "workflow";
15705
+ if (raw.includes("verification")) return "verification";
15706
+ if (raw.includes("caution")) return "caution";
15707
+ return void 0;
15708
+ }
15709
+ function receiptOmissions(omitted, hiddenCautionMemoryCount) {
15710
+ const counts = /* @__PURE__ */ new Map();
15711
+ for (const raw of omitted) {
15712
+ const kind = receiptOmissionKind(raw);
15713
+ if (!kind || raw.endsWith("-heading")) continue;
15714
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
15715
+ }
15716
+ const receipt = [...counts.entries()].map(([kind, count2]) => ({
15717
+ kind,
15718
+ reason: "token-budget",
15719
+ count: count2
15720
+ }));
15721
+ if (hiddenCautionMemoryCount > 0) {
15722
+ receipt.push({
15723
+ kind: "caution",
15724
+ reason: "hidden-by-task-lens",
15725
+ count: hiddenCautionMemoryCount
15726
+ });
15727
+ }
15728
+ return receipt;
15729
+ }
15730
+ function scheduledActions(cautions) {
15731
+ return cautions.filter((caution) => caution.kind === "codegraph-refresh-queued").map((caution) => short(caution.message, 28));
15732
+ }
15315
15733
  function renderTaskWorksetPrompt(input) {
15316
15734
  const maxTokens = input.budget?.maxTokens ?? 180;
15317
15735
  const omitted = [];
15736
+ const selected = [];
15318
15737
  const lines = ["Memorix Autopilot Brief"];
15319
15738
  const task = short(input.task || "Continue the current task.", 34);
15320
- appendLine(lines, "Task: " + task, maxTokens, omitted, "task-detail");
15739
+ appendLine(lines, "Task: " + task, maxTokens, omitted, "task-detail", selected, {
15740
+ kind: "task",
15741
+ reason: "current task supplied by the caller",
15742
+ trust: "source-backed"
15743
+ });
15321
15744
  appendLine(lines, "Task lens: " + input.lens, maxTokens, omitted, "lens");
15322
15745
  if (input.cautions.length > 0 || input.cautionMemory.length > 0) {
15323
15746
  appendLine(lines, "", maxTokens, omitted, "caution-heading");
15324
15747
  appendLine(lines, "Cautions", maxTokens, omitted, "caution-heading");
15325
15748
  for (const caution of input.cautions.slice(0, 6)) {
15326
- appendLine(lines, "- " + short(caution.message, 22), maxTokens, omitted, "caution");
15749
+ appendLine(lines, "- " + short(caution.message, 22), maxTokens, omitted, "caution", selected, {
15750
+ kind: "caution",
15751
+ id: "caution:" + caution.kind,
15752
+ reason: "current project caution",
15753
+ trust: "source-backed"
15754
+ });
15327
15755
  }
15328
15756
  for (const memory of input.cautionMemory.slice(0, 3)) {
15329
15757
  const location = memory.path ? memory.path + (memory.symbol ? "#" + memory.symbol : "") : "no current code location";
@@ -15333,7 +15761,15 @@ function renderTaskWorksetPrompt(input) {
15333
15761
  "- #" + memory.id + " " + memory.status + ": " + short(memory.title, 18) + " (" + location + reason + ")",
15334
15762
  maxTokens,
15335
15763
  omitted,
15336
- "caution-memory"
15764
+ "caution-memory",
15765
+ selected,
15766
+ {
15767
+ kind: "memory",
15768
+ id: "memory:" + memory.id,
15769
+ reason: "task-relevant memory requiring source verification",
15770
+ freshness: freshnessForMemory(memory.status),
15771
+ trust: "historical"
15772
+ }
15337
15773
  );
15338
15774
  }
15339
15775
  if (input.hiddenCautionMemoryCount > 0) {
@@ -15344,13 +15780,22 @@ function renderTaskWorksetPrompt(input) {
15344
15780
  appendLine(lines, "", maxTokens, omitted, "facts-heading");
15345
15781
  appendLine(lines, "Current project facts", maxTokens, omitted, "facts-heading");
15346
15782
  for (const fact of input.currentFacts.slice(0, 4)) {
15347
- appendLine(lines, "- " + short(fact, 40), maxTokens, omitted, "current-fact");
15783
+ appendLine(lines, "- " + short(fact, 40), maxTokens, omitted, "current-fact", selected, {
15784
+ kind: "current-fact",
15785
+ reason: "current project state",
15786
+ trust: "source-backed"
15787
+ });
15348
15788
  }
15349
15789
  }
15350
15790
  if (input.codeState) {
15351
15791
  appendLine(lines, "", maxTokens, omitted, "state-heading");
15352
15792
  appendLine(lines, "Project state", maxTokens, omitted, "state-heading");
15353
- appendLine(lines, input.codeState, maxTokens, omitted, "code-state");
15793
+ appendLine(lines, input.codeState, maxTokens, omitted, "code-state", selected, {
15794
+ kind: "code-state",
15795
+ ...input.provenance.snapshotId ? { id: "snapshot:" + input.provenance.snapshotId } : {},
15796
+ reason: "latest available Code State snapshot",
15797
+ trust: "source-backed"
15798
+ });
15354
15799
  }
15355
15800
  if (input.semanticCode && (input.semanticCode.entryPoints.length > 0 || input.semanticCode.relations.length > 0)) {
15356
15801
  appendLine(lines, "", maxTokens, omitted, "semantic-code-heading");
@@ -15362,7 +15807,14 @@ function renderTaskWorksetPrompt(input) {
15362
15807
  "- " + location + ": " + short(relation.from.name, 12) + " " + short(relation.kind, 8) + " " + short(relation.to.name, 12),
15363
15808
  maxTokens,
15364
15809
  omitted,
15365
- "semantic-relation"
15810
+ "semantic-relation",
15811
+ selected,
15812
+ {
15813
+ kind: "semantic-code",
15814
+ id: "code:" + location,
15815
+ reason: "validated optional semantic code relation",
15816
+ trust: "derived"
15817
+ }
15366
15818
  );
15367
15819
  }
15368
15820
  if (input.semanticCode.relations.length === 0) {
@@ -15373,7 +15825,14 @@ function renderTaskWorksetPrompt(input) {
15373
15825
  "- " + location + ": " + short(entry.name, 16) + " (" + short(entry.kind, 8) + ")",
15374
15826
  maxTokens,
15375
15827
  omitted,
15376
- "semantic-entry"
15828
+ "semantic-entry",
15829
+ selected,
15830
+ {
15831
+ kind: "semantic-code",
15832
+ id: "code:" + location,
15833
+ reason: "validated optional semantic code entry point",
15834
+ trust: "derived"
15835
+ }
15377
15836
  );
15378
15837
  }
15379
15838
  }
@@ -15382,7 +15841,12 @@ function renderTaskWorksetPrompt(input) {
15382
15841
  appendLine(lines, "", maxTokens, omitted, "start-heading");
15383
15842
  appendLine(lines, "Start here", maxTokens, omitted, "start-heading");
15384
15843
  for (const source of input.startHere.slice(0, 5)) {
15385
- appendLine(lines, "- " + source, maxTokens, omitted, "start-here");
15844
+ appendLine(lines, "- " + source, maxTokens, omitted, "start-here", selected, {
15845
+ kind: "start-here",
15846
+ id: "path:" + source,
15847
+ reason: "task-lensed starting point",
15848
+ trust: "derived"
15849
+ });
15386
15850
  }
15387
15851
  }
15388
15852
  if (input.reliableMemory.length > 0) {
@@ -15395,7 +15859,15 @@ function renderTaskWorksetPrompt(input) {
15395
15859
  "- #" + memory.id + " " + memory.type + ": " + short(memory.title, 18) + " (" + location + ")",
15396
15860
  maxTokens,
15397
15861
  omitted,
15398
- "reliable-memory"
15862
+ "reliable-memory",
15863
+ selected,
15864
+ {
15865
+ kind: "memory",
15866
+ id: "memory:" + memory.id,
15867
+ reason: "current code-bound memory",
15868
+ freshness: freshnessForMemory(memory.status),
15869
+ trust: "historical"
15870
+ }
15399
15871
  );
15400
15872
  }
15401
15873
  }
@@ -15403,10 +15875,25 @@ function renderTaskWorksetPrompt(input) {
15403
15875
  appendLine(lines, "", maxTokens, omitted, "knowledge-heading");
15404
15876
  appendLine(lines, "Project knowledge", maxTokens, omitted, "knowledge-heading");
15405
15877
  for (const claim of input.claims.slice(0, 3)) {
15406
- appendLine(lines, "- " + claim.assertion + " [" + claim.id + "]", maxTokens, omitted, "claim");
15878
+ appendLine(lines, "- " + claim.assertion + " [" + claim.id + "]", maxTokens, omitted, "claim", selected, {
15879
+ kind: "claim",
15880
+ id: "claim:" + claim.id,
15881
+ reason: "source-qualified task match",
15882
+ trust: "source-backed"
15883
+ });
15407
15884
  }
15408
15885
  for (const page of input.pages.slice(0, 2)) {
15409
- appendLine(lines, "- page: " + page.relativePath, maxTokens, omitted, "knowledge-page");
15886
+ const supportsDeliveredClaim = page.claimIds.some((claimId) => selected.some((item) => item.kind === "claim" && item.id === "claim:" + claimId));
15887
+ if (!supportsDeliveredClaim) {
15888
+ omitted.push("knowledge-page-dependency");
15889
+ continue;
15890
+ }
15891
+ appendLine(lines, "- page: " + page.relativePath, maxTokens, omitted, "knowledge-page", selected, {
15892
+ kind: "knowledge-page",
15893
+ id: "page:" + page.id,
15894
+ reason: "approved page linked to a selected claim",
15895
+ trust: "source-backed"
15896
+ });
15410
15897
  }
15411
15898
  }
15412
15899
  if (input.workflows.length > 0) {
@@ -15418,7 +15905,14 @@ function renderTaskWorksetPrompt(input) {
15418
15905
  "- " + workflow.title + ": " + workflow.firstPhase.title + " - " + workflow.firstPhase.instructions,
15419
15906
  maxTokens,
15420
15907
  omitted,
15421
- "workflow"
15908
+ "workflow",
15909
+ selected,
15910
+ {
15911
+ kind: "workflow",
15912
+ id: "workflow:" + workflow.id,
15913
+ reason: "task-matching project workflow",
15914
+ trust: "source-backed"
15915
+ }
15422
15916
  );
15423
15917
  }
15424
15918
  }
@@ -15426,16 +15920,23 @@ function renderTaskWorksetPrompt(input) {
15426
15920
  appendLine(lines, "", maxTokens, omitted, "verification-heading");
15427
15921
  appendLine(lines, "Verify", maxTokens, omitted, "verification-heading");
15428
15922
  for (const check of input.verification.slice(0, 4)) {
15429
- appendLine(lines, "- " + short(check, 20), maxTokens, omitted, "verification");
15923
+ appendLine(lines, "- " + short(check, 20), maxTokens, omitted, "verification", selected, {
15924
+ kind: "verification",
15925
+ reason: "task-lensed verification guidance",
15926
+ trust: "derived"
15927
+ });
15430
15928
  }
15431
15929
  }
15432
15930
  return {
15433
15931
  prompt: lines.join("\n"),
15434
15932
  tokenCount: countTextTokens(lines.join("\n")),
15435
- omitted: unique(omitted)
15933
+ omitted: unique(omitted),
15934
+ omittedItems: omitted,
15935
+ selected
15436
15936
  };
15437
15937
  }
15438
15938
  async function buildTaskWorkset(input) {
15939
+ const startedAt = Date.now();
15439
15940
  const task = input.task?.trim() ?? "";
15440
15941
  const maxTokens = Math.max(96, Math.min(Math.floor(input.maxTokens ?? 180), 320));
15441
15942
  const cautions = [...input.runtimeCautions ?? [], ...snapshotCautions(input)];
@@ -15537,6 +16038,18 @@ async function buildTaskWorkset(input) {
15537
16038
  ...base,
15538
16039
  budget: { maxTokens }
15539
16040
  });
16041
+ const receipt = {
16042
+ version: "1.2.2",
16043
+ target: input.deliveryTarget ?? "project-context",
16044
+ elapsedMs: Math.max(0, Date.now() - startedAt),
16045
+ budget: {
16046
+ maxTokens,
16047
+ tokenCount: rendered.tokenCount
16048
+ },
16049
+ selected: rendered.selected,
16050
+ omitted: receiptOmissions(rendered.omittedItems, base.hiddenCautionMemoryCount),
16051
+ scheduledActions: scheduledActions(normalizedCautions)
16052
+ };
15540
16053
  return {
15541
16054
  ...base,
15542
16055
  budget: {
@@ -15544,6 +16057,7 @@ async function buildTaskWorkset(input) {
15544
16057
  tokenCount: rendered.tokenCount,
15545
16058
  omitted: rendered.omitted
15546
16059
  },
16060
+ receipt,
15547
16061
  prompt: rendered.prompt
15548
16062
  };
15549
16063
  }
@@ -16321,6 +16835,9 @@ import path19 from "path";
16321
16835
  function activeProjectObservations(observations2, projectId) {
16322
16836
  return observations2.filter((obs) => obs.projectId === projectId && (obs.status ?? "active") === "active");
16323
16837
  }
16838
+ function deliveryEligibleProjectObservations(observations2, projectId) {
16839
+ return activeProjectObservations(observations2, projectId).filter((observation) => isEligibleForAutomaticDelivery(observation));
16840
+ }
16324
16841
  function decideRefresh(input) {
16325
16842
  if (input.mode === "never") {
16326
16843
  return { performed: false, reason: "disabled", message: "Automatic project scan disabled." };
@@ -16395,13 +16912,27 @@ async function buildAutoProjectContext(input) {
16395
16912
  activeProjectObservations(input.observations, input.project.id)
16396
16913
  );
16397
16914
  try {
16398
- const { enqueueClaimRequalification: enqueueClaimRequalification2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
16915
+ const { MaintenanceTargetStore: MaintenanceTargetStore2 } = await Promise.resolve().then(() => (init_maintenance_targets(), maintenance_targets_exports));
16916
+ new MaintenanceTargetStore2(input.dataDir).register({
16917
+ projectId: input.project.id,
16918
+ projectRoot: input.project.rootPath,
16919
+ dataDir: input.dataDir
16920
+ });
16921
+ const {
16922
+ enqueueClaimRequalification: enqueueClaimRequalification2,
16923
+ enqueueObservationQualification: enqueueObservationQualification2
16924
+ } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
16399
16925
  enqueueClaimRequalification2({
16400
16926
  dataDir: input.dataDir,
16401
16927
  projectId: input.project.id,
16402
16928
  source: "foreground-refresh",
16403
16929
  snapshotId: store.latestSnapshot(input.project.id)?.id
16404
16930
  });
16931
+ enqueueObservationQualification2({
16932
+ dataDir: input.dataDir,
16933
+ projectId: input.project.id,
16934
+ source: "foreground-refresh"
16935
+ });
16405
16936
  } catch {
16406
16937
  }
16407
16938
  refresh = { ...refresh, backfill };
@@ -16418,7 +16949,9 @@ async function buildAutoProjectContext(input) {
16418
16949
  const explain = buildProjectContextExplain({
16419
16950
  project: input.project,
16420
16951
  store,
16421
- observations: input.observations,
16952
+ // Binding sees all active evidence, but automatic delivery sees only
16953
+ // evidence that has passed the control-plane admission gate.
16954
+ observations: deliveryEligibleProjectObservations(input.observations, input.project.id),
16422
16955
  exclude
16423
16956
  });
16424
16957
  const overview = explain.overview;
@@ -16509,7 +17042,8 @@ async function buildAutoProjectContext(input) {
16509
17042
  suspect: overview.freshness.suspect,
16510
17043
  stale: overview.freshness.stale
16511
17044
  },
16512
- runtimeCautions
17045
+ runtimeCautions,
17046
+ ...input.deliveryTarget ? { deliveryTarget: input.deliveryTarget } : {}
16513
17047
  });
16514
17048
  return {
16515
17049
  project: input.project,
@@ -16743,6 +17277,7 @@ var init_auto_context = __esm({
16743
17277
  init_external_provider();
16744
17278
  init_project_context();
16745
17279
  init_store();
17280
+ init_admission();
16746
17281
  init_task_lens();
16747
17282
  DEFAULT_MAX_AGE_MS = 10 * 60 * 1e3;
16748
17283
  }
@@ -16935,7 +17470,8 @@ async function attachTaskWorkset(input) {
16935
17470
  suspect: input.pack.warnings.filter((warning) => warning.status === "suspect").length,
16936
17471
  stale: input.pack.warnings.filter((warning) => warning.status === "stale").length
16937
17472
  },
16938
- runtimeCautions: input.runtimeCautions
17473
+ runtimeCautions: input.runtimeCautions,
17474
+ deliveryTarget: "context-pack"
16939
17475
  });
16940
17476
  return { ...input.pack, workset };
16941
17477
  }
@@ -18347,11 +18883,14 @@ __export(export_import_exports, {
18347
18883
  exportAsMarkdown: () => exportAsMarkdown,
18348
18884
  importFromJson: () => importFromJson
18349
18885
  });
18350
- async function exportAsJson(projectDir2, projectId) {
18886
+ async function exportAsJson(projectDir2, projectId, reader) {
18351
18887
  const store = getObservationStore();
18352
18888
  const allObs = await store.loadAll();
18353
18889
  const allSessions = await getSessionStore().loadAll();
18354
- const projectObs = allObs.filter((o) => o.projectId === projectId);
18890
+ const projectObs = filterReadableObservations(
18891
+ allObs.filter((o) => o.projectId === projectId),
18892
+ reader
18893
+ );
18355
18894
  const projectSessions = allSessions.filter((s) => s.projectId === projectId);
18356
18895
  const typeBreakdown = {};
18357
18896
  for (const obs of projectObs) {
@@ -18370,8 +18909,8 @@ async function exportAsJson(projectDir2, projectId) {
18370
18909
  }
18371
18910
  };
18372
18911
  }
18373
- async function exportAsMarkdown(projectDir2, projectId) {
18374
- const data = await exportAsJson(projectDir2, projectId);
18912
+ async function exportAsMarkdown(projectDir2, projectId, reader) {
18913
+ const data = await exportAsJson(projectDir2, projectId, reader);
18375
18914
  const lines = [];
18376
18915
  lines.push(`# Memorix Export: ${projectId}`);
18377
18916
  lines.push(`Exported: ${data.exportedAt}`);
@@ -18473,6 +19012,7 @@ var init_export_import = __esm({
18473
19012
  "src/memory/export-import.ts"() {
18474
19013
  "use strict";
18475
19014
  init_esm_shims();
19015
+ init_visibility();
18476
19016
  init_obs_store();
18477
19017
  init_session_store();
18478
19018
  OBSERVATION_ICONS2 = {
@@ -18649,6 +19189,7 @@ function isEligible(o, projectId) {
18649
19189
  if (isCommandLog2(o)) return false;
18650
19190
  if (isInactive(o)) return false;
18651
19191
  if (isOtherProject(o, projectId)) return false;
19192
+ if (!isEligibleForKnowledgePromotion(o)) return false;
18652
19193
  if (o.valueCategory === "ephemeral") return false;
18653
19194
  if (!contextualHasSubstance(o)) return false;
18654
19195
  return true;
@@ -18797,6 +19338,7 @@ var init_generator = __esm({
18797
19338
  "src/wiki/generator.ts"() {
18798
19339
  "use strict";
18799
19340
  init_esm_shims();
19341
+ init_admission();
18800
19342
  COMMAND_LOG_TITLE4 = /^(Ran:|Command:|Executed:)\s/i;
18801
19343
  SECTION_DEFS = [
18802
19344
  {
@@ -19083,6 +19625,9 @@ function sendError(res, message, status = 500) {
19083
19625
  function filterByProject(items, projectId) {
19084
19626
  return items.filter((item) => item.projectId === projectId);
19085
19627
  }
19628
+ function filterDashboardObservations(observations2, projectId) {
19629
+ return filterReadableObservations(observations2, { projectId });
19630
+ }
19086
19631
  function isActiveStatus(status) {
19087
19632
  return (status ?? "active") === "active";
19088
19633
  }
@@ -19130,7 +19675,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19130
19675
  switch (apiPath) {
19131
19676
  case "/projects": {
19132
19677
  try {
19133
- const allObs = await getObservationStore().loadAll();
19678
+ const allObs = filterReadableObservations(
19679
+ await getObservationStore().loadAll(),
19680
+ {}
19681
+ );
19134
19682
  const projectSet = /* @__PURE__ */ new Map();
19135
19683
  for (const obs of allObs) {
19136
19684
  if (!isActiveStatus(obs.status)) continue;
@@ -19179,13 +19727,19 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19179
19727
  await initGraphStore(effectiveDataDir);
19180
19728
  const gStore = getGraphStore();
19181
19729
  const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
19182
- const graphObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19730
+ const graphObs = filterDashboardObservations(
19731
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19732
+ effectiveProjectId
19733
+ );
19183
19734
  const scoped = scopeKnowledgeGraphToProject(graph, graphObs);
19184
19735
  sendJson(res, { entities: scoped.entities, relations: scoped.relations });
19185
19736
  break;
19186
19737
  }
19187
19738
  case "/observations": {
19188
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19739
+ const observations2 = filterDashboardObservations(
19740
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19741
+ effectiveProjectId
19742
+ );
19189
19743
  sendJson(res, observations2);
19190
19744
  break;
19191
19745
  }
@@ -19198,7 +19752,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19198
19752
  case "/stats": {
19199
19753
  await initGraphStore(effectiveDataDir);
19200
19754
  const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
19201
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19755
+ const observations2 = filterDashboardObservations(
19756
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19757
+ effectiveProjectId
19758
+ );
19202
19759
  const nextId2 = await getObservationStore().loadIdCounter();
19203
19760
  const projectGraphCounts = computeProjectGraphCounts(graph.entities, graph.relations, observations2);
19204
19761
  const typeCounts = {};
@@ -19316,7 +19873,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19316
19873
  break;
19317
19874
  }
19318
19875
  case "/retention": {
19319
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19876
+ const observations2 = filterDashboardObservations(
19877
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19878
+ effectiveProjectId
19879
+ );
19320
19880
  const now3 = Date.now();
19321
19881
  const scored = observations2.filter((obs) => obs.type !== "probe").map((obs) => {
19322
19882
  const age = now3 - new Date(obs.createdAt || now3).getTime();
@@ -19354,7 +19914,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19354
19914
  const { generateKnowledgeBase: generateKnowledgeBase2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
19355
19915
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
19356
19916
  await initMiniSkillStore2(effectiveDataDir);
19357
- const allObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19917
+ const allObs = filterDashboardObservations(
19918
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19919
+ effectiveProjectId
19920
+ );
19358
19921
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
19359
19922
  const overview = generateKnowledgeBase2({
19360
19923
  projectId: effectiveProjectId,
@@ -19370,7 +19933,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19370
19933
  const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
19371
19934
  await initMiniSkillStore2(effectiveDataDir);
19372
19935
  await initGraphStore2(effectiveDataDir);
19373
- const allObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19936
+ const allObs = filterDashboardObservations(
19937
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19938
+ effectiveProjectId
19939
+ );
19374
19940
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
19375
19941
  const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
19376
19942
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
@@ -19493,7 +20059,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19493
20059
  break;
19494
20060
  }
19495
20061
  case "/identity": {
19496
- const allObs = await getObservationStore().loadAll();
20062
+ const allObs = filterReadableObservations(await getObservationStore().loadAll(), {});
19497
20063
  const allProjectIds = [...new Set(allObs.map((o) => o.projectId).filter(Boolean))];
19498
20064
  let classifyProjectId2 = () => "real";
19499
20065
  let isDirtyProjectId2 = () => false;
@@ -19578,6 +20144,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19578
20144
  sendError(res, "Observation not found", 404);
19579
20145
  } else if (matchObs.projectId !== effectiveProjectId) {
19580
20146
  sendError(res, `Observation #${obsId} belongs to project "${matchObs.projectId}", not "${effectiveProjectId}"`, 403);
20147
+ } else if (!canManageObservation(matchObs, { projectId: effectiveProjectId })) {
20148
+ sendError(res, `Observation #${obsId} is not manageable from the unbound dashboard.`, 403);
19581
20149
  } else {
19582
20150
  await obsStore.remove(obsId);
19583
20151
  try {
@@ -19599,7 +20167,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19599
20167
  if (apiPath === "/export") {
19600
20168
  await initGraphStore(effectiveDataDir);
19601
20169
  const fullGraph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
19602
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
20170
+ const observations2 = filterDashboardObservations(
20171
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
20172
+ effectiveProjectId
20173
+ );
19603
20174
  const nextId2 = await getObservationStore().loadIdCounter();
19604
20175
  const scoped = scopeKnowledgeGraphToProject(fullGraph, observations2);
19605
20176
  const exportData = {
@@ -19923,6 +20494,7 @@ var init_server = __esm({
19923
20494
  init_yaml_loader();
19924
20495
  init_yaml_loader();
19925
20496
  init_graph_scope();
20497
+ init_visibility();
19926
20498
  MIME_TYPES = {
19927
20499
  ".html": "text/html; charset=utf-8",
19928
20500
  ".css": "text/css; charset=utf-8",
@@ -20700,6 +21272,11 @@ async function createHandoffArtifact(input, storeObservation2, teamStore) {
20700
21272
  sourceDetail: "explicit",
20701
21273
  valueCategory: "core",
20702
21274
  // immune to retention archival
21275
+ // A direct handoff is not a project-wide memo. The sender retains access
21276
+ // and the explicit recipient receives a read grant. Broadcast handoffs
21277
+ // are deliberately visible only to active team members.
21278
+ visibility: input.toAgentId ? "personal" : "team",
21279
+ ...input.toAgentId ? { sharedWithAgentIds: [input.toAgentId] } : {},
20703
21280
  createdByAgentId: input.fromAgentId
20704
21281
  });
20705
21282
  try {
@@ -22405,6 +22982,7 @@ var init_hooks_path = __esm({
22405
22982
 
22406
22983
  // src/sdk.ts
22407
22984
  init_esm_shims();
22985
+ init_visibility();
22408
22986
 
22409
22987
  // src/server.ts
22410
22988
  init_esm_shims();
@@ -22610,6 +23188,7 @@ async function createAutoRelations(obs, extracted, graphManager) {
22610
23188
  // src/server.ts
22611
23189
  init_entity_extractor();
22612
23190
  init_graph_scope();
23191
+ init_visibility();
22613
23192
 
22614
23193
  // src/compact/engine.ts
22615
23194
  init_esm_shims();
@@ -22735,7 +23314,15 @@ function formatTimeline(timeline) {
22735
23314
  function formatObservationDetail(doc) {
22736
23315
  const icon = getTypeIcon(doc.type);
22737
23316
  const lines = [];
22738
- const header = buildProvenanceHeader(doc.sourceDetail, doc.valueCategory, doc.source, doc.commitHash, doc.relatedCommits);
23317
+ const header = buildProvenanceHeader(
23318
+ doc.sourceDetail,
23319
+ doc.valueCategory,
23320
+ doc.admissionState,
23321
+ doc.admissionReason,
23322
+ doc.source,
23323
+ doc.commitHash,
23324
+ doc.relatedCommits
23325
+ );
22739
23326
  if (header) {
22740
23327
  lines.push(header);
22741
23328
  lines.push("");
@@ -22770,7 +23357,7 @@ function formatObservationDetail(doc) {
22770
23357
  }
22771
23358
  return lines.join("\n");
22772
23359
  }
22773
- function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash, relatedCommits) {
23360
+ function buildProvenanceHeader(sourceDetail, valueCategory, admissionState, admissionReason, source, commitHash, relatedCommits) {
22774
23361
  const sd = resolveSourceDetail(sourceDetail, source);
22775
23362
  if (!sd) return "";
22776
23363
  const label = sourceKindLabel(sd);
@@ -22781,11 +23368,21 @@ function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash,
22781
23368
  if (verificationLine) {
22782
23369
  lines.push(verificationLine);
22783
23370
  }
22784
- if (valueCategory === "core") {
23371
+ if (valueCategory === "core" && admissionState !== "candidate" && admissionState !== "ephemeral") {
22785
23372
  lines.push("[CORE] Core \u2014 immune to decay");
23373
+ } else if (valueCategory === "core") {
23374
+ lines.push("[CORE] Core candidate \u2014 not durable until qualification completes");
22786
23375
  } else if (valueCategory === "ephemeral") {
22787
23376
  lines.push("[WARN] Ephemeral \u2014 short-lived signal");
22788
23377
  }
23378
+ if (admissionState === "candidate") {
23379
+ lines.push("[PENDING] Candidate \u2014 excluded from automatic context until current Code Memory qualifies it");
23380
+ } else if (admissionState === "ephemeral") {
23381
+ lines.push("[TRACE] Ephemeral capture \u2014 excluded from automatic context");
23382
+ } else if (admissionState === "qualified") {
23383
+ lines.push("[QUALIFIED] Automatic evidence passed current Code Memory qualification");
23384
+ }
23385
+ if (admissionReason) lines.push(`Admission: ${redactCredentials(admissionReason)}`);
22789
23386
  return lines.join("\n");
22790
23387
  }
22791
23388
  function sourceKindLabel(sd) {
@@ -22866,6 +23463,7 @@ init_obs_store();
22866
23463
  init_mini_skill_store();
22867
23464
  init_mini_skills();
22868
23465
  init_secret_filter();
23466
+ init_visibility();
22869
23467
  function normalizeMemoryBrowseQuery(query) {
22870
23468
  const trimmed = query.trim();
22871
23469
  if (!trimmed) return "";
@@ -22908,11 +23506,13 @@ async function compactSearch(options) {
22908
23506
  );
22909
23507
  let formatted = formatIndexTable(entries, searchOptions.query, !options.projectId);
22910
23508
  if (entries.length === 0 && options.projectId) {
22911
- const allObservations = getAllObservations();
23509
+ const allObservations = filterReadableObservations(getAllObservations(), options.reader);
22912
23510
  const projectAliases = new Set(await resolveAliases(options.projectId).catch(() => [options.projectId]));
22913
23511
  const projectHasStoredMemory = allObservations.some((obs) => obs.projectId && projectAliases.has(obs.projectId));
22914
23512
  if (!projectHasStoredMemory) {
22915
- formatted = `This project does not have any Memorix memories yet.
23513
+ formatted = options.reader ? `No Memorix memories are available to this session yet.
23514
+
23515
+ The tool call worked, but this session has no project-visible memory to retrieve.` : `This project does not have any Memorix memories yet.
22916
23516
 
22917
23517
  It looks like a fresh project: the tool call worked, but there is nothing stored to retrieve yet.
22918
23518
 
@@ -22926,8 +23526,8 @@ This project does have stored Memorix memories, but none matched the current que
22926
23526
  const totalTokens = countTextTokens(formatted);
22927
23527
  return { entries, formatted, totalTokens };
22928
23528
  }
22929
- async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3) {
22930
- const result = await getTimeline(anchorId, projectId, depthBefore, depthAfter);
23529
+ async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3, reader) {
23530
+ const result = await getTimeline(anchorId, projectId, depthBefore, depthAfter, reader);
22931
23531
  const timeline = {
22932
23532
  anchorId,
22933
23533
  anchorEntry: result.anchor,
@@ -22938,7 +23538,7 @@ async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter
22938
23538
  const totalTokens = countTextTokens(formatted);
22939
23539
  return { timeline, formatted, totalTokens };
22940
23540
  }
22941
- async function compactDetail(idsOrRefs) {
23541
+ async function compactDetail(idsOrRefs, options = {}) {
22942
23542
  const invalidRefs = [];
22943
23543
  const parsedRefs = [];
22944
23544
  for (let i = 0; i < idsOrRefs.length; i++) {
@@ -22995,7 +23595,7 @@ async function compactDetail(idsOrRefs) {
22995
23595
  const missingRefs = [];
22996
23596
  for (const ref of refs) {
22997
23597
  const obs = getObservation(ref.id, ref.projectId);
22998
- if (obs && (ref.projectId ? obs.projectId === ref.projectId : true)) {
23598
+ if (obs && (ref.projectId ? obs.projectId === ref.projectId : true) && canReadObservation(obs, options.reader)) {
22999
23599
  documentMap.set(toRefKey(ref), {
23000
23600
  id: makeOramaObservationId(obs.projectId, obs.id),
23001
23601
  observationId: obs.id,
@@ -23014,7 +23614,12 @@ async function compactDetail(idsOrRefs) {
23014
23614
  status: obs.status ?? "active",
23015
23615
  source: obs.source ?? "agent",
23016
23616
  sourceDetail: obs.sourceDetail ?? "",
23017
- valueCategory: obs.valueCategory ?? ""
23617
+ valueCategory: obs.valueCategory ?? "",
23618
+ admissionState: obs.admissionState ?? "",
23619
+ admissionReason: obs.admissionReason ?? "",
23620
+ visibility: obs.visibility ?? "project",
23621
+ createdByAgentId: obs.createdByAgentId ?? "",
23622
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
23018
23623
  });
23019
23624
  } else {
23020
23625
  missingRefs.push(ref);
@@ -23024,17 +23629,17 @@ async function compactDetail(idsOrRefs) {
23024
23629
  for (const ref of missingRefs) {
23025
23630
  const fallbackDocs = await getObservationsByIds([ref.id], ref.projectId);
23026
23631
  const doc = fallbackDocs[0];
23027
- if (doc) {
23632
+ if (doc && canReadObservation(doc, options.reader)) {
23028
23633
  documentMap.set(toRefKey(ref), doc);
23029
23634
  continue;
23030
23635
  }
23031
23636
  const stored = await getPersistedObservation(ref);
23032
- if (stored) {
23637
+ if (stored && canReadObservation(stored, options.reader)) {
23033
23638
  documentMap.set(toRefKey(ref), observationToDocument(stored));
23034
23639
  }
23035
23640
  }
23036
23641
  }
23037
- const allObs = getAllObservations();
23642
+ const allObs = filterReadableObservations(getAllObservations(), options.reader);
23038
23643
  const crossRefMap = /* @__PURE__ */ new Map();
23039
23644
  for (const ref of refs) {
23040
23645
  const obs = getObservation(ref.id, ref.projectId);
@@ -23148,7 +23753,12 @@ function observationToDocument(obs) {
23148
23753
  status: obs.status ?? "active",
23149
23754
  source: obs.source ?? "agent",
23150
23755
  sourceDetail: obs.sourceDetail ?? "",
23151
- valueCategory: obs.valueCategory ?? ""
23756
+ valueCategory: obs.valueCategory ?? "",
23757
+ admissionState: obs.admissionState ?? "",
23758
+ admissionReason: obs.admissionReason ?? "",
23759
+ visibility: obs.visibility ?? "project",
23760
+ createdByAgentId: obs.createdByAgentId ?? "",
23761
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
23152
23762
  };
23153
23763
  }
23154
23764
  function formatMiniSkillDetail(skill2, provenanceStatus) {
@@ -23267,7 +23877,9 @@ function auditMemoryQuality(observations2, options) {
23267
23877
  status: obs.status ?? "active",
23268
23878
  source: obs.source ?? "agent",
23269
23879
  sourceDetail: obs.sourceDetail ?? "",
23270
- valueCategory: obs.valueCategory ?? ""
23880
+ valueCategory: obs.valueCategory ?? "",
23881
+ admissionState: obs.admissionState ?? "",
23882
+ admissionReason: obs.admissionReason ?? ""
23271
23883
  }, options.referenceTime) !== "active").map((obs) => makeEntry(obs, "Outside active retention zone"));
23272
23884
  const summary = {
23273
23885
  total: scoped.length,
@@ -23309,6 +23921,7 @@ function auditMemoryQuality(observations2, options) {
23309
23921
 
23310
23922
  // src/memory/graph-context.ts
23311
23923
  init_retention();
23924
+ init_admission();
23312
23925
  function formatGraphContextPrompt(packet) {
23313
23926
  const lines = [
23314
23927
  "## Memory Context Packet",
@@ -23466,7 +24079,9 @@ function scoreForPacket(obs, queryTokens, referenceTime, broadMemoryQuery) {
23466
24079
  status: obs.status ?? "active",
23467
24080
  source: obs.source ?? "agent",
23468
24081
  sourceDetail: obs.sourceDetail ?? "",
23469
- valueCategory: obs.valueCategory ?? ""
24082
+ valueCategory: obs.valueCategory ?? "",
24083
+ admissionState: obs.admissionState ?? "",
24084
+ admissionReason: obs.admissionReason ?? ""
23470
24085
  }, referenceTime) !== "active") {
23471
24086
  score -= 2;
23472
24087
  }
@@ -23560,8 +24175,11 @@ function buildGraphContextPacket(observations2, options) {
23560
24175
  ...audit.issues.orphans.map((entry) => entry.id),
23561
24176
  ...audit.issues.retentionCandidates.map((entry) => entry.id)
23562
24177
  ]);
23563
- const filteredObservations = observations2.filter((obs) => obs.projectId === options.projectId && !riskIds.has(obs.id));
23564
- const baseObservations = filteredObservations.length > 0 ? filteredObservations : observations2;
24178
+ const deliveryEligible = observations2.filter(
24179
+ (obs) => obs.projectId === options.projectId && isEligibleForAutomaticDelivery(obs)
24180
+ );
24181
+ const filteredObservations = deliveryEligible.filter((obs) => !riskIds.has(obs.id));
24182
+ const baseObservations = filteredObservations.length > 0 ? filteredObservations : deliveryEligible;
23565
24183
  const memories = pickMemories(baseObservations, options.projectId, options.query, options.limit ?? 5, referenceTime);
23566
24184
  const entities = buildEntities(memories);
23567
24185
  const entityNames = new Set(entities.map((entity) => entity.name));
@@ -26048,6 +26666,8 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
26048
26666
  let projectResolutionError = null;
26049
26667
  let explicitProjectBound = false;
26050
26668
  let currentAgentId;
26669
+ let teamStore;
26670
+ let initTeamStoreForProject;
26051
26671
  if (detectedProject) {
26052
26672
  rawProject = detectedProject;
26053
26673
  } else {
@@ -26295,7 +26915,7 @@ The path should point to a directory containing a .git folder.`
26295
26915
  };
26296
26916
  const server = existingServer ?? new McpServer({
26297
26917
  name: "memorix",
26298
- version: true ? "1.2.1" : "1.0.1"
26918
+ version: true ? "1.2.2" : "1.0.1"
26299
26919
  });
26300
26920
  const originalRegisterTool = server.registerTool.bind(server);
26301
26921
  server.registerTool = ((name, ...args) => {
@@ -26315,11 +26935,26 @@ The path should point to a directory containing a .git folder.`
26315
26935
  }
26316
26936
  return originalRegisterTool(name, ...args);
26317
26937
  });
26938
+ const getObservationReader = (scope = "project") => {
26939
+ let isTeamMember = false;
26940
+ if (teamFeaturesEnabled && currentAgentId) {
26941
+ try {
26942
+ const agent = teamStore.getAgent(currentAgentId);
26943
+ isTeamMember = agent?.project_id === project.id && agent.status === "active";
26944
+ } catch {
26945
+ }
26946
+ }
26947
+ return {
26948
+ ...scope === "project" ? { projectId: project.id } : {},
26949
+ ...currentAgentId ? { agentId: currentAgentId } : {},
26950
+ isTeamMember
26951
+ };
26952
+ };
26318
26953
  server.registerTool(
26319
26954
  "memorix_store",
26320
26955
  {
26321
26956
  title: "Store Memory",
26322
- 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.",
26957
+ 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.",
26323
26958
  inputSchema: {
26324
26959
  entityName: z2.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
26325
26960
  type: z2.enum(OBSERVATION_TYPES).describe("Observation type for classification"),
@@ -26337,361 +26972,214 @@ The path should point to a directory containing a .git folder.`
26337
26972
  completion: z2.number().optional().describe("Completion percentage 0-100")
26338
26973
  }).optional().describe("Progress tracking for task/feature observations"),
26339
26974
  relatedCommits: z2.array(z2.string()).optional().describe("Git commit hashes this memory relates to (links ground truth \u2194 reasoning)"),
26340
- relatedEntities: z2.array(z2.string()).optional().describe("Other entity names this memory cross-references")
26975
+ relatedEntities: z2.array(z2.string()).optional().describe("Other entity names this memory cross-references"),
26976
+ visibility: z2.enum(["personal", "project", "team"]).optional().describe(
26977
+ "Retrieval scope. Project is the normal shared default; personal/team require memorix_session_start with joinTeam=true."
26978
+ )
26341
26979
  }
26342
26980
  },
26343
- async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities }) => {
26981
+ async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility }) => {
26344
26982
  const unresolved = requireResolvedProject("store memory in the current project");
26345
26983
  if (unresolved) return unresolved;
26346
- return withFreshIndex(async () => {
26347
- let entityName = rawEntityName;
26348
- let type = rawType;
26349
- let title = rawTitle;
26350
- let safeFacts = facts ? coerceStringArray(facts) : void 0;
26351
- const safeFiles = filesModified ? coerceStringArray(filesModified) : void 0;
26352
- const safeConcepts = concepts ? coerceStringArray(concepts) : void 0;
26353
- let formationMode = "active";
26354
- if (process.env.MEMORIX_FORMATION_MODE) {
26355
- formationMode = process.env.MEMORIX_FORMATION_MODE;
26356
- } else {
26357
- try {
26358
- const { getBehaviorConfig: getBehaviorConfig2 } = await Promise.resolve().then(() => (init_behavior(), behavior_exports));
26359
- formationMode = getBehaviorConfig2({ projectRoot: project.rootPath }).formationMode;
26360
- } catch {
26984
+ const requestedVisibility = visibility ?? "project";
26985
+ const reader = getObservationReader();
26986
+ if (requestedVisibility !== "project" && !currentAgentId) {
26987
+ return {
26988
+ content: [{ type: "text", text: "Personal or team memory requires memorix_session_start with joinTeam=true so Memorix can bind an owner." }],
26989
+ isError: true
26990
+ };
26991
+ }
26992
+ if (requestedVisibility === "team" && !reader.isTeamMember) {
26993
+ return {
26994
+ content: [{ type: "text", text: "Team memory requires an active coordination membership in the current project." }],
26995
+ isError: true
26996
+ };
26997
+ }
26998
+ try {
26999
+ return await withFreshIndex(async () => {
27000
+ let entityName = rawEntityName;
27001
+ let type = rawType;
27002
+ let title = rawTitle;
27003
+ let safeFacts = facts ? coerceStringArray(facts) : void 0;
27004
+ const safeFiles = filesModified ? coerceStringArray(filesModified) : void 0;
27005
+ const safeConcepts = concepts ? coerceStringArray(concepts) : void 0;
27006
+ let formationMode = "active";
27007
+ if (process.env.MEMORIX_FORMATION_MODE) {
27008
+ formationMode = process.env.MEMORIX_FORMATION_MODE;
27009
+ } else {
27010
+ try {
27011
+ const { getBehaviorConfig: getBehaviorConfig2 } = await Promise.resolve().then(() => (init_behavior(), behavior_exports));
27012
+ formationMode = getBehaviorConfig2({ projectRoot: project.rootPath }).formationMode;
27013
+ } catch {
27014
+ }
26361
27015
  }
26362
- }
26363
- const useFormation = formationMode === "active";
26364
- let formationResult = null;
26365
- let formationNote = "";
26366
- if (useFormation && !topicKey && !progress) {
26367
- let currentFormationStage = "setup";
26368
- const completedFormationStages = {};
26369
- const formationStartTime = Date.now();
26370
- const onFormationStageEvent = (event) => {
26371
- if (event.status === "start") {
27016
+ const useFormation = formationMode === "active";
27017
+ let formationResult = null;
27018
+ let formationNote = "";
27019
+ if (useFormation && !topicKey && !progress) {
27020
+ let currentFormationStage = "setup";
27021
+ const completedFormationStages = {};
27022
+ const formationStartTime = Date.now();
27023
+ const onFormationStageEvent = (event) => {
27024
+ if (event.status === "start") {
27025
+ currentFormationStage = event.stage;
27026
+ return;
27027
+ }
26372
27028
  currentFormationStage = event.stage;
26373
- return;
26374
- }
26375
- currentFormationStage = event.stage;
26376
- if (event.stageDurationMs !== void 0) {
26377
- completedFormationStages[event.stage] = event.stageDurationMs;
26378
- }
26379
- };
26380
- try {
26381
- const formationConfig = {
26382
- mode: "active",
26383
- useLLM: isLLMEnabled(),
26384
- minValueScore: 0.3,
26385
- searchMemories: async (q, limit, pid) => {
26386
- const result = await compactSearch({ query: q, limit, projectId: pid, status: "active" });
26387
- if (result.entries.length === 0) return [];
26388
- const details = await compactDetail(result.entries.map((e) => e.id));
26389
- return details.documents.map((d, i) => ({
26390
- id: Number(d.id.replace("obs-", "")),
26391
- observationId: d.observationId,
26392
- title: d.title,
26393
- narrative: d.narrative,
26394
- facts: d.facts,
26395
- entityName: d.entityName,
26396
- type: d.type,
26397
- score: result.entries[i]?.score ?? 0
26398
- }));
26399
- },
26400
- getObservation: (id) => {
26401
- const o = getObservation(id);
26402
- if (!o) return null;
26403
- return {
26404
- id: o.id,
26405
- entityName: o.entityName,
26406
- type: o.type,
26407
- title: o.title,
26408
- narrative: o.narrative,
26409
- facts: o.facts,
26410
- topicKey: o.topicKey
26411
- };
26412
- },
26413
- getEntityNames: () => graphManager.getEntityNames(),
26414
- onStageEvent: onFormationStageEvent
27029
+ if (event.stageDurationMs !== void 0) {
27030
+ completedFormationStages[event.stage] = event.stageDurationMs;
27031
+ }
26415
27032
  };
26416
- formationResult = await withTimeout2(
26417
- runFormation({
26418
- entityName,
26419
- type,
26420
- title,
26421
- narrative,
26422
- facts: safeFacts,
26423
- projectId: project.id,
26424
- source: "explicit"
26425
- }, formationConfig),
26426
- FORMATION_TIMEOUT_MS,
26427
- "Formation pipeline"
26428
- );
26429
- const modeIcon = "[FAST]";
26430
- formationNote = `
27033
+ try {
27034
+ const formationConfig = {
27035
+ mode: "active",
27036
+ useLLM: isLLMEnabled(),
27037
+ minValueScore: 0.3,
27038
+ searchMemories: async (q, limit, pid) => {
27039
+ const result = await compactSearch({ query: q, limit, projectId: pid, status: "active", reader });
27040
+ if (result.entries.length === 0) return [];
27041
+ const details = await compactDetail(result.entries.map((e) => e.id), { reader });
27042
+ return details.documents.map((d, i) => ({
27043
+ id: Number(d.id.replace("obs-", "")),
27044
+ observationId: d.observationId,
27045
+ title: d.title,
27046
+ narrative: d.narrative,
27047
+ facts: d.facts,
27048
+ entityName: d.entityName,
27049
+ type: d.type,
27050
+ score: result.entries[i]?.score ?? 0
27051
+ }));
27052
+ },
27053
+ getObservation: (id) => {
27054
+ const o = getObservation(id);
27055
+ if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
27056
+ return {
27057
+ id: o.id,
27058
+ entityName: o.entityName,
27059
+ type: o.type,
27060
+ title: o.title,
27061
+ narrative: o.narrative,
27062
+ facts: o.facts,
27063
+ topicKey: o.topicKey
27064
+ };
27065
+ },
27066
+ getEntityNames: () => graphManager.getEntityNames(),
27067
+ onStageEvent: onFormationStageEvent
27068
+ };
27069
+ formationResult = await withTimeout2(
27070
+ runFormation({
27071
+ entityName,
27072
+ type,
27073
+ title,
27074
+ narrative,
27075
+ facts: safeFacts,
27076
+ projectId: project.id,
27077
+ source: "explicit"
27078
+ }, formationConfig),
27079
+ FORMATION_TIMEOUT_MS,
27080
+ "Formation pipeline"
27081
+ );
27082
+ const modeIcon = "[FAST]";
27083
+ formationNote = `
26431
27084
  ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formationResult.evaluation.score.toFixed(2)}) | ${formationResult.resolution.action} | ${formationResult.pipeline.durationMs}ms`;
26432
- if (formationResult.extraction.extractedFacts.length > 0) {
26433
- formationNote += ` | +${formationResult.extraction.extractedFacts.length} facts`;
26434
- }
26435
- if (formationResult.extraction.titleImproved) formationNote += " | title\u2191";
26436
- if (formationResult.extraction.entityResolved) formationNote += ` | entity\u2192${formationResult.entityName}`;
26437
- if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
26438
- } catch (formationErr) {
26439
- const isTimeout = formationErr instanceof Error && formationErr.message.includes("timed out");
26440
- const elapsedMs = Date.now() - formationStartTime;
26441
- const stageSummary = formatFormationStageDurations(completedFormationStages);
26442
- console.error(
26443
- `[memorix] Formation ${isTimeout ? "timed out" : "failed"} in memorix_store after ${elapsedMs}ms/${FORMATION_TIMEOUT_MS}ms at stage ${currentFormationStage}${stageSummary ? ` | completed: ${stageSummary}` : ""}`
26444
- );
26445
- if (!isTimeout && formationErr instanceof Error) {
26446
- console.error(`[memorix] Formation error: ${formationErr.message}`);
26447
- }
26448
- formationNote = `
27085
+ if (formationResult.extraction.extractedFacts.length > 0) {
27086
+ formationNote += ` | +${formationResult.extraction.extractedFacts.length} facts`;
27087
+ }
27088
+ if (formationResult.extraction.titleImproved) formationNote += " | title\u2191";
27089
+ if (formationResult.extraction.entityResolved) formationNote += ` | entity\u2192${formationResult.entityName}`;
27090
+ if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
27091
+ } catch (formationErr) {
27092
+ const isTimeout = formationErr instanceof Error && formationErr.message.includes("timed out");
27093
+ const elapsedMs = Date.now() - formationStartTime;
27094
+ const stageSummary = formatFormationStageDurations(completedFormationStages);
27095
+ console.error(
27096
+ `[memorix] Formation ${isTimeout ? "timed out" : "failed"} in memorix_store after ${elapsedMs}ms/${FORMATION_TIMEOUT_MS}ms at stage ${currentFormationStage}${stageSummary ? ` | completed: ${stageSummary}` : ""}`
27097
+ );
27098
+ if (!isTimeout && formationErr instanceof Error) {
27099
+ console.error(`[memorix] Formation error: ${formationErr.message}`);
27100
+ }
27101
+ formationNote = `
26449
27102
  [WARN] Formation ${isTimeout ? "timed out" : "failed"} \u2014 storing base observation without enrichment`;
26450
- }
26451
- }
26452
- if (useFormation && formationResult && formationResult.resolution.action !== "new") {
26453
- const { action: action2, targetId, reason } = formationResult.resolution;
26454
- if (action2 === "merge" && targetId) {
26455
- const targetObs = getObservation(targetId);
26456
- if (targetObs) {
26457
- await storeObservation({
26458
- entityName: targetObs.entityName,
26459
- type: targetObs.type,
26460
- title: formationResult.title,
26461
- narrative: formationResult.narrative,
26462
- facts: formationResult.facts,
26463
- filesModified: safeFiles,
26464
- concepts: safeConcepts,
26465
- projectId: project.id,
26466
- topicKey: targetObs.topicKey,
26467
- progress,
26468
- sourceDetail: "explicit",
26469
- createdByAgentId: currentAgentId
26470
- });
26471
- return {
26472
- content: [{
26473
- type: "text",
26474
- text: `[UPDATED] Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
26475
- }]
26476
- };
26477
27103
  }
26478
- } else if (action2 === "evolve" && targetId) {
26479
- const targetObs = getObservation(targetId);
26480
- if (targetObs) {
26481
- await storeObservation({
26482
- entityName: targetObs.entityName,
26483
- type: targetObs.type,
26484
- title: formationResult.title,
26485
- narrative: formationResult.narrative,
26486
- facts: formationResult.facts,
26487
- filesModified: safeFiles,
26488
- concepts: safeConcepts,
26489
- projectId: project.id,
26490
- topicKey: targetObs.topicKey,
26491
- progress,
26492
- sourceDetail: "explicit",
26493
- createdByAgentId: currentAgentId
26494
- });
26495
- return {
26496
- content: [{
26497
- type: "text",
26498
- text: `[UPDATED] Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
26499
- }]
26500
- };
26501
- }
26502
- } else if (action2 === "discard") {
26503
- return {
26504
- content: [{
26505
- type: "text",
26506
- text: `[SKIP] Formation DISCARD: ${reason}${formationNote}`
26507
- }]
26508
- };
26509
27104
  }
26510
- }
26511
- let compactAction = "";
26512
- let compactMerged = false;
26513
- if (!useFormation && !topicKey && !progress) {
26514
- try {
26515
- const searchResult = await compactSearch({
26516
- query: `${title} ${narrative.substring(0, 200)}`,
26517
- limit: 5,
26518
- projectId: project.id,
26519
- status: "active"
26520
- });
26521
- const similarEntries = searchResult.entries.map((e) => e);
26522
- if (similarEntries.length > 0) {
26523
- const similarIds = similarEntries.map((e) => e.id);
26524
- const details = await compactDetail(similarIds);
26525
- const existingMemories = details.documents.map((d, i) => ({
26526
- id: d.observationId,
26527
- title: d.title,
26528
- narrative: d.narrative,
26529
- facts: d.facts,
26530
- score: similarEntries[i]?.score ?? 0
26531
- }));
26532
- const decision = await withTimeout2(
26533
- compactOnWrite(
26534
- { title, narrative, facts: safeFacts ?? [] },
26535
- existingMemories
26536
- ),
26537
- COMPACT_ON_WRITE_TIMEOUT_MS,
26538
- "Compact-on-write"
26539
- );
26540
- if (decision.action === "UPDATE" && decision.targetId) {
26541
- const targetObs = getObservation(decision.targetId);
26542
- if (targetObs) {
26543
- await storeObservation({
26544
- entityName: targetObs.entityName,
26545
- type: targetObs.type,
26546
- title: decision.mergedNarrative ? title : targetObs.title,
26547
- narrative: decision.mergedNarrative ?? narrative,
26548
- facts: decision.mergedFacts ?? safeFacts,
26549
- filesModified: safeFiles,
26550
- concepts: safeConcepts,
26551
- projectId: project.id,
26552
- topicKey: targetObs.topicKey,
26553
- progress,
26554
- sourceDetail: "explicit",
26555
- createdByAgentId: currentAgentId
26556
- });
26557
- compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
26558
- compactMerged = true;
26559
- return {
26560
- content: [{
26561
- type: "text",
26562
- text: `${compactAction}
26563
- Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
26564
- }]
26565
- };
26566
- }
26567
- } else if (decision.action === "NONE") {
27105
+ if (useFormation && formationResult && formationResult.resolution.action !== "new") {
27106
+ const { action: action2, targetId, reason } = formationResult.resolution;
27107
+ if (action2 === "merge" && targetId) {
27108
+ const targetObs = getObservation(targetId);
27109
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27110
+ await storeObservation({
27111
+ entityName: targetObs.entityName,
27112
+ type: targetObs.type,
27113
+ title: formationResult.title,
27114
+ narrative: formationResult.narrative,
27115
+ facts: formationResult.facts,
27116
+ filesModified: safeFiles,
27117
+ concepts: safeConcepts,
27118
+ projectId: project.id,
27119
+ topicKey: targetObs.topicKey,
27120
+ progress,
27121
+ sourceDetail: "explicit",
27122
+ createdByAgentId: currentAgentId,
27123
+ visibility,
27124
+ visibilityReader: reader
27125
+ });
26568
27126
  return {
26569
27127
  content: [{
26570
27128
  type: "text",
26571
- text: `[SKIP] Compact SKIP: ${decision.reason}
26572
- Existing memory #${decision.targetId} already covers this.
26573
- Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27129
+ text: `[UPDATED] Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
26574
27130
  }]
26575
27131
  };
26576
- } else if (decision.action === "DELETE" && decision.targetId) {
26577
- const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
26578
- await resolveObservations2([decision.targetId], "resolved");
26579
- compactAction = ` | Compact: resolved outdated #${decision.targetId}`;
26580
27132
  }
26581
- if (decision.enrichedFacts && decision.enrichedFacts.length > 0) {
26582
- const currentFacts = safeFacts ?? [];
26583
- const newFacts = decision.enrichedFacts.filter((f) => !currentFacts.includes(f));
26584
- if (newFacts.length > 0) {
26585
- compactAction += ` | +${newFacts.length} LLM-extracted facts`;
26586
- }
27133
+ } else if (action2 === "evolve" && targetId) {
27134
+ const targetObs = getObservation(targetId);
27135
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27136
+ await storeObservation({
27137
+ entityName: targetObs.entityName,
27138
+ type: targetObs.type,
27139
+ title: formationResult.title,
27140
+ narrative: formationResult.narrative,
27141
+ facts: formationResult.facts,
27142
+ filesModified: safeFiles,
27143
+ concepts: safeConcepts,
27144
+ projectId: project.id,
27145
+ topicKey: targetObs.topicKey,
27146
+ progress,
27147
+ sourceDetail: "explicit",
27148
+ createdByAgentId: currentAgentId,
27149
+ visibility,
27150
+ visibilityReader: reader
27151
+ });
27152
+ return {
27153
+ content: [{
27154
+ type: "text",
27155
+ text: `[UPDATED] Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
27156
+ }]
27157
+ };
26587
27158
  }
27159
+ } else if (action2 === "discard") {
27160
+ return {
27161
+ content: [{
27162
+ type: "text",
27163
+ text: `[SKIP] Formation DISCARD: ${reason}${formationNote}`
27164
+ }]
27165
+ };
26588
27166
  }
26589
- } catch {
26590
- }
26591
- }
26592
- if (formationResult && formationResult.resolution.action === "new") {
26593
- const llmFacts = formationResult.extraction.extractedFacts;
26594
- if (llmFacts.length > 0) {
26595
- const currentFacts = safeFacts ?? [];
26596
- const currentLower = new Set(currentFacts.map((f) => f.toLowerCase().trim()));
26597
- const newFacts = llmFacts.filter((f) => !currentLower.has(f.toLowerCase().trim()));
26598
- if (newFacts.length > 0) {
26599
- safeFacts = [...currentFacts, ...newFacts];
26600
- }
26601
- }
26602
- if (formationResult.extraction.titleImproved && formationResult.title) {
26603
- title = formationResult.title;
26604
- }
26605
- if (formationResult.extraction.typeCorrected && formationResult.type) {
26606
- type = formationResult.type;
26607
- }
26608
- if (formationResult.extraction.entityResolved && formationResult.entityName) {
26609
- entityName = formationResult.entityName;
26610
- }
26611
- }
26612
- await graphManager.createEntities([
26613
- { name: entityName, entityType: "auto", observations: [] }
26614
- ]);
26615
- let sessionId;
26616
- try {
26617
- const { getActiveSession: getActiveSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
26618
- const active = await getActiveSession2(projectDir2, project.id);
26619
- if (active) sessionId = active.id;
26620
- } catch {
26621
- }
26622
- let finalNarrative = narrative;
26623
- let compressionNote = "";
26624
- try {
26625
- const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
26626
- const { compressed, saved, usedLLM } = await withTimeout2(
26627
- compressNarrative2(narrative, safeFacts, type),
26628
- COMPRESSION_TIMEOUT_MS,
26629
- "Narrative compression"
26630
- );
26631
- if (usedLLM && saved > 0) {
26632
- finalNarrative = compressed;
26633
- compressionNote = ` | compressed -${saved} tokens`;
26634
27167
  }
26635
- } catch {
26636
- }
26637
- let attributionWarning = "";
26638
- try {
26639
- const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
26640
- if (attrCheck.suspicious) {
26641
- attributionWarning = `
26642
- [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.`;
26643
- }
26644
- } catch {
26645
- }
26646
- const { observation: obs, upserted } = await storeObservation({
26647
- entityName,
26648
- type,
26649
- title,
26650
- narrative: finalNarrative,
26651
- facts: safeFacts,
26652
- filesModified: safeFiles,
26653
- concepts: safeConcepts,
26654
- projectId: project.id,
26655
- topicKey,
26656
- sessionId,
26657
- progress,
26658
- relatedCommits,
26659
- relatedEntities,
26660
- sourceDetail: "explicit",
26661
- valueCategory: formationResult?.evaluation.category,
26662
- createdByAgentId: currentAgentId
26663
- });
26664
- await graphManager.addObservations([
26665
- { entityName, contents: [`[#${obs.id}] ${title}`] }
26666
- ]);
26667
- const extracted = extractEntities([title, narrative, ...safeFacts ?? []].join(" "));
26668
- const autoRelCount = await createAutoRelations(obs, extracted, graphManager);
26669
- const enrichmentParts = [];
26670
- const autoFiles = obs.filesModified.filter((f) => !(safeFiles ?? []).includes(f));
26671
- const autoConcepts = obs.concepts.filter((c) => !(safeConcepts ?? []).includes(c));
26672
- if (autoFiles.length > 0) enrichmentParts.push(`+${autoFiles.length} files extracted`);
26673
- if (autoConcepts.length > 0) enrichmentParts.push(`+${autoConcepts.length} concepts enriched`);
26674
- if (autoRelCount > 0) enrichmentParts.push(`+${autoRelCount} relations auto-created`);
26675
- if (obs.hasCausalLanguage) enrichmentParts.push("causal language detected");
26676
- if (upserted) enrichmentParts.push(`topic upserted (rev ${obs.revisionCount ?? 1})`);
26677
- const enrichment = enrichmentParts.length > 0 ? `
26678
- Auto-enriched: ${enrichmentParts.join(", ")}` : "";
26679
- const action = upserted ? "[UPDATED] Updated" : "[OK] Stored";
26680
- if (!useFormation && !topicKey && !progress) {
26681
- const shadowFormation = async () => {
26682
- let oldCompactDecision = null;
27168
+ let compactAction = "";
27169
+ let compactMerged = false;
27170
+ if (!useFormation && !topicKey && !progress) {
26683
27171
  try {
26684
- const compactStart = Date.now();
26685
27172
  const searchResult = await compactSearch({
26686
27173
  query: `${title} ${narrative.substring(0, 200)}`,
26687
27174
  limit: 5,
26688
27175
  projectId: project.id,
26689
- status: "active"
27176
+ status: "active",
27177
+ reader
26690
27178
  });
26691
27179
  const similarEntries = searchResult.entries.map((e) => e);
26692
27180
  if (similarEntries.length > 0) {
26693
27181
  const similarIds = similarEntries.map((e) => e.id);
26694
- const details = await compactDetail(similarIds);
27182
+ const details = await compactDetail(similarIds, { reader });
26695
27183
  const existingMemories = details.documents.map((d, i) => ({
26696
27184
  id: d.observationId,
26697
27185
  title: d.title,
@@ -26699,78 +27187,266 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
26699
27187
  facts: d.facts,
26700
27188
  score: similarEntries[i]?.score ?? 0
26701
27189
  }));
26702
- const decision = await compactOnWrite(
26703
- { title, narrative, facts: safeFacts ?? [] },
26704
- existingMemories
27190
+ const decision = await withTimeout2(
27191
+ compactOnWrite(
27192
+ { title, narrative, facts: safeFacts ?? [] },
27193
+ existingMemories
27194
+ ),
27195
+ COMPACT_ON_WRITE_TIMEOUT_MS,
27196
+ "Compact-on-write"
26705
27197
  );
26706
- oldCompactDecision = {
26707
- action: decision.action,
26708
- targetId: decision.targetId,
26709
- reason: decision.reason,
26710
- durationMs: Date.now() - compactStart
26711
- };
27198
+ if (decision.action === "UPDATE" && decision.targetId) {
27199
+ const targetObs = getObservation(decision.targetId);
27200
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27201
+ await storeObservation({
27202
+ entityName: targetObs.entityName,
27203
+ type: targetObs.type,
27204
+ title: decision.mergedNarrative ? title : targetObs.title,
27205
+ narrative: decision.mergedNarrative ?? narrative,
27206
+ facts: decision.mergedFacts ?? safeFacts,
27207
+ filesModified: safeFiles,
27208
+ concepts: safeConcepts,
27209
+ projectId: project.id,
27210
+ topicKey: targetObs.topicKey,
27211
+ progress,
27212
+ sourceDetail: "explicit",
27213
+ createdByAgentId: currentAgentId,
27214
+ visibility,
27215
+ visibilityReader: reader
27216
+ });
27217
+ compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
27218
+ compactMerged = true;
27219
+ return {
27220
+ content: [{
27221
+ type: "text",
27222
+ text: `${compactAction}
27223
+ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27224
+ }]
27225
+ };
27226
+ }
27227
+ } else if (decision.action === "NONE") {
27228
+ return {
27229
+ content: [{
27230
+ type: "text",
27231
+ text: `[SKIP] Compact SKIP: ${decision.reason}
27232
+ Existing memory #${decision.targetId} already covers this.
27233
+ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27234
+ }]
27235
+ };
27236
+ } else if (decision.action === "DELETE" && decision.targetId) {
27237
+ const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27238
+ await resolveObservations2([decision.targetId], "resolved");
27239
+ compactAction = ` | Compact: resolved outdated #${decision.targetId}`;
27240
+ }
27241
+ if (decision.enrichedFacts && decision.enrichedFacts.length > 0) {
27242
+ const currentFacts = safeFacts ?? [];
27243
+ const newFacts = decision.enrichedFacts.filter((f) => !currentFacts.includes(f));
27244
+ if (newFacts.length > 0) {
27245
+ compactAction += ` | +${newFacts.length} LLM-extracted facts`;
27246
+ }
27247
+ }
26712
27248
  }
26713
27249
  } catch {
26714
27250
  }
26715
- const formationConfig = {
26716
- mode: formationMode,
26717
- useLLM: isLLMEnabled(),
26718
- minValueScore: 0.3,
26719
- searchMemories: async (q, limit, pid) => {
26720
- const result = await compactSearch({ query: q, limit, projectId: pid, status: "active" });
26721
- if (result.entries.length === 0) return [];
26722
- const details = await compactDetail(result.entries.map((e) => e.id));
26723
- return details.documents.map((d, i) => ({
26724
- id: Number(d.id.replace("obs-", "")),
26725
- observationId: d.observationId,
26726
- title: d.title,
26727
- narrative: d.narrative,
26728
- facts: d.facts,
26729
- entityName: d.entityName,
26730
- type: d.type,
26731
- score: result.entries[i]?.score ?? 0
26732
- }));
26733
- },
26734
- getObservation: (id) => {
26735
- const o = getObservation(id);
26736
- if (!o) return null;
26737
- return { id: o.id, entityName: o.entityName, type: o.type, title: o.title, narrative: o.narrative, facts: o.facts, topicKey: o.topicKey };
26738
- },
26739
- getEntityNames: () => graphManager.getEntityNames()
26740
- };
26741
- const formed = await withTimeout2(
26742
- runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
26743
- FORMATION_TIMEOUT_MS,
26744
- "Shadow formation"
27251
+ }
27252
+ if (formationResult && formationResult.resolution.action === "new") {
27253
+ const llmFacts = formationResult.extraction.extractedFacts;
27254
+ if (llmFacts.length > 0) {
27255
+ const currentFacts = safeFacts ?? [];
27256
+ const currentLower = new Set(currentFacts.map((f) => f.toLowerCase().trim()));
27257
+ const newFacts = llmFacts.filter((f) => !currentLower.has(f.toLowerCase().trim()));
27258
+ if (newFacts.length > 0) {
27259
+ safeFacts = [...currentFacts, ...newFacts];
27260
+ }
27261
+ }
27262
+ if (formationResult.extraction.titleImproved && formationResult.title) {
27263
+ title = formationResult.title;
27264
+ }
27265
+ if (formationResult.extraction.typeCorrected && formationResult.type) {
27266
+ type = formationResult.type;
27267
+ }
27268
+ if (formationResult.extraction.entityResolved && formationResult.entityName) {
27269
+ entityName = formationResult.entityName;
27270
+ }
27271
+ }
27272
+ await graphManager.createEntities([
27273
+ { name: entityName, entityType: "auto", observations: [] }
27274
+ ]);
27275
+ let sessionId;
27276
+ try {
27277
+ const { getActiveSession: getActiveSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
27278
+ const active = await getActiveSession2(projectDir2, project.id);
27279
+ if (active) sessionId = active.id;
27280
+ } catch {
27281
+ }
27282
+ let finalNarrative = narrative;
27283
+ let compressionNote = "";
27284
+ try {
27285
+ const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
27286
+ const { compressed, saved, usedLLM } = await withTimeout2(
27287
+ compressNarrative2(narrative, safeFacts, type),
27288
+ COMPRESSION_TIMEOUT_MS,
27289
+ "Narrative compression"
26745
27290
  );
26746
- const { recordBeforeAfterMetrics: recordBeforeAfterMetrics2 } = await Promise.resolve().then(() => (init_formation(), formation_exports));
26747
- if (oldCompactDecision) {
26748
- recordBeforeAfterMetrics2({
26749
- formationAction: formed.resolution.action,
26750
- formationTargetId: formed.resolution.targetId,
26751
- oldCompactAction: oldCompactDecision.action,
26752
- oldCompactTargetId: oldCompactDecision.targetId,
26753
- oldCompactReason: oldCompactDecision.reason,
26754
- formationValueScore: formed.evaluation.score,
26755
- formationValueCategory: formed.evaluation.category,
26756
- formationDurationMs: formed.pipeline.durationMs,
26757
- compactDurationMs: oldCompactDecision.durationMs
26758
- });
27291
+ if (usedLLM && saved > 0) {
27292
+ finalNarrative = compressed;
27293
+ compressionNote = ` | compressed -${saved} tokens`;
26759
27294
  }
26760
- };
26761
- shadowFormation().catch(() => {
27295
+ } catch {
27296
+ }
27297
+ let attributionWarning = "";
27298
+ try {
27299
+ const attrCheck = await checkProjectAttribution(
27300
+ entityName,
27301
+ project.id,
27302
+ filterReadableObservations(getAllObservations(), reader)
27303
+ );
27304
+ if (attrCheck.suspicious) {
27305
+ attributionWarning = `
27306
+ [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.`;
27307
+ }
27308
+ } catch {
27309
+ }
27310
+ const { observation: obs, upserted } = await storeObservation({
27311
+ entityName,
27312
+ type,
27313
+ title,
27314
+ narrative: finalNarrative,
27315
+ facts: safeFacts,
27316
+ filesModified: safeFiles,
27317
+ concepts: safeConcepts,
27318
+ projectId: project.id,
27319
+ topicKey,
27320
+ sessionId,
27321
+ progress,
27322
+ relatedCommits,
27323
+ relatedEntities,
27324
+ sourceDetail: "explicit",
27325
+ valueCategory: formationResult?.evaluation.category,
27326
+ createdByAgentId: currentAgentId,
27327
+ visibility,
27328
+ visibilityReader: reader
26762
27329
  });
26763
- }
26764
- return {
26765
- content: [
26766
- {
26767
- type: "text",
26768
- text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
27330
+ await graphManager.addObservations([
27331
+ { entityName, contents: [`[#${obs.id}] ${title}`] }
27332
+ ]);
27333
+ const extracted = extractEntities([title, narrative, ...safeFacts ?? []].join(" "));
27334
+ const autoRelCount = await createAutoRelations(obs, extracted, graphManager);
27335
+ const enrichmentParts = [];
27336
+ const autoFiles = obs.filesModified.filter((f) => !(safeFiles ?? []).includes(f));
27337
+ const autoConcepts = obs.concepts.filter((c) => !(safeConcepts ?? []).includes(c));
27338
+ if (autoFiles.length > 0) enrichmentParts.push(`+${autoFiles.length} files extracted`);
27339
+ if (autoConcepts.length > 0) enrichmentParts.push(`+${autoConcepts.length} concepts enriched`);
27340
+ if (autoRelCount > 0) enrichmentParts.push(`+${autoRelCount} relations auto-created`);
27341
+ if (obs.hasCausalLanguage) enrichmentParts.push("causal language detected");
27342
+ if (upserted) enrichmentParts.push(`topic upserted (rev ${obs.revisionCount ?? 1})`);
27343
+ const enrichment = enrichmentParts.length > 0 ? `
27344
+ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
27345
+ const action = upserted ? "[UPDATED] Updated" : "[OK] Stored";
27346
+ if (!useFormation && !topicKey && !progress) {
27347
+ const shadowFormation = async () => {
27348
+ let oldCompactDecision = null;
27349
+ try {
27350
+ const compactStart = Date.now();
27351
+ const searchResult = await compactSearch({
27352
+ query: `${title} ${narrative.substring(0, 200)}`,
27353
+ limit: 5,
27354
+ projectId: project.id,
27355
+ status: "active",
27356
+ reader
27357
+ });
27358
+ const similarEntries = searchResult.entries.map((e) => e);
27359
+ if (similarEntries.length > 0) {
27360
+ const similarIds = similarEntries.map((e) => e.id);
27361
+ const details = await compactDetail(similarIds, { reader });
27362
+ const existingMemories = details.documents.map((d, i) => ({
27363
+ id: d.observationId,
27364
+ title: d.title,
27365
+ narrative: d.narrative,
27366
+ facts: d.facts,
27367
+ score: similarEntries[i]?.score ?? 0
27368
+ }));
27369
+ const decision = await compactOnWrite(
27370
+ { title, narrative, facts: safeFacts ?? [] },
27371
+ existingMemories
27372
+ );
27373
+ oldCompactDecision = {
27374
+ action: decision.action,
27375
+ targetId: decision.targetId,
27376
+ reason: decision.reason,
27377
+ durationMs: Date.now() - compactStart
27378
+ };
27379
+ }
27380
+ } catch {
27381
+ }
27382
+ const formationConfig = {
27383
+ mode: formationMode,
27384
+ useLLM: isLLMEnabled(),
27385
+ minValueScore: 0.3,
27386
+ searchMemories: async (q, limit, pid) => {
27387
+ const result = await compactSearch({ query: q, limit, projectId: pid, status: "active", reader });
27388
+ if (result.entries.length === 0) return [];
27389
+ const details = await compactDetail(result.entries.map((e) => e.id), { reader });
27390
+ return details.documents.map((d, i) => ({
27391
+ id: Number(d.id.replace("obs-", "")),
27392
+ observationId: d.observationId,
27393
+ title: d.title,
27394
+ narrative: d.narrative,
27395
+ facts: d.facts,
27396
+ entityName: d.entityName,
27397
+ type: d.type,
27398
+ score: result.entries[i]?.score ?? 0
27399
+ }));
27400
+ },
27401
+ getObservation: (id) => {
27402
+ const o = getObservation(id);
27403
+ if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
27404
+ return { id: o.id, entityName: o.entityName, type: o.type, title: o.title, narrative: o.narrative, facts: o.facts, topicKey: o.topicKey };
27405
+ },
27406
+ getEntityNames: () => graphManager.getEntityNames()
27407
+ };
27408
+ const formed = await withTimeout2(
27409
+ runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
27410
+ FORMATION_TIMEOUT_MS,
27411
+ "Shadow formation"
27412
+ );
27413
+ const { recordBeforeAfterMetrics: recordBeforeAfterMetrics2 } = await Promise.resolve().then(() => (init_formation(), formation_exports));
27414
+ if (oldCompactDecision) {
27415
+ recordBeforeAfterMetrics2({
27416
+ formationAction: formed.resolution.action,
27417
+ formationTargetId: formed.resolution.targetId,
27418
+ oldCompactAction: oldCompactDecision.action,
27419
+ oldCompactTargetId: oldCompactDecision.targetId,
27420
+ oldCompactReason: oldCompactDecision.reason,
27421
+ formationValueScore: formed.evaluation.score,
27422
+ formationValueCategory: formed.evaluation.category,
27423
+ formationDurationMs: formed.pipeline.durationMs,
27424
+ compactDurationMs: oldCompactDecision.durationMs
27425
+ });
27426
+ }
27427
+ };
27428
+ shadowFormation().catch(() => {
27429
+ });
27430
+ }
27431
+ return {
27432
+ content: [
27433
+ {
27434
+ type: "text",
27435
+ text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
26769
27436
  Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}${formationNote}${attributionWarning}`
26770
- }
26771
- ]
27437
+ }
27438
+ ]
27439
+ };
27440
+ });
27441
+ } catch (error) {
27442
+ return {
27443
+ content: [{
27444
+ type: "text",
27445
+ text: error instanceof Error ? error.message : "Failed to store memory."
27446
+ }],
27447
+ isError: true
26772
27448
  };
26773
- });
27449
+ }
26774
27450
  }
26775
27451
  );
26776
27452
  server.registerTool(
@@ -26842,7 +27518,8 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
26842
27518
  // Use scope: 'global' to explicitly search all projects.
26843
27519
  projectId: scope === "global" ? void 0 : project.id,
26844
27520
  status: status ?? "active",
26845
- source
27521
+ source,
27522
+ reader: getObservationReader(scope === "global" ? "global" : "project")
26846
27523
  });
26847
27524
  const timeoutPromise = new Promise(
26848
27525
  (_, reject) => setTimeout(() => reject(new Error(`Search timeout after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
@@ -26900,7 +27577,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
26900
27577
  const unresolved = requireResolvedProject("build graph context for the current project");
26901
27578
  if (unresolved) return unresolved;
26902
27579
  const { getObservationStore: getObservationStore2 } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
26903
- const observations2 = await getObservationStore2().loadByProject(project.id);
27580
+ const observations2 = filterReadableObservations(
27581
+ await getObservationStore2().loadByProject(project.id),
27582
+ getObservationReader()
27583
+ );
26904
27584
  const packet = buildGraphContextPacket(observations2, {
26905
27585
  projectId: project.id,
26906
27586
  query,
@@ -26952,7 +27632,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
26952
27632
  Promise.resolve().then(() => (init_maintenance_jobs(), maintenance_jobs_exports)),
26953
27633
  Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports))
26954
27634
  ]);
26955
- const observations2 = await getObservationStore2().loadByProject(project.id, { status: "active" });
27635
+ const observations2 = filterReadableObservations(
27636
+ await getObservationStore2().loadByProject(project.id, { status: "active" }),
27637
+ getObservationReader()
27638
+ );
26956
27639
  const context = await buildAutoProjectContext2({
26957
27640
  project,
26958
27641
  dataDir: projectDir2,
@@ -27042,7 +27725,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27042
27725
  await store.init(projectDir2);
27043
27726
  const codegraphConfig = getResolvedConfig2({ projectRoot: project.rootPath }).codegraph;
27044
27727
  const exclude = codegraphConfig.excludePatterns;
27045
- const observations2 = await getObservationStore2().loadByProject(project.id, { status: "active" });
27728
+ const observations2 = filterReadableObservations(
27729
+ await getObservationStore2().loadByProject(project.id, { status: "active" }),
27730
+ getObservationReader()
27731
+ );
27046
27732
  observations2.reverse();
27047
27733
  const basePack = assembleContextPackForTask2({
27048
27734
  store,
@@ -27414,9 +28100,15 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27414
28100
  }
27415
28101
  },
27416
28102
  async ({ ids, status }) => {
27417
- const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28103
+ const { resolveObservations: resolveObservations2, getObservation: getObservation2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27418
28104
  const safeIds = (Array.isArray(ids) ? ids : [ids]).map((id) => coerceNumber(id, 0)).filter((id) => id > 0);
27419
- const result = await resolveObservations2(safeIds, status ?? "resolved");
28105
+ const reader = getObservationReader();
28106
+ const authorizedIds = safeIds.filter((id) => {
28107
+ const observation = getObservation2(id, project.id);
28108
+ return observation ? canManageObservation(observation, reader) : false;
28109
+ });
28110
+ const result = await resolveObservations2(authorizedIds, status ?? "resolved");
28111
+ const deniedCount = safeIds.length - authorizedIds.length;
27420
28112
  const parts = [];
27421
28113
  if (result.resolved.length > 0) {
27422
28114
  parts.push(`[OK] Resolved ${result.resolved.length} observation(s): #${result.resolved.join(", #")}`);
@@ -27424,6 +28116,9 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27424
28116
  if (result.notFound.length > 0) {
27425
28117
  parts.push(`[WARN] Not found: #${result.notFound.join(", #")}`);
27426
28118
  }
28119
+ if (deniedCount > 0) {
28120
+ parts.push(`[WARN] Skipped ${deniedCount} observation(s) outside this session's write scope.`);
28121
+ }
27427
28122
  parts.push('\nResolved memories are hidden from default search. Use status="all" to include them.');
27428
28123
  parts.push('[STATS] Run `memorix_retention` with `action: "report"` to check remaining cleanup status.');
27429
28124
  return {
@@ -27453,6 +28148,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27453
28148
  async ({ entityName, decision, alternatives, rationale, constraints, expectedOutcome, risks, concepts, filesModified, relatedCommits, relatedEntities }) => {
27454
28149
  const unresolved = requireResolvedProject("store reasoning in the current project");
27455
28150
  if (unresolved) return unresolved;
28151
+ const reader = getObservationReader();
27456
28152
  return withFreshIndex(async () => {
27457
28153
  const narrativeParts = [rationale];
27458
28154
  if (alternatives && alternatives.length > 0) {
@@ -27475,7 +28171,11 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27475
28171
  ]);
27476
28172
  let reasoningAttributionWarning = "";
27477
28173
  try {
27478
- const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
28174
+ const attrCheck = await checkProjectAttribution(
28175
+ entityName,
28176
+ project.id,
28177
+ filterReadableObservations(getAllObservations(), reader)
28178
+ );
27479
28179
  if (attrCheck.suspicious) {
27480
28180
  reasoningAttributionWarning = `
27481
28181
  [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.`;
@@ -27495,7 +28195,8 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27495
28195
  relatedCommits,
27496
28196
  relatedEntities,
27497
28197
  sourceDetail: "explicit",
27498
- createdByAgentId: currentAgentId
28198
+ createdByAgentId: currentAgentId,
28199
+ visibility: "project"
27499
28200
  });
27500
28201
  await graphManager.addObservations([
27501
28202
  { entityName, contents: [`[#${obs.id}] [REASONING] ${decision}`] }
@@ -27527,7 +28228,11 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
27527
28228
  const minCount = threshold ?? 2;
27528
28229
  let entries;
27529
28230
  try {
27530
- entries = await auditProjectObservations(project.id, await withFreshIndex(() => getAllObservations()), minCount);
28231
+ entries = await auditProjectObservations(
28232
+ project.id,
28233
+ await withFreshIndex(() => filterReadableObservations(getAllObservations(), getObservationReader())),
28234
+ minCount
28235
+ );
27531
28236
  } catch (err) {
27532
28237
  return {
27533
28238
  content: [{
@@ -27592,7 +28297,8 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
27592
28297
  limit: safeLimit,
27593
28298
  type: "reasoning",
27594
28299
  projectId: scope === "global" ? void 0 : project.id,
27595
- status: "active"
28300
+ status: "active",
28301
+ reader: getObservationReader(scope === "global" ? "global" : "project")
27596
28302
  }));
27597
28303
  if (result.entries.length === 0) {
27598
28304
  return {
@@ -27617,7 +28323,11 @@ ${result.formatted}` }]
27617
28323
  },
27618
28324
  async ({ query, dryRun }) => {
27619
28325
  const { getAllObservations: getAllObservations2, resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27620
- const allObs = await withFreshIndex(() => getAllObservations2().filter((o) => (o.status ?? "active") === "active" && o.projectId === project.id));
28326
+ const reader = getObservationReader();
28327
+ const allObs = await withFreshIndex(() => filterReadableObservations(
28328
+ getAllObservations2().filter((o) => (o.status ?? "active") === "active" && o.projectId === project.id),
28329
+ reader
28330
+ ).filter((observation) => canManageObservation(observation, reader)));
27621
28331
  if (allObs.length < 2) {
27622
28332
  return { content: [{ type: "text", text: "Not enough active memories to deduplicate." }] };
27623
28333
  }
@@ -27631,7 +28341,7 @@ ${result.formatted}` }]
27631
28341
  }
27632
28342
  let candidates;
27633
28343
  if (query) {
27634
- const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: "active" });
28344
+ const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: "active", reader });
27635
28345
  const idSet = new Set(searchResult.entries.map((e) => e.id));
27636
28346
  candidates = allObs.filter((o) => idSet.has(o.id));
27637
28347
  } else {
@@ -27721,7 +28431,8 @@ ${actions.join("\n")}`
27721
28431
  safeAnchor,
27722
28432
  project.id,
27723
28433
  safeBefore,
27724
- safeAfter
28434
+ safeAfter,
28435
+ getObservationReader()
27725
28436
  );
27726
28437
  return {
27727
28438
  content: [
@@ -27755,12 +28466,14 @@ ${actions.join("\n")}`
27755
28466
  const safeTypedRefs = coerceStringArray(typedRefs);
27756
28467
  let result;
27757
28468
  try {
28469
+ const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id) || safeTypedRefs.some((ref) => ref.includes("@") && !ref.endsWith(`@${project.id}`));
28470
+ const reader = getObservationReader(hasCrossProjectRef ? "global" : "project");
27758
28471
  if (safeTypedRefs.length > 0) {
27759
- result = await compactDetail(safeTypedRefs);
28472
+ result = await compactDetail(safeTypedRefs, { reader });
27760
28473
  } else if (safeRefs.length > 0) {
27761
- result = await compactDetail(safeRefs);
28474
+ result = await compactDetail(safeRefs, { reader });
27762
28475
  } else {
27763
- result = await compactDetail(safeIds.map((id) => ({ id, projectId: project.id })));
28476
+ result = await compactDetail(safeIds.map((id) => ({ id, projectId: project.id })), { reader });
27764
28477
  }
27765
28478
  } catch (err) {
27766
28479
  return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
@@ -27786,11 +28499,15 @@ ${actions.join("\n")}`
27786
28499
  },
27787
28500
  async (args) => {
27788
28501
  const action = args.action ?? "report";
27789
- const { getRetentionSummary: getRetentionSummary2, getArchiveCandidates: getArchiveCandidates2, rankByRelevance: rankByRelevance2, archiveExpired: archiveExpired2, getRetentionZone: getRetentionZone2, explainRetention: explainRetention2 } = await Promise.resolve().then(() => (init_retention(), retention_exports));
28502
+ const { getRetentionSummary: getRetentionSummary2, getArchiveCandidates: getArchiveCandidates2, rankByRelevance: rankByRelevance2, getRetentionZone: getRetentionZone2, explainRetention: explainRetention2 } = await Promise.resolve().then(() => (init_retention(), retention_exports));
27790
28503
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
27791
28504
  const { search: search2 } = await import("@orama/orama");
27792
- const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27793
- const allObs = await withFreshIndex(() => getAllObservations2());
28505
+ const { getAllObservations: getAllObservations2, resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28506
+ const reader = getObservationReader();
28507
+ const allObs = await withFreshIndex(() => filterReadableObservations(
28508
+ getAllObservations2().filter((observation) => observation.projectId === project.id),
28509
+ reader
28510
+ ));
27794
28511
  const accessMap = /* @__PURE__ */ new Map();
27795
28512
  try {
27796
28513
  const database = await getDb2();
@@ -27807,20 +28524,6 @@ ${actions.join("\n")}`
27807
28524
  }
27808
28525
  } catch {
27809
28526
  }
27810
- if (action === "archive") {
27811
- const result = await archiveExpired2(projectDir2, void 0, accessMap, project.id);
27812
- if (result.archived === 0) {
27813
- return {
27814
- content: [{ type: "text", text: "[OK] No expired observations to archive. All memories are within their retention period." }]
27815
- };
27816
- }
27817
- return {
27818
- content: [{ type: "text", text: `[ARCHIVED] Archived ${result.archived} expired observations (status set to 'archived' in-place)
27819
- ${result.remaining} active observations remaining.
27820
-
27821
- Archived memories are hidden from default search but can be found with status: "all".` }]
27822
- };
27823
- }
27824
28527
  const docs = allObs.map((obs) => ({
27825
28528
  id: `obs-${obs.id}`,
27826
28529
  observationId: obs.id,
@@ -27839,13 +28542,34 @@ Archived memories are hidden from default search but can be found with status: "
27839
28542
  status: obs.status ?? "active",
27840
28543
  source: obs.source ?? "agent",
27841
28544
  sourceDetail: obs.sourceDetail ?? "",
27842
- valueCategory: obs.valueCategory ?? ""
28545
+ valueCategory: obs.valueCategory ?? "",
28546
+ admissionState: obs.admissionState ?? "",
28547
+ admissionReason: obs.admissionReason ?? "",
28548
+ visibility: obs.visibility ?? "project",
28549
+ createdByAgentId: obs.createdByAgentId ?? "",
28550
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
27843
28551
  }));
27844
28552
  if (docs.length === 0) {
27845
28553
  return {
27846
28554
  content: [{ type: "text", text: "No observations found for this project." }]
27847
28555
  };
27848
28556
  }
28557
+ if (action === "archive") {
28558
+ const managedIds = new Set(
28559
+ allObs.filter((observation) => canManageObservation(observation, reader)).map((observation) => observation.id)
28560
+ );
28561
+ const candidates2 = getArchiveCandidates2(docs).filter((document) => managedIds.has(document.observationId));
28562
+ if (candidates2.length === 0) {
28563
+ return {
28564
+ content: [{ type: "text", text: "[OK] No expired memories in this session's write scope to archive." }]
28565
+ };
28566
+ }
28567
+ const result = await resolveObservations2(candidates2.map((document) => document.observationId), "archived");
28568
+ return {
28569
+ content: [{ type: "text", text: `[ARCHIVED] Archived ${result.resolved.length} expired observation(s) in this session's write scope.
28570
+ ${Math.max(0, managedIds.size - result.resolved.length)} visible writable observations remaining.` }]
28571
+ };
28572
+ }
27849
28573
  if (action === "stale") {
27850
28574
  const staleDocs = docs.filter((d) => getRetentionZone2(d) === "stale");
27851
28575
  if (staleDocs.length === 0) {
@@ -28166,7 +28890,10 @@ Archived memories are hidden from default search but can be found with status: "
28166
28890
  const allObs = await withFreshIndex(() => getAllObservations2());
28167
28891
  const scoped = scopeKnowledgeGraphToProject(
28168
28892
  graph,
28169
- allObs.filter((observation) => observation.projectId === project.id)
28893
+ filterReadableObservations(
28894
+ allObs.filter((observation) => observation.projectId === project.id),
28895
+ getObservationReader()
28896
+ )
28170
28897
  );
28171
28898
  return { entities: scoped.entities, relations: scoped.relations };
28172
28899
  }
@@ -28441,7 +29168,10 @@ ${skill2.content}` }]
28441
29168
  };
28442
29169
  }
28443
29170
  const { getObservationStore: getStore } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
28444
- const allObs = await getStore().loadAll();
29171
+ const allObs = filterReadableObservations(
29172
+ (await getStore().loadAll()).filter((observation) => observation.projectId === project.id),
29173
+ getObservationReader()
29174
+ );
28445
29175
  const obsData = allObs.map((o) => ({
28446
29176
  id: o.id || 0,
28447
29177
  entityName: o.entityName || "unknown",
@@ -28540,7 +29270,11 @@ ${skill2.content}` }]
28540
29270
  }
28541
29271
  const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28542
29272
  const allObs = await withFreshIndex(() => getAllObservations2());
28543
- const matched = allObs.filter((o) => observationIds.includes(o.id));
29273
+ const reader = getObservationReader();
29274
+ const matched = filterReadableObservations(
29275
+ allObs.filter((observation) => observation.projectId === project.id && observationIds.includes(observation.id)),
29276
+ reader
29277
+ );
28544
29278
  if (matched.length === 0) {
28545
29279
  return { content: [{ type: "text", text: `No observations found for IDs: [${observationIds.join(", ")}]. Use \`memorix_search\` to find valid IDs.` }], isError: true };
28546
29280
  }
@@ -28548,6 +29282,13 @@ ${skill2.content}` }]
28548
29282
  if (nonActive.length > 0) {
28549
29283
  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 };
28550
29284
  }
29285
+ const nonProjectShared = matched.filter((observation) => resolveObservationVisibility(observation) !== "project");
29286
+ if (nonProjectShared.length > 0) {
29287
+ return {
29288
+ content: [{ type: "text", text: `Cannot promote private or team-scoped observations: ${nonProjectShared.map((observation) => `#${observation.id}`).join(", ")}. Promote only deliberate project-shared knowledge.` }],
29289
+ isError: true
29290
+ };
29291
+ }
28551
29292
  const skill2 = await promoteToMiniSkill2(projectDir2, project.id, matched, { trigger, instruction, tags });
28552
29293
  const lines = [
28553
29294
  `[OK] Created mini-skill #${skill2.id}`,
@@ -28706,8 +29447,11 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
28706
29447
  const lastSeen = registeredAgent.last_seen_obs_generation;
28707
29448
  const store = getObservationStore();
28708
29449
  const currentGen = store.getGeneration();
28709
- const projectObs = await withFreshIndex(() => getAllObservations().filter(
28710
- (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
29450
+ const projectObs = await withFreshIndex(() => filterReadableObservations(
29451
+ getAllObservations().filter(
29452
+ (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
29453
+ ),
29454
+ getObservationReader()
28711
29455
  ));
28712
29456
  const wm = computeWatermark2(lastSeen, currentGen, projectObs.length);
28713
29457
  if (wm.newObservationCount > 0) {
@@ -28876,7 +29620,7 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
28876
29620
  "memorix_transfer",
28877
29621
  {
28878
29622
  title: "Transfer Memories",
28879
- 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).',
29623
+ 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).',
28880
29624
  inputSchema: {
28881
29625
  action: z2.enum(["export", "import"]).describe("Operation: export or import"),
28882
29626
  format: z2.enum(["json", "markdown"]).optional().describe("Export format (for export, default: json)"),
@@ -28887,10 +29631,10 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
28887
29631
  if (action === "export") {
28888
29632
  const { exportAsJson: exportAsJson2, exportAsMarkdown: exportAsMarkdown2 } = await Promise.resolve().then(() => (init_export_import(), export_import_exports));
28889
29633
  if (format === "markdown") {
28890
- const md = await exportAsMarkdown2(projectDir2, project.id);
29634
+ const md = await exportAsMarkdown2(projectDir2, project.id, getObservationReader());
28891
29635
  return { content: [{ type: "text", text: md }] };
28892
29636
  }
28893
- const data = await exportAsJson2(projectDir2, project.id);
29637
+ const data = await exportAsJson2(projectDir2, project.id, getObservationReader());
28894
29638
  const json = JSON.stringify(data, null, 2);
28895
29639
  return {
28896
29640
  content: [{
@@ -29087,8 +29831,6 @@ ${json}
29087
29831
  }
29088
29832
  }
29089
29833
  );
29090
- let teamStore;
29091
- let initTeamStoreForProject;
29092
29834
  if (teamFeaturesEnabled) {
29093
29835
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
29094
29836
  initTeamStoreForProject = initTeamStore2;
@@ -29361,7 +30103,7 @@ ${lines.join("\n")}` }] };
29361
30103
  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.`,
29362
30104
  inputSchema: {
29363
30105
  action: z2.enum(["send", "broadcast", "inbox"]).describe("Operation to perform"),
29364
- from: z2.string().optional().describe("Sender agent ID (for send/broadcast)"),
30106
+ from: z2.string().optional().describe("Your sender agent ID. Omit it to use this session identity."),
29365
30107
  to: z2.string().optional().describe("Receiver agent ID (for send)"),
29366
30108
  type: z2.enum(["request", "response", "info", "announcement", "contract", "error", "handoff"]).optional().describe("Message type (for send/broadcast)"),
29367
30109
  content: z2.string().optional().describe("Message content (for send/broadcast)"),
@@ -29372,13 +30114,20 @@ ${lines.join("\n")}` }] };
29372
30114
  }
29373
30115
  },
29374
30116
  async ({ action, from, to, type: msgType, content, agentId, markRead, toRole, handoffStatus }) => {
30117
+ const requireCurrentTeamAgent = () => {
30118
+ if (!currentAgentId) return null;
30119
+ const agent = teamStore.getAgent(currentAgentId);
30120
+ return agent?.project_id === project.id && agent.status === "active" ? agent : null;
30121
+ };
29375
30122
  if (action === "send") {
29376
- if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for send" }], isError: true };
30123
+ const sender = requireCurrentTeamAgent();
30124
+ if (!sender || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] active session identity, type, and content required for send" }], isError: true };
30125
+ if (from && from !== currentAgentId) return { content: [{ type: "text", text: "[ERROR] from must match the current session identity" }], isError: true };
29377
30126
  if (!to && !toRole) return { content: [{ type: "text", text: "[ERROR] either to (agent ID) or toRole is required for send" }], isError: true };
29378
30127
  if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
29379
30128
  const msg = teamStore.sendMessage({
29380
30129
  projectId: project.id,
29381
- senderAgentId: from,
30130
+ senderAgentId: currentAgentId,
29382
30131
  recipientAgentId: to ?? null,
29383
30132
  type: msgType,
29384
30133
  content,
@@ -29390,11 +30139,13 @@ ${lines.join("\n")}` }] };
29390
30139
  return { content: [{ type: "text", text: `Message sent (${msgType}) to ${target} | ID: ${msg.id.slice(0, 8)}\u2026${toRole ? ` [role: ${toRole}]` : ""}` }] };
29391
30140
  }
29392
30141
  if (action === "broadcast") {
29393
- if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for broadcast" }], isError: true };
30142
+ const sender = requireCurrentTeamAgent();
30143
+ if (!sender || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] active session identity, type, and content required for broadcast" }], isError: true };
30144
+ if (from && from !== currentAgentId) return { content: [{ type: "text", text: "[ERROR] from must match the current session identity" }], isError: true };
29394
30145
  if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
29395
30146
  const msg = teamStore.sendMessage({
29396
30147
  projectId: project.id,
29397
- senderAgentId: from,
30148
+ senderAgentId: currentAgentId,
29398
30149
  recipientAgentId: null,
29399
30150
  type: msgType,
29400
30151
  content
@@ -29402,8 +30153,12 @@ ${lines.join("\n")}` }] };
29402
30153
  if ("error" in msg) return { content: [{ type: "text", text: `[ERROR] ${msg.error}` }], isError: true };
29403
30154
  return { content: [{ type: "text", text: `Broadcast (${msgType}) | ID: ${msg.id.slice(0, 8)}\u2026` }] };
29404
30155
  }
29405
- const inboxId = agentId || from || "";
29406
- if (!inboxId) return { content: [{ type: "text", text: "[ERROR] agentId required for inbox" }], isError: true };
30156
+ const inboxAgent = requireCurrentTeamAgent();
30157
+ if (!inboxAgent) return { content: [{ type: "text", text: "[ERROR] active session identity required for inbox" }], isError: true };
30158
+ if (agentId && agentId !== currentAgentId || from && from !== currentAgentId) {
30159
+ return { content: [{ type: "text", text: "[ERROR] inbox access is limited to the current session identity" }], isError: true };
30160
+ }
30161
+ const inboxId = currentAgentId;
29407
30162
  const inbox = teamStore.getInbox(project.id, inboxId);
29408
30163
  const unread = teamStore.getUnreadCount(project.id, inboxId);
29409
30164
  if (inbox.length === 0) return { content: [{ type: "text", text: "Inbox empty" }] };
@@ -29431,24 +30186,34 @@ ${lines.join("\n")}` }] };
29431
30186
  },
29432
30187
  async ({ agentId, markInboxRead }) => {
29433
30188
  const { computeWatermark: computeWatermark2, computePoll: computePoll2 } = await Promise.resolve().then(() => (init_poll(), poll_exports));
30189
+ if (agentId && agentId !== currentAgentId) {
30190
+ return {
30191
+ content: [{ type: "text", text: "[ERROR] agentId must match the current session identity." }],
30192
+ isError: true
30193
+ };
30194
+ }
30195
+ const effectiveAgentId = currentAgentId;
29434
30196
  let watermark = computeWatermark2(0, 0, 0);
29435
- if (agentId) {
29436
- const agent = teamStore.getAgent(agentId);
30197
+ if (effectiveAgentId) {
30198
+ const agent = teamStore.getAgent(effectiveAgentId);
29437
30199
  if (agent) {
29438
30200
  const lastSeen = agent.last_seen_obs_generation;
29439
30201
  const store = getObservationStore();
29440
30202
  const currentGen = store.getGeneration();
29441
- const projectObs = await withFreshIndex(() => getAllObservations().filter(
29442
- (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
30203
+ const projectObs = await withFreshIndex(() => filterReadableObservations(
30204
+ getAllObservations().filter(
30205
+ (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
30206
+ ),
30207
+ getObservationReader()
29443
30208
  ));
29444
30209
  watermark = computeWatermark2(lastSeen, currentGen, projectObs.length);
29445
- teamStore.updateWatermark(agentId, currentGen);
29446
- teamStore.heartbeat(agentId);
30210
+ teamStore.updateWatermark(effectiveAgentId, currentGen);
30211
+ teamStore.heartbeat(effectiveAgentId);
29447
30212
  }
29448
30213
  }
29449
- const poll = computePoll2(teamStore, project.id, agentId ?? null, watermark);
29450
- if (markInboxRead && agentId) {
29451
- teamStore.markAllRead(project.id, agentId);
30214
+ const poll = computePoll2(teamStore, project.id, effectiveAgentId ?? null, watermark);
30215
+ if (markInboxRead && effectiveAgentId) {
30216
+ teamStore.markAllRead(project.id, effectiveAgentId);
29452
30217
  }
29453
30218
  const lines = [];
29454
30219
  if (poll.agent) {
@@ -29508,7 +30273,7 @@ ${lines.join("\n")}` }] };
29508
30273
  "memorix_handoff",
29509
30274
  {
29510
30275
  title: "Team Handoff \u2014 Agent Context Transfer",
29511
- 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.",
30276
+ 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.",
29512
30277
  inputSchema: {
29513
30278
  fromAgentId: z2.string().describe("Your agent ID (from team_manage join or session_start with joinTeam=true)"),
29514
30279
  summary: z2.string().describe("Human-readable summary of what you did and what needs to happen next"),
@@ -29521,6 +30286,34 @@ ${lines.join("\n")}` }] };
29521
30286
  },
29522
30287
  async ({ fromAgentId, summary, context, toAgentId, taskId, filesModified, concepts }) => {
29523
30288
  const { createHandoffArtifact: createHandoffArtifact2 } = await Promise.resolve().then(() => (init_handoff(), handoff_exports));
30289
+ if (!currentAgentId) {
30290
+ return {
30291
+ content: [{ type: "text", text: "Create a coordination identity first: call memorix_session_start with joinTeam=true." }],
30292
+ isError: true
30293
+ };
30294
+ }
30295
+ if (fromAgentId !== currentAgentId) {
30296
+ return {
30297
+ content: [{ type: "text", text: "fromAgentId must match the identity returned for this session. Memorix will not create a handoff on behalf of another agent." }],
30298
+ isError: true
30299
+ };
30300
+ }
30301
+ const sender = teamStore.getAgent(currentAgentId);
30302
+ if (!sender || sender.project_id !== project.id || sender.status !== "active") {
30303
+ return {
30304
+ content: [{ type: "text", text: "The current coordination identity is not an active member of this project." }],
30305
+ isError: true
30306
+ };
30307
+ }
30308
+ if (toAgentId) {
30309
+ const recipient = teamStore.getAgent(toAgentId);
30310
+ if (!recipient || recipient.project_id !== project.id) {
30311
+ return {
30312
+ content: [{ type: "text", text: "The handoff recipient must be an agent registered in the current project." }],
30313
+ isError: true
30314
+ };
30315
+ }
30316
+ }
29524
30317
  const result = await createHandoffArtifact2(
29525
30318
  {
29526
30319
  projectId: project.id,
@@ -29760,6 +30553,8 @@ var MemoryClient = class {
29760
30553
  _projectId;
29761
30554
  _projectRoot;
29762
30555
  _dataDir;
30556
+ /** SDK calls are unbound by default, so they see only project-shared records. */
30557
+ _reader;
29763
30558
  _closed = false;
29764
30559
  // Internal module references — loaded lazily to avoid top-level side effects
29765
30560
  _observations;
@@ -29771,6 +30566,7 @@ var MemoryClient = class {
29771
30566
  this._projectId = projectId;
29772
30567
  this._projectRoot = projectRoot;
29773
30568
  this._dataDir = dataDir;
30569
+ this._reader = { projectId };
29774
30570
  }
29775
30571
  /** The canonical project ID (derived from Git remote or local path) */
29776
30572
  get projectId() {
@@ -29829,7 +30625,8 @@ var MemoryClient = class {
29829
30625
  this._ensureOpen();
29830
30626
  return this._observations.storeObservation({
29831
30627
  ...input,
29832
- projectId: this._projectId
30628
+ projectId: this._projectId,
30629
+ visibilityReader: this._reader
29833
30630
  });
29834
30631
  }
29835
30632
  /**
@@ -29853,7 +30650,8 @@ var MemoryClient = class {
29853
30650
  limit: options.limit ?? 20,
29854
30651
  type: options.type,
29855
30652
  source: options.source,
29856
- status: options.status === "all" ? void 0 : options.status ?? "active"
30653
+ status: options.status === "all" ? void 0 : options.status ?? "active",
30654
+ reader: this._reader
29857
30655
  };
29858
30656
  return this._oramaStore.searchObservations(searchOpts);
29859
30657
  }
@@ -29864,7 +30662,8 @@ var MemoryClient = class {
29864
30662
  this._ensureOpen();
29865
30663
  await this._freshness.withFreshIndex(() => {
29866
30664
  });
29867
- return this._observations.getObservation(id, this._projectId);
30665
+ const observation = this._observations.getObservation(id, this._projectId);
30666
+ return observation && canReadObservation(observation, this._reader) ? observation : void 0;
29868
30667
  }
29869
30668
  /**
29870
30669
  * Get all observations for this project.
@@ -29873,7 +30672,10 @@ var MemoryClient = class {
29873
30672
  this._ensureOpen();
29874
30673
  await this._freshness.withFreshIndex(() => {
29875
30674
  });
29876
- return this._observations.getProjectObservations(this._projectId);
30675
+ return filterReadableObservations(
30676
+ this._observations.getProjectObservations(this._projectId),
30677
+ this._reader
30678
+ );
29877
30679
  }
29878
30680
  /**
29879
30681
  * Get the total observation count for this project.
@@ -29882,7 +30684,10 @@ var MemoryClient = class {
29882
30684
  this._ensureOpen();
29883
30685
  await this._freshness.withFreshIndex(() => {
29884
30686
  });
29885
- return this._observations.getProjectObservations(this._projectId).length;
30687
+ return filterReadableObservations(
30688
+ this._observations.getProjectObservations(this._projectId),
30689
+ this._reader
30690
+ ).length;
29886
30691
  }
29887
30692
  /**
29888
30693
  * Mark observations as resolved or archived.
@@ -29898,7 +30703,16 @@ var MemoryClient = class {
29898
30703
  */
29899
30704
  async resolve(ids, status = "resolved") {
29900
30705
  this._ensureOpen();
29901
- return this._observations.resolveObservations(ids, status);
30706
+ const manageableIds = ids.filter((id) => {
30707
+ const observation = this._observations.getObservation(id, this._projectId);
30708
+ return observation && canManageObservation(observation, this._reader);
30709
+ });
30710
+ const deniedIds = ids.filter((id) => !manageableIds.includes(id));
30711
+ const result = await this._observations.resolveObservations(manageableIds, status);
30712
+ return {
30713
+ resolved: result.resolved,
30714
+ notFound: [.../* @__PURE__ */ new Set([...result.notFound, ...deniedIds])]
30715
+ };
29902
30716
  }
29903
30717
  /**
29904
30718
  * Release resources (close SQLite handle, reset index).