@pcircle/memesh 4.7.3 → 4.8.1

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 (140) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +3 -1
  4. package/README.de.md +42 -16
  5. package/README.md +46 -19
  6. package/README.zh-TW.md +42 -17
  7. package/dashboard/dist/index.html +1 -1
  8. package/dist/core/agent-message-storage.d.ts +76 -0
  9. package/dist/core/agent-message-storage.d.ts.map +1 -0
  10. package/dist/core/agent-message-storage.js +359 -0
  11. package/dist/core/agent-message-storage.js.map +1 -0
  12. package/dist/core/agent-messaging.d.ts +224 -0
  13. package/dist/core/agent-messaging.d.ts.map +1 -0
  14. package/dist/core/agent-messaging.js +881 -0
  15. package/dist/core/agent-messaging.js.map +1 -0
  16. package/dist/core/agent-router.d.ts +194 -0
  17. package/dist/core/agent-router.d.ts.map +1 -0
  18. package/dist/core/agent-router.js +1138 -0
  19. package/dist/core/agent-router.js.map +1 -0
  20. package/dist/core/briefing.d.ts.map +1 -1
  21. package/dist/core/briefing.js +8 -1
  22. package/dist/core/briefing.js.map +1 -1
  23. package/dist/core/config.d.ts.map +1 -1
  24. package/dist/core/config.js.map +1 -1
  25. package/dist/core/doctor.d.ts +19 -0
  26. package/dist/core/doctor.d.ts.map +1 -1
  27. package/dist/core/doctor.js +176 -2
  28. package/dist/core/doctor.js.map +1 -1
  29. package/dist/core/dreamer.d.ts +8 -5
  30. package/dist/core/dreamer.d.ts.map +1 -1
  31. package/dist/core/dreamer.js +88 -3
  32. package/dist/core/dreamer.js.map +1 -1
  33. package/dist/core/product-improvements.d.ts +61 -0
  34. package/dist/core/product-improvements.d.ts.map +1 -0
  35. package/dist/core/product-improvements.js +216 -0
  36. package/dist/core/product-improvements.js.map +1 -0
  37. package/dist/core/repo-state.d.ts +11 -0
  38. package/dist/core/repo-state.d.ts.map +1 -0
  39. package/dist/core/repo-state.js +70 -0
  40. package/dist/core/repo-state.js.map +1 -0
  41. package/dist/core/schema-export.d.ts.map +1 -1
  42. package/dist/core/schema-export.js +70 -0
  43. package/dist/core/schema-export.js.map +1 -1
  44. package/dist/core/task-state.d.ts.map +1 -1
  45. package/dist/core/task-state.js +3 -3
  46. package/dist/core/task-state.js.map +1 -1
  47. package/dist/core/updater.d.ts +2 -0
  48. package/dist/core/updater.d.ts.map +1 -1
  49. package/dist/core/updater.js +11 -5
  50. package/dist/core/updater.js.map +1 -1
  51. package/dist/core/work-topology.d.ts.map +1 -1
  52. package/dist/core/work-topology.js +1 -0
  53. package/dist/core/work-topology.js.map +1 -1
  54. package/dist/host-adapters/acp-client.d.ts +147 -0
  55. package/dist/host-adapters/acp-client.d.ts.map +1 -0
  56. package/dist/host-adapters/acp-client.js +793 -0
  57. package/dist/host-adapters/acp-client.js.map +1 -0
  58. package/dist/host-adapters/claude-channel.d.ts +75 -0
  59. package/dist/host-adapters/claude-channel.d.ts.map +1 -0
  60. package/dist/host-adapters/claude-channel.js +255 -0
  61. package/dist/host-adapters/claude-channel.js.map +1 -0
  62. package/dist/host-adapters/codex-app-server.d.ts +83 -0
  63. package/dist/host-adapters/codex-app-server.d.ts.map +1 -0
  64. package/dist/host-adapters/codex-app-server.js +360 -0
  65. package/dist/host-adapters/codex-app-server.js.map +1 -0
  66. package/dist/host-adapters/codex-cli-queue.d.ts +17 -0
  67. package/dist/host-adapters/codex-cli-queue.d.ts.map +1 -0
  68. package/dist/host-adapters/codex-cli-queue.js +85 -0
  69. package/dist/host-adapters/codex-cli-queue.js.map +1 -0
  70. package/dist/host-runtime/acp.d.ts +57 -0
  71. package/dist/host-runtime/acp.d.ts.map +1 -0
  72. package/dist/host-runtime/acp.js +338 -0
  73. package/dist/host-runtime/acp.js.map +1 -0
  74. package/dist/host-runtime/claude.d.ts +41 -0
  75. package/dist/host-runtime/claude.d.ts.map +1 -0
  76. package/dist/host-runtime/claude.js +213 -0
  77. package/dist/host-runtime/claude.js.map +1 -0
  78. package/dist/host-runtime/codex-session.d.ts +24 -0
  79. package/dist/host-runtime/codex-session.d.ts.map +1 -0
  80. package/dist/host-runtime/codex-session.js +98 -0
  81. package/dist/host-runtime/codex-session.js.map +1 -0
  82. package/dist/host-runtime/codex.d.ts +33 -0
  83. package/dist/host-runtime/codex.d.ts.map +1 -0
  84. package/dist/host-runtime/codex.js +215 -0
  85. package/dist/host-runtime/codex.js.map +1 -0
  86. package/dist/host-runtime/config.d.ts +9 -0
  87. package/dist/host-runtime/config.d.ts.map +1 -0
  88. package/dist/host-runtime/config.js +91 -0
  89. package/dist/host-runtime/config.js.map +1 -0
  90. package/dist/host-runtime/router-client.d.ts +38 -0
  91. package/dist/host-runtime/router-client.d.ts.map +1 -0
  92. package/dist/host-runtime/router-client.js +463 -0
  93. package/dist/host-runtime/router-client.js.map +1 -0
  94. package/dist/host-runtime/router.d.ts +3 -0
  95. package/dist/host-runtime/router.d.ts.map +1 -0
  96. package/dist/host-runtime/router.js +43 -0
  97. package/dist/host-runtime/router.js.map +1 -0
  98. package/dist/mcp/server.js +2 -2
  99. package/dist/mcp/server.js.map +1 -1
  100. package/dist/skills-manifest.json +22 -17
  101. package/dist/storage/conflicts.d.ts.map +1 -1
  102. package/dist/storage/conflicts.js +8 -1
  103. package/dist/storage/conflicts.js.map +1 -1
  104. package/dist/storage/schema.d.ts +1 -1
  105. package/dist/storage/schema.d.ts.map +1 -1
  106. package/dist/storage/schema.js +255 -0
  107. package/dist/storage/schema.js.map +1 -1
  108. package/dist/transports/agent-messaging.d.ts +11 -0
  109. package/dist/transports/agent-messaging.d.ts.map +1 -0
  110. package/dist/transports/agent-messaging.js +273 -0
  111. package/dist/transports/agent-messaging.js.map +1 -0
  112. package/dist/transports/cli/cli.d.ts +6 -1
  113. package/dist/transports/cli/cli.d.ts.map +1 -1
  114. package/dist/transports/cli/cli.js +392 -17
  115. package/dist/transports/cli/cli.js.map +1 -1
  116. package/dist/transports/http/server.d.ts.map +1 -1
  117. package/dist/transports/http/server.js +31 -2
  118. package/dist/transports/http/server.js.map +1 -1
  119. package/dist/transports/mcp/handlers.d.ts +152 -1
  120. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  121. package/dist/transports/mcp/handlers.js +108 -3
  122. package/dist/transports/mcp/handlers.js.map +1 -1
  123. package/dist/transports/schemas.d.ts +106 -1
  124. package/dist/transports/schemas.d.ts.map +1 -1
  125. package/dist/transports/schemas.js +86 -0
  126. package/dist/transports/schemas.js.map +1 -1
  127. package/docs/platforms/README.md +167 -0
  128. package/docs/platforms/agent-messaging.md +290 -0
  129. package/hooks/hooks.json +11 -0
  130. package/llms-install.md +101 -25
  131. package/package.json +14 -5
  132. package/scripts/hooks/_generated/repo-state.js +77 -0
  133. package/scripts/hooks/_generated/schema.js +255 -0
  134. package/scripts/hooks/_generated/task-state.js +3 -3
  135. package/scripts/hooks/_generated/work-topology.js +1 -0
  136. package/scripts/hooks/_shared.js +77 -97
  137. package/scripts/hooks/auto-update-runner.mjs +243 -0
  138. package/scripts/hooks/session-start.js +12 -2
  139. package/scripts/hooks/session-summary.js +7 -6
  140. package/skills/memesh/SKILL.md +40 -1
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ closeSync,
5
+ linkSync,
6
+ openSync,
7
+ readFileSync,
8
+ realpathSync,
9
+ unlinkSync,
10
+ writeFileSync,
11
+ writeSync,
12
+ } from 'fs';
13
+ import { createHash, randomBytes } from 'crypto';
14
+ import { pathToFileURL } from 'url';
15
+ import { runGlobalUpdate } from '../../dist/core/updater.js';
16
+
17
+ export const AUTO_UPDATE_LOCK_TTL_MS = 10 * 60 * 1000;
18
+ export const AUTO_UPDATE_RECOVERY_TTL_MS = 30 * 1000;
19
+ const MAX_RECOVERY_GENERATIONS = 64;
20
+
21
+ function ownerToken() {
22
+ return `${process.pid}-${Date.now()}-${randomBytes(8).toString('hex')}`;
23
+ }
24
+
25
+ function readLock(lockPath) {
26
+ try {
27
+ const [token, pidRaw, startedAtRaw, version] = readFileSync(lockPath, 'utf8').split('\n');
28
+ const pid = Number(pidRaw);
29
+ const startedAt = Number(startedAtRaw);
30
+ if (!token || !Number.isSafeInteger(pid) || pid <= 0 || !Number.isFinite(startedAt)) return null;
31
+ return { token, pid, startedAt, version: version || null };
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ function processIsAlive(pid) {
38
+ try {
39
+ process.kill(pid, 0);
40
+ return true;
41
+ } catch (err) {
42
+ return err?.code !== 'ESRCH';
43
+ }
44
+ }
45
+
46
+ function createOwnedFile(filePath, token, payload) {
47
+ const fd = openSync(filePath, 'wx', 0o600);
48
+ try {
49
+ writeFileSync(fd, payload);
50
+ } finally {
51
+ closeSync(fd);
52
+ }
53
+ return token;
54
+ }
55
+
56
+ function tryCreateLock(lockPath, version) {
57
+ const token = ownerToken();
58
+ const startedAt = Date.now();
59
+ try {
60
+ createOwnedFile(
61
+ lockPath,
62
+ token,
63
+ `${token}\n${process.pid}\n${startedAt}\n${version}\n`,
64
+ );
65
+ return { acquired: true, lockPath, ownerToken: token, recoveredStale: false };
66
+ } catch (err) {
67
+ if (err?.code === 'EEXIST') {
68
+ return { acquired: false, lockPath, ownerToken: null, recoveredStale: false };
69
+ }
70
+ throw err;
71
+ }
72
+ }
73
+
74
+ export function autoUpdateRecoveryClaimPath(lockPath, staleToken, generation = 0) {
75
+ const tokenHash = createHash('sha256').update(staleToken).digest('hex').slice(0, 24);
76
+ return `${lockPath}.recover.${tokenHash}.${generation}`;
77
+ }
78
+
79
+ function tryCreateRecoveryClaim(claimPath) {
80
+ const token = ownerToken();
81
+ const candidatePath = `${claimPath}.candidate.${token}`;
82
+ createOwnedFile(
83
+ candidatePath,
84
+ token,
85
+ `${token}\n${process.pid}\n${Date.now()}\nrecovery\n`,
86
+ );
87
+ try {
88
+ // The candidate is complete before the fixed claim path appears. linkSync
89
+ // gives the destination O_EXCL semantics, so a crash cannot leave a
90
+ // partially written claim that blocks recovery forever.
91
+ linkSync(candidatePath, claimPath);
92
+ return { acquired: true, claimPath, ownerToken: token };
93
+ } catch (err) {
94
+ if (err?.code === 'EEXIST') {
95
+ return { acquired: false, claimPath, ownerToken: null };
96
+ }
97
+ throw err;
98
+ } finally {
99
+ try { unlinkSync(candidatePath); } catch { /* crash-only candidate orphan */ }
100
+ }
101
+ }
102
+
103
+ function tryAcquireRecoveryClaim(lockPath, staleToken) {
104
+ for (let generation = 0; generation < MAX_RECOVERY_GENERATIONS; generation += 1) {
105
+ const claimPath = autoUpdateRecoveryClaimPath(lockPath, staleToken, generation);
106
+ const created = tryCreateRecoveryClaim(claimPath);
107
+ if (created.acquired) return created;
108
+
109
+ const existing = readLock(claimPath);
110
+ if (!existing) return null;
111
+ if (Date.now() - existing.startedAt <= AUTO_UPDATE_RECOVERY_TTL_MS) return null;
112
+ if (processIsAlive(existing.pid)) return null;
113
+ // A complete claim whose owner crashed remains immutable. All contenders
114
+ // derive the same successor generation, where O_EXCL elects one recovery
115
+ // owner without deleting or replacing the orphaned claim.
116
+ }
117
+ return null;
118
+ }
119
+
120
+ export function releaseAutoUpdateLock(lockPath, token) {
121
+ const current = readLock(lockPath);
122
+ if (!current || current.token !== token) return false;
123
+ try {
124
+ unlinkSync(lockPath);
125
+ return true;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ export function recoverObservedStaleAutoUpdateLock(lockPath, observed) {
132
+ const recovery = tryAcquireRecoveryClaim(lockPath, observed.token);
133
+ if (!recovery?.acquired) return false;
134
+
135
+ try {
136
+ const current = readLock(lockPath);
137
+ if (!current || current.token !== observed.token) return false;
138
+ if (Date.now() - current.startedAt <= AUTO_UPDATE_LOCK_TTL_MS) return false;
139
+ if (processIsAlive(current.pid)) return false;
140
+ try {
141
+ unlinkSync(lockPath);
142
+ return true;
143
+ } catch (err) {
144
+ if (err?.code === 'ENOENT') return false;
145
+ throw err;
146
+ }
147
+ } finally {
148
+ releaseAutoUpdateLock(recovery.claimPath, recovery.ownerToken);
149
+ }
150
+ }
151
+
152
+ export function tryAcquireAutoUpdateLock(lockPath, version) {
153
+ const created = tryCreateLock(lockPath, version);
154
+ if (created.acquired) return created;
155
+
156
+ const observed = readLock(lockPath);
157
+ if (!observed) return created;
158
+ if (Date.now() - observed.startedAt <= AUTO_UPDATE_LOCK_TTL_MS) return created;
159
+ if (processIsAlive(observed.pid)) return created;
160
+
161
+ // Recovery never installs. It removes only the exact stale token observed
162
+ // under a separate O_EXCL claim, then defers so a later trigger must acquire
163
+ // the normal update lock. No delayed stale contender touches a fresh lock.
164
+ const recoveredStale = recoverObservedStaleAutoUpdateLock(lockPath, observed);
165
+ return { ...created, recoveredStale };
166
+ }
167
+
168
+ function writeLine(fd, line) {
169
+ try {
170
+ writeSync(fd, `[memesh auto-update] ${line}\n`);
171
+ return true;
172
+ } catch {
173
+ return false;
174
+ }
175
+ }
176
+
177
+ function errorMessage(err) {
178
+ const raw = err instanceof Error ? err.message : String(err);
179
+ return raw.replace(/[\r\n]+/g, ' ').slice(0, 500);
180
+ }
181
+
182
+ export function runAutoUpdate(targetVersion, lockPath) {
183
+ let lock;
184
+ try {
185
+ lock = tryAcquireAutoUpdateLock(lockPath, targetVersion);
186
+ } catch (err) {
187
+ writeLine(2, `FAILED target=${targetVersion} stage=lock error=${errorMessage(err)}`);
188
+ return 1;
189
+ }
190
+
191
+ if (!lock.acquired) {
192
+ const state = lock.recoveredStale ? 'RECOVERED_STALE' : 'IN_PROGRESS';
193
+ writeLine(1, `${state} target=${targetVersion} lock=${lockPath}`);
194
+ return 0;
195
+ }
196
+
197
+ if (!writeLine(1, `START target=${targetVersion} pid=${process.pid}`)) {
198
+ releaseAutoUpdateLock(lockPath, lock.ownerToken);
199
+ return 1;
200
+ }
201
+
202
+ let installedVersion = null;
203
+ let updateError = null;
204
+ try {
205
+ installedVersion = runGlobalUpdate(targetVersion).installedVersion;
206
+ } catch (err) {
207
+ updateError = err;
208
+ }
209
+
210
+ const released = releaseAutoUpdateLock(lockPath, lock.ownerToken);
211
+ if (updateError) {
212
+ writeLine(
213
+ 2,
214
+ `FAILED target=${targetVersion} stage=install-or-readback error=${errorMessage(updateError)}`,
215
+ );
216
+ if (!released) writeLine(2, `FAILED target=${targetVersion} stage=lock-release`);
217
+ return 1;
218
+ }
219
+ if (!released) {
220
+ writeLine(2, `FAILED target=${targetVersion} stage=lock-release installed=${installedVersion}`);
221
+ return 1;
222
+ }
223
+
224
+ writeLine(1, `SUCCESS target=${targetVersion} installed=${installedVersion}`);
225
+ return 0;
226
+ }
227
+
228
+ async function main() {
229
+ const [targetVersion, lockPath] = process.argv.slice(2);
230
+ if (!targetVersion || !lockPath) {
231
+ writeLine(2, 'FAILED stage=arguments error=expected target version and lock path');
232
+ process.exitCode = 1;
233
+ return;
234
+ }
235
+ process.exitCode = runAutoUpdate(targetVersion, lockPath);
236
+ }
237
+
238
+ const invokedPath = process.argv[1]
239
+ ? pathToFileURL(realpathSync(process.argv[1])).href
240
+ : null;
241
+ if (invokedPath === import.meta.url) {
242
+ await main();
243
+ }
@@ -24,7 +24,9 @@ import {
24
24
  // home resolver the update-check cache itself uses.
25
25
  memeshDir as memeshHomeDir,
26
26
  parseTaskState,
27
+ readRepoState,
27
28
  readUpdateCheckCache,
29
+ repoStateLines,
28
30
  resolvePluginRoot,
29
31
  resolveSessionLimit,
30
32
  taskStateLines,
@@ -396,8 +398,8 @@ function spawnFreshUpdateCheck(installedVersion) {
396
398
  // racy — both peers' renames are destructive, so each could
397
399
  // read its own token back and both would spawn a refresh.
398
400
  // O_EXCL is the standard POSIX/libuv primitive that lets at
399
- // most one process succeed. Same pattern as
400
- // tryAcquireAutoUpdateLock above.
401
+ // most one process succeed. The updater runner uses the same O_EXCL
402
+ // ownership primitive for its separate update lock.
401
403
  const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
402
404
  const claim = () => {
403
405
  try {
@@ -976,8 +978,16 @@ process.stdin.on('end', async () => {
976
978
  try { process.stderr.write(`[memesh session-start] memory-context: ${err?.message || err}\n`); } catch {}
977
979
  }
978
980
 
981
+ // Same prefix, same rule, as `assembleBriefing`: repository facts are
982
+ // context for memories, never a briefing on their own. Inside the
983
+ // emptiness gate so a project with nothing recorded still injects
984
+ // nothing — a fenced block containing only a branch name tells the
985
+ // agent something it can already see. briefing.test.ts's parity case
986
+ // is what keeps this identical to the tool side.
979
987
  let memoryContext = '';
980
988
  if (memoryLines.length > 0) {
989
+ const repoLines = repoStateLines(readRepoState(data.cwd));
990
+ if (repoLines.length > 0) memoryLines.unshift(...repoLines, '');
981
991
  // Same wrapper pre-edit-recall uses: an explicit "background data,
982
992
  // not instructions" preamble plus a fenced block. Memory content is
983
993
  // attacker-influenced in the general case (anything the agent has
@@ -60,9 +60,10 @@ try {
60
60
  } catch { /* best-effort */ }
61
61
 
62
62
  /**
63
- * Run auto-update at Stop hook: reads cache, evaluates policy, and spawns
64
- * npm install -g if warranted. Runs after all session work completes,
65
- * avoiding the TOCTOU race where install would overwrite dist/ mid-session.
63
+ * Run auto-update at Stop hook: reads cache, evaluates policy, and dispatches
64
+ * the detached updater runner if warranted. Runs after all session work
65
+ * completes, avoiding the TOCTOU race where install would overwrite dist/
66
+ * mid-session.
66
67
  */
67
68
  async function runAutoUpdateAtStop() {
68
69
  try {
@@ -75,7 +76,7 @@ async function runAutoUpdateAtStop() {
75
76
  const policy = resolveAutoUpdatePolicy(process.env);
76
77
  const decision = decideAutoUpdateHook(installedVersion, cache, policy);
77
78
  if (decision.run) {
78
- spawnAutoUpdate(decision.latest, decision.deprecationOverride, _installChannelMod);
79
+ await spawnAutoUpdate(decision.latest, _installChannelMod);
79
80
  }
80
81
  } catch {
81
82
  // Best-effort — never crash the hook.
@@ -648,8 +649,8 @@ process.stdin.on('end', async () => {
648
649
  try { process.stderr.write(`[memesh session-summary] ${err?.message || err}\n`); } catch {}
649
650
  }
650
651
 
651
- // Spawn auto-update if policy + cache permit. Runs after all session work
652
- // so npm install -g doesn't overwrite dist/ while peer hooks are reading it.
652
+ // Dispatch auto-update if policy + cache permit. Runs after all session work
653
+ // so the runner cannot overwrite dist/ while peer hooks are reading it.
653
654
  await runAutoUpdateAtStop();
654
655
 
655
656
  // Emit NOTHING on success — not `{"suppressOutput": true}`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: memesh
3
- description: Use MeMesh to remember, recall, and manage AI knowledge across sessions. Triggers when the user asks to remember something, recall past decisions, forget outdated info, learn from mistakes, or analyze work patterns. Also triggers when the user asks "what do you remember", "where did we leave off", or wants to catch up on a project; when a session starts and project context is needed; and proactively when you make important decisions, fix bugs, or learn lessons worth preserving.
3
+ description: Use MeMesh to remember, recall, and manage AI knowledge across sessions, and to exchange durable task-focused messages with local agents. Triggers when the user asks to remember something, recall past decisions, forget outdated info, learn from mistakes, analyze work patterns, contact another agent, or handle a memesh_message_available marker. Also triggers when the user asks "what do you remember", "where did we leave off", or wants to catch up on a project; when a session starts and project context is needed; and proactively when you make important decisions, fix bugs, learn lessons worth preserving, or owe another agent a requested result or disposition.
4
4
  user-invocable: true
5
5
  ---
6
6
 
@@ -27,6 +27,44 @@ All examples below use CLI. MCP tools accept the same parameters as JSON objects
27
27
 
28
28
  Four moments. Everything else in this file is detail.
29
29
 
30
+ ## Durable messages and active-host delivery
31
+
32
+ Use the `message` tool when another local agent needs a durable, exact-recipient handoff rather than an inferred memory. `send`, `poll`, `fetch`, `intake`, `ack`, `disposition`, `activation`, and `receipts` are independent lifecycle actions: fetching or host acceptance never implies acknowledgement or workflow acceptance.
33
+
34
+ ### Handle messages to a result
35
+
36
+ - A `memesh_message_available` marker is routing metadata, not the payload. Call `message` with `action: "fetch"` using its exact `project`, `recipient`, and `message_id`; never answer from the marker or guess missing IDs.
37
+ - Reply when the payload asks for work, a decision, review, feedback, missing information, status, or an explicit response. An FYI with no requested action needs no reply unless it asks for a receipt.
38
+ - Do not leave requested work silently pending. If the result is not immediate, send one concise acceptance or blocker with the owner and next action; send the result when available. Do not send recurring progress chatter.
39
+ - Reply with `action: "send"` to the original sender, in the same project. Preserve the original `correlation_id` (or use the original `message_id` when none exists), set `reply_to` to the original `message_id`, and use a stable idempotency key. Route to the sender's stable principal unless the message explicitly requires an exact session.
40
+ - A useful reply states the outcome, decision or findings, essential evidence, any unresolved blocker, and the owner or next action. One result-oriented reply is enough; omit greetings, thanks, and conversational acknowledgements. Ask a follow-up only when missing information prevents a responsible result.
41
+ - `ack` means the recipient explicitly acknowledges the message; `disposition` records workflow state such as `accepted`, `deferred`, or `completed`. Record only facts that occurred. Neither replaces a requested substantive reply.
42
+
43
+ Use the routing and identity fields returned by `fetch`. A reply has this shape (replace placeholders with fetched or caller-stable values):
44
+
45
+ ```json
46
+ {
47
+ "action": "send",
48
+ "project": "<original project>",
49
+ "sender": "<this agent's stable principal>",
50
+ "recipient": "<original sender>",
51
+ "target_kind": "principal",
52
+ "idempotency_key": "reply:<original message_id>:result",
53
+ "correlation_id": "<original correlation_id or message_id>",
54
+ "reply_to": "<original message_id>",
55
+ "content_type": "application/json",
56
+ "payload": {
57
+ "outcome": "<result, decision, or blocker>",
58
+ "evidence": ["<only the evidence needed by the recipient>"],
59
+ "next": "<owner and next action, if any>"
60
+ }
61
+ }
62
+ ```
63
+
64
+ An active compatible managed host can receive a native push, which removes polling for that live delivery. One-time provider enablement and a MeMesh-managed Codex app-server, Claude Channel, or Gemini ACP session may be required; ordinary unattached sessions are presence-only/inbound-unavailable. Adapter imports and a live router socket do not prove host registration or `host_accept`. Do not promise that a stopped, missing, or replaced session will wake up: it is not resumed or silently rerouted. Use the stable principal for logical routing, and an exact session/generation only when delivery must not move to a replacement connection. Local owns durable storage and host-native delivery; Cloud relay, A2A, SSE, discovery, or fetch is not host delivery.
65
+
66
+ Durable audit does not mean unbounded silent growth. Owners can inspect it with `memesh message storage report --cutoff <ISO timestamp>`, preview bounded terminal-payload tombstones with `memesh message storage prune --cutoff <ISO timestamp>`, and explicitly add `--apply`. Never prune unresolved/offline-pending work. `MEMESH_AGENT_MESSAGE_STORAGE_QUOTA_BYTES` is an optional owner policy; there is no default quota or automatic pruning.
67
+
30
68
  **SESSION START → load the briefing (once).**
31
69
  Call the `briefing` MCP tool or run `memesh briefing`. It returns the assembled
32
70
  work topology: where the work was left off (goal / next / blocked / done),
@@ -79,6 +117,7 @@ If MeMesh is installed as a Claude Code plugin, these happen **without any actio
79
117
  | **Stop** | Session ends | Auto-captures session knowledge + runs LLM failure analysis → lessons |
80
118
  | **PreCompact** | Before context compaction | Saves important knowledge before history is compressed |
81
119
  | **PreToolUse (Bash)** | Before a command runs | Fires accepted lesson-guards — warns when a recorded mistake is about to repeat |
120
+ | **SessionStart (Codex, async)** | A configured Codex session starts or resumes | Registers that exact live thread for metadata-only MeMesh message wakeups; exits without registering outside the configured workspace |
82
121
 
83
122
  Because of the SessionStart hook: **in Claude Code, do NOT call `briefing` at
84
123
  session start — it is already in your context.** Call it only mid-session