sentinelayer-cli 0.39.2 → 0.41.0

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 (62) hide show
  1. package/README.md +4 -4
  2. package/package.json +2 -3
  3. package/src/agents/ai-governance/tools/base.js +19 -166
  4. package/src/agents/backend/tools/base.js +33 -186
  5. package/src/agents/code-quality/tools/base.js +27 -154
  6. package/src/agents/data-layer/tools/base.js +35 -180
  7. package/src/agents/documentation/tools/base.js +18 -146
  8. package/src/agents/infrastructure/tools/base.js +17 -164
  9. package/src/agents/observability/tools/base.js +35 -180
  10. package/src/agents/performance/tools/base.js +35 -205
  11. package/src/agents/release/tools/base.js +35 -180
  12. package/src/agents/reliability/tools/base.js +35 -180
  13. package/src/agents/security/tools/base.js +33 -187
  14. package/src/agents/shared-tools/domain-base.js +246 -0
  15. package/src/agents/supply-chain/tools/base.js +15 -144
  16. package/src/agents/testing/tools/base.js +33 -184
  17. package/src/cli.js +5 -0
  18. package/src/commands/mcp.js +1 -1
  19. package/src/commands/ring-owner.js +58 -0
  20. package/src/commands/session.js +703 -263
  21. package/src/engram/governance.js +67 -0
  22. package/src/engram/index.js +64 -0
  23. package/src/engram/namespace.js +89 -0
  24. package/src/engram/observations.js +34 -0
  25. package/src/engram/sla.js +104 -0
  26. package/src/engram/store.js +115 -0
  27. package/src/engram/tools.js +146 -0
  28. package/src/engram/trust.js +105 -0
  29. package/src/legacy-cli.js +6 -3
  30. package/src/mcp/registry.js +27 -10
  31. package/src/mcp/session-stdio-server.js +209 -18
  32. package/src/mcp/smoke.js +9 -8
  33. package/src/pocket/ring-owner.js +97 -0
  34. package/src/review/local-review.js +9 -5
  35. package/src/review/omargate-orchestrator.js +111 -14
  36. package/src/review/spec-binding.js +16 -1
  37. package/src/scan/generator.js +1 -1
  38. package/src/session/agent-registry.js +22 -63
  39. package/src/session/checkpoints.js +194 -0
  40. package/src/session/coordination-guidance.js +6 -6
  41. package/src/session/daemon.js +19 -124
  42. package/src/session/file-lease-integrations.js +1300 -0
  43. package/src/session/file-locks.js +967 -464
  44. package/src/session/listener.js +243 -20
  45. package/src/session/listeners.js +71 -80
  46. package/src/session/paths.js +2 -0
  47. package/src/session/recall/embedder.js +181 -0
  48. package/src/session/recall/entities.js +0 -0
  49. package/src/session/recall/index-build.js +38 -0
  50. package/src/session/recall/index-core.js +108 -0
  51. package/src/session/recall/index.js +131 -0
  52. package/src/session/recall/needle-eval.js +279 -0
  53. package/src/session/recall/observation-core.js +183 -0
  54. package/src/session/recall/observations.js +21 -0
  55. package/src/session/recall/provenance.js +153 -0
  56. package/src/session/recall/retrieve.js +374 -0
  57. package/src/session/recall/text.js +0 -0
  58. package/src/session/recall/token-stats.js +81 -0
  59. package/src/session/recap.js +29 -23
  60. package/src/session/remote-hydrate.js +5 -1
  61. package/src/session/sync.js +661 -11
  62. package/src/session/wake/pump.js +8 -3
@@ -0,0 +1,105 @@
1
+ /**
2
+ * ENGRAM §2 — the trust seal on write.
3
+ *
4
+ * Vocabulary is DELIBERATELY aligned with the server-side `ServiceTrustLevel`
5
+ * subsystem (sentinelayer-api `src/models/service_trust.py`: L0_connected ->
6
+ * L5_remediation_ready) so §2.1 converges onto that existing certification
7
+ * system instead of forking a divergent trust model. P0 stays CLI-local and
8
+ * does NOT touch the server subsystem — it only borrows the vocabulary.
9
+ *
10
+ * Rule (from the convergence + guest-token model): only VERIFIED writes are
11
+ * AUTHORITATIVE; untrusted/guest/unverified writes are retrievable-but-marked,
12
+ * never authoritative, never a governed-action trigger. `sealed`/`revoked`
13
+ * memories are never surfaced to an unauthorized scope (the §10 analog).
14
+ *
15
+ * Real verification (AIdenID provider-signature / delegation-proof) is
16
+ * external (§2.1); P0 classifies from local identity signals.
17
+ */
18
+
19
+ export const TRUST_LEVELS = Object.freeze([
20
+ "L0_connected",
21
+ "L1_audited",
22
+ "L2_memory_certified",
23
+ "L3_watch_enabled",
24
+ "L4_governance_grade",
25
+ "L5_remediation_ready",
26
+ ]);
27
+
28
+ const RANK = new Map(TRUST_LEVELS.map((level, index) => [level, index]));
29
+
30
+ /** Writes at or above this level are AUTHORITATIVE. */
31
+ export const AUTHORITATIVE_FLOOR = "L2_memory_certified";
32
+
33
+ export function trustRank(level) {
34
+ return RANK.has(level) ? RANK.get(level) : 0;
35
+ }
36
+
37
+ export function isAuthoritative(level) {
38
+ return trustRank(level) >= trustRank(AUTHORITATIVE_FLOOR);
39
+ }
40
+
41
+ /**
42
+ * Classify a write's trust level from the caller. AIdenID verification is
43
+ * external (§2.1); until then a caller is authoritative only if it presents a
44
+ * verified identity.
45
+ * @param {object} [caller] { id, kind?, verified?, trustLevel? }
46
+ * @returns {string} a TRUST_LEVELS value
47
+ */
48
+ export function classifyWrite(caller = {}) {
49
+ if (!caller || !caller.id || caller.kind === "guest") return "L0_connected";
50
+ if (caller.verified === true) {
51
+ return caller.trustLevel && RANK.has(caller.trustLevel) ? caller.trustLevel : "L2_memory_certified";
52
+ }
53
+ return "L1_audited"; // known but unverified -> retrievable, NOT authoritative
54
+ }
55
+
56
+ /**
57
+ * Classify a source-adapter observation (e.g. a session event) from its
58
+ * author when the item carried no explicit trust.
59
+ * @param {object} obs
60
+ * @returns {string}
61
+ */
62
+ export function classifyObservation(obs = {}) {
63
+ const agentId = String(obs.agentId || "").toLowerCase();
64
+ if (!agentId || agentId === "unknown" || agentId === "cli-user" || agentId.startsWith("guest") || agentId.startsWith("agent-")) {
65
+ return "L0_connected";
66
+ }
67
+ return "L1_audited";
68
+ }
69
+
70
+ /**
71
+ * Attach the trust seal to an item at write time.
72
+ * @param {object} item
73
+ * @param {object} ctx { caller }
74
+ * @returns {object} item with { trust, sealed, revoked }
75
+ */
76
+ export function sealItem(item, { caller } = {}) {
77
+ // Trust is DERIVED from the caller's identity, never self-asserted. A caller
78
+ // MAY downgrade their own write (mark it less trusted); they may NEVER upgrade
79
+ // above their identity-derived ceiling — otherwise an unverified/guest caller
80
+ // could forge an AUTHORITATIVE memory by supplying `trust` in the payload,
81
+ // defeating the "unverified -> never authoritative" invariant above.
82
+ const derived = classifyWrite(caller);
83
+ const asserted = item?.trust && RANK.has(item.trust) ? item.trust : derived;
84
+ const trust = trustRank(asserted) <= trustRank(derived) ? asserted : derived;
85
+ return {
86
+ ...item,
87
+ trust,
88
+ sealed: item?.sealed === true,
89
+ revoked: item?.revoked === true,
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Recall visibility gate: revoked memory NEVER surfaces; sealed memory
95
+ * surfaces only to an authorized scope. Untrusted memory DOES surface (marked
96
+ * non-authoritative by the caller).
97
+ * @param {object} obs
98
+ * @param {object} [ctx] { authorized }
99
+ * @returns {boolean}
100
+ */
101
+ export function visibleUnderScope(obs, { authorized = false } = {}) {
102
+ if (obs?.revoked === true) return false;
103
+ if (obs?.sealed === true && !authorized) return false;
104
+ return true;
105
+ }
package/src/legacy-cli.js CHANGED
@@ -216,7 +216,10 @@ function printUsage() {
216
216
  console.log(" sl session templates --json List available session quick-start templates");
217
217
  console.log(" sl session join <id> --name <n> Join a session as an agent");
218
218
  console.log(" sl session say <id> \"msg\" --json Append a message event to session stream");
219
- console.log(" sl session say <id> \"lock: <file> - <intent>\" Request an exclusive file lock via Senti");
219
+ console.log(" sl session lock <id> <file> --agent <agent> --intent <text> Claim an API-backed file lease outside chat");
220
+ console.log(" sl session guard-install <id> --agent <agent> Install terminal/editor lease preflights");
221
+ console.log(" sl session guard-uninstall <id> --agent <agent> Remove managed preflights before CLI rollback");
222
+ console.log(" sl session unlock <id> <file> --agent <agent> Release an API-backed file lease");
220
223
  console.log(" sl session say <id> \"assign: @agent <task>\" Create task assignment + lease");
221
224
  console.log(" sl session say <id> \"assign: @*:reviewer <task>\" Wildcard route to least-busy role");
222
225
  console.log(" sl session say <id> \"accepted: task <task-id>\" / \"done: task <task-id>\" Task transitions");
@@ -2517,7 +2520,7 @@ jobs:
2517
2520
  fi
2518
2521
  - name: Run Omar Gate
2519
2522
  id: omar
2520
- uses: mrrCarter/sentinelayer-v1-action@a496be33a466c0cc3f8616d66bbd7d78f7d3c31d
2523
+ uses: mrrCarter/sentinelayer-v1-action@8afb7dbb42a5e1c25233d422c4cabe401ba02380
2521
2524
  with:
2522
2525
  github_token: \${{ github.token }}
2523
2526
  openai_api_key: \${{ secrets.OPENAI_API_KEY }}
@@ -2626,7 +2629,7 @@ jobs:
2626
2629
  },
2627
2630
  "scan": {
2628
2631
  "mode": env("OMAR_SCAN_MODE", "deep"),
2629
- "action_ref": "mrrCarter/sentinelayer-v1-action@a496be33a466c0cc3f8616d66bbd7d78f7d3c31d",
2632
+ "action_ref": "mrrCarter/sentinelayer-v1-action@8afb7dbb42a5e1c25233d422c4cabe401ba02380",
2630
2633
  "llm_provider": env("OMAR_LLM_PROVIDER", "openai"),
2631
2634
  "model": env("OMAR_MODEL", "gpt-5.3-codex"),
2632
2635
  "model_fallback": env("OMAR_MODEL_FALLBACK", "gpt-4.1-mini"),
@@ -525,7 +525,7 @@ export function buildSentinelayerSessionRegistryTemplate({ generatedAt = new Dat
525
525
  name: "poll_inbox",
526
526
  title: "Poll Senti Inbox",
527
527
  description:
528
- "Poll durable SentinelLayer session events after an optional cursor and return events visible to one agent.",
528
+ "Poll durable SentinelLayer session events after an optional cursor, return events visible to one agent, and advance one monotonic read cursor.",
529
529
  input_schema: {
530
530
  type: "object",
531
531
  additionalProperties: false,
@@ -567,13 +567,14 @@ export function buildSentinelayerSessionRegistryTemplate({ generatedAt = new Dat
567
567
  name: "read_history",
568
568
  title: "Read Senti History",
569
569
  description:
570
- "Hydrate a bounded recent, older, or after-cursor SentinelLayer session transcript window without recipient filtering.",
570
+ "Hydrate a bounded recent, older, or after-cursor SentinelLayer session transcript window and advance one monotonic read cursor without appending view events.",
571
571
  input_schema: {
572
572
  type: "object",
573
573
  additionalProperties: false,
574
574
  required: ["sessionId"],
575
575
  properties: {
576
576
  sessionId: { type: "string" },
577
+ agentId: { type: "string" },
577
578
  cursor: { type: "string" },
578
579
  beforeSequence: { type: "integer", minimum: 1 },
579
580
  limit: { type: "integer", minimum: 1, maximum: 200 },
@@ -654,7 +655,7 @@ export function buildSentinelayerSessionRegistryTemplate({ generatedAt = new Dat
654
655
  name: "session_action",
655
656
  title: "Record Senti Session Action",
656
657
  description:
657
- "Record a low-noise message action such as ack, working_on, disregard, view, like, dislike, or reply against a target session event.",
658
+ "Record a low-noise message action such as ack, working_on, disregard, like, dislike, or reply; view advances the monotonic read cursor instead of appending an action.",
658
659
  input_schema: {
659
660
  type: "object",
660
661
  additionalProperties: false,
@@ -787,7 +788,7 @@ export function buildSentinelayerSessionRegistryTemplate({ generatedAt = new Dat
787
788
  name: "session_lock",
788
789
  title: "Lock Senti Files",
789
790
  description:
790
- "Claim session-scoped file locks before editing files, using the same fail-closed lock registry as the CLI.",
791
+ "Claim session-scoped file leases through the authoritative SentinelLayer API before editing. Lease lifecycle operations never create session events.",
791
792
  input_schema: {
792
793
  type: "object",
793
794
  additionalProperties: false,
@@ -803,8 +804,16 @@ export function buildSentinelayerSessionRegistryTemplate({ generatedAt = new Dat
803
804
  },
804
805
  intent: { type: "string" },
805
806
  ttlSeconds: { type: "integer", minimum: 1 },
806
- syncRemote: { type: "boolean" },
807
- awaitRemoteSync: { type: "boolean" },
807
+ syncRemote: {
808
+ type: "boolean",
809
+ description:
810
+ "Deprecated compatibility input; ignored because the authenticated API is always authoritative.",
811
+ },
812
+ awaitRemoteSync: {
813
+ type: "boolean",
814
+ description:
815
+ "Deprecated compatibility input; ignored because the authenticated API is always authoritative.",
816
+ },
808
817
  },
809
818
  },
810
819
  transport: {
@@ -833,7 +842,7 @@ export function buildSentinelayerSessionRegistryTemplate({ generatedAt = new Dat
833
842
  name: "session_unlock",
834
843
  title: "Unlock Senti Files",
835
844
  description:
836
- "Release session-scoped file locks held by an agent.",
845
+ "Release authoritative session-scoped file leases held by an agent without writing to the session transcript.",
837
846
  input_schema: {
838
847
  type: "object",
839
848
  additionalProperties: false,
@@ -849,8 +858,16 @@ export function buildSentinelayerSessionRegistryTemplate({ generatedAt = new Dat
849
858
  },
850
859
  reason: { type: "string" },
851
860
  force: { type: "boolean" },
852
- syncRemote: { type: "boolean" },
853
- awaitRemoteSync: { type: "boolean" },
861
+ syncRemote: {
862
+ type: "boolean",
863
+ description:
864
+ "Deprecated compatibility input; ignored because the authenticated API is always authoritative.",
865
+ },
866
+ awaitRemoteSync: {
867
+ type: "boolean",
868
+ description:
869
+ "Deprecated compatibility input; ignored because the authenticated API is always authoritative.",
870
+ },
854
871
  },
855
872
  },
856
873
  transport: {
@@ -879,7 +896,7 @@ export function buildSentinelayerSessionRegistryTemplate({ generatedAt = new Dat
879
896
  name: "session_locks",
880
897
  title: "List Senti File Locks",
881
898
  description:
882
- "List active file locks for a session.",
899
+ "List active authoritative file leases for a session.",
883
900
  input_schema: {
884
901
  type: "object",
885
902
  additionalProperties: false,
@@ -7,16 +7,19 @@ import {
7
7
  buildCliCommandMcpTools,
8
8
  createCliCommandMcpToolHandlers,
9
9
  } from "./cli-command-tools.js";
10
- import { appendToStream } from "../session/stream.js";
10
+ import { appendToStream, readStream } from "../session/stream.js";
11
11
  import { listFileLocks, lockFile, unlockFile } from "../session/file-locks.js";
12
12
  import { eventMatchesAgent } from "../session/listener.js";
13
13
  import { isSessionControlEvent } from "../session/control-events.js";
14
+ import { buildObservations } from "../session/recall/observations.js";
15
+ import { createMemoryService } from "../engram/index.js";
14
16
  import {
15
17
  createSessionMessageAction,
16
18
  listSessionMessageActions,
17
19
  pollSessionEvents,
18
20
  pollSessionEventsBefore,
19
21
  syncSessionEventToApi,
22
+ updateSessionReadCursor,
20
23
  } from "../session/sync.js";
21
24
 
22
25
  export const SESSION_MCP_SERVER_NAME = "sentinelayer-session-mcp";
@@ -185,6 +188,27 @@ function summarizeSessionEvent(event = {}) {
185
188
  });
186
189
  }
187
190
 
191
+ function latestReadCursorTarget(events = [], fallbackCursor = "") {
192
+ let targetSequenceId = null;
193
+ let targetCursor = normalizeString(fallbackCursor);
194
+ for (const event of Array.isArray(events) ? events : []) {
195
+ const sequenceId = normalizePositiveInteger(
196
+ event?.sequenceId ?? event?.sequence_id,
197
+ null,
198
+ );
199
+ if (
200
+ sequenceId &&
201
+ (targetSequenceId === null || sequenceId >= targetSequenceId)
202
+ ) {
203
+ targetSequenceId = sequenceId;
204
+ targetCursor = normalizeString(event?.cursor) || targetCursor;
205
+ }
206
+ }
207
+ return targetSequenceId || targetCursor
208
+ ? { targetSequenceId, targetCursor }
209
+ : null;
210
+ }
211
+
188
212
  function sessionEventMatchesClientMessageId(event, clientMessageId) {
189
213
  const normalizedClientMessageId = normalizeString(clientMessageId);
190
214
  if (!normalizedClientMessageId || !isPlainObject(event)) {
@@ -368,7 +392,9 @@ async function confirmSessionEventVisible(
368
392
  }
369
393
  lastReason = "not_visible";
370
394
  const nextCursor = normalizeString(result.cursor);
371
- if (!events.length || !nextCursor || nextCursor === pageCursor) {
395
+ const scanExhausted = result.scanExhausted ?? result.scan_exhausted;
396
+ const emptyScanCanContinue = !events.length && scanExhausted === false;
397
+ if (!nextCursor || nextCursor === pageCursor || (!events.length && !emptyScanCanContinue)) {
372
398
  break;
373
399
  }
374
400
  pageCursor = nextCursor;
@@ -514,7 +540,9 @@ async function readSessionHistoryWindow({
514
540
  events.push(...materialEvents);
515
541
  const nextCursor = normalizeString(result.cursor) || pageCursor;
516
542
  responseCursor = nextCursor || responseCursor;
517
- if (!rawEvents.length || !nextCursor || nextCursor === pageCursor) break;
543
+ const scanExhausted = result.scanExhausted ?? result.scan_exhausted;
544
+ const emptyScanCanContinue = !rawEvents.length && scanExhausted === false;
545
+ if (!nextCursor || nextCursor === pageCursor || (!rawEvents.length && !emptyScanCanContinue)) break;
518
546
  pageCursor = nextCursor;
519
547
  continue;
520
548
  }
@@ -836,6 +864,7 @@ export function createSessionMcpToolHandlers({
836
864
  pollSessionEventsBeforeFn = pollSessionEventsBefore,
837
865
  listSessionMessageActionsFn = listSessionMessageActions,
838
866
  createSessionMessageActionFn = createSessionMessageAction,
867
+ updateSessionReadCursorFn = updateSessionReadCursor,
839
868
  lockFileFn = lockFile,
840
869
  unlockFileFn = unlockFile,
841
870
  listFileLocksFn = listFileLocks,
@@ -845,6 +874,29 @@ export function createSessionMcpToolHandlers({
845
874
  uuidFn = randomUUID,
846
875
  now = () => new Date().toISOString(),
847
876
  } = {}) {
877
+ // ENGRAM §2 (Memory-as-a-Service): session is namespace #1. The SL-specific
878
+ // session adapter maps a session's events to the generic observation model,
879
+ // which keeps the detachable engram core (src/engram/*) free of any
880
+ // session-runtime import. Generic namespaces (project:/org:/ns:) prove the
881
+ // engine is sellable on its own.
882
+ const memoryService = createMemoryService({
883
+ storeRoot: targetPath,
884
+ adapters: {
885
+ session: async (sessionId) => {
886
+ const events = await readStream(sessionId, { targetPath, tail: 0 }).catch(() => []);
887
+ return buildObservations(events, { sessionId }).observations;
888
+ },
889
+ },
890
+ });
891
+ const memoryCaller = (input = {}) => {
892
+ const agentId = normalizeString(input.agentId || input.agent_id) || "cli-user";
893
+ return {
894
+ id: agentId,
895
+ kind: agentId.startsWith("guest") ? "guest" : "agent",
896
+ // AIdenID verification is external (§2.1); P0 writes are non-authoritative.
897
+ verified: false,
898
+ };
899
+ };
848
900
  return {
849
901
  async poll_inbox(input = {}) {
850
902
  const sessionId = requireSessionId(input);
@@ -891,6 +943,14 @@ export function createSessionMcpToolHandlers({
891
943
  const recentHumanActivity = actionResult?.ok
892
944
  ? recentHumanActivityFromActions(actionResult, { limit: actionLimit })
893
945
  : [];
946
+ const readTarget = latestReadCursorTarget(result.events, result.cursor);
947
+ const readCursor = readTarget
948
+ ? await updateSessionReadCursorFn(sessionId, {
949
+ targetPath,
950
+ ...readTarget,
951
+ agentId,
952
+ })
953
+ : null;
894
954
 
895
955
  return {
896
956
  ok: true,
@@ -902,6 +962,7 @@ export function createSessionMcpToolHandlers({
902
962
  inboxCount: events.length,
903
963
  recentHumanActivityCount: recentHumanActivity.length,
904
964
  recentHumanActivity,
965
+ readCursor,
905
966
  actionProjection: actionResult
906
967
  ? {
907
968
  ok: Boolean(actionResult.ok),
@@ -966,6 +1027,21 @@ export function createSessionMcpToolHandlers({
966
1027
  const recentHumanActivity = actionResult?.ok
967
1028
  ? recentHumanActivityFromActions(actionResult, { limit: actionLimit })
968
1029
  : [];
1030
+ const readAgentId = normalizeAgentId(
1031
+ input.agentId ||
1032
+ input.agent_id ||
1033
+ input.agent ||
1034
+ process.env.SENTINELAYER_AGENT_ID,
1035
+ "",
1036
+ );
1037
+ const readTarget = latestReadCursorTarget(events, result.cursor);
1038
+ const readCursor = readTarget
1039
+ ? await updateSessionReadCursorFn(sessionId, {
1040
+ targetPath,
1041
+ ...readTarget,
1042
+ agentId: readAgentId,
1043
+ })
1044
+ : null;
969
1045
 
970
1046
  return {
971
1047
  ok: true,
@@ -979,6 +1055,7 @@ export function createSessionMcpToolHandlers({
979
1055
  pageCount: normalizePositiveInteger(result.pages, 0),
980
1056
  recentHumanActivityCount: recentHumanActivity.length,
981
1057
  recentHumanActivity,
1058
+ readCursor,
982
1059
  actionProjection: actionResult
983
1060
  ? {
984
1061
  ok: Boolean(actionResult.ok),
@@ -1061,8 +1138,6 @@ export function createSessionMcpToolHandlers({
1061
1138
  intent,
1062
1139
  ttlSeconds,
1063
1140
  targetPath,
1064
- syncRemote: input.syncRemote !== false && input.sync_remote !== false,
1065
- awaitRemoteSync: input.awaitRemoteSync !== false && input.await_remote_sync !== false,
1066
1141
  });
1067
1142
  results.push({
1068
1143
  file: normalizeString(result?.file) || file,
@@ -1094,10 +1169,7 @@ export function createSessionMcpToolHandlers({
1094
1169
  for (const file of files) {
1095
1170
  const result = await unlockFileFn(sessionId, agentId, file, {
1096
1171
  reason,
1097
- force: false,
1098
1172
  targetPath,
1099
- syncRemote: input.syncRemote !== false && input.sync_remote !== false,
1100
- awaitRemoteSync: input.awaitRemoteSync !== false && input.await_remote_sync !== false,
1101
1173
  });
1102
1174
  results.push({
1103
1175
  file: normalizeString(result?.file) || file,
@@ -1170,15 +1242,113 @@ export function createSessionMcpToolHandlers({
1170
1242
  sleepFn,
1171
1243
  });
1172
1244
  },
1245
+ async "memory.write"(input = {}) {
1246
+ return memoryService.tools.write({
1247
+ scope: input.scope,
1248
+ items: input.items,
1249
+ caller: memoryCaller(input),
1250
+ });
1251
+ },
1252
+ async "memory.recall"(input = {}) {
1253
+ return memoryService.tools.recall({
1254
+ scope: input.scope,
1255
+ query: input.query,
1256
+ k: input.k,
1257
+ role: input.role,
1258
+ caller: memoryCaller(input),
1259
+ });
1260
+ },
1261
+ async "memory.summarize"(input = {}) {
1262
+ return memoryService.tools.summarize({
1263
+ scope: input.scope,
1264
+ focus: input.focus,
1265
+ k: input.k,
1266
+ caller: memoryCaller(input),
1267
+ });
1268
+ },
1173
1269
  };
1174
1270
  }
1175
1271
 
1176
1272
  export const SESSION_MCP_TOOLS = Object.freeze([
1273
+ {
1274
+ name: "memory.write",
1275
+ title: "ENGRAM memory.write",
1276
+ description:
1277
+ "Remember facts/decisions/docs/snippets into a scoped namespace. Idempotent (content-hash dedup); every write is trust-sealed (verified -> authoritative; unverified/guest -> retrievable-but-marked, never authoritative).",
1278
+ inputSchema: {
1279
+ type: "object",
1280
+ additionalProperties: false,
1281
+ required: ["scope", "items"],
1282
+ properties: {
1283
+ scope: {
1284
+ type: "string",
1285
+ minLength: 1,
1286
+ description: "Namespace scope: session:<id> | project:<name> | org:<name> | agent:<id> | ns:<name>.",
1287
+ },
1288
+ items: {
1289
+ type: "array",
1290
+ minItems: 1,
1291
+ maxItems: MAX_TOOL_LIMIT,
1292
+ items: {
1293
+ type: "object",
1294
+ required: ["text"],
1295
+ properties: {
1296
+ text: { type: "string", minLength: 1, maxLength: MAX_MESSAGE_CHARS },
1297
+ kind: { type: "string" },
1298
+ topics: { type: "array", items: { type: "string" } },
1299
+ files: { type: "array", items: { type: "string" } },
1300
+ mentions: { type: "array", items: { type: "string" } },
1301
+ sealed: { type: "boolean" },
1302
+ },
1303
+ },
1304
+ },
1305
+ agentId: { type: "string" },
1306
+ },
1307
+ },
1308
+ security: { scopes: ["memory:write"], requires_human_approval: false, kill_switch: "enabled" },
1309
+ },
1310
+ {
1311
+ name: "memory.recall",
1312
+ title: "ENGRAM memory.recall",
1313
+ description:
1314
+ "Recall the top-K relevant memories from a scoped namespace (dense int8 exact-scan + BM25 + RRF + bounded graph diffusion + ACT-R), each with a one-line provenance path. Deterministic + auditable.",
1315
+ inputSchema: {
1316
+ type: "object",
1317
+ additionalProperties: false,
1318
+ required: ["scope", "query"],
1319
+ properties: {
1320
+ scope: { type: "string", minLength: 1 },
1321
+ query: { type: "string", minLength: 2 },
1322
+ k: { type: "integer", minimum: 1, maximum: 50, default: 12 },
1323
+ role: { type: "string" },
1324
+ agentId: { type: "string" },
1325
+ },
1326
+ },
1327
+ security: { scopes: ["memory:read"], durable_receipt_required: true, requires_human_approval: false, kill_switch: "enabled" },
1328
+ },
1329
+ {
1330
+ name: "memory.summarize",
1331
+ title: "ENGRAM memory.summarize",
1332
+ description:
1333
+ "Summarize a namespace focused on a topic: recall DETERMINISTICALLY selects the relevant subset, then a renderer GENERATES prose over ONLY that subset (never a model in selection).",
1334
+ inputSchema: {
1335
+ type: "object",
1336
+ additionalProperties: false,
1337
+ required: ["scope", "focus"],
1338
+ properties: {
1339
+ scope: { type: "string", minLength: 1 },
1340
+ focus: { type: "string", minLength: 2 },
1341
+ k: { type: "integer", minimum: 1, maximum: 50, default: 12 },
1342
+ agentId: { type: "string" },
1343
+ },
1344
+ },
1345
+ security: { scopes: ["memory:read"], durable_receipt_required: true, requires_human_approval: false, kill_switch: "enabled" },
1346
+ },
1177
1347
  {
1178
1348
  name: "poll_inbox",
1179
1349
  title: "Poll Senti Inbox",
1180
1350
  description:
1181
- "Poll durable SentinelLayer session events after an optional cursor and return only events addressed or visible to the agent.",
1351
+ "Poll durable SentinelLayer session events after an optional cursor, return only events addressed or visible to the agent, and advance one monotonic read cursor.",
1182
1352
  inputSchema: {
1183
1353
  type: "object",
1184
1354
  additionalProperties: false,
@@ -1200,13 +1370,14 @@ export const SESSION_MCP_TOOLS = Object.freeze([
1200
1370
  name: "read_history",
1201
1371
  title: "Read Senti History",
1202
1372
  description:
1203
- "Hydrate a bounded recent, older, or after-cursor session transcript window without recipient filtering.",
1373
+ "Hydrate a bounded recent, older, or after-cursor session transcript window and advance one monotonic read cursor without appending view events.",
1204
1374
  inputSchema: {
1205
1375
  type: "object",
1206
1376
  additionalProperties: false,
1207
1377
  required: ["sessionId"],
1208
1378
  properties: {
1209
1379
  sessionId: { type: "string", minLength: 1 },
1380
+ agentId: { type: "string", minLength: 1 },
1210
1381
  cursor: { type: "string" },
1211
1382
  beforeSequence: { type: "integer", minimum: 1 },
1212
1383
  limit: { type: "integer", minimum: 1, maximum: MAX_TOOL_LIMIT, default: DEFAULT_TOOL_LIMIT },
@@ -1248,7 +1419,7 @@ export const SESSION_MCP_TOOLS = Object.freeze([
1248
1419
  name: "session_action",
1249
1420
  title: "Record Senti Session Action",
1250
1421
  description:
1251
- "Record a low-noise message action such as ack, working_on, disregard, view, like, dislike, or reply against a target session event.",
1422
+ "Record a low-noise message action such as ack, working_on, disregard, like, dislike, or reply; view advances the monotonic read cursor instead of appending an action.",
1252
1423
  inputSchema: {
1253
1424
  type: "object",
1254
1425
  additionalProperties: false,
@@ -1315,7 +1486,7 @@ export const SESSION_MCP_TOOLS = Object.freeze([
1315
1486
  name: "session_lock",
1316
1487
  title: "Lock Senti Files",
1317
1488
  description:
1318
- "Claim session-scoped file locks before editing files, using the same fail-closed lock registry as the CLI.",
1489
+ "Claim session-scoped file leases through the authoritative SentinelLayer API before editing. Lease lifecycle operations never create session events.",
1319
1490
  inputSchema: {
1320
1491
  type: "object",
1321
1492
  additionalProperties: false,
@@ -1331,8 +1502,18 @@ export const SESSION_MCP_TOOLS = Object.freeze([
1331
1502
  },
1332
1503
  intent: { type: "string" },
1333
1504
  ttlSeconds: { type: "integer", minimum: 1, default: 300 },
1334
- syncRemote: { type: "boolean", default: true },
1335
- awaitRemoteSync: { type: "boolean", default: true },
1505
+ syncRemote: {
1506
+ type: "boolean",
1507
+ default: true,
1508
+ description:
1509
+ "Deprecated compatibility input; ignored because the authenticated API is always authoritative.",
1510
+ },
1511
+ awaitRemoteSync: {
1512
+ type: "boolean",
1513
+ default: true,
1514
+ description:
1515
+ "Deprecated compatibility input; ignored because the authenticated API is always authoritative.",
1516
+ },
1336
1517
  },
1337
1518
  },
1338
1519
  },
@@ -1340,7 +1521,7 @@ export const SESSION_MCP_TOOLS = Object.freeze([
1340
1521
  name: "session_unlock",
1341
1522
  title: "Unlock Senti Files",
1342
1523
  description:
1343
- "Release session-scoped file locks held by an agent.",
1524
+ "Release authoritative session-scoped file leases held by an agent without writing to the session transcript.",
1344
1525
  inputSchema: {
1345
1526
  type: "object",
1346
1527
  additionalProperties: false,
@@ -1355,8 +1536,18 @@ export const SESSION_MCP_TOOLS = Object.freeze([
1355
1536
  ],
1356
1537
  },
1357
1538
  reason: { type: "string" },
1358
- syncRemote: { type: "boolean", default: true },
1359
- awaitRemoteSync: { type: "boolean", default: true },
1539
+ syncRemote: {
1540
+ type: "boolean",
1541
+ default: true,
1542
+ description:
1543
+ "Deprecated compatibility input; ignored because the authenticated API is always authoritative.",
1544
+ },
1545
+ awaitRemoteSync: {
1546
+ type: "boolean",
1547
+ default: true,
1548
+ description:
1549
+ "Deprecated compatibility input; ignored because the authenticated API is always authoritative.",
1550
+ },
1360
1551
  },
1361
1552
  },
1362
1553
  },
@@ -1364,7 +1555,7 @@ export const SESSION_MCP_TOOLS = Object.freeze([
1364
1555
  name: "session_locks",
1365
1556
  title: "List Senti File Locks",
1366
1557
  description:
1367
- "List active file locks for a session.",
1558
+ "List active authoritative file leases for a session.",
1368
1559
  inputSchema: {
1369
1560
  type: "object",
1370
1561
  additionalProperties: false,