memorix 1.2.1 → 1.2.3

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 (90) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +14 -2
  3. package/README.zh-CN.md +14 -2
  4. package/dist/cli/index.js +15424 -13780
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/index.js +1337 -536
  7. package/dist/index.js.map +1 -1
  8. package/dist/maintenance-runner.d.ts +1 -1
  9. package/dist/maintenance-runner.js +8458 -8087
  10. package/dist/maintenance-runner.js.map +1 -1
  11. package/dist/memcode-runtime/CHANGELOG.md +23 -0
  12. package/dist/sdk.d.ts +7 -2
  13. package/dist/sdk.js +1365 -542
  14. package/dist/sdk.js.map +1 -1
  15. package/dist/types.d.ts +49 -1
  16. package/dist/types.js.map +1 -1
  17. package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
  18. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
  19. package/docs/API_REFERENCE.md +24 -4
  20. package/docs/README.md +1 -1
  21. package/docs/dev-log/progress.txt +101 -11
  22. package/package.json +1 -1
  23. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  24. package/src/cli/command-guide.ts +192 -0
  25. package/src/cli/commands/audit.ts +9 -4
  26. package/src/cli/commands/cleanup.ts +5 -1
  27. package/src/cli/commands/codegraph.ts +15 -5
  28. package/src/cli/commands/context.ts +3 -2
  29. package/src/cli/commands/doctor.ts +4 -2
  30. package/src/cli/commands/explain.ts +9 -3
  31. package/src/cli/commands/handoff.ts +21 -7
  32. package/src/cli/commands/identity.ts +116 -0
  33. package/src/cli/commands/ingest-image.ts +5 -3
  34. package/src/cli/commands/lock.ts +11 -10
  35. package/src/cli/commands/memory.ts +58 -21
  36. package/src/cli/commands/message.ts +19 -14
  37. package/src/cli/commands/operator-shared.ts +98 -3
  38. package/src/cli/commands/poll.ts +16 -6
  39. package/src/cli/commands/reasoning.ts +17 -3
  40. package/src/cli/commands/retention.ts +9 -4
  41. package/src/cli/commands/serve-http.ts +8 -2
  42. package/src/cli/commands/session.ts +44 -10
  43. package/src/cli/commands/skills.ts +10 -5
  44. package/src/cli/commands/status.ts +4 -3
  45. package/src/cli/commands/task.ts +26 -17
  46. package/src/cli/commands/team.ts +14 -10
  47. package/src/cli/commands/transfer.ts +63 -10
  48. package/src/cli/identity.ts +89 -0
  49. package/src/cli/index.ts +96 -19
  50. package/src/cli/invocation.ts +115 -0
  51. package/src/cli/tui/chat-service.ts +41 -18
  52. package/src/cli/tui/data.ts +23 -44
  53. package/src/cli/tui/operator-context.ts +60 -0
  54. package/src/cli/tui/session-service.ts +3 -2
  55. package/src/cli/tui/views/MemoryView.tsx +10 -8
  56. package/src/codegraph/auto-context.ts +31 -2
  57. package/src/codegraph/context-pack.ts +1 -0
  58. package/src/codegraph/project-context.ts +2 -0
  59. package/src/compact/engine.ts +26 -10
  60. package/src/compact/index-format.ts +25 -2
  61. package/src/dashboard/server.ts +46 -9
  62. package/src/hooks/admission.ts +117 -0
  63. package/src/hooks/handler.ts +98 -91
  64. package/src/knowledge/context-assembly.ts +97 -0
  65. package/src/knowledge/workset.ts +179 -10
  66. package/src/memory/admission.ts +57 -0
  67. package/src/memory/consolidation.ts +13 -2
  68. package/src/memory/disclosure-policy.ts +6 -1
  69. package/src/memory/export-import.ts +11 -3
  70. package/src/memory/graph-context.ts +8 -2
  71. package/src/memory/observations.ts +162 -4
  72. package/src/memory/quality-audit.ts +2 -0
  73. package/src/memory/retention.ts +22 -2
  74. package/src/memory/session.ts +29 -11
  75. package/src/memory/visibility.ts +80 -0
  76. package/src/orchestrate/memorix-bridge.ts +38 -0
  77. package/src/runtime/control-plane-maintenance.ts +1 -0
  78. package/src/runtime/isolated-maintenance.ts +1 -0
  79. package/src/runtime/lifecycle.ts +18 -0
  80. package/src/runtime/maintenance-jobs.ts +1 -0
  81. package/src/runtime/maintenance-runner.ts +2 -0
  82. package/src/runtime/project-maintenance.ts +89 -0
  83. package/src/sdk.ts +35 -5
  84. package/src/server.ts +267 -83
  85. package/src/store/orama-store.ts +61 -6
  86. package/src/store/sqlite-db.ts +23 -1
  87. package/src/store/sqlite-store.ts +12 -2
  88. package/src/team/handoff.ts +7 -0
  89. package/src/types.ts +51 -0
  90. package/src/wiki/generator.ts +2 -0
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
  }
@@ -14972,7 +15346,7 @@ async function startSession(projectDir2, projectId, opts) {
14972
15346
  status: "active",
14973
15347
  agent: opts?.agent
14974
15348
  };
14975
- const previousContext = await getSessionContext(projectDir2, projectId);
15349
+ const previousContext = await getSessionContext(projectDir2, projectId, 3, opts?.reader);
14976
15350
  const sessionStore = getSessionStore();
14977
15351
  const aliasSet = await resolveProjectIds(projectId);
14978
15352
  await sessionStore.atomicRolloverInsert(session, [...aliasSet], now3);
@@ -14990,23 +15364,27 @@ async function endSession(projectDir2, sessionId, summary) {
14990
15364
  await sessionStore.update(session);
14991
15365
  return session;
14992
15366
  }
14993
- async function getSessionContext(projectDir2, projectId, limit = 3) {
15367
+ async function getSessionContext(projectDir2, projectId, limit = 3, reader) {
14994
15368
  const aliasSet = await resolveProjectIds(projectId);
14995
15369
  const [sessions, allObs] = await Promise.all([
14996
15370
  loadAliasSessions(aliasSet),
14997
15371
  loadAliasActiveObservations(aliasSet)
14998
15372
  ]);
15373
+ const readableObs = reader ? allObs.filter((observation) => {
15374
+ const observationReader = reader.projectId && aliasSet.has(observation.projectId) ? { ...reader, projectId: observation.projectId } : reader;
15375
+ return canReadObservation(observation, observationReader);
15376
+ }) : allObs;
14999
15377
  const isNoisySummary = (summary) => {
15000
15378
  if (!summary) return false;
15001
15379
  return NOISE_PATTERNS2.some((p) => p.test(summary)) || SYSTEM_SELF_PATTERNS.some((p) => p.test(summary));
15002
15380
  };
15003
15381
  const projectSessions = sessions.filter((session) => session.status === "completed").filter((session) => !isNoisySummary(session.summary)).sort((a, b) => new Date(b.endedAt || b.startedAt).getTime() - new Date(a.endedAt || a.startedAt).getTime()).slice(0, limit);
15004
- if (projectSessions.length === 0 && allObs.length === 0) {
15382
+ if (projectSessions.length === 0 && readableObs.length === 0) {
15005
15383
  return "";
15006
15384
  }
15007
15385
  const lines = [];
15008
15386
  const projectTokens = tokenizeProjectId(projectId);
15009
- const projectObs = allObs.filter((obs) => !isNoiseObservation(obs) && !isSystemSelfObservation(obs));
15387
+ const projectObs = readableObs.filter((obs) => !isNoiseObservation(obs) && !isSystemSelfObservation(obs));
15010
15388
  const l2Scored = projectObs.filter((obs) => PRIORITY_TYPES.has(obs.type) && classifyLayer(obs) === "L2").map((obs) => ({ obs, score: scoreObservationForSessionContext(obs, projectTokens) })).sort((a, b) => {
15011
15389
  if (b.score !== a.score) return b.score - a.score;
15012
15390
  return new Date(b.obs.createdAt).getTime() - new Date(a.obs.createdAt).getTime();
@@ -15023,16 +15401,16 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
15023
15401
  return true;
15024
15402
  });
15025
15403
  })() : 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);
15404
+ 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
15405
  const l3GitCount = projectObs.filter((obs) => classifyLayer(obs) === "L3").length;
15028
- const totalHookCount = projectObs.filter((obs) => classifyLayer(obs) === "L1").length;
15406
+ const totalHookCount = projectObs.filter((obs) => isEligibleForAutomaticDelivery(obs) && classifyLayer(obs) === "L1").length;
15029
15407
  const activeEntities = [
15030
15408
  ...new Set(l2Obs.map((o) => o.entityName).filter((n) => !!n && n.trim().length > 0))
15031
15409
  ].slice(0, 5);
15032
15410
  const hasL1Content = l1HookObs.length > 0 || l3GitCount > 0;
15033
15411
  if (hasL1Content) {
15034
15412
  let graphNeighbors = [];
15035
- if (activeEntities.length > 0) {
15413
+ if (!reader && activeEntities.length > 0) {
15036
15414
  try {
15037
15415
  const graphMgr = new KnowledgeGraphManager(projectDir2);
15038
15416
  await graphMgr.init();
@@ -15151,12 +15529,14 @@ var init_session = __esm({
15151
15529
  "src/memory/session.ts"() {
15152
15530
  "use strict";
15153
15531
  init_esm_shims();
15532
+ init_admission();
15154
15533
  init_disclosure_policy();
15155
15534
  init_aliases();
15156
15535
  init_obs_store();
15157
15536
  init_session_store();
15158
15537
  init_graph();
15159
15538
  init_secret_filter();
15539
+ init_visibility();
15160
15540
  PRIORITY_TYPES = /* @__PURE__ */ new Set(["gotcha", "decision", "problem-solution", "trade-off", "discovery"]);
15161
15541
  TYPE_EMOJI = {
15162
15542
  "gotcha": "[DISCOVERY]",
@@ -15303,27 +15683,80 @@ function workflowOutput(selection) {
15303
15683
  cautions: selection.cautions.map((caution) => short(caution, 22))
15304
15684
  };
15305
15685
  }
15306
- function appendLine(lines, candidate, maxTokens, omitted, omittedKind) {
15686
+ function appendLine(lines, candidate, maxTokens, omitted, omittedKind, selected, receiptSelection) {
15307
15687
  const next = lines.length ? lines.join("\n") + "\n" + candidate : candidate;
15308
15688
  if (countTextTokens(next) <= maxTokens) {
15309
15689
  lines.push(candidate);
15690
+ if (receiptSelection) selected?.push(receiptSelection);
15310
15691
  return true;
15311
15692
  }
15312
15693
  omitted.push(omittedKind);
15313
15694
  return false;
15314
15695
  }
15696
+ function freshnessForMemory(status) {
15697
+ if (status === "current" || status === "suspect" || status === "stale") return status;
15698
+ return "unknown";
15699
+ }
15700
+ function receiptOmissionKind(raw) {
15701
+ if (raw.includes("task")) return "task";
15702
+ if (raw.includes("fact")) return "current-fact";
15703
+ if (raw.includes("state")) return "code-state";
15704
+ if (raw.includes("semantic")) return "semantic-code";
15705
+ if (raw.includes("start")) return "start-here";
15706
+ if (raw.includes("memory")) return "memory";
15707
+ if (raw.includes("claim")) return "claim";
15708
+ if (raw.includes("knowledge-page")) return "knowledge-page";
15709
+ if (raw.includes("workflow")) return "workflow";
15710
+ if (raw.includes("verification")) return "verification";
15711
+ if (raw.includes("caution")) return "caution";
15712
+ return void 0;
15713
+ }
15714
+ function receiptOmissions(omitted, hiddenCautionMemoryCount) {
15715
+ const counts = /* @__PURE__ */ new Map();
15716
+ for (const raw of omitted) {
15717
+ const kind = receiptOmissionKind(raw);
15718
+ if (!kind || raw.endsWith("-heading")) continue;
15719
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
15720
+ }
15721
+ const receipt = [...counts.entries()].map(([kind, count2]) => ({
15722
+ kind,
15723
+ reason: "token-budget",
15724
+ count: count2
15725
+ }));
15726
+ if (hiddenCautionMemoryCount > 0) {
15727
+ receipt.push({
15728
+ kind: "caution",
15729
+ reason: "hidden-by-task-lens",
15730
+ count: hiddenCautionMemoryCount
15731
+ });
15732
+ }
15733
+ return receipt;
15734
+ }
15735
+ function scheduledActions(cautions) {
15736
+ return cautions.filter((caution) => caution.kind === "codegraph-refresh-queued").map((caution) => short(caution.message, 28));
15737
+ }
15315
15738
  function renderTaskWorksetPrompt(input) {
15316
15739
  const maxTokens = input.budget?.maxTokens ?? 180;
15317
15740
  const omitted = [];
15741
+ const selected = [];
15318
15742
  const lines = ["Memorix Autopilot Brief"];
15319
15743
  const task = short(input.task || "Continue the current task.", 34);
15320
- appendLine(lines, "Task: " + task, maxTokens, omitted, "task-detail");
15744
+ appendLine(lines, "Task: " + task, maxTokens, omitted, "task-detail", selected, {
15745
+ kind: "task",
15746
+ reason: "current task supplied by the caller",
15747
+ trust: "source-backed"
15748
+ });
15321
15749
  appendLine(lines, "Task lens: " + input.lens, maxTokens, omitted, "lens");
15322
15750
  if (input.cautions.length > 0 || input.cautionMemory.length > 0) {
15323
15751
  appendLine(lines, "", maxTokens, omitted, "caution-heading");
15324
15752
  appendLine(lines, "Cautions", maxTokens, omitted, "caution-heading");
15325
15753
  for (const caution of input.cautions.slice(0, 6)) {
15326
- appendLine(lines, "- " + short(caution.message, 22), maxTokens, omitted, "caution");
15754
+ appendLine(lines, "- " + short(caution.message, 22), maxTokens, omitted, "caution", selected, {
15755
+ kind: "caution",
15756
+ id: "caution:" + caution.kind,
15757
+ reason: "current project caution",
15758
+ trust: "source-backed"
15759
+ });
15327
15760
  }
15328
15761
  for (const memory of input.cautionMemory.slice(0, 3)) {
15329
15762
  const location = memory.path ? memory.path + (memory.symbol ? "#" + memory.symbol : "") : "no current code location";
@@ -15333,7 +15766,15 @@ function renderTaskWorksetPrompt(input) {
15333
15766
  "- #" + memory.id + " " + memory.status + ": " + short(memory.title, 18) + " (" + location + reason + ")",
15334
15767
  maxTokens,
15335
15768
  omitted,
15336
- "caution-memory"
15769
+ "caution-memory",
15770
+ selected,
15771
+ {
15772
+ kind: "memory",
15773
+ id: "memory:" + memory.id,
15774
+ reason: "task-relevant memory requiring source verification",
15775
+ freshness: freshnessForMemory(memory.status),
15776
+ trust: "historical"
15777
+ }
15337
15778
  );
15338
15779
  }
15339
15780
  if (input.hiddenCautionMemoryCount > 0) {
@@ -15344,13 +15785,22 @@ function renderTaskWorksetPrompt(input) {
15344
15785
  appendLine(lines, "", maxTokens, omitted, "facts-heading");
15345
15786
  appendLine(lines, "Current project facts", maxTokens, omitted, "facts-heading");
15346
15787
  for (const fact of input.currentFacts.slice(0, 4)) {
15347
- appendLine(lines, "- " + short(fact, 40), maxTokens, omitted, "current-fact");
15788
+ appendLine(lines, "- " + short(fact, 40), maxTokens, omitted, "current-fact", selected, {
15789
+ kind: "current-fact",
15790
+ reason: "current project state",
15791
+ trust: "source-backed"
15792
+ });
15348
15793
  }
15349
15794
  }
15350
15795
  if (input.codeState) {
15351
15796
  appendLine(lines, "", maxTokens, omitted, "state-heading");
15352
15797
  appendLine(lines, "Project state", maxTokens, omitted, "state-heading");
15353
- appendLine(lines, input.codeState, maxTokens, omitted, "code-state");
15798
+ appendLine(lines, input.codeState, maxTokens, omitted, "code-state", selected, {
15799
+ kind: "code-state",
15800
+ ...input.provenance.snapshotId ? { id: "snapshot:" + input.provenance.snapshotId } : {},
15801
+ reason: "latest available Code State snapshot",
15802
+ trust: "source-backed"
15803
+ });
15354
15804
  }
15355
15805
  if (input.semanticCode && (input.semanticCode.entryPoints.length > 0 || input.semanticCode.relations.length > 0)) {
15356
15806
  appendLine(lines, "", maxTokens, omitted, "semantic-code-heading");
@@ -15362,7 +15812,14 @@ function renderTaskWorksetPrompt(input) {
15362
15812
  "- " + location + ": " + short(relation.from.name, 12) + " " + short(relation.kind, 8) + " " + short(relation.to.name, 12),
15363
15813
  maxTokens,
15364
15814
  omitted,
15365
- "semantic-relation"
15815
+ "semantic-relation",
15816
+ selected,
15817
+ {
15818
+ kind: "semantic-code",
15819
+ id: "code:" + location,
15820
+ reason: "validated optional semantic code relation",
15821
+ trust: "derived"
15822
+ }
15366
15823
  );
15367
15824
  }
15368
15825
  if (input.semanticCode.relations.length === 0) {
@@ -15373,7 +15830,14 @@ function renderTaskWorksetPrompt(input) {
15373
15830
  "- " + location + ": " + short(entry.name, 16) + " (" + short(entry.kind, 8) + ")",
15374
15831
  maxTokens,
15375
15832
  omitted,
15376
- "semantic-entry"
15833
+ "semantic-entry",
15834
+ selected,
15835
+ {
15836
+ kind: "semantic-code",
15837
+ id: "code:" + location,
15838
+ reason: "validated optional semantic code entry point",
15839
+ trust: "derived"
15840
+ }
15377
15841
  );
15378
15842
  }
15379
15843
  }
@@ -15382,7 +15846,12 @@ function renderTaskWorksetPrompt(input) {
15382
15846
  appendLine(lines, "", maxTokens, omitted, "start-heading");
15383
15847
  appendLine(lines, "Start here", maxTokens, omitted, "start-heading");
15384
15848
  for (const source of input.startHere.slice(0, 5)) {
15385
- appendLine(lines, "- " + source, maxTokens, omitted, "start-here");
15849
+ appendLine(lines, "- " + source, maxTokens, omitted, "start-here", selected, {
15850
+ kind: "start-here",
15851
+ id: "path:" + source,
15852
+ reason: "task-lensed starting point",
15853
+ trust: "derived"
15854
+ });
15386
15855
  }
15387
15856
  }
15388
15857
  if (input.reliableMemory.length > 0) {
@@ -15395,7 +15864,15 @@ function renderTaskWorksetPrompt(input) {
15395
15864
  "- #" + memory.id + " " + memory.type + ": " + short(memory.title, 18) + " (" + location + ")",
15396
15865
  maxTokens,
15397
15866
  omitted,
15398
- "reliable-memory"
15867
+ "reliable-memory",
15868
+ selected,
15869
+ {
15870
+ kind: "memory",
15871
+ id: "memory:" + memory.id,
15872
+ reason: "current code-bound memory",
15873
+ freshness: freshnessForMemory(memory.status),
15874
+ trust: "historical"
15875
+ }
15399
15876
  );
15400
15877
  }
15401
15878
  }
@@ -15403,10 +15880,25 @@ function renderTaskWorksetPrompt(input) {
15403
15880
  appendLine(lines, "", maxTokens, omitted, "knowledge-heading");
15404
15881
  appendLine(lines, "Project knowledge", maxTokens, omitted, "knowledge-heading");
15405
15882
  for (const claim of input.claims.slice(0, 3)) {
15406
- appendLine(lines, "- " + claim.assertion + " [" + claim.id + "]", maxTokens, omitted, "claim");
15883
+ appendLine(lines, "- " + claim.assertion + " [" + claim.id + "]", maxTokens, omitted, "claim", selected, {
15884
+ kind: "claim",
15885
+ id: "claim:" + claim.id,
15886
+ reason: "source-qualified task match",
15887
+ trust: "source-backed"
15888
+ });
15407
15889
  }
15408
15890
  for (const page of input.pages.slice(0, 2)) {
15409
- appendLine(lines, "- page: " + page.relativePath, maxTokens, omitted, "knowledge-page");
15891
+ const supportsDeliveredClaim = page.claimIds.some((claimId) => selected.some((item) => item.kind === "claim" && item.id === "claim:" + claimId));
15892
+ if (!supportsDeliveredClaim) {
15893
+ omitted.push("knowledge-page-dependency");
15894
+ continue;
15895
+ }
15896
+ appendLine(lines, "- page: " + page.relativePath, maxTokens, omitted, "knowledge-page", selected, {
15897
+ kind: "knowledge-page",
15898
+ id: "page:" + page.id,
15899
+ reason: "approved page linked to a selected claim",
15900
+ trust: "source-backed"
15901
+ });
15410
15902
  }
15411
15903
  }
15412
15904
  if (input.workflows.length > 0) {
@@ -15418,7 +15910,14 @@ function renderTaskWorksetPrompt(input) {
15418
15910
  "- " + workflow.title + ": " + workflow.firstPhase.title + " - " + workflow.firstPhase.instructions,
15419
15911
  maxTokens,
15420
15912
  omitted,
15421
- "workflow"
15913
+ "workflow",
15914
+ selected,
15915
+ {
15916
+ kind: "workflow",
15917
+ id: "workflow:" + workflow.id,
15918
+ reason: "task-matching project workflow",
15919
+ trust: "source-backed"
15920
+ }
15422
15921
  );
15423
15922
  }
15424
15923
  }
@@ -15426,16 +15925,23 @@ function renderTaskWorksetPrompt(input) {
15426
15925
  appendLine(lines, "", maxTokens, omitted, "verification-heading");
15427
15926
  appendLine(lines, "Verify", maxTokens, omitted, "verification-heading");
15428
15927
  for (const check of input.verification.slice(0, 4)) {
15429
- appendLine(lines, "- " + short(check, 20), maxTokens, omitted, "verification");
15928
+ appendLine(lines, "- " + short(check, 20), maxTokens, omitted, "verification", selected, {
15929
+ kind: "verification",
15930
+ reason: "task-lensed verification guidance",
15931
+ trust: "derived"
15932
+ });
15430
15933
  }
15431
15934
  }
15432
15935
  return {
15433
15936
  prompt: lines.join("\n"),
15434
15937
  tokenCount: countTextTokens(lines.join("\n")),
15435
- omitted: unique(omitted)
15938
+ omitted: unique(omitted),
15939
+ omittedItems: omitted,
15940
+ selected
15436
15941
  };
15437
15942
  }
15438
15943
  async function buildTaskWorkset(input) {
15944
+ const startedAt = Date.now();
15439
15945
  const task = input.task?.trim() ?? "";
15440
15946
  const maxTokens = Math.max(96, Math.min(Math.floor(input.maxTokens ?? 180), 320));
15441
15947
  const cautions = [...input.runtimeCautions ?? [], ...snapshotCautions(input)];
@@ -15537,6 +16043,18 @@ async function buildTaskWorkset(input) {
15537
16043
  ...base,
15538
16044
  budget: { maxTokens }
15539
16045
  });
16046
+ const receipt = {
16047
+ version: "1.2.2",
16048
+ target: input.deliveryTarget ?? "project-context",
16049
+ elapsedMs: Math.max(0, Date.now() - startedAt),
16050
+ budget: {
16051
+ maxTokens,
16052
+ tokenCount: rendered.tokenCount
16053
+ },
16054
+ selected: rendered.selected,
16055
+ omitted: receiptOmissions(rendered.omittedItems, base.hiddenCautionMemoryCount),
16056
+ scheduledActions: scheduledActions(normalizedCautions)
16057
+ };
15540
16058
  return {
15541
16059
  ...base,
15542
16060
  budget: {
@@ -15544,6 +16062,7 @@ async function buildTaskWorkset(input) {
15544
16062
  tokenCount: rendered.tokenCount,
15545
16063
  omitted: rendered.omitted
15546
16064
  },
16065
+ receipt,
15547
16066
  prompt: rendered.prompt
15548
16067
  };
15549
16068
  }
@@ -16321,6 +16840,9 @@ import path19 from "path";
16321
16840
  function activeProjectObservations(observations2, projectId) {
16322
16841
  return observations2.filter((obs) => obs.projectId === projectId && (obs.status ?? "active") === "active");
16323
16842
  }
16843
+ function deliveryEligibleProjectObservations(observations2, projectId) {
16844
+ return activeProjectObservations(observations2, projectId).filter((observation) => isEligibleForAutomaticDelivery(observation));
16845
+ }
16324
16846
  function decideRefresh(input) {
16325
16847
  if (input.mode === "never") {
16326
16848
  return { performed: false, reason: "disabled", message: "Automatic project scan disabled." };
@@ -16395,13 +16917,27 @@ async function buildAutoProjectContext(input) {
16395
16917
  activeProjectObservations(input.observations, input.project.id)
16396
16918
  );
16397
16919
  try {
16398
- const { enqueueClaimRequalification: enqueueClaimRequalification2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
16920
+ const { MaintenanceTargetStore: MaintenanceTargetStore2 } = await Promise.resolve().then(() => (init_maintenance_targets(), maintenance_targets_exports));
16921
+ new MaintenanceTargetStore2(input.dataDir).register({
16922
+ projectId: input.project.id,
16923
+ projectRoot: input.project.rootPath,
16924
+ dataDir: input.dataDir
16925
+ });
16926
+ const {
16927
+ enqueueClaimRequalification: enqueueClaimRequalification2,
16928
+ enqueueObservationQualification: enqueueObservationQualification2
16929
+ } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
16399
16930
  enqueueClaimRequalification2({
16400
16931
  dataDir: input.dataDir,
16401
16932
  projectId: input.project.id,
16402
16933
  source: "foreground-refresh",
16403
16934
  snapshotId: store.latestSnapshot(input.project.id)?.id
16404
16935
  });
16936
+ enqueueObservationQualification2({
16937
+ dataDir: input.dataDir,
16938
+ projectId: input.project.id,
16939
+ source: "foreground-refresh"
16940
+ });
16405
16941
  } catch {
16406
16942
  }
16407
16943
  refresh = { ...refresh, backfill };
@@ -16418,7 +16954,9 @@ async function buildAutoProjectContext(input) {
16418
16954
  const explain = buildProjectContextExplain({
16419
16955
  project: input.project,
16420
16956
  store,
16421
- observations: input.observations,
16957
+ // Binding sees all active evidence, but automatic delivery sees only
16958
+ // evidence that has passed the control-plane admission gate.
16959
+ observations: deliveryEligibleProjectObservations(input.observations, input.project.id),
16422
16960
  exclude
16423
16961
  });
16424
16962
  const overview = explain.overview;
@@ -16509,7 +17047,8 @@ async function buildAutoProjectContext(input) {
16509
17047
  suspect: overview.freshness.suspect,
16510
17048
  stale: overview.freshness.stale
16511
17049
  },
16512
- runtimeCautions
17050
+ runtimeCautions,
17051
+ ...input.deliveryTarget ? { deliveryTarget: input.deliveryTarget } : {}
16513
17052
  });
16514
17053
  return {
16515
17054
  project: input.project,
@@ -16743,6 +17282,7 @@ var init_auto_context = __esm({
16743
17282
  init_external_provider();
16744
17283
  init_project_context();
16745
17284
  init_store();
17285
+ init_admission();
16746
17286
  init_task_lens();
16747
17287
  DEFAULT_MAX_AGE_MS = 10 * 60 * 1e3;
16748
17288
  }
@@ -16935,7 +17475,8 @@ async function attachTaskWorkset(input) {
16935
17475
  suspect: input.pack.warnings.filter((warning) => warning.status === "suspect").length,
16936
17476
  stale: input.pack.warnings.filter((warning) => warning.status === "stale").length
16937
17477
  },
16938
- runtimeCautions: input.runtimeCautions
17478
+ runtimeCautions: input.runtimeCautions,
17479
+ deliveryTarget: "context-pack"
16939
17480
  });
16940
17481
  return { ...input.pack, workset };
16941
17482
  }
@@ -18347,11 +18888,14 @@ __export(export_import_exports, {
18347
18888
  exportAsMarkdown: () => exportAsMarkdown,
18348
18889
  importFromJson: () => importFromJson
18349
18890
  });
18350
- async function exportAsJson(projectDir2, projectId) {
18891
+ async function exportAsJson(projectDir2, projectId, reader) {
18351
18892
  const store = getObservationStore();
18352
18893
  const allObs = await store.loadAll();
18353
18894
  const allSessions = await getSessionStore().loadAll();
18354
- const projectObs = allObs.filter((o) => o.projectId === projectId);
18895
+ const projectObs = filterReadableObservations(
18896
+ allObs.filter((o) => o.projectId === projectId),
18897
+ reader
18898
+ );
18355
18899
  const projectSessions = allSessions.filter((s) => s.projectId === projectId);
18356
18900
  const typeBreakdown = {};
18357
18901
  for (const obs of projectObs) {
@@ -18370,8 +18914,8 @@ async function exportAsJson(projectDir2, projectId) {
18370
18914
  }
18371
18915
  };
18372
18916
  }
18373
- async function exportAsMarkdown(projectDir2, projectId) {
18374
- const data = await exportAsJson(projectDir2, projectId);
18917
+ async function exportAsMarkdown(projectDir2, projectId, reader) {
18918
+ const data = await exportAsJson(projectDir2, projectId, reader);
18375
18919
  const lines = [];
18376
18920
  lines.push(`# Memorix Export: ${projectId}`);
18377
18921
  lines.push(`Exported: ${data.exportedAt}`);
@@ -18473,6 +19017,7 @@ var init_export_import = __esm({
18473
19017
  "src/memory/export-import.ts"() {
18474
19018
  "use strict";
18475
19019
  init_esm_shims();
19020
+ init_visibility();
18476
19021
  init_obs_store();
18477
19022
  init_session_store();
18478
19023
  OBSERVATION_ICONS2 = {
@@ -18649,6 +19194,7 @@ function isEligible(o, projectId) {
18649
19194
  if (isCommandLog2(o)) return false;
18650
19195
  if (isInactive(o)) return false;
18651
19196
  if (isOtherProject(o, projectId)) return false;
19197
+ if (!isEligibleForKnowledgePromotion(o)) return false;
18652
19198
  if (o.valueCategory === "ephemeral") return false;
18653
19199
  if (!contextualHasSubstance(o)) return false;
18654
19200
  return true;
@@ -18797,6 +19343,7 @@ var init_generator = __esm({
18797
19343
  "src/wiki/generator.ts"() {
18798
19344
  "use strict";
18799
19345
  init_esm_shims();
19346
+ init_admission();
18800
19347
  COMMAND_LOG_TITLE4 = /^(Ran:|Command:|Executed:)\s/i;
18801
19348
  SECTION_DEFS = [
18802
19349
  {
@@ -19083,6 +19630,9 @@ function sendError(res, message, status = 500) {
19083
19630
  function filterByProject(items, projectId) {
19084
19631
  return items.filter((item) => item.projectId === projectId);
19085
19632
  }
19633
+ function filterDashboardObservations(observations2, projectId) {
19634
+ return filterReadableObservations(observations2, { projectId });
19635
+ }
19086
19636
  function isActiveStatus(status) {
19087
19637
  return (status ?? "active") === "active";
19088
19638
  }
@@ -19130,7 +19680,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19130
19680
  switch (apiPath) {
19131
19681
  case "/projects": {
19132
19682
  try {
19133
- const allObs = await getObservationStore().loadAll();
19683
+ const allObs = filterReadableObservations(
19684
+ await getObservationStore().loadAll(),
19685
+ {}
19686
+ );
19134
19687
  const projectSet = /* @__PURE__ */ new Map();
19135
19688
  for (const obs of allObs) {
19136
19689
  if (!isActiveStatus(obs.status)) continue;
@@ -19179,13 +19732,19 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19179
19732
  await initGraphStore(effectiveDataDir);
19180
19733
  const gStore = getGraphStore();
19181
19734
  const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
19182
- const graphObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19735
+ const graphObs = filterDashboardObservations(
19736
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19737
+ effectiveProjectId
19738
+ );
19183
19739
  const scoped = scopeKnowledgeGraphToProject(graph, graphObs);
19184
19740
  sendJson(res, { entities: scoped.entities, relations: scoped.relations });
19185
19741
  break;
19186
19742
  }
19187
19743
  case "/observations": {
19188
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19744
+ const observations2 = filterDashboardObservations(
19745
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19746
+ effectiveProjectId
19747
+ );
19189
19748
  sendJson(res, observations2);
19190
19749
  break;
19191
19750
  }
@@ -19198,7 +19757,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19198
19757
  case "/stats": {
19199
19758
  await initGraphStore(effectiveDataDir);
19200
19759
  const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
19201
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19760
+ const observations2 = filterDashboardObservations(
19761
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19762
+ effectiveProjectId
19763
+ );
19202
19764
  const nextId2 = await getObservationStore().loadIdCounter();
19203
19765
  const projectGraphCounts = computeProjectGraphCounts(graph.entities, graph.relations, observations2);
19204
19766
  const typeCounts = {};
@@ -19316,7 +19878,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19316
19878
  break;
19317
19879
  }
19318
19880
  case "/retention": {
19319
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19881
+ const observations2 = filterDashboardObservations(
19882
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19883
+ effectiveProjectId
19884
+ );
19320
19885
  const now3 = Date.now();
19321
19886
  const scored = observations2.filter((obs) => obs.type !== "probe").map((obs) => {
19322
19887
  const age = now3 - new Date(obs.createdAt || now3).getTime();
@@ -19354,7 +19919,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19354
19919
  const { generateKnowledgeBase: generateKnowledgeBase2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
19355
19920
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
19356
19921
  await initMiniSkillStore2(effectiveDataDir);
19357
- const allObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19922
+ const allObs = filterDashboardObservations(
19923
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19924
+ effectiveProjectId
19925
+ );
19358
19926
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
19359
19927
  const overview = generateKnowledgeBase2({
19360
19928
  projectId: effectiveProjectId,
@@ -19370,7 +19938,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19370
19938
  const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
19371
19939
  await initMiniSkillStore2(effectiveDataDir);
19372
19940
  await initGraphStore2(effectiveDataDir);
19373
- const allObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19941
+ const allObs = filterDashboardObservations(
19942
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19943
+ effectiveProjectId
19944
+ );
19374
19945
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
19375
19946
  const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
19376
19947
  const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
@@ -19493,7 +20064,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19493
20064
  break;
19494
20065
  }
19495
20066
  case "/identity": {
19496
- const allObs = await getObservationStore().loadAll();
20067
+ const allObs = filterReadableObservations(await getObservationStore().loadAll(), {});
19497
20068
  const allProjectIds = [...new Set(allObs.map((o) => o.projectId).filter(Boolean))];
19498
20069
  let classifyProjectId2 = () => "real";
19499
20070
  let isDirtyProjectId2 = () => false;
@@ -19578,6 +20149,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19578
20149
  sendError(res, "Observation not found", 404);
19579
20150
  } else if (matchObs.projectId !== effectiveProjectId) {
19580
20151
  sendError(res, `Observation #${obsId} belongs to project "${matchObs.projectId}", not "${effectiveProjectId}"`, 403);
20152
+ } else if (!canManageObservation(matchObs, { projectId: effectiveProjectId })) {
20153
+ sendError(res, `Observation #${obsId} is not manageable from the unbound dashboard.`, 403);
19581
20154
  } else {
19582
20155
  await obsStore.remove(obsId);
19583
20156
  try {
@@ -19599,7 +20172,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19599
20172
  if (apiPath === "/export") {
19600
20173
  await initGraphStore(effectiveDataDir);
19601
20174
  const fullGraph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
19602
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
20175
+ const observations2 = filterDashboardObservations(
20176
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
20177
+ effectiveProjectId
20178
+ );
19603
20179
  const nextId2 = await getObservationStore().loadIdCounter();
19604
20180
  const scoped = scopeKnowledgeGraphToProject(fullGraph, observations2);
19605
20181
  const exportData = {
@@ -19923,6 +20499,7 @@ var init_server = __esm({
19923
20499
  init_yaml_loader();
19924
20500
  init_yaml_loader();
19925
20501
  init_graph_scope();
20502
+ init_visibility();
19926
20503
  MIME_TYPES = {
19927
20504
  ".html": "text/html; charset=utf-8",
19928
20505
  ".css": "text/css; charset=utf-8",
@@ -20700,6 +21277,11 @@ async function createHandoffArtifact(input, storeObservation2, teamStore) {
20700
21277
  sourceDetail: "explicit",
20701
21278
  valueCategory: "core",
20702
21279
  // immune to retention archival
21280
+ // A direct handoff is not a project-wide memo. The sender retains access
21281
+ // and the explicit recipient receives a read grant. Broadcast handoffs
21282
+ // are deliberately visible only to active team members.
21283
+ visibility: input.toAgentId ? "personal" : "team",
21284
+ ...input.toAgentId ? { sharedWithAgentIds: [input.toAgentId] } : {},
20703
21285
  createdByAgentId: input.fromAgentId
20704
21286
  });
20705
21287
  try {
@@ -22405,6 +22987,7 @@ var init_hooks_path = __esm({
22405
22987
 
22406
22988
  // src/sdk.ts
22407
22989
  init_esm_shims();
22990
+ init_visibility();
22408
22991
 
22409
22992
  // src/server.ts
22410
22993
  init_esm_shims();
@@ -22610,6 +23193,7 @@ async function createAutoRelations(obs, extracted, graphManager) {
22610
23193
  // src/server.ts
22611
23194
  init_entity_extractor();
22612
23195
  init_graph_scope();
23196
+ init_visibility();
22613
23197
 
22614
23198
  // src/compact/engine.ts
22615
23199
  init_esm_shims();
@@ -22735,7 +23319,15 @@ function formatTimeline(timeline) {
22735
23319
  function formatObservationDetail(doc) {
22736
23320
  const icon = getTypeIcon(doc.type);
22737
23321
  const lines = [];
22738
- const header = buildProvenanceHeader(doc.sourceDetail, doc.valueCategory, doc.source, doc.commitHash, doc.relatedCommits);
23322
+ const header = buildProvenanceHeader(
23323
+ doc.sourceDetail,
23324
+ doc.valueCategory,
23325
+ doc.admissionState,
23326
+ doc.admissionReason,
23327
+ doc.source,
23328
+ doc.commitHash,
23329
+ doc.relatedCommits
23330
+ );
22739
23331
  if (header) {
22740
23332
  lines.push(header);
22741
23333
  lines.push("");
@@ -22770,7 +23362,7 @@ function formatObservationDetail(doc) {
22770
23362
  }
22771
23363
  return lines.join("\n");
22772
23364
  }
22773
- function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash, relatedCommits) {
23365
+ function buildProvenanceHeader(sourceDetail, valueCategory, admissionState, admissionReason, source, commitHash, relatedCommits) {
22774
23366
  const sd = resolveSourceDetail(sourceDetail, source);
22775
23367
  if (!sd) return "";
22776
23368
  const label = sourceKindLabel(sd);
@@ -22781,11 +23373,21 @@ function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash,
22781
23373
  if (verificationLine) {
22782
23374
  lines.push(verificationLine);
22783
23375
  }
22784
- if (valueCategory === "core") {
23376
+ if (valueCategory === "core" && admissionState !== "candidate" && admissionState !== "ephemeral") {
22785
23377
  lines.push("[CORE] Core \u2014 immune to decay");
23378
+ } else if (valueCategory === "core") {
23379
+ lines.push("[CORE] Core candidate \u2014 not durable until qualification completes");
22786
23380
  } else if (valueCategory === "ephemeral") {
22787
23381
  lines.push("[WARN] Ephemeral \u2014 short-lived signal");
22788
23382
  }
23383
+ if (admissionState === "candidate") {
23384
+ lines.push("[PENDING] Candidate \u2014 excluded from automatic context until current Code Memory qualifies it");
23385
+ } else if (admissionState === "ephemeral") {
23386
+ lines.push("[TRACE] Ephemeral capture \u2014 excluded from automatic context");
23387
+ } else if (admissionState === "qualified") {
23388
+ lines.push("[QUALIFIED] Automatic evidence passed current Code Memory qualification");
23389
+ }
23390
+ if (admissionReason) lines.push(`Admission: ${redactCredentials(admissionReason)}`);
22789
23391
  return lines.join("\n");
22790
23392
  }
22791
23393
  function sourceKindLabel(sd) {
@@ -22866,6 +23468,7 @@ init_obs_store();
22866
23468
  init_mini_skill_store();
22867
23469
  init_mini_skills();
22868
23470
  init_secret_filter();
23471
+ init_visibility();
22869
23472
  function normalizeMemoryBrowseQuery(query) {
22870
23473
  const trimmed = query.trim();
22871
23474
  if (!trimmed) return "";
@@ -22908,11 +23511,13 @@ async function compactSearch(options) {
22908
23511
  );
22909
23512
  let formatted = formatIndexTable(entries, searchOptions.query, !options.projectId);
22910
23513
  if (entries.length === 0 && options.projectId) {
22911
- const allObservations = getAllObservations();
23514
+ const allObservations = filterReadableObservations(getAllObservations(), options.reader);
22912
23515
  const projectAliases = new Set(await resolveAliases(options.projectId).catch(() => [options.projectId]));
22913
23516
  const projectHasStoredMemory = allObservations.some((obs) => obs.projectId && projectAliases.has(obs.projectId));
22914
23517
  if (!projectHasStoredMemory) {
22915
- formatted = `This project does not have any Memorix memories yet.
23518
+ formatted = options.reader ? `No Memorix memories are available to this session yet.
23519
+
23520
+ The tool call worked, but this session has no project-visible memory to retrieve.` : `This project does not have any Memorix memories yet.
22916
23521
 
22917
23522
  It looks like a fresh project: the tool call worked, but there is nothing stored to retrieve yet.
22918
23523
 
@@ -22926,8 +23531,8 @@ This project does have stored Memorix memories, but none matched the current que
22926
23531
  const totalTokens = countTextTokens(formatted);
22927
23532
  return { entries, formatted, totalTokens };
22928
23533
  }
22929
- async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3) {
22930
- const result = await getTimeline(anchorId, projectId, depthBefore, depthAfter);
23534
+ async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3, reader) {
23535
+ const result = await getTimeline(anchorId, projectId, depthBefore, depthAfter, reader);
22931
23536
  const timeline = {
22932
23537
  anchorId,
22933
23538
  anchorEntry: result.anchor,
@@ -22938,7 +23543,7 @@ async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter
22938
23543
  const totalTokens = countTextTokens(formatted);
22939
23544
  return { timeline, formatted, totalTokens };
22940
23545
  }
22941
- async function compactDetail(idsOrRefs) {
23546
+ async function compactDetail(idsOrRefs, options = {}) {
22942
23547
  const invalidRefs = [];
22943
23548
  const parsedRefs = [];
22944
23549
  for (let i = 0; i < idsOrRefs.length; i++) {
@@ -22995,7 +23600,7 @@ async function compactDetail(idsOrRefs) {
22995
23600
  const missingRefs = [];
22996
23601
  for (const ref of refs) {
22997
23602
  const obs = getObservation(ref.id, ref.projectId);
22998
- if (obs && (ref.projectId ? obs.projectId === ref.projectId : true)) {
23603
+ if (obs && (ref.projectId ? obs.projectId === ref.projectId : true) && canReadObservation(obs, options.reader)) {
22999
23604
  documentMap.set(toRefKey(ref), {
23000
23605
  id: makeOramaObservationId(obs.projectId, obs.id),
23001
23606
  observationId: obs.id,
@@ -23014,7 +23619,12 @@ async function compactDetail(idsOrRefs) {
23014
23619
  status: obs.status ?? "active",
23015
23620
  source: obs.source ?? "agent",
23016
23621
  sourceDetail: obs.sourceDetail ?? "",
23017
- valueCategory: obs.valueCategory ?? ""
23622
+ valueCategory: obs.valueCategory ?? "",
23623
+ admissionState: obs.admissionState ?? "",
23624
+ admissionReason: obs.admissionReason ?? "",
23625
+ visibility: obs.visibility ?? "project",
23626
+ createdByAgentId: obs.createdByAgentId ?? "",
23627
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
23018
23628
  });
23019
23629
  } else {
23020
23630
  missingRefs.push(ref);
@@ -23024,17 +23634,17 @@ async function compactDetail(idsOrRefs) {
23024
23634
  for (const ref of missingRefs) {
23025
23635
  const fallbackDocs = await getObservationsByIds([ref.id], ref.projectId);
23026
23636
  const doc = fallbackDocs[0];
23027
- if (doc) {
23637
+ if (doc && canReadObservation(doc, options.reader)) {
23028
23638
  documentMap.set(toRefKey(ref), doc);
23029
23639
  continue;
23030
23640
  }
23031
23641
  const stored = await getPersistedObservation(ref);
23032
- if (stored) {
23642
+ if (stored && canReadObservation(stored, options.reader)) {
23033
23643
  documentMap.set(toRefKey(ref), observationToDocument(stored));
23034
23644
  }
23035
23645
  }
23036
23646
  }
23037
- const allObs = getAllObservations();
23647
+ const allObs = filterReadableObservations(getAllObservations(), options.reader);
23038
23648
  const crossRefMap = /* @__PURE__ */ new Map();
23039
23649
  for (const ref of refs) {
23040
23650
  const obs = getObservation(ref.id, ref.projectId);
@@ -23148,7 +23758,12 @@ function observationToDocument(obs) {
23148
23758
  status: obs.status ?? "active",
23149
23759
  source: obs.source ?? "agent",
23150
23760
  sourceDetail: obs.sourceDetail ?? "",
23151
- valueCategory: obs.valueCategory ?? ""
23761
+ valueCategory: obs.valueCategory ?? "",
23762
+ admissionState: obs.admissionState ?? "",
23763
+ admissionReason: obs.admissionReason ?? "",
23764
+ visibility: obs.visibility ?? "project",
23765
+ createdByAgentId: obs.createdByAgentId ?? "",
23766
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
23152
23767
  };
23153
23768
  }
23154
23769
  function formatMiniSkillDetail(skill2, provenanceStatus) {
@@ -23267,7 +23882,9 @@ function auditMemoryQuality(observations2, options) {
23267
23882
  status: obs.status ?? "active",
23268
23883
  source: obs.source ?? "agent",
23269
23884
  sourceDetail: obs.sourceDetail ?? "",
23270
- valueCategory: obs.valueCategory ?? ""
23885
+ valueCategory: obs.valueCategory ?? "",
23886
+ admissionState: obs.admissionState ?? "",
23887
+ admissionReason: obs.admissionReason ?? ""
23271
23888
  }, options.referenceTime) !== "active").map((obs) => makeEntry(obs, "Outside active retention zone"));
23272
23889
  const summary = {
23273
23890
  total: scoped.length,
@@ -23309,6 +23926,7 @@ function auditMemoryQuality(observations2, options) {
23309
23926
 
23310
23927
  // src/memory/graph-context.ts
23311
23928
  init_retention();
23929
+ init_admission();
23312
23930
  function formatGraphContextPrompt(packet) {
23313
23931
  const lines = [
23314
23932
  "## Memory Context Packet",
@@ -23466,7 +24084,9 @@ function scoreForPacket(obs, queryTokens, referenceTime, broadMemoryQuery) {
23466
24084
  status: obs.status ?? "active",
23467
24085
  source: obs.source ?? "agent",
23468
24086
  sourceDetail: obs.sourceDetail ?? "",
23469
- valueCategory: obs.valueCategory ?? ""
24087
+ valueCategory: obs.valueCategory ?? "",
24088
+ admissionState: obs.admissionState ?? "",
24089
+ admissionReason: obs.admissionReason ?? ""
23470
24090
  }, referenceTime) !== "active") {
23471
24091
  score -= 2;
23472
24092
  }
@@ -23560,8 +24180,11 @@ function buildGraphContextPacket(observations2, options) {
23560
24180
  ...audit.issues.orphans.map((entry) => entry.id),
23561
24181
  ...audit.issues.retentionCandidates.map((entry) => entry.id)
23562
24182
  ]);
23563
- const filteredObservations = observations2.filter((obs) => obs.projectId === options.projectId && !riskIds.has(obs.id));
23564
- const baseObservations = filteredObservations.length > 0 ? filteredObservations : observations2;
24183
+ const deliveryEligible = observations2.filter(
24184
+ (obs) => obs.projectId === options.projectId && isEligibleForAutomaticDelivery(obs)
24185
+ );
24186
+ const filteredObservations = deliveryEligible.filter((obs) => !riskIds.has(obs.id));
24187
+ const baseObservations = filteredObservations.length > 0 ? filteredObservations : deliveryEligible;
23565
24188
  const memories = pickMemories(baseObservations, options.projectId, options.query, options.limit ?? 5, referenceTime);
23566
24189
  const entities = buildEntities(memories);
23567
24190
  const entityNames = new Set(entities.map((entity) => entity.name));
@@ -26048,6 +26671,8 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
26048
26671
  let projectResolutionError = null;
26049
26672
  let explicitProjectBound = false;
26050
26673
  let currentAgentId;
26674
+ let teamStore;
26675
+ let initTeamStoreForProject;
26051
26676
  if (detectedProject) {
26052
26677
  rawProject = detectedProject;
26053
26678
  } else {
@@ -26295,7 +26920,7 @@ The path should point to a directory containing a .git folder.`
26295
26920
  };
26296
26921
  const server = existingServer ?? new McpServer({
26297
26922
  name: "memorix",
26298
- version: true ? "1.2.1" : "1.0.1"
26923
+ version: true ? "1.2.3" : "1.0.1"
26299
26924
  });
26300
26925
  const originalRegisterTool = server.registerTool.bind(server);
26301
26926
  server.registerTool = ((name, ...args) => {
@@ -26315,11 +26940,26 @@ The path should point to a directory containing a .git folder.`
26315
26940
  }
26316
26941
  return originalRegisterTool(name, ...args);
26317
26942
  });
26943
+ const getObservationReader = (scope = "project") => {
26944
+ let isTeamMember = false;
26945
+ if (teamFeaturesEnabled && currentAgentId) {
26946
+ try {
26947
+ const agent = teamStore.getAgent(currentAgentId);
26948
+ isTeamMember = agent?.project_id === project.id && agent.status === "active";
26949
+ } catch {
26950
+ }
26951
+ }
26952
+ return {
26953
+ ...scope === "project" ? { projectId: project.id } : {},
26954
+ ...currentAgentId ? { agentId: currentAgentId } : {},
26955
+ isTeamMember
26956
+ };
26957
+ };
26318
26958
  server.registerTool(
26319
26959
  "memorix_store",
26320
26960
  {
26321
26961
  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.",
26962
+ 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
26963
  inputSchema: {
26324
26964
  entityName: z2.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
26325
26965
  type: z2.enum(OBSERVATION_TYPES).describe("Observation type for classification"),
@@ -26337,361 +26977,214 @@ The path should point to a directory containing a .git folder.`
26337
26977
  completion: z2.number().optional().describe("Completion percentage 0-100")
26338
26978
  }).optional().describe("Progress tracking for task/feature observations"),
26339
26979
  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")
26980
+ relatedEntities: z2.array(z2.string()).optional().describe("Other entity names this memory cross-references"),
26981
+ visibility: z2.enum(["personal", "project", "team"]).optional().describe(
26982
+ "Retrieval scope. Project is the normal shared default; personal/team require memorix_session_start with joinTeam=true."
26983
+ )
26341
26984
  }
26342
26985
  },
26343
- async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities }) => {
26986
+ async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility }) => {
26344
26987
  const unresolved = requireResolvedProject("store memory in the current project");
26345
26988
  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 {
26989
+ const requestedVisibility = visibility ?? "project";
26990
+ const reader = getObservationReader();
26991
+ if (requestedVisibility !== "project" && !currentAgentId) {
26992
+ return {
26993
+ content: [{ type: "text", text: "Personal or team memory requires memorix_session_start with joinTeam=true so Memorix can bind an owner." }],
26994
+ isError: true
26995
+ };
26996
+ }
26997
+ if (requestedVisibility === "team" && !reader.isTeamMember) {
26998
+ return {
26999
+ content: [{ type: "text", text: "Team memory requires an active coordination membership in the current project." }],
27000
+ isError: true
27001
+ };
27002
+ }
27003
+ try {
27004
+ return await withFreshIndex(async () => {
27005
+ let entityName = rawEntityName;
27006
+ let type = rawType;
27007
+ let title = rawTitle;
27008
+ let safeFacts = facts ? coerceStringArray(facts) : void 0;
27009
+ const safeFiles = filesModified ? coerceStringArray(filesModified) : void 0;
27010
+ const safeConcepts = concepts ? coerceStringArray(concepts) : void 0;
27011
+ let formationMode = "active";
27012
+ if (process.env.MEMORIX_FORMATION_MODE) {
27013
+ formationMode = process.env.MEMORIX_FORMATION_MODE;
27014
+ } else {
27015
+ try {
27016
+ const { getBehaviorConfig: getBehaviorConfig2 } = await Promise.resolve().then(() => (init_behavior(), behavior_exports));
27017
+ formationMode = getBehaviorConfig2({ projectRoot: project.rootPath }).formationMode;
27018
+ } catch {
27019
+ }
26361
27020
  }
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") {
27021
+ const useFormation = formationMode === "active";
27022
+ let formationResult = null;
27023
+ let formationNote = "";
27024
+ if (useFormation && !topicKey && !progress) {
27025
+ let currentFormationStage = "setup";
27026
+ const completedFormationStages = {};
27027
+ const formationStartTime = Date.now();
27028
+ const onFormationStageEvent = (event) => {
27029
+ if (event.status === "start") {
27030
+ currentFormationStage = event.stage;
27031
+ return;
27032
+ }
26372
27033
  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
27034
+ if (event.stageDurationMs !== void 0) {
27035
+ completedFormationStages[event.stage] = event.stageDurationMs;
27036
+ }
26415
27037
  };
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 = `
27038
+ try {
27039
+ const formationConfig = {
27040
+ mode: "active",
27041
+ useLLM: isLLMEnabled(),
27042
+ minValueScore: 0.3,
27043
+ searchMemories: async (q, limit, pid) => {
27044
+ const result = await compactSearch({ query: q, limit, projectId: pid, status: "active", reader });
27045
+ if (result.entries.length === 0) return [];
27046
+ const details = await compactDetail(result.entries.map((e) => e.id), { reader });
27047
+ return details.documents.map((d, i) => ({
27048
+ id: Number(d.id.replace("obs-", "")),
27049
+ observationId: d.observationId,
27050
+ title: d.title,
27051
+ narrative: d.narrative,
27052
+ facts: d.facts,
27053
+ entityName: d.entityName,
27054
+ type: d.type,
27055
+ score: result.entries[i]?.score ?? 0
27056
+ }));
27057
+ },
27058
+ getObservation: (id) => {
27059
+ const o = getObservation(id);
27060
+ if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
27061
+ return {
27062
+ id: o.id,
27063
+ entityName: o.entityName,
27064
+ type: o.type,
27065
+ title: o.title,
27066
+ narrative: o.narrative,
27067
+ facts: o.facts,
27068
+ topicKey: o.topicKey
27069
+ };
27070
+ },
27071
+ getEntityNames: () => graphManager.getEntityNames(),
27072
+ onStageEvent: onFormationStageEvent
27073
+ };
27074
+ formationResult = await withTimeout2(
27075
+ runFormation({
27076
+ entityName,
27077
+ type,
27078
+ title,
27079
+ narrative,
27080
+ facts: safeFacts,
27081
+ projectId: project.id,
27082
+ source: "explicit"
27083
+ }, formationConfig),
27084
+ FORMATION_TIMEOUT_MS,
27085
+ "Formation pipeline"
27086
+ );
27087
+ const modeIcon = "[FAST]";
27088
+ formationNote = `
26431
27089
  ${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 = `
27090
+ if (formationResult.extraction.extractedFacts.length > 0) {
27091
+ formationNote += ` | +${formationResult.extraction.extractedFacts.length} facts`;
27092
+ }
27093
+ if (formationResult.extraction.titleImproved) formationNote += " | title\u2191";
27094
+ if (formationResult.extraction.entityResolved) formationNote += ` | entity\u2192${formationResult.entityName}`;
27095
+ if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
27096
+ } catch (formationErr) {
27097
+ const isTimeout = formationErr instanceof Error && formationErr.message.includes("timed out");
27098
+ const elapsedMs = Date.now() - formationStartTime;
27099
+ const stageSummary = formatFormationStageDurations(completedFormationStages);
27100
+ console.error(
27101
+ `[memorix] Formation ${isTimeout ? "timed out" : "failed"} in memorix_store after ${elapsedMs}ms/${FORMATION_TIMEOUT_MS}ms at stage ${currentFormationStage}${stageSummary ? ` | completed: ${stageSummary}` : ""}`
27102
+ );
27103
+ if (!isTimeout && formationErr instanceof Error) {
27104
+ console.error(`[memorix] Formation error: ${formationErr.message}`);
27105
+ }
27106
+ formationNote = `
26449
27107
  [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
27108
  }
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
27109
  }
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") {
27110
+ if (useFormation && formationResult && formationResult.resolution.action !== "new") {
27111
+ const { action: action2, targetId, reason } = formationResult.resolution;
27112
+ if (action2 === "merge" && targetId) {
27113
+ const targetObs = getObservation(targetId);
27114
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27115
+ await storeObservation({
27116
+ entityName: targetObs.entityName,
27117
+ type: targetObs.type,
27118
+ title: formationResult.title,
27119
+ narrative: formationResult.narrative,
27120
+ facts: formationResult.facts,
27121
+ filesModified: safeFiles,
27122
+ concepts: safeConcepts,
27123
+ projectId: project.id,
27124
+ topicKey: targetObs.topicKey,
27125
+ progress,
27126
+ sourceDetail: "explicit",
27127
+ createdByAgentId: currentAgentId,
27128
+ visibility,
27129
+ visibilityReader: reader
27130
+ });
26568
27131
  return {
26569
27132
  content: [{
26570
27133
  type: "text",
26571
- text: `[SKIP] Compact SKIP: ${decision.reason}
26572
- Existing memory #${decision.targetId} already covers this.
26573
- Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27134
+ text: `[UPDATED] Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
26574
27135
  }]
26575
27136
  };
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
27137
  }
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
- }
27138
+ } else if (action2 === "evolve" && targetId) {
27139
+ const targetObs = getObservation(targetId);
27140
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27141
+ await storeObservation({
27142
+ entityName: targetObs.entityName,
27143
+ type: targetObs.type,
27144
+ title: formationResult.title,
27145
+ narrative: formationResult.narrative,
27146
+ facts: formationResult.facts,
27147
+ filesModified: safeFiles,
27148
+ concepts: safeConcepts,
27149
+ projectId: project.id,
27150
+ topicKey: targetObs.topicKey,
27151
+ progress,
27152
+ sourceDetail: "explicit",
27153
+ createdByAgentId: currentAgentId,
27154
+ visibility,
27155
+ visibilityReader: reader
27156
+ });
27157
+ return {
27158
+ content: [{
27159
+ type: "text",
27160
+ text: `[UPDATED] Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
27161
+ }]
27162
+ };
26587
27163
  }
27164
+ } else if (action2 === "discard") {
27165
+ return {
27166
+ content: [{
27167
+ type: "text",
27168
+ text: `[SKIP] Formation DISCARD: ${reason}${formationNote}`
27169
+ }]
27170
+ };
26588
27171
  }
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
- }
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
27172
  }
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;
27173
+ let compactAction = "";
27174
+ let compactMerged = false;
27175
+ if (!useFormation && !topicKey && !progress) {
26683
27176
  try {
26684
- const compactStart = Date.now();
26685
27177
  const searchResult = await compactSearch({
26686
27178
  query: `${title} ${narrative.substring(0, 200)}`,
26687
27179
  limit: 5,
26688
27180
  projectId: project.id,
26689
- status: "active"
27181
+ status: "active",
27182
+ reader
26690
27183
  });
26691
27184
  const similarEntries = searchResult.entries.map((e) => e);
26692
27185
  if (similarEntries.length > 0) {
26693
27186
  const similarIds = similarEntries.map((e) => e.id);
26694
- const details = await compactDetail(similarIds);
27187
+ const details = await compactDetail(similarIds, { reader });
26695
27188
  const existingMemories = details.documents.map((d, i) => ({
26696
27189
  id: d.observationId,
26697
27190
  title: d.title,
@@ -26699,78 +27192,266 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
26699
27192
  facts: d.facts,
26700
27193
  score: similarEntries[i]?.score ?? 0
26701
27194
  }));
26702
- const decision = await compactOnWrite(
26703
- { title, narrative, facts: safeFacts ?? [] },
26704
- existingMemories
27195
+ const decision = await withTimeout2(
27196
+ compactOnWrite(
27197
+ { title, narrative, facts: safeFacts ?? [] },
27198
+ existingMemories
27199
+ ),
27200
+ COMPACT_ON_WRITE_TIMEOUT_MS,
27201
+ "Compact-on-write"
26705
27202
  );
26706
- oldCompactDecision = {
26707
- action: decision.action,
26708
- targetId: decision.targetId,
26709
- reason: decision.reason,
26710
- durationMs: Date.now() - compactStart
26711
- };
27203
+ if (decision.action === "UPDATE" && decision.targetId) {
27204
+ const targetObs = getObservation(decision.targetId);
27205
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27206
+ await storeObservation({
27207
+ entityName: targetObs.entityName,
27208
+ type: targetObs.type,
27209
+ title: decision.mergedNarrative ? title : targetObs.title,
27210
+ narrative: decision.mergedNarrative ?? narrative,
27211
+ facts: decision.mergedFacts ?? safeFacts,
27212
+ filesModified: safeFiles,
27213
+ concepts: safeConcepts,
27214
+ projectId: project.id,
27215
+ topicKey: targetObs.topicKey,
27216
+ progress,
27217
+ sourceDetail: "explicit",
27218
+ createdByAgentId: currentAgentId,
27219
+ visibility,
27220
+ visibilityReader: reader
27221
+ });
27222
+ compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
27223
+ compactMerged = true;
27224
+ return {
27225
+ content: [{
27226
+ type: "text",
27227
+ text: `${compactAction}
27228
+ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27229
+ }]
27230
+ };
27231
+ }
27232
+ } else if (decision.action === "NONE") {
27233
+ return {
27234
+ content: [{
27235
+ type: "text",
27236
+ text: `[SKIP] Compact SKIP: ${decision.reason}
27237
+ Existing memory #${decision.targetId} already covers this.
27238
+ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27239
+ }]
27240
+ };
27241
+ } else if (decision.action === "DELETE" && decision.targetId) {
27242
+ const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27243
+ await resolveObservations2([decision.targetId], "resolved");
27244
+ compactAction = ` | Compact: resolved outdated #${decision.targetId}`;
27245
+ }
27246
+ if (decision.enrichedFacts && decision.enrichedFacts.length > 0) {
27247
+ const currentFacts = safeFacts ?? [];
27248
+ const newFacts = decision.enrichedFacts.filter((f) => !currentFacts.includes(f));
27249
+ if (newFacts.length > 0) {
27250
+ compactAction += ` | +${newFacts.length} LLM-extracted facts`;
27251
+ }
27252
+ }
26712
27253
  }
26713
27254
  } catch {
26714
27255
  }
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"
27256
+ }
27257
+ if (formationResult && formationResult.resolution.action === "new") {
27258
+ const llmFacts = formationResult.extraction.extractedFacts;
27259
+ if (llmFacts.length > 0) {
27260
+ const currentFacts = safeFacts ?? [];
27261
+ const currentLower = new Set(currentFacts.map((f) => f.toLowerCase().trim()));
27262
+ const newFacts = llmFacts.filter((f) => !currentLower.has(f.toLowerCase().trim()));
27263
+ if (newFacts.length > 0) {
27264
+ safeFacts = [...currentFacts, ...newFacts];
27265
+ }
27266
+ }
27267
+ if (formationResult.extraction.titleImproved && formationResult.title) {
27268
+ title = formationResult.title;
27269
+ }
27270
+ if (formationResult.extraction.typeCorrected && formationResult.type) {
27271
+ type = formationResult.type;
27272
+ }
27273
+ if (formationResult.extraction.entityResolved && formationResult.entityName) {
27274
+ entityName = formationResult.entityName;
27275
+ }
27276
+ }
27277
+ await graphManager.createEntities([
27278
+ { name: entityName, entityType: "auto", observations: [] }
27279
+ ]);
27280
+ let sessionId;
27281
+ try {
27282
+ const { getActiveSession: getActiveSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
27283
+ const active = await getActiveSession2(projectDir2, project.id);
27284
+ if (active) sessionId = active.id;
27285
+ } catch {
27286
+ }
27287
+ let finalNarrative = narrative;
27288
+ let compressionNote = "";
27289
+ try {
27290
+ const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
27291
+ const { compressed, saved, usedLLM } = await withTimeout2(
27292
+ compressNarrative2(narrative, safeFacts, type),
27293
+ COMPRESSION_TIMEOUT_MS,
27294
+ "Narrative compression"
26745
27295
  );
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
- });
27296
+ if (usedLLM && saved > 0) {
27297
+ finalNarrative = compressed;
27298
+ compressionNote = ` | compressed -${saved} tokens`;
26759
27299
  }
26760
- };
26761
- shadowFormation().catch(() => {
27300
+ } catch {
27301
+ }
27302
+ let attributionWarning = "";
27303
+ try {
27304
+ const attrCheck = await checkProjectAttribution(
27305
+ entityName,
27306
+ project.id,
27307
+ filterReadableObservations(getAllObservations(), reader)
27308
+ );
27309
+ if (attrCheck.suspicious) {
27310
+ attributionWarning = `
27311
+ [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.`;
27312
+ }
27313
+ } catch {
27314
+ }
27315
+ const { observation: obs, upserted } = await storeObservation({
27316
+ entityName,
27317
+ type,
27318
+ title,
27319
+ narrative: finalNarrative,
27320
+ facts: safeFacts,
27321
+ filesModified: safeFiles,
27322
+ concepts: safeConcepts,
27323
+ projectId: project.id,
27324
+ topicKey,
27325
+ sessionId,
27326
+ progress,
27327
+ relatedCommits,
27328
+ relatedEntities,
27329
+ sourceDetail: "explicit",
27330
+ valueCategory: formationResult?.evaluation.category,
27331
+ createdByAgentId: currentAgentId,
27332
+ visibility,
27333
+ visibilityReader: reader
26762
27334
  });
26763
- }
26764
- return {
26765
- content: [
26766
- {
26767
- type: "text",
26768
- text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
27335
+ await graphManager.addObservations([
27336
+ { entityName, contents: [`[#${obs.id}] ${title}`] }
27337
+ ]);
27338
+ const extracted = extractEntities([title, narrative, ...safeFacts ?? []].join(" "));
27339
+ const autoRelCount = await createAutoRelations(obs, extracted, graphManager);
27340
+ const enrichmentParts = [];
27341
+ const autoFiles = obs.filesModified.filter((f) => !(safeFiles ?? []).includes(f));
27342
+ const autoConcepts = obs.concepts.filter((c) => !(safeConcepts ?? []).includes(c));
27343
+ if (autoFiles.length > 0) enrichmentParts.push(`+${autoFiles.length} files extracted`);
27344
+ if (autoConcepts.length > 0) enrichmentParts.push(`+${autoConcepts.length} concepts enriched`);
27345
+ if (autoRelCount > 0) enrichmentParts.push(`+${autoRelCount} relations auto-created`);
27346
+ if (obs.hasCausalLanguage) enrichmentParts.push("causal language detected");
27347
+ if (upserted) enrichmentParts.push(`topic upserted (rev ${obs.revisionCount ?? 1})`);
27348
+ const enrichment = enrichmentParts.length > 0 ? `
27349
+ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
27350
+ const action = upserted ? "[UPDATED] Updated" : "[OK] Stored";
27351
+ if (!useFormation && !topicKey && !progress) {
27352
+ const shadowFormation = async () => {
27353
+ let oldCompactDecision = null;
27354
+ try {
27355
+ const compactStart = Date.now();
27356
+ const searchResult = await compactSearch({
27357
+ query: `${title} ${narrative.substring(0, 200)}`,
27358
+ limit: 5,
27359
+ projectId: project.id,
27360
+ status: "active",
27361
+ reader
27362
+ });
27363
+ const similarEntries = searchResult.entries.map((e) => e);
27364
+ if (similarEntries.length > 0) {
27365
+ const similarIds = similarEntries.map((e) => e.id);
27366
+ const details = await compactDetail(similarIds, { reader });
27367
+ const existingMemories = details.documents.map((d, i) => ({
27368
+ id: d.observationId,
27369
+ title: d.title,
27370
+ narrative: d.narrative,
27371
+ facts: d.facts,
27372
+ score: similarEntries[i]?.score ?? 0
27373
+ }));
27374
+ const decision = await compactOnWrite(
27375
+ { title, narrative, facts: safeFacts ?? [] },
27376
+ existingMemories
27377
+ );
27378
+ oldCompactDecision = {
27379
+ action: decision.action,
27380
+ targetId: decision.targetId,
27381
+ reason: decision.reason,
27382
+ durationMs: Date.now() - compactStart
27383
+ };
27384
+ }
27385
+ } catch {
27386
+ }
27387
+ const formationConfig = {
27388
+ mode: formationMode,
27389
+ useLLM: isLLMEnabled(),
27390
+ minValueScore: 0.3,
27391
+ searchMemories: async (q, limit, pid) => {
27392
+ const result = await compactSearch({ query: q, limit, projectId: pid, status: "active", reader });
27393
+ if (result.entries.length === 0) return [];
27394
+ const details = await compactDetail(result.entries.map((e) => e.id), { reader });
27395
+ return details.documents.map((d, i) => ({
27396
+ id: Number(d.id.replace("obs-", "")),
27397
+ observationId: d.observationId,
27398
+ title: d.title,
27399
+ narrative: d.narrative,
27400
+ facts: d.facts,
27401
+ entityName: d.entityName,
27402
+ type: d.type,
27403
+ score: result.entries[i]?.score ?? 0
27404
+ }));
27405
+ },
27406
+ getObservation: (id) => {
27407
+ const o = getObservation(id);
27408
+ if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
27409
+ return { id: o.id, entityName: o.entityName, type: o.type, title: o.title, narrative: o.narrative, facts: o.facts, topicKey: o.topicKey };
27410
+ },
27411
+ getEntityNames: () => graphManager.getEntityNames()
27412
+ };
27413
+ const formed = await withTimeout2(
27414
+ runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
27415
+ FORMATION_TIMEOUT_MS,
27416
+ "Shadow formation"
27417
+ );
27418
+ const { recordBeforeAfterMetrics: recordBeforeAfterMetrics2 } = await Promise.resolve().then(() => (init_formation(), formation_exports));
27419
+ if (oldCompactDecision) {
27420
+ recordBeforeAfterMetrics2({
27421
+ formationAction: formed.resolution.action,
27422
+ formationTargetId: formed.resolution.targetId,
27423
+ oldCompactAction: oldCompactDecision.action,
27424
+ oldCompactTargetId: oldCompactDecision.targetId,
27425
+ oldCompactReason: oldCompactDecision.reason,
27426
+ formationValueScore: formed.evaluation.score,
27427
+ formationValueCategory: formed.evaluation.category,
27428
+ formationDurationMs: formed.pipeline.durationMs,
27429
+ compactDurationMs: oldCompactDecision.durationMs
27430
+ });
27431
+ }
27432
+ };
27433
+ shadowFormation().catch(() => {
27434
+ });
27435
+ }
27436
+ return {
27437
+ content: [
27438
+ {
27439
+ type: "text",
27440
+ text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
26769
27441
  Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}${formationNote}${attributionWarning}`
26770
- }
26771
- ]
27442
+ }
27443
+ ]
27444
+ };
27445
+ });
27446
+ } catch (error) {
27447
+ return {
27448
+ content: [{
27449
+ type: "text",
27450
+ text: error instanceof Error ? error.message : "Failed to store memory."
27451
+ }],
27452
+ isError: true
26772
27453
  };
26773
- });
27454
+ }
26774
27455
  }
26775
27456
  );
26776
27457
  server.registerTool(
@@ -26842,7 +27523,8 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
26842
27523
  // Use scope: 'global' to explicitly search all projects.
26843
27524
  projectId: scope === "global" ? void 0 : project.id,
26844
27525
  status: status ?? "active",
26845
- source
27526
+ source,
27527
+ reader: getObservationReader(scope === "global" ? "global" : "project")
26846
27528
  });
26847
27529
  const timeoutPromise = new Promise(
26848
27530
  (_, reject) => setTimeout(() => reject(new Error(`Search timeout after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
@@ -26900,7 +27582,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
26900
27582
  const unresolved = requireResolvedProject("build graph context for the current project");
26901
27583
  if (unresolved) return unresolved;
26902
27584
  const { getObservationStore: getObservationStore2 } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
26903
- const observations2 = await getObservationStore2().loadByProject(project.id);
27585
+ const observations2 = filterReadableObservations(
27586
+ await getObservationStore2().loadByProject(project.id),
27587
+ getObservationReader()
27588
+ );
26904
27589
  const packet = buildGraphContextPacket(observations2, {
26905
27590
  projectId: project.id,
26906
27591
  query,
@@ -26952,7 +27637,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
26952
27637
  Promise.resolve().then(() => (init_maintenance_jobs(), maintenance_jobs_exports)),
26953
27638
  Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports))
26954
27639
  ]);
26955
- const observations2 = await getObservationStore2().loadByProject(project.id, { status: "active" });
27640
+ const observations2 = filterReadableObservations(
27641
+ await getObservationStore2().loadByProject(project.id, { status: "active" }),
27642
+ getObservationReader()
27643
+ );
26956
27644
  const context = await buildAutoProjectContext2({
26957
27645
  project,
26958
27646
  dataDir: projectDir2,
@@ -27042,7 +27730,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27042
27730
  await store.init(projectDir2);
27043
27731
  const codegraphConfig = getResolvedConfig2({ projectRoot: project.rootPath }).codegraph;
27044
27732
  const exclude = codegraphConfig.excludePatterns;
27045
- const observations2 = await getObservationStore2().loadByProject(project.id, { status: "active" });
27733
+ const observations2 = filterReadableObservations(
27734
+ await getObservationStore2().loadByProject(project.id, { status: "active" }),
27735
+ getObservationReader()
27736
+ );
27046
27737
  observations2.reverse();
27047
27738
  const basePack = assembleContextPackForTask2({
27048
27739
  store,
@@ -27414,9 +28105,15 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27414
28105
  }
27415
28106
  },
27416
28107
  async ({ ids, status }) => {
27417
- const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28108
+ const { resolveObservations: resolveObservations2, getObservation: getObservation2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27418
28109
  const safeIds = (Array.isArray(ids) ? ids : [ids]).map((id) => coerceNumber(id, 0)).filter((id) => id > 0);
27419
- const result = await resolveObservations2(safeIds, status ?? "resolved");
28110
+ const reader = getObservationReader();
28111
+ const authorizedIds = safeIds.filter((id) => {
28112
+ const observation = getObservation2(id, project.id);
28113
+ return observation ? canManageObservation(observation, reader) : false;
28114
+ });
28115
+ const result = await resolveObservations2(authorizedIds, status ?? "resolved");
28116
+ const deniedCount = safeIds.length - authorizedIds.length;
27420
28117
  const parts = [];
27421
28118
  if (result.resolved.length > 0) {
27422
28119
  parts.push(`[OK] Resolved ${result.resolved.length} observation(s): #${result.resolved.join(", #")}`);
@@ -27424,6 +28121,9 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27424
28121
  if (result.notFound.length > 0) {
27425
28122
  parts.push(`[WARN] Not found: #${result.notFound.join(", #")}`);
27426
28123
  }
28124
+ if (deniedCount > 0) {
28125
+ parts.push(`[WARN] Skipped ${deniedCount} observation(s) outside this session's write scope.`);
28126
+ }
27427
28127
  parts.push('\nResolved memories are hidden from default search. Use status="all" to include them.');
27428
28128
  parts.push('[STATS] Run `memorix_retention` with `action: "report"` to check remaining cleanup status.');
27429
28129
  return {
@@ -27453,6 +28153,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27453
28153
  async ({ entityName, decision, alternatives, rationale, constraints, expectedOutcome, risks, concepts, filesModified, relatedCommits, relatedEntities }) => {
27454
28154
  const unresolved = requireResolvedProject("store reasoning in the current project");
27455
28155
  if (unresolved) return unresolved;
28156
+ const reader = getObservationReader();
27456
28157
  return withFreshIndex(async () => {
27457
28158
  const narrativeParts = [rationale];
27458
28159
  if (alternatives && alternatives.length > 0) {
@@ -27475,7 +28176,11 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27475
28176
  ]);
27476
28177
  let reasoningAttributionWarning = "";
27477
28178
  try {
27478
- const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
28179
+ const attrCheck = await checkProjectAttribution(
28180
+ entityName,
28181
+ project.id,
28182
+ filterReadableObservations(getAllObservations(), reader)
28183
+ );
27479
28184
  if (attrCheck.suspicious) {
27480
28185
  reasoningAttributionWarning = `
27481
28186
  [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 +28200,8 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27495
28200
  relatedCommits,
27496
28201
  relatedEntities,
27497
28202
  sourceDetail: "explicit",
27498
- createdByAgentId: currentAgentId
28203
+ createdByAgentId: currentAgentId,
28204
+ visibility: "project"
27499
28205
  });
27500
28206
  await graphManager.addObservations([
27501
28207
  { entityName, contents: [`[#${obs.id}] [REASONING] ${decision}`] }
@@ -27527,7 +28233,11 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
27527
28233
  const minCount = threshold ?? 2;
27528
28234
  let entries;
27529
28235
  try {
27530
- entries = await auditProjectObservations(project.id, await withFreshIndex(() => getAllObservations()), minCount);
28236
+ entries = await auditProjectObservations(
28237
+ project.id,
28238
+ await withFreshIndex(() => filterReadableObservations(getAllObservations(), getObservationReader())),
28239
+ minCount
28240
+ );
27531
28241
  } catch (err) {
27532
28242
  return {
27533
28243
  content: [{
@@ -27592,7 +28302,8 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
27592
28302
  limit: safeLimit,
27593
28303
  type: "reasoning",
27594
28304
  projectId: scope === "global" ? void 0 : project.id,
27595
- status: "active"
28305
+ status: "active",
28306
+ reader: getObservationReader(scope === "global" ? "global" : "project")
27596
28307
  }));
27597
28308
  if (result.entries.length === 0) {
27598
28309
  return {
@@ -27617,7 +28328,11 @@ ${result.formatted}` }]
27617
28328
  },
27618
28329
  async ({ query, dryRun }) => {
27619
28330
  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));
28331
+ const reader = getObservationReader();
28332
+ const allObs = await withFreshIndex(() => filterReadableObservations(
28333
+ getAllObservations2().filter((o) => (o.status ?? "active") === "active" && o.projectId === project.id),
28334
+ reader
28335
+ ).filter((observation) => canManageObservation(observation, reader)));
27621
28336
  if (allObs.length < 2) {
27622
28337
  return { content: [{ type: "text", text: "Not enough active memories to deduplicate." }] };
27623
28338
  }
@@ -27631,7 +28346,7 @@ ${result.formatted}` }]
27631
28346
  }
27632
28347
  let candidates;
27633
28348
  if (query) {
27634
- const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: "active" });
28349
+ const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: "active", reader });
27635
28350
  const idSet = new Set(searchResult.entries.map((e) => e.id));
27636
28351
  candidates = allObs.filter((o) => idSet.has(o.id));
27637
28352
  } else {
@@ -27721,7 +28436,8 @@ ${actions.join("\n")}`
27721
28436
  safeAnchor,
27722
28437
  project.id,
27723
28438
  safeBefore,
27724
- safeAfter
28439
+ safeAfter,
28440
+ getObservationReader()
27725
28441
  );
27726
28442
  return {
27727
28443
  content: [
@@ -27755,12 +28471,14 @@ ${actions.join("\n")}`
27755
28471
  const safeTypedRefs = coerceStringArray(typedRefs);
27756
28472
  let result;
27757
28473
  try {
28474
+ const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id) || safeTypedRefs.some((ref) => ref.includes("@") && !ref.endsWith(`@${project.id}`));
28475
+ const reader = getObservationReader(hasCrossProjectRef ? "global" : "project");
27758
28476
  if (safeTypedRefs.length > 0) {
27759
- result = await compactDetail(safeTypedRefs);
28477
+ result = await compactDetail(safeTypedRefs, { reader });
27760
28478
  } else if (safeRefs.length > 0) {
27761
- result = await compactDetail(safeRefs);
28479
+ result = await compactDetail(safeRefs, { reader });
27762
28480
  } else {
27763
- result = await compactDetail(safeIds.map((id) => ({ id, projectId: project.id })));
28481
+ result = await compactDetail(safeIds.map((id) => ({ id, projectId: project.id })), { reader });
27764
28482
  }
27765
28483
  } catch (err) {
27766
28484
  return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
@@ -27786,11 +28504,15 @@ ${actions.join("\n")}`
27786
28504
  },
27787
28505
  async (args) => {
27788
28506
  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));
28507
+ const { getRetentionSummary: getRetentionSummary2, getArchiveCandidates: getArchiveCandidates2, rankByRelevance: rankByRelevance2, getRetentionZone: getRetentionZone2, explainRetention: explainRetention2 } = await Promise.resolve().then(() => (init_retention(), retention_exports));
27790
28508
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
27791
28509
  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());
28510
+ const { getAllObservations: getAllObservations2, resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28511
+ const reader = getObservationReader();
28512
+ const allObs = await withFreshIndex(() => filterReadableObservations(
28513
+ getAllObservations2().filter((observation) => observation.projectId === project.id),
28514
+ reader
28515
+ ));
27794
28516
  const accessMap = /* @__PURE__ */ new Map();
27795
28517
  try {
27796
28518
  const database = await getDb2();
@@ -27807,20 +28529,6 @@ ${actions.join("\n")}`
27807
28529
  }
27808
28530
  } catch {
27809
28531
  }
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
28532
  const docs = allObs.map((obs) => ({
27825
28533
  id: `obs-${obs.id}`,
27826
28534
  observationId: obs.id,
@@ -27839,13 +28547,34 @@ Archived memories are hidden from default search but can be found with status: "
27839
28547
  status: obs.status ?? "active",
27840
28548
  source: obs.source ?? "agent",
27841
28549
  sourceDetail: obs.sourceDetail ?? "",
27842
- valueCategory: obs.valueCategory ?? ""
28550
+ valueCategory: obs.valueCategory ?? "",
28551
+ admissionState: obs.admissionState ?? "",
28552
+ admissionReason: obs.admissionReason ?? "",
28553
+ visibility: obs.visibility ?? "project",
28554
+ createdByAgentId: obs.createdByAgentId ?? "",
28555
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
27843
28556
  }));
27844
28557
  if (docs.length === 0) {
27845
28558
  return {
27846
28559
  content: [{ type: "text", text: "No observations found for this project." }]
27847
28560
  };
27848
28561
  }
28562
+ if (action === "archive") {
28563
+ const managedIds = new Set(
28564
+ allObs.filter((observation) => canManageObservation(observation, reader)).map((observation) => observation.id)
28565
+ );
28566
+ const candidates2 = getArchiveCandidates2(docs).filter((document) => managedIds.has(document.observationId));
28567
+ if (candidates2.length === 0) {
28568
+ return {
28569
+ content: [{ type: "text", text: "[OK] No expired memories in this session's write scope to archive." }]
28570
+ };
28571
+ }
28572
+ const result = await resolveObservations2(candidates2.map((document) => document.observationId), "archived");
28573
+ return {
28574
+ content: [{ type: "text", text: `[ARCHIVED] Archived ${result.resolved.length} expired observation(s) in this session's write scope.
28575
+ ${Math.max(0, managedIds.size - result.resolved.length)} visible writable observations remaining.` }]
28576
+ };
28577
+ }
27849
28578
  if (action === "stale") {
27850
28579
  const staleDocs = docs.filter((d) => getRetentionZone2(d) === "stale");
27851
28580
  if (staleDocs.length === 0) {
@@ -28166,7 +28895,10 @@ Archived memories are hidden from default search but can be found with status: "
28166
28895
  const allObs = await withFreshIndex(() => getAllObservations2());
28167
28896
  const scoped = scopeKnowledgeGraphToProject(
28168
28897
  graph,
28169
- allObs.filter((observation) => observation.projectId === project.id)
28898
+ filterReadableObservations(
28899
+ allObs.filter((observation) => observation.projectId === project.id),
28900
+ getObservationReader()
28901
+ )
28170
28902
  );
28171
28903
  return { entities: scoped.entities, relations: scoped.relations };
28172
28904
  }
@@ -28441,7 +29173,10 @@ ${skill2.content}` }]
28441
29173
  };
28442
29174
  }
28443
29175
  const { getObservationStore: getStore } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
28444
- const allObs = await getStore().loadAll();
29176
+ const allObs = filterReadableObservations(
29177
+ (await getStore().loadAll()).filter((observation) => observation.projectId === project.id),
29178
+ getObservationReader()
29179
+ );
28445
29180
  const obsData = allObs.map((o) => ({
28446
29181
  id: o.id || 0,
28447
29182
  entityName: o.entityName || "unknown",
@@ -28540,7 +29275,11 @@ ${skill2.content}` }]
28540
29275
  }
28541
29276
  const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28542
29277
  const allObs = await withFreshIndex(() => getAllObservations2());
28543
- const matched = allObs.filter((o) => observationIds.includes(o.id));
29278
+ const reader = getObservationReader();
29279
+ const matched = filterReadableObservations(
29280
+ allObs.filter((observation) => observation.projectId === project.id && observationIds.includes(observation.id)),
29281
+ reader
29282
+ );
28544
29283
  if (matched.length === 0) {
28545
29284
  return { content: [{ type: "text", text: `No observations found for IDs: [${observationIds.join(", ")}]. Use \`memorix_search\` to find valid IDs.` }], isError: true };
28546
29285
  }
@@ -28548,6 +29287,13 @@ ${skill2.content}` }]
28548
29287
  if (nonActive.length > 0) {
28549
29288
  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
29289
  }
29290
+ const nonProjectShared = matched.filter((observation) => resolveObservationVisibility(observation) !== "project");
29291
+ if (nonProjectShared.length > 0) {
29292
+ return {
29293
+ content: [{ type: "text", text: `Cannot promote private or team-scoped observations: ${nonProjectShared.map((observation) => `#${observation.id}`).join(", ")}. Promote only deliberate project-shared knowledge.` }],
29294
+ isError: true
29295
+ };
29296
+ }
28551
29297
  const skill2 = await promoteToMiniSkill2(projectDir2, project.id, matched, { trigger, instruction, tags });
28552
29298
  const lines = [
28553
29299
  `[OK] Created mini-skill #${skill2.id}`,
@@ -28675,7 +29421,6 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
28675
29421
  const unresolved = requireResolvedProject("start a project session");
28676
29422
  if (unresolved) return unresolved;
28677
29423
  const { startSession: startSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
28678
- const result = await startSession2(projectDir2, project.id, { sessionId, agent });
28679
29424
  const llmStatus = isLLMEnabled() ? `LLM enhanced mode: ${getLLMConfig()?.provider}/${getLLMConfig()?.model} (fact extraction + auto-dedup active)` : "LLM mode: off (set MEMORIX_LLM_API_KEY to enable enhanced memory quality)";
28680
29425
  const shouldJoinTeam = !!joinTeam;
28681
29426
  let registeredAgent = null;
@@ -28706,8 +29451,11 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
28706
29451
  const lastSeen = registeredAgent.last_seen_obs_generation;
28707
29452
  const store = getObservationStore();
28708
29453
  const currentGen = store.getGeneration();
28709
- const projectObs = await withFreshIndex(() => getAllObservations().filter(
28710
- (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
29454
+ const projectObs = await withFreshIndex(() => filterReadableObservations(
29455
+ getAllObservations().filter(
29456
+ (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
29457
+ ),
29458
+ getObservationReader()
28711
29459
  ));
28712
29460
  const wm = computeWatermark2(lastSeen, currentGen, projectObs.length);
28713
29461
  if (wm.newObservationCount > 0) {
@@ -28729,6 +29477,11 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
28729
29477
  }
28730
29478
  } catch {
28731
29479
  }
29480
+ const result = await startSession2(projectDir2, project.id, {
29481
+ sessionId,
29482
+ agent,
29483
+ reader: getObservationReader()
29484
+ });
28732
29485
  const lines = [
28733
29486
  `[OK] Session started: ${result.session.id}`,
28734
29487
  `Project: ${project.name} (${project.id})`,
@@ -28851,7 +29604,7 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
28851
29604
  async ({ limit }) => {
28852
29605
  const safeLimit = limit != null ? coerceNumber(limit, 3) : 3;
28853
29606
  const { getSessionContext: getSessionContext2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session(), session_exports));
28854
- const context = await getSessionContext2(projectDir2, project.id, safeLimit);
29607
+ const context = await getSessionContext2(projectDir2, project.id, safeLimit, getObservationReader());
28855
29608
  const sessions = await listSessions2(projectDir2, project.id);
28856
29609
  const activeSessions = sessions.filter((s) => s.status === "active");
28857
29610
  const completedSessions = sessions.filter((s) => s.status === "completed");
@@ -28876,7 +29629,7 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
28876
29629
  "memorix_transfer",
28877
29630
  {
28878
29631
  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).',
29632
+ 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
29633
  inputSchema: {
28881
29634
  action: z2.enum(["export", "import"]).describe("Operation: export or import"),
28882
29635
  format: z2.enum(["json", "markdown"]).optional().describe("Export format (for export, default: json)"),
@@ -28887,10 +29640,10 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
28887
29640
  if (action === "export") {
28888
29641
  const { exportAsJson: exportAsJson2, exportAsMarkdown: exportAsMarkdown2 } = await Promise.resolve().then(() => (init_export_import(), export_import_exports));
28889
29642
  if (format === "markdown") {
28890
- const md = await exportAsMarkdown2(projectDir2, project.id);
29643
+ const md = await exportAsMarkdown2(projectDir2, project.id, getObservationReader());
28891
29644
  return { content: [{ type: "text", text: md }] };
28892
29645
  }
28893
- const data = await exportAsJson2(projectDir2, project.id);
29646
+ const data = await exportAsJson2(projectDir2, project.id, getObservationReader());
28894
29647
  const json = JSON.stringify(data, null, 2);
28895
29648
  return {
28896
29649
  content: [{
@@ -29087,8 +29840,6 @@ ${json}
29087
29840
  }
29088
29841
  }
29089
29842
  );
29090
- let teamStore;
29091
- let initTeamStoreForProject;
29092
29843
  if (teamFeaturesEnabled) {
29093
29844
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
29094
29845
  initTeamStoreForProject = initTeamStore2;
@@ -29361,7 +30112,7 @@ ${lines.join("\n")}` }] };
29361
30112
  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
30113
  inputSchema: {
29363
30114
  action: z2.enum(["send", "broadcast", "inbox"]).describe("Operation to perform"),
29364
- from: z2.string().optional().describe("Sender agent ID (for send/broadcast)"),
30115
+ from: z2.string().optional().describe("Your sender agent ID. Omit it to use this session identity."),
29365
30116
  to: z2.string().optional().describe("Receiver agent ID (for send)"),
29366
30117
  type: z2.enum(["request", "response", "info", "announcement", "contract", "error", "handoff"]).optional().describe("Message type (for send/broadcast)"),
29367
30118
  content: z2.string().optional().describe("Message content (for send/broadcast)"),
@@ -29372,13 +30123,20 @@ ${lines.join("\n")}` }] };
29372
30123
  }
29373
30124
  },
29374
30125
  async ({ action, from, to, type: msgType, content, agentId, markRead, toRole, handoffStatus }) => {
30126
+ const requireCurrentTeamAgent = () => {
30127
+ if (!currentAgentId) return null;
30128
+ const agent = teamStore.getAgent(currentAgentId);
30129
+ return agent?.project_id === project.id && agent.status === "active" ? agent : null;
30130
+ };
29375
30131
  if (action === "send") {
29376
- if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for send" }], isError: true };
30132
+ const sender = requireCurrentTeamAgent();
30133
+ if (!sender || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] active session identity, type, and content required for send" }], isError: true };
30134
+ if (from && from !== currentAgentId) return { content: [{ type: "text", text: "[ERROR] from must match the current session identity" }], isError: true };
29377
30135
  if (!to && !toRole) return { content: [{ type: "text", text: "[ERROR] either to (agent ID) or toRole is required for send" }], isError: true };
29378
30136
  if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
29379
30137
  const msg = teamStore.sendMessage({
29380
30138
  projectId: project.id,
29381
- senderAgentId: from,
30139
+ senderAgentId: currentAgentId,
29382
30140
  recipientAgentId: to ?? null,
29383
30141
  type: msgType,
29384
30142
  content,
@@ -29390,11 +30148,13 @@ ${lines.join("\n")}` }] };
29390
30148
  return { content: [{ type: "text", text: `Message sent (${msgType}) to ${target} | ID: ${msg.id.slice(0, 8)}\u2026${toRole ? ` [role: ${toRole}]` : ""}` }] };
29391
30149
  }
29392
30150
  if (action === "broadcast") {
29393
- if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for broadcast" }], isError: true };
30151
+ const sender = requireCurrentTeamAgent();
30152
+ if (!sender || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] active session identity, type, and content required for broadcast" }], isError: true };
30153
+ if (from && from !== currentAgentId) return { content: [{ type: "text", text: "[ERROR] from must match the current session identity" }], isError: true };
29394
30154
  if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
29395
30155
  const msg = teamStore.sendMessage({
29396
30156
  projectId: project.id,
29397
- senderAgentId: from,
30157
+ senderAgentId: currentAgentId,
29398
30158
  recipientAgentId: null,
29399
30159
  type: msgType,
29400
30160
  content
@@ -29402,8 +30162,12 @@ ${lines.join("\n")}` }] };
29402
30162
  if ("error" in msg) return { content: [{ type: "text", text: `[ERROR] ${msg.error}` }], isError: true };
29403
30163
  return { content: [{ type: "text", text: `Broadcast (${msgType}) | ID: ${msg.id.slice(0, 8)}\u2026` }] };
29404
30164
  }
29405
- const inboxId = agentId || from || "";
29406
- if (!inboxId) return { content: [{ type: "text", text: "[ERROR] agentId required for inbox" }], isError: true };
30165
+ const inboxAgent = requireCurrentTeamAgent();
30166
+ if (!inboxAgent) return { content: [{ type: "text", text: "[ERROR] active session identity required for inbox" }], isError: true };
30167
+ if (agentId && agentId !== currentAgentId || from && from !== currentAgentId) {
30168
+ return { content: [{ type: "text", text: "[ERROR] inbox access is limited to the current session identity" }], isError: true };
30169
+ }
30170
+ const inboxId = currentAgentId;
29407
30171
  const inbox = teamStore.getInbox(project.id, inboxId);
29408
30172
  const unread = teamStore.getUnreadCount(project.id, inboxId);
29409
30173
  if (inbox.length === 0) return { content: [{ type: "text", text: "Inbox empty" }] };
@@ -29431,24 +30195,34 @@ ${lines.join("\n")}` }] };
29431
30195
  },
29432
30196
  async ({ agentId, markInboxRead }) => {
29433
30197
  const { computeWatermark: computeWatermark2, computePoll: computePoll2 } = await Promise.resolve().then(() => (init_poll(), poll_exports));
30198
+ if (agentId && agentId !== currentAgentId) {
30199
+ return {
30200
+ content: [{ type: "text", text: "[ERROR] agentId must match the current session identity." }],
30201
+ isError: true
30202
+ };
30203
+ }
30204
+ const effectiveAgentId = currentAgentId;
29434
30205
  let watermark = computeWatermark2(0, 0, 0);
29435
- if (agentId) {
29436
- const agent = teamStore.getAgent(agentId);
30206
+ if (effectiveAgentId) {
30207
+ const agent = teamStore.getAgent(effectiveAgentId);
29437
30208
  if (agent) {
29438
30209
  const lastSeen = agent.last_seen_obs_generation;
29439
30210
  const store = getObservationStore();
29440
30211
  const currentGen = store.getGeneration();
29441
- const projectObs = await withFreshIndex(() => getAllObservations().filter(
29442
- (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
30212
+ const projectObs = await withFreshIndex(() => filterReadableObservations(
30213
+ getAllObservations().filter(
30214
+ (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
30215
+ ),
30216
+ getObservationReader()
29443
30217
  ));
29444
30218
  watermark = computeWatermark2(lastSeen, currentGen, projectObs.length);
29445
- teamStore.updateWatermark(agentId, currentGen);
29446
- teamStore.heartbeat(agentId);
30219
+ teamStore.updateWatermark(effectiveAgentId, currentGen);
30220
+ teamStore.heartbeat(effectiveAgentId);
29447
30221
  }
29448
30222
  }
29449
- const poll = computePoll2(teamStore, project.id, agentId ?? null, watermark);
29450
- if (markInboxRead && agentId) {
29451
- teamStore.markAllRead(project.id, agentId);
30223
+ const poll = computePoll2(teamStore, project.id, effectiveAgentId ?? null, watermark);
30224
+ if (markInboxRead && effectiveAgentId) {
30225
+ teamStore.markAllRead(project.id, effectiveAgentId);
29452
30226
  }
29453
30227
  const lines = [];
29454
30228
  if (poll.agent) {
@@ -29508,7 +30282,7 @@ ${lines.join("\n")}` }] };
29508
30282
  "memorix_handoff",
29509
30283
  {
29510
30284
  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.",
30285
+ 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
30286
  inputSchema: {
29513
30287
  fromAgentId: z2.string().describe("Your agent ID (from team_manage join or session_start with joinTeam=true)"),
29514
30288
  summary: z2.string().describe("Human-readable summary of what you did and what needs to happen next"),
@@ -29521,6 +30295,34 @@ ${lines.join("\n")}` }] };
29521
30295
  },
29522
30296
  async ({ fromAgentId, summary, context, toAgentId, taskId, filesModified, concepts }) => {
29523
30297
  const { createHandoffArtifact: createHandoffArtifact2 } = await Promise.resolve().then(() => (init_handoff(), handoff_exports));
30298
+ if (!currentAgentId) {
30299
+ return {
30300
+ content: [{ type: "text", text: "Create a coordination identity first: call memorix_session_start with joinTeam=true." }],
30301
+ isError: true
30302
+ };
30303
+ }
30304
+ if (fromAgentId !== currentAgentId) {
30305
+ return {
30306
+ content: [{ type: "text", text: "fromAgentId must match the identity returned for this session. Memorix will not create a handoff on behalf of another agent." }],
30307
+ isError: true
30308
+ };
30309
+ }
30310
+ const sender = teamStore.getAgent(currentAgentId);
30311
+ if (!sender || sender.project_id !== project.id || sender.status !== "active") {
30312
+ return {
30313
+ content: [{ type: "text", text: "The current coordination identity is not an active member of this project." }],
30314
+ isError: true
30315
+ };
30316
+ }
30317
+ if (toAgentId) {
30318
+ const recipient = teamStore.getAgent(toAgentId);
30319
+ if (!recipient || recipient.project_id !== project.id) {
30320
+ return {
30321
+ content: [{ type: "text", text: "The handoff recipient must be an agent registered in the current project." }],
30322
+ isError: true
30323
+ };
30324
+ }
30325
+ }
29524
30326
  const result = await createHandoffArtifact2(
29525
30327
  {
29526
30328
  projectId: project.id,
@@ -29760,6 +30562,8 @@ var MemoryClient = class {
29760
30562
  _projectId;
29761
30563
  _projectRoot;
29762
30564
  _dataDir;
30565
+ /** SDK calls are unbound by default, so they see only project-shared records. */
30566
+ _reader;
29763
30567
  _closed = false;
29764
30568
  // Internal module references — loaded lazily to avoid top-level side effects
29765
30569
  _observations;
@@ -29771,6 +30575,7 @@ var MemoryClient = class {
29771
30575
  this._projectId = projectId;
29772
30576
  this._projectRoot = projectRoot;
29773
30577
  this._dataDir = dataDir;
30578
+ this._reader = { projectId };
29774
30579
  }
29775
30580
  /** The canonical project ID (derived from Git remote or local path) */
29776
30581
  get projectId() {
@@ -29829,7 +30634,8 @@ var MemoryClient = class {
29829
30634
  this._ensureOpen();
29830
30635
  return this._observations.storeObservation({
29831
30636
  ...input,
29832
- projectId: this._projectId
30637
+ projectId: this._projectId,
30638
+ visibilityReader: this._reader
29833
30639
  });
29834
30640
  }
29835
30641
  /**
@@ -29853,7 +30659,8 @@ var MemoryClient = class {
29853
30659
  limit: options.limit ?? 20,
29854
30660
  type: options.type,
29855
30661
  source: options.source,
29856
- status: options.status === "all" ? void 0 : options.status ?? "active"
30662
+ status: options.status === "all" ? void 0 : options.status ?? "active",
30663
+ reader: this._reader
29857
30664
  };
29858
30665
  return this._oramaStore.searchObservations(searchOpts);
29859
30666
  }
@@ -29864,7 +30671,8 @@ var MemoryClient = class {
29864
30671
  this._ensureOpen();
29865
30672
  await this._freshness.withFreshIndex(() => {
29866
30673
  });
29867
- return this._observations.getObservation(id, this._projectId);
30674
+ const observation = this._observations.getObservation(id, this._projectId);
30675
+ return observation && canReadObservation(observation, this._reader) ? observation : void 0;
29868
30676
  }
29869
30677
  /**
29870
30678
  * Get all observations for this project.
@@ -29873,7 +30681,10 @@ var MemoryClient = class {
29873
30681
  this._ensureOpen();
29874
30682
  await this._freshness.withFreshIndex(() => {
29875
30683
  });
29876
- return this._observations.getProjectObservations(this._projectId);
30684
+ return filterReadableObservations(
30685
+ this._observations.getProjectObservations(this._projectId),
30686
+ this._reader
30687
+ );
29877
30688
  }
29878
30689
  /**
29879
30690
  * Get the total observation count for this project.
@@ -29882,7 +30693,10 @@ var MemoryClient = class {
29882
30693
  this._ensureOpen();
29883
30694
  await this._freshness.withFreshIndex(() => {
29884
30695
  });
29885
- return this._observations.getProjectObservations(this._projectId).length;
30696
+ return filterReadableObservations(
30697
+ this._observations.getProjectObservations(this._projectId),
30698
+ this._reader
30699
+ ).length;
29886
30700
  }
29887
30701
  /**
29888
30702
  * Mark observations as resolved or archived.
@@ -29898,7 +30712,16 @@ var MemoryClient = class {
29898
30712
  */
29899
30713
  async resolve(ids, status = "resolved") {
29900
30714
  this._ensureOpen();
29901
- return this._observations.resolveObservations(ids, status);
30715
+ const manageableIds = ids.filter((id) => {
30716
+ const observation = this._observations.getObservation(id, this._projectId);
30717
+ return observation && canManageObservation(observation, this._reader);
30718
+ });
30719
+ const deniedIds = ids.filter((id) => !manageableIds.includes(id));
30720
+ const result = await this._observations.resolveObservations(manageableIds, status);
30721
+ return {
30722
+ resolved: result.resolved,
30723
+ notFound: [.../* @__PURE__ */ new Set([...result.notFound, ...deniedIds])]
30724
+ };
29902
30725
  }
29903
30726
  /**
29904
30727
  * Release resources (close SQLite handle, reset index).